|
| 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