summaryrefslogtreecommitdiff
path: root/zephyr/shim/src/fan.c
diff options
context:
space:
mode:
Diffstat (limited to 'zephyr/shim/src/fan.c')
-rw-r--r--zephyr/shim/src/fan.c380
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;
+}