Skip to content

Commit 32b5d34

Browse files
committed
fan: Re-add interpolation-based algorithm
Signed-off-by: Tim Crawford <tcrawford@system76.com>
1 parent c028136 commit 32b5d34

File tree

7 files changed

+354
-63
lines changed

7 files changed

+354
-63
lines changed

src/app/main/Makefile.mk

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ app-y += battery.c
55
app-y += config.c
66
app-y += ecpm.c
77
app-$(CONFIG_BUS_ESPI) += espi.c
8-
app-y += fan.c
98
app-y += gctrl.c
109
app-y += kbc.c
1110
app-y += kbscan.c
@@ -63,21 +62,7 @@ endif
6362
endif
6463

6564
# Fan configs
66-
ifneq ($(CONFIG_FAN1_PWM),)
67-
CFLAGS += -DFAN1_PWM=$(CONFIG_FAN1_PWM)
68-
ifneq ($(CONFIG_FAN1_PWM_MIN),)
69-
CFLAGS += -DFAN1_PWM_MIN=$(CONFIG_FAN1_PWM_MIN)
70-
endif
71-
CFLAGS += -DBOARD_FAN1_POINTS=$(CONFIG_FAN1_POINTS)
72-
endif
73-
74-
ifneq ($(CONFIG_FAN2_PWM),)
75-
CFLAGS += -DFAN2_PWM=$(CONFIG_FAN2_PWM)
76-
ifneq ($(CONFIG_FAN2_PWM_MIN),)
77-
CFLAGS += -DFAN2_PWM_MIN=$(CONFIG_FAN2_PWM_MIN)
78-
endif
79-
CFLAGS += -DBOARD_FAN2_POINTS=$(CONFIG_FAN2_POINTS)
80-
endif
65+
include $(APP_DIR)/fan/Makefile.mk
8166

8267
# Set battery charging thresholds
8368
BATTERY_START_THRESHOLD?=90

src/app/main/fan/Makefile.mk

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# SPDX-License-Identifier: GPL-3.0-only
2+
3+
ifneq ($(CONFIG_FAN1_PWM),)
4+
CFLAGS += -DFAN1_PWM=$(CONFIG_FAN1_PWM)
5+
ifneq ($(CONFIG_FAN1_PWM_MIN),)
6+
CFLAGS += -DFAN1_PWM_MIN=$(CONFIG_FAN1_PWM_MIN)
7+
endif
8+
CFLAGS += -DBOARD_FAN1_POINTS=$(CONFIG_FAN1_POINTS)
9+
endif
10+
11+
ifneq ($(CONFIG_FAN2_PWM),)
12+
CFLAGS += -DFAN2_PWM=$(CONFIG_FAN2_PWM)
13+
ifneq ($(CONFIG_FAN2_PWM_MIN),)
14+
CFLAGS += -DFAN2_PWM_MIN=$(CONFIG_FAN2_PWM_MIN)
15+
endif
16+
CFLAGS += -DBOARD_FAN2_POINTS=$(CONFIG_FAN2_POINTS)
17+
endif
18+
19+
#CONFIG_FAN_CTRL_INTERP = y
20+
CONFIG_FAN_CTRL_STEP = y
21+
22+
ifeq ($(CONFIG_FAN_CTRL_INTERP),y)
23+
CFLAGS += -DCONFIG_FAN_CTRL_INTERP=1
24+
app-y += fan/interp.c
25+
else ifeq ($(CONFIG_FAN_CTRL_STEP),y)
26+
CFLAGS += -DCONFIG_FAN_CTRL_STEP=1
27+
app-y += fan/step.c
28+
endif
29+
30+
app-y += fan/common.c

src/app/main/fan/common.c

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// SPDX-License-Identifier: GPL-3.0-only
2+
3+
#include <app/fan.h>
4+
#include <ec/pwm.h>
5+
6+
bool fan_max = false;
7+
static enum FanMode fan_mode = FAN_MODE_AUTO;
8+
9+
#define TACH_FREQ (CONFIG_CLOCK_FREQ_KHZ * 1000UL)
10+
11+
// Fan Speed (RPM) = 60 / (1/fs sec * {FnTMRR, FnRLRR} * P)
12+
// - n: 1 or 2
13+
// - P: the numbers of square pulses per revolution
14+
// - fs: sample rate (FreqEC / 128)
15+
// - {FnTMRR, FnTLRR} = 0000h: Fan Speed is zero
16+
#define TACH_TO_RPM(x) (60UL * TACH_FREQ / 128UL / 2UL / (x))
17+
18+
uint16_t fan_get_tach0_rpm(void) {
19+
uint16_t rpm = (F1TMRR << 8) | F1TLRR;
20+
21+
if (rpm)
22+
rpm = TACH_TO_RPM(rpm);
23+
24+
return rpm;
25+
}
26+
27+
uint16_t fan_get_tach1_rpm(void) {
28+
uint16_t rpm = (F2TMRR << 8) | F2TLRR;
29+
30+
if (rpm)
31+
rpm = TACH_TO_RPM(rpm);
32+
33+
return rpm;
34+
}
35+
36+
void fan_reset(void) {
37+
// Do not manually set fans to maximum speed
38+
fan_max = false;
39+
}
40+
41+
enum FanMode fan_get_mode(void) {
42+
return fan_mode;
43+
}
44+
45+
void fan_set_mode(enum FanMode mode) {
46+
fan_mode = mode;
47+
}

src/app/main/fan/interp.c

Lines changed: 258 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,258 @@
1+
// SPDX-License-Identifier: GPL-3.0-only
2+
3+
#include <app/fan.h>
4+
#include <app/power.h>
5+
#include <common/debug.h>
6+
#include <common/macro.h>
7+
#include <ec/pwm.h>
8+
#include <drivers/dgpu/dgpu.h>
9+
10+
#if CONFIG_PLATFORM_INTEL
11+
#include <app/peci.h>
12+
#endif
13+
14+
15+
static uint8_t last_fan1_duty = 0;
16+
static uint8_t last_fan2_duty = 0;
17+
18+
uint8_t fan1_pwm_actual = 0;
19+
uint8_t fan1_pwm_target = 0;
20+
uint16_t fan1_rpm = 0;
21+
22+
uint8_t fan2_pwm_actual = 0;
23+
uint8_t fan2_pwm_target = 0;
24+
uint16_t fan2_rpm = 0;
25+
26+
#define MAX_FAN_SPEED CTR0
27+
#define MIN_FAN_SPEED 0
28+
29+
#ifndef SMOOTH_FANS_UP
30+
#define SMOOTH_FANS_UP 45 // default to ~11 seconds for full ramp-up
31+
#endif
32+
#ifndef SMOOTH_FANS_DOWN
33+
#define SMOOTH_FANS_DOWN 100 // default to ~25 seconds for full ramp-down
34+
#endif
35+
36+
#ifndef SMOOTH_FANS_MIN
37+
#define SMOOTH_FANS_MIN 0 // default to smoothing all fan speed changes
38+
#endif
39+
40+
#define MAX_JUMP_UP ((MAX_FAN_SPEED - MIN_FAN_SPEED) / (uint8_t)SMOOTH_FANS_UP)
41+
#define MAX_JUMP_DOWN ((MAX_FAN_SPEED - MIN_FAN_SPEED) / (uint8_t)SMOOTH_FANS_DOWN)
42+
43+
#define MIN_SPEED_TO_SMOOTH PWM_DUTY(SMOOTH_FANS_MIN)
44+
45+
// Fan speed is the lowest requested over HEATUP seconds
46+
#ifndef BOARD_FAN1_HEATUP
47+
#define BOARD_FAN1_HEATUP 4
48+
#endif
49+
50+
static uint8_t FAN1_HEATUP[BOARD_FAN1_HEATUP] = { 0 };
51+
52+
// Fan speed is the highest HEATUP speed over COOLDOWN seconds
53+
#ifndef BOARD_FAN1_COOLDOWN
54+
#define BOARD_FAN1_COOLDOWN 10
55+
#endif
56+
57+
static uint8_t FAN1_COOLDOWN[BOARD_FAN1_COOLDOWN] = { 0 };
58+
59+
// Fan curve with temperature in degrees C, duty cycle in percent
60+
static const struct FanPoint __code FAN1_POINTS[] = {
61+
#ifndef BOARD_FAN1_POINTS
62+
#error Board must declare fan points
63+
#else
64+
BOARD_FAN1_POINTS
65+
#endif
66+
};
67+
68+
static const struct Fan __code FAN1 = {
69+
.points = FAN1_POINTS,
70+
.points_size = ARRAY_SIZE(FAN1_POINTS),
71+
.heatup = FAN1_HEATUP,
72+
.heatup_size = ARRAY_SIZE(FAN1_HEATUP),
73+
.cooldown = FAN1_COOLDOWN,
74+
.cooldown_size = ARRAY_SIZE(FAN1_COOLDOWN),
75+
};
76+
77+
#ifdef FAN2_PWM
78+
79+
// Fan speed is the lowest requested over HEATUP seconds
80+
#ifndef BOARD_FAN2_HEATUP
81+
#define BOARD_FAN2_HEATUP 4
82+
#endif
83+
84+
static uint8_t FAN2_HEATUP[BOARD_FAN2_HEATUP] = { 0 };
85+
86+
// Fan speed is the highest HEATUP speed over COOLDOWN seconds
87+
#ifndef BOARD_FAN2_COOLDOWN
88+
#define BOARD_FAN2_COOLDOWN 10
89+
#endif
90+
91+
static uint8_t FAN2_COOLDOWN[BOARD_FAN2_COOLDOWN] = { 0 };
92+
93+
// Fan curve with temperature in degrees C, duty cycle in percent
94+
static const struct FanPoint __code FAN2_POINTS[] = {
95+
#ifndef BOARD_FAN2_POINTS
96+
#error Board must declare fan points
97+
#else
98+
BOARD_FAN2_POINTS
99+
#endif
100+
};
101+
102+
static const struct Fan __code FAN2 = {
103+
.points = FAN2_POINTS,
104+
.points_size = ARRAY_SIZE(FAN2_POINTS),
105+
.heatup = FAN2_HEATUP,
106+
.heatup_size = ARRAY_SIZE(FAN2_HEATUP),
107+
.cooldown = FAN2_COOLDOWN,
108+
.cooldown_size = ARRAY_SIZE(FAN2_COOLDOWN),
109+
};
110+
111+
#endif // FAN2_PWM
112+
113+
// Get duty cycle based on temperature, adapted from
114+
// https://github.com/pop-os/system76-power/blob/master/src/fan.rs
115+
static uint8_t fan_duty(const struct Fan *const fan, int16_t temp) {
116+
for (uint8_t i = 0; i < fan->points_size; i++) {
117+
const struct FanPoint *cur = &fan->points[i];
118+
119+
// If exactly the current temp, return the current duty
120+
if (temp == cur->temp) {
121+
return cur->duty;
122+
} else if (temp < cur->temp) {
123+
// If lower than first temp, return 0%
124+
if (i == 0) {
125+
return 0;
126+
} else {
127+
const struct FanPoint *prev = &fan->points[i - 1];
128+
129+
// If in between current temp and previous temp, interpolate
130+
if (temp > prev->temp) {
131+
int16_t dtemp = (cur->temp - prev->temp);
132+
int16_t dduty = ((int16_t)cur->duty) - ((int16_t)prev->duty);
133+
return (uint8_t)(
134+
((int16_t)prev->duty) +
135+
((temp - prev->temp) * dduty) / dtemp
136+
);
137+
}
138+
}
139+
}
140+
}
141+
142+
// If no point is found, return 100%
143+
return CTR0;
144+
}
145+
146+
static uint8_t fan_smooth(uint8_t last_duty, uint8_t duty) {
147+
uint8_t next_duty = duty;
148+
149+
// ramping down
150+
if (duty < last_duty) {
151+
// out of bounds (lower) safeguard
152+
uint8_t smoothed = last_duty < MIN_FAN_SPEED + MAX_JUMP_DOWN
153+
? MIN_FAN_SPEED
154+
: last_duty - MAX_JUMP_DOWN;
155+
156+
// use smoothed value if above min and if smoothed is closer than raw
157+
if (last_duty > MIN_SPEED_TO_SMOOTH && smoothed > duty) {
158+
next_duty = smoothed;
159+
}
160+
}
161+
162+
// ramping up
163+
if (duty > last_duty) {
164+
// out of bounds (higher) safeguard
165+
uint8_t smoothed = last_duty > MAX_FAN_SPEED - MAX_JUMP_UP
166+
? MAX_FAN_SPEED
167+
: last_duty + MAX_JUMP_UP;
168+
169+
// use smoothed value if above min and if smoothed is closer than raw
170+
if (duty > MIN_SPEED_TO_SMOOTH && smoothed < duty) {
171+
next_duty = smoothed;
172+
}
173+
}
174+
175+
return next_duty;
176+
}
177+
178+
static uint8_t fan_heatup(const struct Fan *const fan, uint8_t duty) {
179+
uint8_t lowest = duty;
180+
181+
uint8_t i;
182+
for (i = 0; (i + 1) < fan->heatup_size; i++) {
183+
uint8_t value = fan->heatup[i + 1];
184+
if (value < lowest) {
185+
lowest = value;
186+
}
187+
fan->heatup[i] = value;
188+
}
189+
fan->heatup[i] = duty;
190+
191+
return lowest;
192+
}
193+
194+
static uint8_t fan_cooldown(const struct Fan *const fan, uint8_t duty) {
195+
uint8_t highest = duty;
196+
197+
uint8_t i;
198+
for (i = 0; (i + 1) < fan->cooldown_size; i++) {
199+
uint8_t value = fan->cooldown[i + 1];
200+
if (value > highest) {
201+
highest = value;
202+
}
203+
fan->cooldown[i] = value;
204+
}
205+
fan->cooldown[i] = duty;
206+
207+
return highest;
208+
}
209+
210+
static uint8_t fan_get_duty(const struct Fan *const fan, int16_t temp) {
211+
uint8_t duty;
212+
213+
if (power_state == POWER_STATE_S0) {
214+
duty = fan_duty(fan, temp);
215+
if (fan_max) {
216+
duty = CTR0;
217+
} else {
218+
duty = fan_heatup(fan, duty);
219+
duty = fan_cooldown(fan, duty);
220+
}
221+
} else {
222+
duty = 0;
223+
}
224+
225+
return duty;
226+
}
227+
228+
void fan_event(void) {
229+
#if CONFIG_HAVE_DGPU
230+
int16_t sys_temp = MAX(peci_temp, dgpu_temp);
231+
#else
232+
int16_t sys_temp = peci_temp;
233+
#endif
234+
235+
// set FAN1 duty
236+
fan1_pwm_target = fan_get_duty(&FAN1, sys_temp);
237+
if (fan1_pwm_target != fan1_pwm_actual) {
238+
TRACE("FAN1 target duty=%d\n", fan1_pwm_target);
239+
last_fan1_duty = fan1_pwm_target = fan_smooth(last_fan1_duty, fan1_pwm_target);
240+
fan1_pwm_actual = fan_max ? MAX_FAN_SPEED : fan1_pwm_target;
241+
TRACE("FAN1 duty smoothed=%d\n", fan1_pwm_target);
242+
}
243+
FAN1_PWM = fan1_pwm_actual;
244+
fan1_rpm = fan_get_tach0_rpm();
245+
246+
#ifdef FAN2_PWM
247+
// set FAN2 duty
248+
fan2_pwm_target = fan_get_duty(&FAN2, sys_temp);
249+
if (fan2_pwm_target != fan2_pwm_actual) {
250+
TRACE("FAN2 target duty = %d\n", fan2_pwm_target);
251+
last_fan2_duty = fan2_pwm_target = fan_smooth(last_fan2_duty, fan2_pwm_target);
252+
fan2_pwm_actual = fan_max ? MAX_FAN_SPEED : fan2_pwm_target;
253+
TRACE("FAN2 duty smoothed = %d\n", fan2_pwm_target);
254+
}
255+
FAN2_PWM = fan2_pwm_actual;
256+
fan2_rpm = fan_get_tach1_rpm();
257+
#endif
258+
}

0 commit comments

Comments
 (0)