Skip to content

Commit fb8e659

Browse files
Sung-Chi Ligroeck
authored andcommitted
hwmon: (cros_ec) add PWM control over fans
Newer EC firmware supports controlling fans through host commands, so adding corresponding implementations for controlling these fans in the driver for other kernel services and userspace to control them. The driver will first probe the supported host command versions (get and set of fan PWM values, get and set of fan control mode) to see if the connected EC fulfills the requirements of controlling the fan, then exposes corresponding sysfs nodes for userspace to control the fan with corresponding read and write implementations. As EC will automatically change the fan mode to auto when the device is suspended, the power management hooks are added as well to keep the fan control mode and fan PWM value consistent during suspend and resume. As we need to access the hwmon device in the power management hook, update the driver by storing the hwmon device in the driver data as well. Signed-off-by: Sung-Chi Li <lschyi@chromium.org> Acked-by: Thomas Weißschuh <linux@weissschuh.net> Link: https://lore.kernel.org/r/20250911-cros_ec_fan-v6-2-a1446cc098af@google.com Signed-off-by: Guenter Roeck <linux@roeck-us.net>
1 parent 60ac65a commit fb8e659

2 files changed

Lines changed: 234 additions & 1 deletion

File tree

Documentation/hwmon/cros_ec_hwmon.rst

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,7 @@ ChromeOS embedded controller used in Chromebooks and other devices.
2323

2424
The channel labels exposed via hwmon are retrieved from the EC itself.
2525

26-
Fan and temperature readings are supported.
26+
Fan and temperature readings are supported. PWM fan control is also supported if
27+
the EC also supports setting fan PWM values and fan mode. Note that EC will
28+
switch fan control mode back to auto when suspended. This driver will restore
29+
the fan state to what they were before suspended when resumed.

drivers/hwmon/cros_ec_hwmon.c

Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
#include <linux/device.h>
99
#include <linux/hwmon.h>
10+
#include <linux/math.h>
1011
#include <linux/mod_devicetable.h>
1112
#include <linux/module.h>
1213
#include <linux/platform_device.h>
@@ -17,10 +18,17 @@
1718

1819
#define DRV_NAME "cros-ec-hwmon"
1920

21+
#define CROS_EC_HWMON_PWM_GET_FAN_DUTY_CMD_VERSION 0
22+
#define CROS_EC_HWMON_PWM_SET_FAN_DUTY_CMD_VERSION 1
23+
#define CROS_EC_HWMON_THERMAL_AUTO_FAN_CTRL_CMD_VERSION 2
24+
2025
struct cros_ec_hwmon_priv {
2126
struct cros_ec_device *cros_ec;
2227
const char *temp_sensor_names[EC_TEMP_SENSOR_ENTRIES + EC_TEMP_SENSOR_B_ENTRIES];
2328
u8 usable_fans;
29+
bool fan_control_supported;
30+
u8 manual_fans; /* bits to indicate whether the fan is set to manual */
31+
u8 manual_fan_pwm[EC_FAN_SPEED_ENTRIES];
2432
};
2533

2634
static int cros_ec_hwmon_read_fan_speed(struct cros_ec_device *cros_ec, u8 index, u16 *speed)
@@ -36,6 +44,42 @@ static int cros_ec_hwmon_read_fan_speed(struct cros_ec_device *cros_ec, u8 index
3644
return 0;
3745
}
3846

47+
static int cros_ec_hwmon_read_pwm_value(struct cros_ec_device *cros_ec, u8 index, u8 *pwm_value)
48+
{
49+
struct ec_params_pwm_get_fan_duty req = {
50+
.fan_idx = index,
51+
};
52+
struct ec_response_pwm_get_fan_duty resp;
53+
int ret;
54+
55+
ret = cros_ec_cmd(cros_ec, CROS_EC_HWMON_PWM_GET_FAN_DUTY_CMD_VERSION,
56+
EC_CMD_PWM_GET_FAN_DUTY, &req, sizeof(req), &resp, sizeof(resp));
57+
if (ret < 0)
58+
return ret;
59+
60+
*pwm_value = (u8)DIV_ROUND_CLOSEST(le32_to_cpu(resp.percent) * 255, 100);
61+
return 0;
62+
}
63+
64+
static int cros_ec_hwmon_read_pwm_enable(struct cros_ec_device *cros_ec, u8 index,
65+
u8 *control_method)
66+
{
67+
struct ec_params_auto_fan_ctrl_v2 req = {
68+
.cmd = EC_AUTO_FAN_CONTROL_CMD_GET,
69+
.fan_idx = index,
70+
};
71+
struct ec_response_auto_fan_control resp;
72+
int ret;
73+
74+
ret = cros_ec_cmd(cros_ec, CROS_EC_HWMON_THERMAL_AUTO_FAN_CTRL_CMD_VERSION,
75+
EC_CMD_THERMAL_AUTO_FAN_CTRL, &req, sizeof(req), &resp, sizeof(resp));
76+
if (ret < 0)
77+
return ret;
78+
79+
*control_method = resp.is_auto ? 2 : 1;
80+
return 0;
81+
}
82+
3983
static int cros_ec_hwmon_read_temp(struct cros_ec_device *cros_ec, u8 index, u8 *temp)
4084
{
4185
unsigned int offset;
@@ -75,6 +119,8 @@ static int cros_ec_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
75119
{
76120
struct cros_ec_hwmon_priv *priv = dev_get_drvdata(dev);
77121
int ret = -EOPNOTSUPP;
122+
u8 control_method;
123+
u8 pwm_value;
78124
u16 speed;
79125
u8 temp;
80126

@@ -92,6 +138,17 @@ static int cros_ec_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
92138
if (ret == 0)
93139
*val = cros_ec_hwmon_is_error_fan(speed);
94140
}
141+
} else if (type == hwmon_pwm) {
142+
if (attr == hwmon_pwm_enable) {
143+
ret = cros_ec_hwmon_read_pwm_enable(priv->cros_ec, channel,
144+
&control_method);
145+
if (ret == 0)
146+
*val = control_method;
147+
} else if (attr == hwmon_pwm_input) {
148+
ret = cros_ec_hwmon_read_pwm_value(priv->cros_ec, channel, &pwm_value);
149+
if (ret == 0)
150+
*val = pwm_value;
151+
}
95152
} else if (type == hwmon_temp) {
96153
if (attr == hwmon_temp_input) {
97154
ret = cros_ec_hwmon_read_temp(priv->cros_ec, channel, &temp);
@@ -124,6 +181,74 @@ static int cros_ec_hwmon_read_string(struct device *dev, enum hwmon_sensor_types
124181
return -EOPNOTSUPP;
125182
}
126183

184+
static int cros_ec_hwmon_set_fan_pwm_val(struct cros_ec_device *cros_ec, u8 index, u8 val)
185+
{
186+
struct ec_params_pwm_set_fan_duty_v1 req = {
187+
.fan_idx = index,
188+
.percent = DIV_ROUND_CLOSEST((uint32_t)val * 100, 255),
189+
};
190+
int ret;
191+
192+
ret = cros_ec_cmd(cros_ec, CROS_EC_HWMON_PWM_SET_FAN_DUTY_CMD_VERSION,
193+
EC_CMD_PWM_SET_FAN_DUTY, &req, sizeof(req), NULL, 0);
194+
if (ret < 0)
195+
return ret;
196+
return 0;
197+
}
198+
199+
static int cros_ec_hwmon_write_pwm_input(struct cros_ec_device *cros_ec, u8 index, u8 val)
200+
{
201+
u8 control_method;
202+
int ret;
203+
204+
ret = cros_ec_hwmon_read_pwm_enable(cros_ec, index, &control_method);
205+
if (ret)
206+
return ret;
207+
if (control_method != 1)
208+
return -EOPNOTSUPP;
209+
210+
return cros_ec_hwmon_set_fan_pwm_val(cros_ec, index, val);
211+
}
212+
213+
static int cros_ec_hwmon_write_pwm_enable(struct cros_ec_device *cros_ec, u8 index, u8 val)
214+
{
215+
struct ec_params_auto_fan_ctrl_v2 req = {
216+
.fan_idx = index,
217+
.cmd = EC_AUTO_FAN_CONTROL_CMD_SET,
218+
};
219+
int ret;
220+
221+
/* No CrOS EC supports no fan speed control */
222+
if (val == 0)
223+
return -EOPNOTSUPP;
224+
225+
req.set_auto = (val != 1) ? true : false;
226+
ret = cros_ec_cmd(cros_ec, CROS_EC_HWMON_THERMAL_AUTO_FAN_CTRL_CMD_VERSION,
227+
EC_CMD_THERMAL_AUTO_FAN_CTRL, &req, sizeof(req), NULL, 0);
228+
if (ret < 0)
229+
return ret;
230+
return 0;
231+
}
232+
233+
static int cros_ec_hwmon_write(struct device *dev, enum hwmon_sensor_types type, u32 attr,
234+
int channel, long val)
235+
{
236+
struct cros_ec_hwmon_priv *priv = dev_get_drvdata(dev);
237+
238+
if (type == hwmon_pwm) {
239+
switch (attr) {
240+
case hwmon_pwm_input:
241+
return cros_ec_hwmon_write_pwm_input(priv->cros_ec, channel, val);
242+
case hwmon_pwm_enable:
243+
return cros_ec_hwmon_write_pwm_enable(priv->cros_ec, channel, val);
244+
default:
245+
return -EOPNOTSUPP;
246+
}
247+
}
248+
249+
return -EOPNOTSUPP;
250+
}
251+
127252
static umode_t cros_ec_hwmon_is_visible(const void *data, enum hwmon_sensor_types type,
128253
u32 attr, int channel)
129254
{
@@ -132,6 +257,9 @@ static umode_t cros_ec_hwmon_is_visible(const void *data, enum hwmon_sensor_type
132257
if (type == hwmon_fan) {
133258
if (priv->usable_fans & BIT(channel))
134259
return 0444;
260+
} else if (type == hwmon_pwm) {
261+
if (priv->fan_control_supported && priv->usable_fans & BIT(channel))
262+
return 0644;
135263
} else if (type == hwmon_temp) {
136264
if (priv->temp_sensor_names[channel])
137265
return 0444;
@@ -147,6 +275,11 @@ static const struct hwmon_channel_info * const cros_ec_hwmon_info[] = {
147275
HWMON_F_INPUT | HWMON_F_FAULT,
148276
HWMON_F_INPUT | HWMON_F_FAULT,
149277
HWMON_F_INPUT | HWMON_F_FAULT),
278+
HWMON_CHANNEL_INFO(pwm,
279+
HWMON_PWM_INPUT | HWMON_PWM_ENABLE,
280+
HWMON_PWM_INPUT | HWMON_PWM_ENABLE,
281+
HWMON_PWM_INPUT | HWMON_PWM_ENABLE,
282+
HWMON_PWM_INPUT | HWMON_PWM_ENABLE),
150283
HWMON_CHANNEL_INFO(temp,
151284
HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
152285
HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
@@ -178,6 +311,7 @@ static const struct hwmon_channel_info * const cros_ec_hwmon_info[] = {
178311
static const struct hwmon_ops cros_ec_hwmon_ops = {
179312
.read = cros_ec_hwmon_read,
180313
.read_string = cros_ec_hwmon_read_string,
314+
.write = cros_ec_hwmon_write,
181315
.is_visible = cros_ec_hwmon_is_visible,
182316
};
183317

@@ -233,6 +367,25 @@ static void cros_ec_hwmon_probe_fans(struct cros_ec_hwmon_priv *priv)
233367
}
234368
}
235369

370+
static inline bool is_cros_ec_cmd_available(struct cros_ec_device *cros_ec,
371+
u16 cmd, u8 version)
372+
{
373+
int ret;
374+
375+
ret = cros_ec_get_cmd_versions(cros_ec, cmd);
376+
return ret >= 0 && (ret & EC_VER_MASK(version));
377+
}
378+
379+
static bool cros_ec_hwmon_probe_fan_control_supported(struct cros_ec_device *cros_ec)
380+
{
381+
return is_cros_ec_cmd_available(cros_ec, EC_CMD_PWM_GET_FAN_DUTY,
382+
CROS_EC_HWMON_PWM_GET_FAN_DUTY_CMD_VERSION) &&
383+
is_cros_ec_cmd_available(cros_ec, EC_CMD_PWM_SET_FAN_DUTY,
384+
CROS_EC_HWMON_PWM_SET_FAN_DUTY_CMD_VERSION) &&
385+
is_cros_ec_cmd_available(cros_ec, EC_CMD_THERMAL_AUTO_FAN_CTRL,
386+
CROS_EC_HWMON_THERMAL_AUTO_FAN_CTRL_CMD_VERSION);
387+
}
388+
236389
static int cros_ec_hwmon_probe(struct platform_device *pdev)
237390
{
238391
struct device *dev = &pdev->dev;
@@ -259,13 +412,88 @@ static int cros_ec_hwmon_probe(struct platform_device *pdev)
259412

260413
cros_ec_hwmon_probe_temp_sensors(dev, priv, thermal_version);
261414
cros_ec_hwmon_probe_fans(priv);
415+
priv->fan_control_supported = cros_ec_hwmon_probe_fan_control_supported(priv->cros_ec);
262416

263417
hwmon_dev = devm_hwmon_device_register_with_info(dev, "cros_ec", priv,
264418
&cros_ec_hwmon_chip_info, NULL);
419+
platform_set_drvdata(pdev, priv);
265420

266421
return PTR_ERR_OR_ZERO(hwmon_dev);
267422
}
268423

424+
static int cros_ec_hwmon_suspend(struct platform_device *pdev, pm_message_t state)
425+
{
426+
struct cros_ec_hwmon_priv *priv = platform_get_drvdata(pdev);
427+
u8 control_method;
428+
size_t i;
429+
int ret;
430+
431+
if (!priv->fan_control_supported)
432+
return 0;
433+
434+
/* EC sets fan control to auto after suspended, store settings before suspending. */
435+
for (i = 0; i < EC_FAN_SPEED_ENTRIES; i++) {
436+
if (!(priv->usable_fans & BIT(i)))
437+
continue;
438+
439+
ret = cros_ec_hwmon_read_pwm_enable(priv->cros_ec, i, &control_method);
440+
if (ret) {
441+
dev_warn(&pdev->dev, "failed to get mode setting for fan %zu: %d\n", i,
442+
ret);
443+
continue;
444+
}
445+
446+
if (control_method != 1) {
447+
priv->manual_fans &= ~BIT(i);
448+
continue;
449+
} else {
450+
priv->manual_fans |= BIT(i);
451+
}
452+
453+
ret = cros_ec_hwmon_read_pwm_value(priv->cros_ec, i, &priv->manual_fan_pwm[i]);
454+
/*
455+
* If storing the value failed, invalidate the stored mode value by setting it
456+
* to auto control. EC will automatically switch to auto mode for that fan after
457+
* suspended.
458+
*/
459+
if (ret) {
460+
dev_warn(&pdev->dev, "failed to get PWM setting for fan %zu: %pe\n", i,
461+
ERR_PTR(ret));
462+
priv->manual_fans &= ~BIT(i);
463+
continue;
464+
}
465+
}
466+
467+
return 0;
468+
}
469+
470+
static int cros_ec_hwmon_resume(struct platform_device *pdev)
471+
{
472+
const struct cros_ec_hwmon_priv *priv = platform_get_drvdata(pdev);
473+
size_t i;
474+
int ret;
475+
476+
if (!priv->fan_control_supported)
477+
return 0;
478+
479+
/* EC sets fan control to auto after suspend, restore to settings before suspend. */
480+
for (i = 0; i < EC_FAN_SPEED_ENTRIES; i++) {
481+
if (!(priv->manual_fans & BIT(i)))
482+
continue;
483+
484+
/*
485+
* Setting fan PWM value to EC will change the mode to manual for that fan in EC as
486+
* well, so we do not need to issue a separate fan mode to manual call.
487+
*/
488+
ret = cros_ec_hwmon_set_fan_pwm_val(priv->cros_ec, i, priv->manual_fan_pwm[i]);
489+
if (ret)
490+
dev_warn(&pdev->dev, "failed to restore settings for fan %zu: %pe\n", i,
491+
ERR_PTR(ret));
492+
}
493+
494+
return 0;
495+
}
496+
269497
static const struct platform_device_id cros_ec_hwmon_id[] = {
270498
{ DRV_NAME, 0 },
271499
{}
@@ -274,6 +502,8 @@ static const struct platform_device_id cros_ec_hwmon_id[] = {
274502
static struct platform_driver cros_ec_hwmon_driver = {
275503
.driver.name = DRV_NAME,
276504
.probe = cros_ec_hwmon_probe,
505+
.suspend = pm_ptr(cros_ec_hwmon_suspend),
506+
.resume = pm_ptr(cros_ec_hwmon_resume),
277507
.id_table = cros_ec_hwmon_id,
278508
};
279509
module_platform_driver(cros_ec_hwmon_driver);

0 commit comments

Comments
 (0)