File Index
Files and directories
Sign in
FILE

cros_ec_hwmon.c

File preview with syntax highlighting.

linux-6.19.14/drivers/hwmon/cros_ec_hwmon.c C
  1// SPDX-License-Identifier: GPL-2.0-or-later
  2/*
  3 *  ChromeOS EC driver for hwmon
  4 *
  5 *  Copyright (C) 2024 Thomas Weißschuh <linux@weissschuh.net>
  6 */
  7
  8#include <linux/device.h>
  9#include <linux/hwmon.h>
 10#include <linux/math.h>
 11#include <linux/mod_devicetable.h>
 12#include <linux/module.h>
 13#include <linux/platform_device.h>
 14#include <linux/platform_data/cros_ec_commands.h>
 15#include <linux/platform_data/cros_ec_proto.h>
 16#include <linux/thermal.h>
 17#include <linux/types.h>
 18#include <linux/units.h>
 19
 20#define DRV_NAME	"cros-ec-hwmon"
 21
 22#define CROS_EC_HWMON_PWM_GET_FAN_DUTY_CMD_VERSION	0
 23#define CROS_EC_HWMON_PWM_SET_FAN_DUTY_CMD_VERSION	1
 24#define CROS_EC_HWMON_THERMAL_AUTO_FAN_CTRL_CMD_VERSION	2
 25
 26struct cros_ec_hwmon_priv {
 27	struct cros_ec_device *cros_ec;
 28	const char *temp_sensor_names[EC_TEMP_SENSOR_ENTRIES + EC_TEMP_SENSOR_B_ENTRIES];
 29	u8 usable_fans;
 30	bool fan_control_supported;
 31	u8 manual_fans; /* bits to indicate whether the fan is set to manual */
 32	u8 manual_fan_pwm[EC_FAN_SPEED_ENTRIES];
 33};
 34
 35struct cros_ec_hwmon_cooling_priv {
 36	struct cros_ec_hwmon_priv *hwmon_priv;
 37	u8 index;
 38};
 39
 40static int cros_ec_hwmon_read_fan_speed(struct cros_ec_device *cros_ec, u8 index, u16 *speed)
 41{
 42	int ret;
 43	__le16 __speed;
 44
 45	ret = cros_ec_cmd_readmem(cros_ec, EC_MEMMAP_FAN + index * 2, 2, &__speed);
 46	if (ret < 0)
 47		return ret;
 48
 49	*speed = le16_to_cpu(__speed);
 50	return 0;
 51}
 52
 53static int cros_ec_hwmon_read_pwm_value(struct cros_ec_device *cros_ec, u8 index, u8 *pwm_value)
 54{
 55	struct ec_params_pwm_get_fan_duty req = {
 56		.fan_idx = index,
 57	};
 58	struct ec_response_pwm_get_fan_duty resp;
 59	int ret;
 60
 61	ret = cros_ec_cmd(cros_ec, CROS_EC_HWMON_PWM_GET_FAN_DUTY_CMD_VERSION,
 62			  EC_CMD_PWM_GET_FAN_DUTY, &req, sizeof(req), &resp, sizeof(resp));
 63	if (ret < 0)
 64		return ret;
 65
 66	*pwm_value = (u8)DIV_ROUND_CLOSEST(le32_to_cpu(resp.percent) * 255, 100);
 67	return 0;
 68}
 69
 70static int cros_ec_hwmon_read_pwm_enable(struct cros_ec_device *cros_ec, u8 index,
 71					 u8 *control_method)
 72{
 73	struct ec_params_auto_fan_ctrl_v2 req = {
 74		.cmd = EC_AUTO_FAN_CONTROL_CMD_GET,
 75		.fan_idx = index,
 76	};
 77	struct ec_response_auto_fan_control resp;
 78	int ret;
 79
 80	ret = cros_ec_cmd(cros_ec, CROS_EC_HWMON_THERMAL_AUTO_FAN_CTRL_CMD_VERSION,
 81			  EC_CMD_THERMAL_AUTO_FAN_CTRL, &req, sizeof(req), &resp, sizeof(resp));
 82	if (ret < 0)
 83		return ret;
 84
 85	*control_method = resp.is_auto ? 2 : 1;
 86	return 0;
 87}
 88
 89static int cros_ec_hwmon_read_temp(struct cros_ec_device *cros_ec, u8 index, u8 *temp)
 90{
 91	unsigned int offset;
 92	int ret;
 93
 94	if (index < EC_TEMP_SENSOR_ENTRIES)
 95		offset = EC_MEMMAP_TEMP_SENSOR + index;
 96	else
 97		offset = EC_MEMMAP_TEMP_SENSOR_B + index - EC_TEMP_SENSOR_ENTRIES;
 98
 99	ret = cros_ec_cmd_readmem(cros_ec, offset, 1, temp);
100	if (ret < 0)
101		return ret;
102	return 0;
103}
104
105static bool cros_ec_hwmon_is_error_fan(u16 speed)
106{
107	return speed == EC_FAN_SPEED_NOT_PRESENT || speed == EC_FAN_SPEED_STALLED;
108}
109
110static bool cros_ec_hwmon_is_error_temp(u8 temp)
111{
112	return temp == EC_TEMP_SENSOR_NOT_PRESENT     ||
113	       temp == EC_TEMP_SENSOR_ERROR           ||
114	       temp == EC_TEMP_SENSOR_NOT_POWERED     ||
115	       temp == EC_TEMP_SENSOR_NOT_CALIBRATED;
116}
117
118static long cros_ec_hwmon_temp_to_millicelsius(u8 temp)
119{
120	return kelvin_to_millicelsius((((long)temp) + EC_TEMP_SENSOR_OFFSET));
121}
122
123static int cros_ec_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
124			      u32 attr, int channel, long *val)
125{
126	struct cros_ec_hwmon_priv *priv = dev_get_drvdata(dev);
127	int ret = -EOPNOTSUPP;
128	u8 control_method;
129	u8 pwm_value;
130	u16 speed;
131	u8 temp;
132
133	if (type == hwmon_fan) {
134		if (attr == hwmon_fan_input) {
135			ret = cros_ec_hwmon_read_fan_speed(priv->cros_ec, channel, &speed);
136			if (ret == 0) {
137				if (cros_ec_hwmon_is_error_fan(speed))
138					ret = -ENODATA;
139				else
140					*val = speed;
141			}
142		} else if (attr == hwmon_fan_fault) {
143			ret = cros_ec_hwmon_read_fan_speed(priv->cros_ec, channel, &speed);
144			if (ret == 0)
145				*val = cros_ec_hwmon_is_error_fan(speed);
146		}
147	} else if (type == hwmon_pwm) {
148		if (attr == hwmon_pwm_enable) {
149			ret = cros_ec_hwmon_read_pwm_enable(priv->cros_ec, channel,
150							    &control_method);
151			if (ret == 0)
152				*val = control_method;
153		} else if (attr == hwmon_pwm_input) {
154			ret = cros_ec_hwmon_read_pwm_value(priv->cros_ec, channel, &pwm_value);
155			if (ret == 0)
156				*val = pwm_value;
157		}
158	} else if (type == hwmon_temp) {
159		if (attr == hwmon_temp_input) {
160			ret = cros_ec_hwmon_read_temp(priv->cros_ec, channel, &temp);
161			if (ret == 0) {
162				if (cros_ec_hwmon_is_error_temp(temp))
163					ret = -ENODATA;
164				else
165					*val = cros_ec_hwmon_temp_to_millicelsius(temp);
166			}
167		} else if (attr == hwmon_temp_fault) {
168			ret = cros_ec_hwmon_read_temp(priv->cros_ec, channel, &temp);
169			if (ret == 0)
170				*val = cros_ec_hwmon_is_error_temp(temp);
171		}
172	}
173
174	return ret;
175}
176
177static int cros_ec_hwmon_read_string(struct device *dev, enum hwmon_sensor_types type,
178				     u32 attr, int channel, const char **str)
179{
180	struct cros_ec_hwmon_priv *priv = dev_get_drvdata(dev);
181
182	if (type == hwmon_temp && attr == hwmon_temp_label) {
183		*str = priv->temp_sensor_names[channel];
184		return 0;
185	}
186
187	return -EOPNOTSUPP;
188}
189
190static int cros_ec_hwmon_set_fan_pwm_val(struct cros_ec_device *cros_ec, u8 index, u8 val)
191{
192	struct ec_params_pwm_set_fan_duty_v1 req = {
193		.fan_idx = index,
194		.percent = DIV_ROUND_CLOSEST((uint32_t)val * 100, 255),
195	};
196	int ret;
197
198	ret = cros_ec_cmd(cros_ec, CROS_EC_HWMON_PWM_SET_FAN_DUTY_CMD_VERSION,
199			  EC_CMD_PWM_SET_FAN_DUTY, &req, sizeof(req), NULL, 0);
200	if (ret < 0)
201		return ret;
202	return 0;
203}
204
205static int cros_ec_hwmon_write_pwm_input(struct cros_ec_device *cros_ec, u8 index, u8 val)
206{
207	u8 control_method;
208	int ret;
209
210	ret = cros_ec_hwmon_read_pwm_enable(cros_ec, index, &control_method);
211	if (ret)
212		return ret;
213	if (control_method != 1)
214		return -EOPNOTSUPP;
215
216	return cros_ec_hwmon_set_fan_pwm_val(cros_ec, index, val);
217}
218
219static int cros_ec_hwmon_write_pwm_enable(struct cros_ec_device *cros_ec, u8 index, u8 val)
220{
221	struct ec_params_auto_fan_ctrl_v2 req = {
222		.fan_idx = index,
223		.cmd = EC_AUTO_FAN_CONTROL_CMD_SET,
224	};
225	int ret;
226
227	/* No CrOS EC supports no fan speed control */
228	if (val == 0)
229		return -EOPNOTSUPP;
230
231	req.set_auto = (val != 1) ? true : false;
232	ret = cros_ec_cmd(cros_ec, CROS_EC_HWMON_THERMAL_AUTO_FAN_CTRL_CMD_VERSION,
233			  EC_CMD_THERMAL_AUTO_FAN_CTRL, &req, sizeof(req), NULL, 0);
234	if (ret < 0)
235		return ret;
236	return 0;
237}
238
239static int cros_ec_hwmon_write(struct device *dev, enum hwmon_sensor_types type, u32 attr,
240			       int channel, long val)
241{
242	struct cros_ec_hwmon_priv *priv = dev_get_drvdata(dev);
243
244	if (type == hwmon_pwm) {
245		switch (attr) {
246		case hwmon_pwm_input:
247			return cros_ec_hwmon_write_pwm_input(priv->cros_ec, channel, val);
248		case hwmon_pwm_enable:
249			return cros_ec_hwmon_write_pwm_enable(priv->cros_ec, channel, val);
250		default:
251			return -EOPNOTSUPP;
252		}
253	}
254
255	return -EOPNOTSUPP;
256}
257
258static umode_t cros_ec_hwmon_is_visible(const void *data, enum hwmon_sensor_types type,
259					u32 attr, int channel)
260{
261	const struct cros_ec_hwmon_priv *priv = data;
262
263	if (type == hwmon_fan) {
264		if (priv->usable_fans & BIT(channel))
265			return 0444;
266	} else if (type == hwmon_pwm) {
267		if (priv->fan_control_supported && priv->usable_fans & BIT(channel))
268			return 0644;
269	} else if (type == hwmon_temp) {
270		if (priv->temp_sensor_names[channel])
271			return 0444;
272	}
273
274	return 0;
275}
276
277static const struct hwmon_channel_info * const cros_ec_hwmon_info[] = {
278	HWMON_CHANNEL_INFO(chip, HWMON_C_REGISTER_TZ),
279	HWMON_CHANNEL_INFO(fan,
280			   HWMON_F_INPUT | HWMON_F_FAULT,
281			   HWMON_F_INPUT | HWMON_F_FAULT,
282			   HWMON_F_INPUT | HWMON_F_FAULT,
283			   HWMON_F_INPUT | HWMON_F_FAULT),
284	HWMON_CHANNEL_INFO(pwm,
285			   HWMON_PWM_INPUT | HWMON_PWM_ENABLE,
286			   HWMON_PWM_INPUT | HWMON_PWM_ENABLE,
287			   HWMON_PWM_INPUT | HWMON_PWM_ENABLE,
288			   HWMON_PWM_INPUT | HWMON_PWM_ENABLE),
289	HWMON_CHANNEL_INFO(temp,
290			   HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
291			   HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
292			   HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
293			   HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
294			   HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
295			   HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
296			   HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
297			   HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
298			   HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
299			   HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
300			   HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
301			   HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
302			   HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
303			   HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
304			   HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
305			   HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
306			   HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
307			   HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
308			   HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
309			   HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
310			   HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
311			   HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
312			   HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
313			   HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL),
314	NULL
315};
316
317static int cros_ec_hwmon_cooling_get_max_state(struct thermal_cooling_device *cdev,
318					       unsigned long *val)
319{
320	*val = 255;
321	return 0;
322}
323
324static int cros_ec_hwmon_cooling_get_cur_state(struct thermal_cooling_device *cdev,
325					       unsigned long *val)
326{
327	const struct cros_ec_hwmon_cooling_priv *priv = cdev->devdata;
328	u8 read_val;
329	int ret;
330
331	ret = cros_ec_hwmon_read_pwm_value(priv->hwmon_priv->cros_ec, priv->index, &read_val);
332	if (ret)
333		return ret;
334
335	*val = read_val;
336	return 0;
337}
338
339static int cros_ec_hwmon_cooling_set_cur_state(struct thermal_cooling_device *cdev,
340					       unsigned long val)
341{
342	const struct cros_ec_hwmon_cooling_priv *priv = cdev->devdata;
343
344	return cros_ec_hwmon_write_pwm_input(priv->hwmon_priv->cros_ec, priv->index, val);
345}
346
347static const struct thermal_cooling_device_ops cros_ec_thermal_cooling_ops = {
348	.get_max_state = cros_ec_hwmon_cooling_get_max_state,
349	.get_cur_state = cros_ec_hwmon_cooling_get_cur_state,
350	.set_cur_state = cros_ec_hwmon_cooling_set_cur_state,
351};
352
353static const struct hwmon_ops cros_ec_hwmon_ops = {
354	.read = cros_ec_hwmon_read,
355	.read_string = cros_ec_hwmon_read_string,
356	.write = cros_ec_hwmon_write,
357	.is_visible = cros_ec_hwmon_is_visible,
358};
359
360static const struct hwmon_chip_info cros_ec_hwmon_chip_info = {
361	.ops = &cros_ec_hwmon_ops,
362	.info = cros_ec_hwmon_info,
363};
364
365static void cros_ec_hwmon_probe_temp_sensors(struct device *dev, struct cros_ec_hwmon_priv *priv,
366					     u8 thermal_version)
367{
368	struct ec_params_temp_sensor_get_info req = {};
369	struct ec_response_temp_sensor_get_info resp;
370	size_t candidates, i, sensor_name_size;
371	int ret;
372	u8 temp;
373
374	if (thermal_version < 2)
375		candidates = EC_TEMP_SENSOR_ENTRIES;
376	else
377		candidates = ARRAY_SIZE(priv->temp_sensor_names);
378
379	for (i = 0; i < candidates; i++) {
380		if (cros_ec_hwmon_read_temp(priv->cros_ec, i, &temp) < 0)
381			continue;
382
383		if (temp == EC_TEMP_SENSOR_NOT_PRESENT)
384			continue;
385
386		req.id = i;
387		ret = cros_ec_cmd(priv->cros_ec, 0, EC_CMD_TEMP_SENSOR_GET_INFO,
388				  &req, sizeof(req), &resp, sizeof(resp));
389		if (ret < 0)
390			continue;
391
392		sensor_name_size = strnlen(resp.sensor_name, sizeof(resp.sensor_name));
393		priv->temp_sensor_names[i] = devm_kasprintf(dev, GFP_KERNEL, "%.*s",
394							    (int)sensor_name_size,
395							    resp.sensor_name);
396	}
397}
398
399static void cros_ec_hwmon_probe_fans(struct cros_ec_hwmon_priv *priv)
400{
401	u16 speed;
402	size_t i;
403	int ret;
404
405	for (i = 0; i < EC_FAN_SPEED_ENTRIES; i++) {
406		ret = cros_ec_hwmon_read_fan_speed(priv->cros_ec, i, &speed);
407		if (ret == 0 && speed != EC_FAN_SPEED_NOT_PRESENT)
408			priv->usable_fans |= BIT(i);
409	}
410}
411
412static inline bool is_cros_ec_cmd_available(struct cros_ec_device *cros_ec,
413					    u16 cmd, u8 version)
414{
415	int ret;
416
417	ret = cros_ec_get_cmd_versions(cros_ec, cmd);
418	return ret >= 0 && (ret & EC_VER_MASK(version));
419}
420
421static bool cros_ec_hwmon_probe_fan_control_supported(struct cros_ec_device *cros_ec)
422{
423	return is_cros_ec_cmd_available(cros_ec, EC_CMD_PWM_GET_FAN_DUTY,
424					CROS_EC_HWMON_PWM_GET_FAN_DUTY_CMD_VERSION) &&
425	       is_cros_ec_cmd_available(cros_ec, EC_CMD_PWM_SET_FAN_DUTY,
426					CROS_EC_HWMON_PWM_SET_FAN_DUTY_CMD_VERSION) &&
427	       is_cros_ec_cmd_available(cros_ec, EC_CMD_THERMAL_AUTO_FAN_CTRL,
428					CROS_EC_HWMON_THERMAL_AUTO_FAN_CTRL_CMD_VERSION);
429}
430
431static void cros_ec_hwmon_register_fan_cooling_devices(struct device *dev,
432						       struct cros_ec_hwmon_priv *priv)
433{
434	struct cros_ec_hwmon_cooling_priv *cpriv;
435	struct thermal_cooling_device *cdev;
436	const char *type;
437	size_t i;
438
439	if (!IS_ENABLED(CONFIG_THERMAL))
440		return;
441
442	if (!priv->fan_control_supported)
443		return;
444
445	for (i = 0; i < EC_FAN_SPEED_ENTRIES; i++) {
446		if (!(priv->usable_fans & BIT(i)))
447			continue;
448
449		cpriv = devm_kzalloc(dev, sizeof(*cpriv), GFP_KERNEL);
450		if (!cpriv)
451			continue;
452
453		type = devm_kasprintf(dev, GFP_KERNEL, "%s-fan%zu", dev_name(dev), i);
454		if (!type) {
455			dev_warn(dev, "no memory to compose cooling device type for fan %zu\n", i);
456			continue;
457		}
458
459		cpriv->hwmon_priv = priv;
460		cpriv->index = i;
461		cdev = devm_thermal_of_cooling_device_register(dev, NULL, type, cpriv,
462							       &cros_ec_thermal_cooling_ops);
463		if (IS_ERR(cdev)) {
464			dev_warn(dev, "failed to register fan %zu as a cooling device: %pe\n", i,
465				 cdev);
466			continue;
467		}
468	}
469}
470
471static int cros_ec_hwmon_probe(struct platform_device *pdev)
472{
473	struct device *dev = &pdev->dev;
474	struct cros_ec_dev *ec_dev = dev_get_drvdata(dev->parent);
475	struct cros_ec_device *cros_ec = ec_dev->ec_dev;
476	struct cros_ec_hwmon_priv *priv;
477	struct device *hwmon_dev;
478	u8 thermal_version;
479	int ret;
480
481	ret = cros_ec_cmd_readmem(cros_ec, EC_MEMMAP_THERMAL_VERSION, 1, &thermal_version);
482	if (ret < 0)
483		return ret;
484
485	/* Covers both fan and temp sensors */
486	if (thermal_version == 0)
487		return -ENODEV;
488
489	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
490	if (!priv)
491		return -ENOMEM;
492
493	priv->cros_ec = cros_ec;
494
495	cros_ec_hwmon_probe_temp_sensors(dev, priv, thermal_version);
496	cros_ec_hwmon_probe_fans(priv);
497	priv->fan_control_supported = cros_ec_hwmon_probe_fan_control_supported(priv->cros_ec);
498	cros_ec_hwmon_register_fan_cooling_devices(dev, priv);
499
500	hwmon_dev = devm_hwmon_device_register_with_info(dev, "cros_ec", priv,
501							 &cros_ec_hwmon_chip_info, NULL);
502	platform_set_drvdata(pdev, priv);
503
504	return PTR_ERR_OR_ZERO(hwmon_dev);
505}
506
507static int cros_ec_hwmon_suspend(struct platform_device *pdev, pm_message_t state)
508{
509	struct cros_ec_hwmon_priv *priv = platform_get_drvdata(pdev);
510	u8 control_method;
511	size_t i;
512	int ret;
513
514	if (!priv->fan_control_supported)
515		return 0;
516
517	/* EC sets fan control to auto after suspended, store settings before suspending. */
518	for (i = 0; i < EC_FAN_SPEED_ENTRIES; i++) {
519		if (!(priv->usable_fans & BIT(i)))
520			continue;
521
522		ret = cros_ec_hwmon_read_pwm_enable(priv->cros_ec, i, &control_method);
523		if (ret) {
524			dev_warn(&pdev->dev, "failed to get mode setting for fan %zu: %d\n", i,
525				 ret);
526			continue;
527		}
528
529		if (control_method != 1) {
530			priv->manual_fans &= ~BIT(i);
531			continue;
532		} else {
533			priv->manual_fans |= BIT(i);
534		}
535
536		ret = cros_ec_hwmon_read_pwm_value(priv->cros_ec, i, &priv->manual_fan_pwm[i]);
537		/*
538		 * If storing the value failed, invalidate the stored mode value by setting it
539		 * to auto control. EC will automatically switch to auto mode for that fan after
540		 * suspended.
541		 */
542		if (ret) {
543			dev_warn(&pdev->dev, "failed to get PWM setting for fan %zu: %pe\n", i,
544				 ERR_PTR(ret));
545			priv->manual_fans &= ~BIT(i);
546			continue;
547		}
548	}
549
550	return 0;
551}
552
553static int cros_ec_hwmon_resume(struct platform_device *pdev)
554{
555	const struct cros_ec_hwmon_priv *priv = platform_get_drvdata(pdev);
556	size_t i;
557	int ret;
558
559	if (!priv->fan_control_supported)
560		return 0;
561
562	/* EC sets fan control to auto after suspend, restore to settings before suspend. */
563	for (i = 0; i < EC_FAN_SPEED_ENTRIES; i++) {
564		if (!(priv->manual_fans & BIT(i)))
565			continue;
566
567		/*
568		 * Setting fan PWM value to EC will change the mode to manual for that fan in EC as
569		 * well, so we do not need to issue a separate fan mode to manual call.
570		 */
571		ret = cros_ec_hwmon_set_fan_pwm_val(priv->cros_ec, i, priv->manual_fan_pwm[i]);
572		if (ret)
573			dev_warn(&pdev->dev, "failed to restore settings for fan %zu: %pe\n", i,
574				 ERR_PTR(ret));
575	}
576
577	return 0;
578}
579
580static const struct platform_device_id cros_ec_hwmon_id[] = {
581	{ DRV_NAME, 0 },
582	{}
583};
584
585static struct platform_driver cros_ec_hwmon_driver = {
586	.driver.name	= DRV_NAME,
587	.probe		= cros_ec_hwmon_probe,
588	.suspend	= pm_ptr(cros_ec_hwmon_suspend),
589	.resume		= pm_ptr(cros_ec_hwmon_resume),
590	.id_table	= cros_ec_hwmon_id,
591};
592module_platform_driver(cros_ec_hwmon_driver);
593
594MODULE_DEVICE_TABLE(platform, cros_ec_hwmon_id);
595MODULE_DESCRIPTION("ChromeOS EC Hardware Monitoring Driver");
596MODULE_AUTHOR("Thomas Weißschuh <linux@weissschuh.net");
597MODULE_LICENSE("GPL");
~/ Read-only file preview.