summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--common/fan.c70
-rw-r--r--include/fan.h66
-rw-r--r--test/fan.c114
3 files changed, 237 insertions, 13 deletions
diff --git a/common/fan.c b/common/fan.c
index 398a149268..e734dccda8 100644
--- a/common/fan.c
+++ b/common/fan.c
@@ -56,23 +56,81 @@ void fan_set_count(int count)
* Convert the percentage to a target RPM. We can't simply scale all
* the way down to zero because most fans won't turn that slowly, so
* we'll map [1,100] => [FAN_MIN,FAN_MAX], and [0] => "off".
-*/
-int fan_percent_to_rpm(int fan, int pct)
+ */
+int fan_percent_to_rpm(int fan_index, int temp_ratio)
{
int rpm, max, min;
- if (!pct) {
+ if (temp_ratio <= 0) {
rpm = 0;
} else {
- min = fans[fan].rpm->rpm_min;
- max = fans[fan].rpm->rpm_max;
- rpm = ((pct - 1) * max + (100 - pct) * min) / 99;
+ min = fans[fan_index].rpm->rpm_min;
+ max = fans[fan_index].rpm->rpm_max;
+ rpm = ((temp_ratio - 1) * max + (100 - temp_ratio) * min) / 99;
}
return rpm;
}
#endif /* CONFIG_FAN_RPM_CUSTOM */
+int temp_ratio_to_rpm_hysteresis(const struct fan_step_1_1 *fan_table,
+ int num_fan_levels, int fan_index,
+ int temp_ratio, void (*on_change)(void))
+{
+ static int previous_temp_ratio;
+ const int previous_rpm = fan_get_rpm_target(FAN_CH(fan_index));
+ int rpm;
+
+ if (temp_ratio <= fan_table[0].decreasing_temp_ratio_threshold) {
+ rpm = 0;
+ } else if (previous_rpm == 0 &&
+ temp_ratio < fan_table[0].increasing_temp_ratio_threshold) {
+ rpm = 0;
+ } else {
+ /*
+ * Comparing temp_ratio and previous_temp_ratio, trichotomy:
+ * 1. decreasing path. (check the decreasing threshold)
+ * 2. increasing path. (check the increasing threshold)
+ * 3. invariant path. (return the current RPM)
+ */
+ if (temp_ratio < previous_temp_ratio) {
+ int i = num_fan_levels - 1;
+
+ while (i > 0 &&
+ fan_table[i].decreasing_temp_ratio_threshold >=
+ temp_ratio) {
+ i--;
+ }
+ rpm = fan_table[i].rpm;
+ } else if (temp_ratio > previous_temp_ratio) {
+ int i = 0;
+
+ while (i < num_fan_levels &&
+ fan_table[i].increasing_temp_ratio_threshold <=
+ temp_ratio) {
+ i++;
+ }
+ if (i > 0) {
+ i--;
+ }
+ rpm = fan_table[i].rpm;
+ } else {
+ rpm = previous_rpm;
+ }
+ }
+
+ previous_temp_ratio = temp_ratio;
+
+ if (rpm != previous_rpm) {
+ cprints(CC_THERMAL, "Setting fan %d RPM to %d", fan_index, rpm);
+ if (on_change) {
+ on_change();
+ }
+ }
+
+ return rpm;
+}
+
/* The thermal task will only call this function with pct in [0,100]. */
test_mockable void fan_set_percent_needed(int fan, int pct)
{
diff --git a/include/fan.h b/include/fan.h
index fd49649547..0f777a6556 100644
--- a/include/fan.h
+++ b/include/fan.h
@@ -45,7 +45,19 @@ extern const struct fan_t fans[];
#endif
/* For convenience */
-#define FAN_CH(fan) fans[fan].conf->ch
+#define FAN_CH(fan) fans[fan].conf->ch
+/* Calculate temp_ratio as a macro. common/thermal.c defines the same
+ * function, but it cannot be used at file scope.
+ */
+#define THERMAL_FAN_PERCENT(low, high, cur) \
+ (((low) < (cur) && (cur) < (high)) ? \
+ (100 * ((cur) - (low)) / ((high) - (low))) : \
+ ((cur) <= (low) ? 0 : 100))
+/* Convert a temperature in centigrade to a temp_ratio, assuming constants
+ * temp_fan_off, temp_fan_max, already in Kelvin. Helpful for fan tables.
+ */
+#define TEMP_TO_RATIO(temp_c) \
+ (THERMAL_FAN_PERCENT((temp_fan_off), (temp_fan_max), (C_TO_K(temp_c))))
/**
* Set the amount of active cooling needed. The thermal control task will call
@@ -57,15 +69,57 @@ extern const struct fan_t fans[];
void fan_set_percent_needed(int fan, int pct);
/**
- * This function translates the percentage of cooling needed into a target RPM.
+ * Convert temp_ratio (temperature as a percentage of the ec_thermal_config
+ * .temp_fan_off to .temp_fan_max range, also cooling effort needed) into a
+ * target fan RPM.
* The default implementation should be sufficient for most needs, but
* individual boards may provide a custom version if needed (see config.h).
*
- * @param fan Fan number (index into fans[])
- * @param pct Percentage of cooling effort needed (always in [0,100])
- * Return Target RPM for fan
+ * @param fan Fan number (index into fans[])
+ * @param temp_ratio Temperature as fraction of temp_fan_off to
+ * temp_fan_max range, expressed as a percent ([0,100]).
+ * Return Target RPM for fan
+ */
+int fan_percent_to_rpm(int fan_index, int temp_ratio);
+/* Data structure to hold a tuple of parameters for one sensor and one fan. */
+struct fan_step_1_1 {
+ /* lowest temp_ratio (exclusive) to apply this rpm when decreasing.
+ * Use this rpm until temp_ratio falls to or below this threshold.
+ */
+ int decreasing_temp_ratio_threshold;
+ /* lowest temp_ratio (inclusive) to apply this rpm when increasing.
+ * Use this rpm when temp_ratio exceeds this threshold.
+ */
+ int increasing_temp_ratio_threshold;
+ int rpm;
+};
+/**
+ * Convert temp_ratio (temperature as a percentage of the ec_thermal_config
+ * .temp_fan_off to .temp_fan_max range) into a target fan RPM.
+ *
+ * This function adapts the most popular custom version of fan_percent_to_rpm,
+ * which provides hysteresis to reduce temperature/fan speed oscillations.
+ *
+ * To refactor to this, convert the fan_step-based fan_table to fan_step_1_1 by
+ * removing the first (.rpm = 0) element and using
+ * decreasing/increasing_temp_ratio_threshold for off/on respectively.
+ * See example in ../test/fan.c.
+ *
+ * @param fan_table Pointer to ordered array of fan_step_1_1 structs.
+ * There is no need to have any element with .rpm = 0.
+ * Function assumes 0 when temp_ratio is below the
+ * thresholds in the index-0 element.
+ * @param num_fan_levels Size of fan_table
+ * @param fan_index Fan number (index into fans[])
+ * @param temp_ratio Temperature as fraction of temp_fan_off to
+ * temp_fan_max range, expressed as a percent ([0,100]).
+ * @param on_change Pointer to function to be run when the target fan
+ * rpm changes, such as ezkinil board_print_temps().
+ * Return Target RPM for fan
*/
-int fan_percent_to_rpm(int fan, int pct);
+int temp_ratio_to_rpm_hysteresis(const struct fan_step_1_1 *fan_table,
+ int num_fan_levels, int fan_index,
+ int temp_ratio, void (*on_change)(void));
/**
diff --git a/test/fan.c b/test/fan.c
index d03aa0213c..99a3847257 100644
--- a/test/fan.c
+++ b/test/fan.c
@@ -105,9 +105,121 @@ static int test_fan(void)
return EC_SUCCESS;
}
-void run_test(int argc, char **argv)
+/* Provide a test driver to make test easier to read. */
+int temp_to_rpm(int temperature_c)
+{
+ const int temp_fan_off = C_TO_K(35);
+ const int temp_fan_max = C_TO_K(55);
+ const struct fan_step_1_1 fan_table[] = {
+ { .decreasing_temp_ratio_threshold = TEMP_TO_RATIO(35),
+ .increasing_temp_ratio_threshold = TEMP_TO_RATIO(41),
+ .rpm = 2500 },
+ { .decreasing_temp_ratio_threshold = TEMP_TO_RATIO(37),
+ .increasing_temp_ratio_threshold = TEMP_TO_RATIO(43),
+ .rpm = 3200 },
+ { .decreasing_temp_ratio_threshold = TEMP_TO_RATIO(42),
+ .increasing_temp_ratio_threshold = TEMP_TO_RATIO(45),
+ .rpm = 3500 },
+ { .decreasing_temp_ratio_threshold = TEMP_TO_RATIO(44),
+ .increasing_temp_ratio_threshold = TEMP_TO_RATIO(47),
+ .rpm = 3900 },
+ { .decreasing_temp_ratio_threshold = TEMP_TO_RATIO(46),
+ .increasing_temp_ratio_threshold = TEMP_TO_RATIO(49),
+ .rpm = 4500 },
+ { .decreasing_temp_ratio_threshold = TEMP_TO_RATIO(48),
+ .increasing_temp_ratio_threshold = TEMP_TO_RATIO(52),
+ .rpm = 5100 },
+ { .decreasing_temp_ratio_threshold = TEMP_TO_RATIO(51),
+ .increasing_temp_ratio_threshold = TEMP_TO_RATIO(55),
+ .rpm = 5400 },
+ };
+ const int num_fan_levels = ARRAY_SIZE(fan_table);
+ int temp_ratio = TEMP_TO_RATIO(temperature_c);
+
+ int rpm = temp_ratio_to_rpm_hysteresis(fan_table, num_fan_levels, 0,
+ temp_ratio, NULL);
+
+ fan_set_rpm_target(FAN_CH(0), rpm);
+ return rpm;
+}
+
+static int test_temp_ratio_to_rpm_hysteresis(void)
+{
+ const int ZERO = 0;
+ /* set initial value to be different so that a log message appears */
+ fan_set_rpm_target(FAN_CH(0), 5400);
+ /* initial turn-on behavior, ramp up. @ represents fan speed; + temp */
+ TEST_ASSERT(temp_to_rpm(30) == ZERO); /* @+. . 40 . 50 .60 */
+ TEST_ASSERT(temp_to_rpm(30) == ZERO); /* @+. . . . . . */
+ TEST_ASSERT(temp_to_rpm(35) == ZERO); /* @ + . . . . */
+ TEST_ASSERT(temp_to_rpm(37) == ZERO); /* @ . + . . . . */
+ TEST_ASSERT(temp_to_rpm(39) == ZERO); /* @ . +. . . . */
+ TEST_ASSERT(temp_to_rpm(40) == ZERO); /* @ . + . . . */
+ TEST_ASSERT(temp_to_rpm(41) == 2500); /* @. .+ . . . */
+ TEST_ASSERT(temp_to_rpm(36) == 2500); /* @.+ . . . . */
+ TEST_ASSERT(temp_to_rpm(42) == 2500); /* @. . + . . . */
+ TEST_ASSERT(temp_to_rpm(43) == 3200); /* @ . + . . . */
+ TEST_ASSERT(temp_to_rpm(38) == 3200); /* @ + . . . . */
+ TEST_ASSERT(temp_to_rpm(44) == 3200); /* @ . +. . . */
+ TEST_ASSERT(temp_to_rpm(45) == 3500); /* .@ . + . . */
+ TEST_ASSERT(temp_to_rpm(43) == 3500); /* .@ . + . . . */
+ TEST_ASSERT(temp_to_rpm(46) == 3500); /* .@ . .+ . . */
+ TEST_ASSERT(temp_to_rpm(47) == 3900); /* . @ . . + . . */
+ TEST_ASSERT(temp_to_rpm(45) == 3900); /* . @ . + . . */
+ TEST_ASSERT(temp_to_rpm(48) == 3900); /* . @ . . + . . */
+ TEST_ASSERT(temp_to_rpm(49) == 4500); /* . @ . . +. . */
+ TEST_ASSERT(temp_to_rpm(47) == 4500); /* . @ . . + . . */
+ TEST_ASSERT(temp_to_rpm(51) == 4500); /* . @ . . .+ . */
+ TEST_ASSERT(temp_to_rpm(52) == 5100); /* . @. . . + . */
+ TEST_ASSERT(temp_to_rpm(49) == 5100); /* . @. . +. . */
+ TEST_ASSERT(temp_to_rpm(54) == 5100); /* . @. . . +. */
+ TEST_ASSERT(temp_to_rpm(55) == 5400); /* . @ . . + */
+ TEST_ASSERT(temp_to_rpm(52) == 5400); /* . @ . . + . */
+ TEST_ASSERT(temp_to_rpm(60) == 5400); /* . @ . 50 ..+ */
+ /* cool-down */
+ TEST_ASSERT(temp_to_rpm(55) == 5400); /* . @ . . + */
+ TEST_ASSERT(temp_to_rpm(52) == 5400); /* . @ . . + . */
+ TEST_ASSERT(temp_to_rpm(51) == 5100); /* . @. . .+ . */
+ TEST_ASSERT(temp_to_rpm(54) == 5100); /* . @. . . +. */
+ TEST_ASSERT(temp_to_rpm(49) == 5100); /* . @. . +. . */
+ TEST_ASSERT(temp_to_rpm(48) == 4500); /* . @ . . + . . */
+ TEST_ASSERT(temp_to_rpm(51) == 4500); /* . @ . . .+ . */
+ TEST_ASSERT(temp_to_rpm(47) == 4500); /* . @ . . + . . */
+ TEST_ASSERT(temp_to_rpm(46) == 3900); /* . @ . .+ . . */
+ TEST_ASSERT(temp_to_rpm(48) == 3900); /* . @ . . + . . */
+ TEST_ASSERT(temp_to_rpm(45) == 3900); /* . @ . + . . */
+ TEST_ASSERT(temp_to_rpm(44) == 3500); /* .@ . +. . . */
+ TEST_ASSERT(temp_to_rpm(46) == 3500); /* .@ . .+ . . */
+ TEST_ASSERT(temp_to_rpm(43) == 3500); /* .@ . + . . . */
+ TEST_ASSERT(temp_to_rpm(42) == 3200); /* @ . + . . . */
+ TEST_ASSERT(temp_to_rpm(44) == 3200); /* @ . +. . . */
+ TEST_ASSERT(temp_to_rpm(38) == 3200); /* @ + . . . . */
+ TEST_ASSERT(temp_to_rpm(37) == 2500); /* @. + . . . . */
+ TEST_ASSERT(temp_to_rpm(42) == 2500); /* @. . + . . . */
+ TEST_ASSERT(temp_to_rpm(36) == 2500); /* @.+ . . . . */
+ TEST_ASSERT(temp_to_rpm(35) == ZERO); /* @ + 40 . 50 . */
+ /* warm up again */
+ TEST_ASSERT(temp_to_rpm(38) == ZERO); /* @ . + . . . . */
+ /* jumping */
+ TEST_ASSERT(temp_to_rpm(46) == 3500); /* .@ . .+ . . */
+ TEST_ASSERT(temp_to_rpm(36) == 2500); /* @.+ . . . . */
+ TEST_ASSERT(temp_to_rpm(35) == ZERO); /* @ + . . . . */
+ TEST_ASSERT(temp_to_rpm(37) == ZERO); /* @ . + . . . . */
+ TEST_ASSERT(temp_to_rpm(46) == 3500); /* .@ . .+ . . */
+ TEST_ASSERT(temp_to_rpm(54) == 5100); /* . @. . . +. */
+ TEST_ASSERT(temp_to_rpm(55) == 5400); /* . @ . . + */
+ TEST_ASSERT(temp_to_rpm(60) == 5400); /* . @ . . ..+ */
+ TEST_ASSERT(temp_to_rpm(53) == 5400); /* . @ . . + . */
+ TEST_ASSERT(temp_to_rpm(46) == 3900); /* . @ . .+ . . */
+ TEST_ASSERT(temp_to_rpm(30) == ZERO); /* @+. . 40 . 50 . */
+
+ return EC_SUCCESS;
+}
+
+void run_test(int argc, const char **argv)
{
RUN_TEST(test_fan);
+ RUN_TEST(test_temp_ratio_to_rpm_hysteresis);
test_print_result();
}