summaryrefslogtreecommitdiff
path: root/zephyr/shim/src
diff options
context:
space:
mode:
authorDawid Niedzwiecki <dn@semihalf.com>2021-03-02 14:46:32 +0100
committerCommit Bot <commit-bot@chromium.org>2021-03-08 17:31:52 +0000
commitcfbbbb93b44799c61f5591b9658d650136ab5ac4 (patch)
tree40c0a2598c3c8b46f3fb187812d399e5fa3fc2a6 /zephyr/shim/src
parent1a7ba0f3822829ad69d860ee41bb4ae756c4756d (diff)
downloadchrome-ec-cfbbbb93b44799c61f5591b9658d650136ab5ac4.tar.gz
zephyr: add shim fan
Add fan support to Zephyr. The fan is controlled with PWM and the fan speed is measured with the tachometer. Fan properties are defied in the dts file. The struct fan_t fans[] array and enum fan_channel are generated automatically according to named-fans node in the device tree. The implementation is based on the npxc driver, but it is platform-independent. It assumes that there are PWM channel to control the fan and the tachometer sensor to measure RPM (in RPM mode). If it is not the case in the future, the shim may be split per chip. Enable fan related console commands - fanduty, fanset, faninfo, fanauto and host commands - EC_CMD_PWM_GET_FAN_TARGET_RPM, EC_CMD_PWM_SET_FAN_TARGET_RPM, EC_CMD_PWM_SET_FAN_DUTY, EC_CMD_THERMAL_AUTO_FAN_CTRL. BUG=b:174851463 BRANCH=none TEST=build Zephyr TEST=Run "fanset"/"fanduty" commands and check with "faninfo" if the fan follows the command. Then run "fanauto" and verify that the fan controls the temperature correctly. Signed-off-by: Dawid Niedzwiecki <dn@semihalf.com> Change-Id: I52255848e271fb5b204cd1eb8a797e2fbc81130d Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/ec/+/2729375 Reviewed-by: Keith Short <keithshort@chromium.org> Commit-Queue: Keith Short <keithshort@chromium.org>
Diffstat (limited to 'zephyr/shim/src')
-rw-r--r--zephyr/shim/src/CMakeLists.txt1
-rw-r--r--zephyr/shim/src/fan.c380
2 files changed, 381 insertions, 0 deletions
diff --git a/zephyr/shim/src/CMakeLists.txt b/zephyr/shim/src/CMakeLists.txt
index 0311299074..c93736d14e 100644
--- a/zephyr/shim/src/CMakeLists.txt
+++ b/zephyr/shim/src/CMakeLists.txt
@@ -17,6 +17,7 @@ zephyr_sources_ifdef(no_libgcc libgcc_${ARCH}.S)
zephyr_sources_ifdef(CONFIG_PLATFORM_EC_ADC adc.c)
zephyr_sources_ifdef(CONFIG_PLATFORM_EC_ESPI espi.c)
+zephyr_sources_ifdef(CONFIG_PLATFORM_EC_FAN fan.c)
zephyr_sources_ifdef(CONFIG_PLATFORM_EC_FLASH flash.c)
zephyr_sources_ifdef(CONFIG_PLATFORM_EC_HOOKS hooks.c)
zephyr_sources_ifdef(CONFIG_PLATFORM_EC_HOSTCMD host_command.c)
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;
+}