diff options
Diffstat (limited to 'zephyr/shim/src/fan.c')
-rw-r--r-- | zephyr/shim/src/fan.c | 380 |
1 files changed, 380 insertions, 0 deletions
diff --git a/zephyr/shim/src/fan.c b/zephyr/shim/src/fan.c new file mode 100644 index 0000000000..1e38dff050 --- /dev/null +++ b/zephyr/shim/src/fan.c @@ -0,0 +1,380 @@ +/* Copyright 2021 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "fan.h" +#include "pwm.h" +#include "pwm/pwm.h" +#include "system.h" +#include "math_util.h" +#include "hooks.h" +#include "gpio_signal.h" +#include "gpio.h" +#include <logging/log.h> +#include <sys/util_macro.h> +#include <drivers/sensor.h> + +LOG_MODULE_REGISTER(fan_shim, LOG_LEVEL_ERR); + +#define FAN_CONFIGS(node_id) \ + const struct fan_conf node_id_conf = { \ + .flags = (COND_CODE_1(DT_PROP(node_id, not_use_rpm_mode), \ + (0), (FAN_USE_RPM_MODE))) | \ + (COND_CODE_1(DT_PROP(node_id, use_fast_start), \ + (FAN_USE_FAST_START), (0))), \ + .ch = node_id, \ + .pgood_gpio = COND_CODE_1( \ + DT_NODE_HAS_PROP(node_id, pgood_gpio), \ + (GPIO_SIGNAL(DT_PHANDLE(node_id, pgood_gpio))), \ + (GPIO_UNIMPLEMENTED)), \ + .enable_gpio = COND_CODE_1( \ + DT_NODE_HAS_PROP(node_id, enable_gpio), \ + (GPIO_SIGNAL(DT_PHANDLE(node_id, enable_gpio))), \ + (GPIO_UNIMPLEMENTED)), \ + }; \ + const struct fan_rpm node_id_rpm = { \ + .rpm_min = DT_PROP(node_id, rpm_min), \ + .rpm_start = DT_PROP(node_id, rpm_start), \ + .rpm_max = DT_PROP(node_id, rpm_max), \ + }; + +#define FAN_INST(node_id) \ + [node_id] = { \ + .conf = &node_id_conf, \ + .rpm = &node_id_rpm, \ + }, + +#define FAN_CONTROL_INST(node_id) \ + [node_id] = { \ + .pwm_id = PWM_CHANNEL(DT_PHANDLE(node_id, pwm)), \ + }, + +#if DT_NODE_EXISTS(DT_INST(0, named_fans)) +DT_FOREACH_CHILD(DT_INST(0, named_fans), FAN_CONFIGS) +#endif /* named_fan */ + +const struct fan_t fans[] = { +#if DT_NODE_EXISTS(DT_INST(0, named_fans)) + DT_FOREACH_CHILD(DT_INST(0, named_fans), FAN_INST) +#endif /* named_fan */ +}; + +#define TACHO_DEV_INIT(node_id) { \ + fan_control[node_id].tach = \ + device_get_binding(DT_PROP_BY_PHANDLE(node_id, tach, label)); \ + } + +/* Rpm deviation (Unit:percent) */ +#ifndef RPM_DEVIATION +#define RPM_DEVIATION 7 +#endif + +/* Margin of target rpm */ +#define RPM_MARGIN(rpm_target) (((rpm_target)*RPM_DEVIATION) / 100) + +/* Fan mode */ +enum fan_mode { + /* FAN rpm mode */ + FAN_RPM = 0, + /* FAN duty mode */ + FAN_DUTY, +}; + +/* Fan status data structure */ +struct fan_status_t { + /* Fan mode */ + enum fan_mode current_fan_mode; + /* Actual rpm */ + int rpm_actual; + /* Target rpm */ + int rpm_target; + /* Fan config flags */ + unsigned int flags; + /* Automatic fan status */ + enum fan_status auto_status; +}; + +/* Data structure to define tachometer. */ +struct fan_control_t { + const struct device *tach; + enum pwm_channel pwm_id; +}; + +static struct fan_status_t fan_status[FAN_CH_COUNT]; +static int rpm_pre[FAN_CH_COUNT]; +static struct fan_control_t fan_control[] = { +#if DT_NODE_EXISTS(DT_INST(0, named_fans)) + DT_FOREACH_CHILD(DT_INST(0, named_fans), FAN_CONTROL_INST) +#endif /* named_fan */ +}; + +/** + * Get fan rpm value + * + * @param ch operation channel + * @return Actual rpm + */ +static int fan_rpm(int ch) +{ + struct sensor_value val = { 0 }; + + sensor_sample_fetch_chan(fan_control[ch].tach, SENSOR_CHAN_RPM); + sensor_channel_get(fan_control[ch].tach, SENSOR_CHAN_RPM, &val); + return (int)val.val1; +} + +/** + * Check all fans are stopped + * + * @return 1: all fans are stopped. 0: else. + */ +static int fan_all_disabled(void) +{ + int ch; + + for (ch = 0; ch < fan_get_count(); ch++) + if (fan_status[ch].auto_status != FAN_STATUS_STOPPED) + return 0; + return 1; +} + +/** + * Adjust fan duty by difference between target and actual rpm + * + * @param ch operation channel + * @param rpm_diff difference between target and actual rpm + * @param duty current fan duty + */ +static void fan_adjust_duty(int ch, int rpm_diff, int duty) +{ + int duty_step = 0; + + /* Find suitable duty step */ + if (ABS(rpm_diff) >= 2000) + duty_step = 20; + else if (ABS(rpm_diff) >= 1000) + duty_step = 10; + else if (ABS(rpm_diff) >= 500) + duty_step = 5; + else if (ABS(rpm_diff) >= 250) + duty_step = 3; + else + duty_step = 1; + + /* Adjust fan duty step by step */ + if (rpm_diff > 0) + duty = MIN(duty + duty_step, 100); + else + duty = MAX(duty - duty_step, 1); + + fan_set_duty(ch, duty); + + LOG_DBG("fan%d: duty %d, rpm_diff %d", ch, duty, rpm_diff); +} + +/** + * Smart fan control function. + * + * The function sets the pwm duty to reach the target rpm + * + * @param ch operation channel + * @param rpm_actual actual operation rpm value + * @param rpm_target target operation rpm value + * @return current fan control status + */ +enum fan_status fan_smart_control(int ch, int rpm_actual, int rpm_target) +{ + int duty, rpm_diff; + + /* wait rpm is stable */ + if (ABS(rpm_actual - rpm_pre[ch]) > RPM_MARGIN(rpm_actual)) { + rpm_pre[ch] = rpm_actual; + return FAN_STATUS_CHANGING; + } + + /* Record previous rpm */ + rpm_pre[ch] = rpm_actual; + + /* Adjust PWM duty */ + rpm_diff = rpm_target - rpm_actual; + duty = fan_get_duty(ch); + if (duty == 0 && rpm_target == 0) + return FAN_STATUS_STOPPED; + + /* Increase PWM duty */ + if (rpm_diff > RPM_MARGIN(rpm_target)) { + if (duty == 100) + return FAN_STATUS_FRUSTRATED; + + fan_adjust_duty(ch, rpm_diff, duty); + return FAN_STATUS_CHANGING; + /* Decrease PWM duty */ + } else if (rpm_diff < -RPM_MARGIN(rpm_target)) { + if (duty == 1 && rpm_target != 0) + return FAN_STATUS_FRUSTRATED; + + fan_adjust_duty(ch, rpm_diff, duty); + return FAN_STATUS_CHANGING; + } + + return FAN_STATUS_LOCKED; +} + +void fan_tick_func(void) +{ + int ch; + + for (ch = 0; ch < FAN_CH_COUNT; ch++) { + volatile struct fan_status_t *p_status = fan_status + ch; + /* Make sure rpm mode is enabled */ + if (p_status->current_fan_mode != FAN_RPM) { + /* Fan in duty mode still want rpm_actual being updated. + */ + if (p_status->flags & FAN_USE_RPM_MODE) { + p_status->rpm_actual = fan_rpm(ch); + if (p_status->rpm_actual > 0) + p_status->auto_status = + FAN_STATUS_LOCKED; + else + p_status->auto_status = + FAN_STATUS_STOPPED; + continue; + } else { + if (fan_get_duty(ch) > 0) + p_status->auto_status = + FAN_STATUS_LOCKED; + else + p_status->auto_status = + FAN_STATUS_STOPPED; + } + continue; + } + if (!fan_get_enabled(ch)) + continue; + /* Get actual rpm */ + p_status->rpm_actual = fan_rpm(ch); + /* Do smart fan stuff */ + p_status->auto_status = fan_smart_control( + ch, p_status->rpm_actual, p_status->rpm_target); + } +} +DECLARE_HOOK(HOOK_TICK, fan_tick_func, HOOK_PRIO_DEFAULT); + +int fan_get_duty(int ch) +{ + enum pwm_channel pwm_id = fan_control[ch].pwm_id; + + /* Return percent */ + return pwm_get_duty(pwm_id); +} + +int fan_get_rpm_mode(int ch) +{ + return fan_status[ch].current_fan_mode == FAN_RPM ? 1 : 0; +} + +void fan_set_rpm_mode(int ch, int rpm_mode) +{ + if (rpm_mode && (fan_status[ch].flags & FAN_USE_RPM_MODE)) + fan_status[ch].current_fan_mode = FAN_RPM; + else + fan_status[ch].current_fan_mode = FAN_DUTY; +} + +int fan_get_rpm_actual(int ch) +{ + /* Check PWM is enabled first */ + if (fan_get_duty(ch) == 0) + return 0; + + LOG_DBG("fan %d: get actual rpm = %d", ch, fan_status[ch].rpm_actual); + return fan_status[ch].rpm_actual; +} + +int fan_get_enabled(int ch) +{ + enum pwm_channel pwm_id = fan_control[ch].pwm_id; + + return pwm_get_enabled(pwm_id); +} + +void fan_set_enabled(int ch, int enabled) +{ + enum pwm_channel pwm_id = fan_control[ch].pwm_id; + + if (!enabled) + fan_status[ch].auto_status = FAN_STATUS_STOPPED; + pwm_enable(pwm_id, enabled); +} + +void fan_channel_setup(int ch, unsigned int flags) +{ + volatile struct fan_status_t *p_status = fan_status + ch; + + if (flags & FAN_USE_RPM_MODE) { + DT_FOREACH_CHILD(DT_INST(0, named_fans), TACHO_DEV_INIT) + } + + p_status->flags = flags; + /* Set default fan states */ + p_status->current_fan_mode = FAN_DUTY; + p_status->auto_status = FAN_STATUS_STOPPED; +} + +void fan_set_duty(int ch, int percent) +{ + enum pwm_channel pwm_id = fan_control[ch].pwm_id; + + /* duty is zero */ + if (!percent) { + fan_status[ch].auto_status = FAN_STATUS_STOPPED; + if (fan_all_disabled()) + enable_sleep(SLEEP_MASK_FAN); + } else + disable_sleep(SLEEP_MASK_FAN); + + /* Set the duty cycle of PWM */ + pwm_set_duty(pwm_id, percent); +} + +int fan_get_rpm_target(int ch) +{ + return fan_status[ch].rpm_target; +} + +enum fan_status fan_get_status(int ch) +{ + return fan_status[ch].auto_status; +} + +void fan_set_rpm_target(int ch, int rpm) +{ + if (rpm == 0) { + /* If rpm = 0, disable PWM immediately. Why?*/ + fan_set_duty(ch, 0); + } else { + /* This is the counterpart of disabling PWM above. */ + if (!fan_get_enabled(ch)) + fan_set_enabled(ch, 1); + if (rpm > fans[ch].rpm->rpm_max) + rpm = fans[ch].rpm->rpm_max; + else if (rpm < fans[ch].rpm->rpm_min) + rpm = fans[ch].rpm->rpm_min; + } + + /* Set target rpm */ + fan_status[ch].rpm_target = rpm; + LOG_DBG("fan %d: set target rpm = %d", ch, fan_status[ch].rpm_target); +} + +int fan_is_stalled(int ch) +{ + int is_pgood = 1; + + if (gpio_is_implemented(fans[ch].conf->enable_gpio)) + is_pgood = gpio_get_level(fans[ch].conf->enable_gpio); + + return fan_get_enabled(ch) && fan_get_duty(ch) && + !fan_get_rpm_actual(ch) && is_pgood; +} |