[PATCH 3/5] hwmon: (cros_ec) Add support for PWM fan control

From: Thomas Weißschuh
Date: Sat Jun 08 2024 - 04:13:03 EST


Implement setting fan duty cycle and automatic management through
EC_CMD_PWM_SET_FAN_DUTY and EC_CMD_THERMAL_AUTO_FAN_CTRL.

The hardware does not support reading back the values again,
so that is emulated in the driver.

Signed-off-by: Thomas Weißschuh <linux@xxxxxxxxxxxxxx>
---
Documentation/hwmon/cros_ec_hwmon.rst | 1 +
drivers/hwmon/cros_ec_hwmon.c | 128 +++++++++++++++++++++++++++++++++-
2 files changed, 127 insertions(+), 2 deletions(-)

diff --git a/Documentation/hwmon/cros_ec_hwmon.rst b/Documentation/hwmon/cros_ec_hwmon.rst
index 4aede331baeb..3cc345425aac 100644
--- a/Documentation/hwmon/cros_ec_hwmon.rst
+++ b/Documentation/hwmon/cros_ec_hwmon.rst
@@ -27,4 +27,5 @@ Supported features:

- Current fan speed
- Target fan speed (for fan 1 only)
+ - PWM-based fan control
- Current temperature
diff --git a/drivers/hwmon/cros_ec_hwmon.c b/drivers/hwmon/cros_ec_hwmon.c
index 09b8057e1223..48b7073a4991 100644
--- a/drivers/hwmon/cros_ec_hwmon.c
+++ b/drivers/hwmon/cros_ec_hwmon.c
@@ -7,6 +7,7 @@

#include <linux/device.h>
#include <linux/hwmon.h>
+#include <linux/math.h>
#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/platform_device.h>
@@ -17,10 +18,19 @@

#define DRV_NAME "cros-ec-hwmon"

+enum cros_ec_hwmon_fan_mode {
+ cros_ec_hwmon_fan_mode_full = 0,
+ cros_ec_hwmon_fan_mode_manual = 1,
+ cros_ec_hwmon_fan_mode_auto = 2,
+};
+
struct cros_ec_hwmon_priv {
struct cros_ec_device *cros_ec;
const char *temp_sensor_names[EC_TEMP_SENSOR_ENTRIES + EC_TEMP_SENSOR_B_ENTRIES];
u8 usable_fans;
+ bool has_fan_pwm;
+ u8 fan_pwm[EC_FAN_SPEED_ENTRIES];
+ enum cros_ec_hwmon_fan_mode fan_mode[EC_FAN_SPEED_ENTRIES];
};

static int cros_ec_hwmon_read_fan_speed(struct cros_ec_device *cros_ec, u8 index, u16 *speed)
@@ -50,6 +60,27 @@ static int cros_ec_hwmon_read_fan_target(struct cros_ec_device *cros_ec, u16 *sp
return 0;
}

+static int cros_ec_hwmon_set_fan_auto(struct cros_ec_device *cros_ec, u8 index)
+{
+ struct ec_params_auto_fan_ctrl_v1 req = {
+ .fan_idx = index,
+ };
+
+ return cros_ec_cmd(cros_ec, 1, EC_CMD_THERMAL_AUTO_FAN_CTRL,
+ &req, sizeof(req), NULL, 0);
+}
+
+static int cros_ec_hwmon_set_fan_duty_cycle(struct cros_ec_device *cros_ec, u8 index, u8 percent)
+{
+ struct ec_params_pwm_set_fan_duty_v1 req = {
+ .fan_idx = index,
+ .percent = percent,
+ };
+
+ return cros_ec_cmd(cros_ec, 1, EC_CMD_PWM_SET_FAN_DUTY,
+ &req, sizeof(req), NULL, 0);
+}
+
static int cros_ec_hwmon_read_temp(struct cros_ec_device *cros_ec, u8 index, u8 *temp)
{
unsigned int offset;
@@ -111,6 +142,17 @@ static int cros_ec_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
if (ret == 0)
*val = speed;
}
+
+ } else if (type == hwmon_pwm) {
+ if (attr == hwmon_pwm_input) {
+ *val = priv->fan_pwm[channel];
+ ret = 0;
+
+ } else if (attr == hwmon_pwm_enable) {
+ *val = priv->fan_mode[channel];
+ ret = 0;
+ }
+
} else if (type == hwmon_temp) {
if (attr == hwmon_temp_input) {
ret = cros_ec_hwmon_read_temp(priv->cros_ec, channel, &temp);
@@ -143,6 +185,61 @@ static int cros_ec_hwmon_read_string(struct device *dev, enum hwmon_sensor_types
return -EOPNOTSUPP;
}

+static int cros_ec_hwmon_write_pwm_input(struct cros_ec_hwmon_priv *priv, int channel, long val)
+{
+ u8 percent;
+ int ret;
+
+ if (val < 0 || val > 255)
+ return -EINVAL;
+
+ percent = DIV_ROUND_CLOSEST(val * 100, 255);
+
+ if (priv->fan_mode[channel] == cros_ec_hwmon_fan_mode_manual) {
+ ret = cros_ec_hwmon_set_fan_duty_cycle(priv->cros_ec, channel, percent);
+ if (ret < 0)
+ return ret;
+ }
+
+ priv->fan_pwm[channel] = percent;
+ return 0;
+}
+
+static int cros_ec_hwmon_write_pwm_enable(struct cros_ec_hwmon_priv *priv, int channel, long val)
+{
+ int ret;
+
+ if (val == cros_ec_hwmon_fan_mode_full)
+ ret = cros_ec_hwmon_set_fan_duty_cycle(priv->cros_ec, channel, 100);
+ else if (val == cros_ec_hwmon_fan_mode_manual)
+ ret = cros_ec_hwmon_set_fan_duty_cycle(priv->cros_ec, channel,
+ priv->fan_pwm[channel]);
+ else if (val == cros_ec_hwmon_fan_mode_auto)
+ ret = cros_ec_hwmon_set_fan_auto(priv->cros_ec, channel);
+ else
+ return -EINVAL;
+
+ priv->fan_mode[channel] = val;
+ return ret;
+}
+
+static int cros_ec_hwmon_write(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long val)
+{
+ struct cros_ec_hwmon_priv *priv = dev_get_drvdata(dev);
+ int ret = -EOPNOTSUPP;
+
+ if (type == hwmon_pwm) {
+ if (attr == hwmon_pwm_input)
+ ret = cros_ec_hwmon_write_pwm_input(priv, channel, val);
+
+ else if (attr == hwmon_pwm_enable)
+ ret = cros_ec_hwmon_write_pwm_enable(priv, channel, val);
+ }
+
+ return ret;
+}
+
static umode_t cros_ec_hwmon_is_visible(const void *data, enum hwmon_sensor_types type,
u32 attr, int channel)
{
@@ -156,6 +253,11 @@ static umode_t cros_ec_hwmon_is_visible(const void *data, enum hwmon_sensor_type

if (priv->usable_fans & BIT(channel))
return 0444;
+
+ } else if (type == hwmon_pwm) {
+ if (priv->has_fan_pwm && priv->usable_fans & BIT(channel))
+ return 0644;
+
} else if (type == hwmon_temp) {
if (priv->temp_sensor_names[channel])
return 0444;
@@ -170,6 +272,11 @@ static const struct hwmon_channel_info * const cros_ec_hwmon_info[] = {
HWMON_F_INPUT | HWMON_F_FAULT,
HWMON_F_INPUT | HWMON_F_FAULT,
HWMON_F_INPUT | HWMON_F_FAULT),
+ HWMON_CHANNEL_INFO(pwm,
+ HWMON_PWM_INPUT | HWMON_PWM_ENABLE,
+ HWMON_PWM_INPUT | HWMON_PWM_ENABLE,
+ HWMON_PWM_INPUT | HWMON_PWM_ENABLE,
+ HWMON_PWM_INPUT | HWMON_PWM_ENABLE),
HWMON_CHANNEL_INFO(temp,
HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
@@ -201,6 +308,7 @@ static const struct hwmon_channel_info * const cros_ec_hwmon_info[] = {
static const struct hwmon_ops cros_ec_hwmon_ops = {
.read = cros_ec_hwmon_read,
.read_string = cros_ec_hwmon_read_string,
+ .write = cros_ec_hwmon_write,
.is_visible = cros_ec_hwmon_is_visible,
};

@@ -249,10 +357,26 @@ static void cros_ec_hwmon_probe_fans(struct cros_ec_hwmon_priv *priv)
size_t i;
int ret;

+ ret = cros_ec_cmd_versions(priv->cros_ec, EC_CMD_PWM_SET_FAN_DUTY);
+ if (ret >= 0 && ret & EC_VER_MASK(1))
+ priv->has_fan_pwm = 1;
+
for (i = 0; i < EC_FAN_SPEED_ENTRIES; i++) {
ret = cros_ec_hwmon_read_fan_speed(priv->cros_ec, i, &speed);
- if (ret == 0 && speed != EC_FAN_SPEED_NOT_PRESENT)
- priv->usable_fans |= BIT(i);
+ if (ret < 0)
+ continue;
+ if (speed == EC_FAN_SPEED_NOT_PRESENT)
+ continue;
+
+ priv->usable_fans |= BIT(i);
+
+ if (priv->has_fan_pwm) {
+ priv->fan_mode[i] = cros_ec_hwmon_fan_mode_auto;
+ ret = cros_ec_hwmon_set_fan_auto(priv->cros_ec, i);
+ if (ret != 0)
+ priv->has_fan_pwm = 0;
+ }
+
}
}


--
2.45.2