Skip to content

Commit 89c4913

Browse files
committed
Merge tag 'tag-chrome-platform-for-v6.11' of git://git.kernel.org/pub/scm/linux/kernel/git/chrome-platform/linux
Pull chrome platform updates from Tzung-Bi Shih: "New code: - Add "cros_ec_hwmon" driver to expose fan speed and temperature - Add "cros_charge-control" driver to control charge thresholds and behaviour - Add module parameter "log_poll_period_ms" in cros_ec_debugfs for tuning the poll period - Support version 3 of EC_CMD_GET_NEXT_EVENT and keyboard matrix Fixes: - Fix a race condition in accessing MEC (Microchip EC) memory between ACPI and kernel. Serialize the memory access by an AML (ACPI Machine Language) mutex - Fix an issue of wrong EC message version in cros_ec_debugfs Misc: - Fix kernel-doc errors and cleanups" * tag 'tag-chrome-platform-for-v6.11' of git://git.kernel.org/pub/scm/linux/kernel/git/chrome-platform/linux: (28 commits) power: supply: cros_charge-control: Fix signedness bug in charge_behaviour_store() power: supply: cros_charge-control: Avoid accessing attributes out of bounds power: supply: cros_charge-control: don't load if Framework control is present power: supply: add ChromeOS EC based charge control driver platform/chrome: cros_ec_proto: Introduce cros_ec_get_cmd_versions() platform/chrome: Update binary interface for EC-based charge control ACPI: battery: add devm_battery_hook_register() dt-bindings: input: cros-ec-keyboard: Add keyboard matrix v3.0 platform/chrome: cros_ec_lpc: Handle zero length read/write platform/chrome: cros_ec_lpc: Fix error code in cros_ec_lpc_mec_read_bytes() platform/chrome: cros_ec_debugfs: fix wrong EC message version platform/chrome: cros_ec_proto: update Kunit test for get_next_data_v3 platform/chrome: cros_ec_proto: add missing MODULE_DESCRIPTION() macro hwmon: (cros_ec) Fix access to restricted __le16 hwmon: (cros_ec) Prevent read overflow in probe() platform/chrome: cros_ec_lpc: Add quirks for Framework Laptop platform/chrome: cros_ec_lpc: Add a new quirk for AML mutex platform/chrome: cros_ec_lpc: Add a new quirk for ACPI id platform/chrome: cros_ec_lpc: MEC access can use an AML mutex platform/chrome: cros_ec_lpc: MEC access can return error code ...
2 parents d8764c1 + 4baf1cc commit 89c4913

File tree

22 files changed

+1270
-99
lines changed

22 files changed

+1270
-99
lines changed

Documentation/hwmon/cros_ec_hwmon.rst

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
.. SPDX-License-Identifier: GPL-2.0-or-later
2+
3+
Kernel driver cros_ec_hwmon
4+
===========================
5+
6+
Supported chips:
7+
8+
* ChromeOS embedded controllers.
9+
10+
Prefix: 'cros_ec'
11+
12+
Addresses scanned: -
13+
14+
Author:
15+
16+
- Thomas Weißschuh <[email protected]>
17+
18+
Description
19+
-----------
20+
21+
This driver implements support for hardware monitoring commands exposed by the
22+
ChromeOS embedded controller used in Chromebooks and other devices.
23+
24+
The channel labels exposed via hwmon are retrieved from the EC itself.
25+
26+
Fan and temperature readings are supported.

Documentation/hwmon/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ Hardware Monitoring Kernel Drivers
5858
coretemp
5959
corsair-cpro
6060
corsair-psu
61+
cros_ec_hwmon
6162
da9052
6263
da9055
6364
dell-smm-hwmon

MAINTAINERS

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5128,11 +5128,25 @@ S: Maintained
51285128
F: Documentation/devicetree/bindings/sound/google,cros-ec-codec.yaml
51295129
F: sound/soc/codecs/cros_ec_codec.*
51305130

5131+
CHROMEOS EC CHARGE CONTROL
5132+
M: Thomas Weißschuh <[email protected]>
5133+
S: Maintained
5134+
F: drivers/power/supply/cros_charge-control.c
5135+
5136+
CHROMEOS EC HARDWARE MONITORING
5137+
M: Thomas Weißschuh <[email protected]>
5138+
5139+
5140+
S: Maintained
5141+
F: Documentation/hwmon/cros_ec_hwmon.rst
5142+
F: drivers/hwmon/cros_ec_hwmon.c
5143+
51315144
CHROMEOS EC SUBDRIVERS
51325145
M: Benson Leung <[email protected]>
51335146
R: Guenter Roeck <[email protected]>
51345147
51355148
S: Maintained
5149+
F: drivers/power/supply/cros_charge-control.c
51365150
F: drivers/power/supply/cros_usbpd-charger.c
51375151
N: cros_ec
51385152
N: cros-ec

drivers/acpi/battery.c

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -756,6 +756,21 @@ void battery_hook_register(struct acpi_battery_hook *hook)
756756
}
757757
EXPORT_SYMBOL_GPL(battery_hook_register);
758758

759+
static void devm_battery_hook_unregister(void *data)
760+
{
761+
struct acpi_battery_hook *hook = data;
762+
763+
battery_hook_unregister(hook);
764+
}
765+
766+
int devm_battery_hook_register(struct device *dev, struct acpi_battery_hook *hook)
767+
{
768+
battery_hook_register(hook);
769+
770+
return devm_add_action_or_reset(dev, devm_battery_hook_unregister, hook);
771+
}
772+
EXPORT_SYMBOL_GPL(devm_battery_hook_register);
773+
759774
/*
760775
* This function gets called right after the battery sysfs
761776
* attributes have been added, so that the drivers that

drivers/hwmon/Kconfig

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -506,6 +506,17 @@ config SENSORS_CORSAIR_PSU
506506
This driver can also be built as a module. If so, the module
507507
will be called corsair-psu.
508508

509+
config SENSORS_CROS_EC
510+
tristate "ChromeOS Embedded Controller sensors"
511+
depends on MFD_CROS_EC_DEV
512+
default MFD_CROS_EC_DEV
513+
help
514+
If you say yes here you get support for ChromeOS Embedded Controller
515+
sensors.
516+
517+
This driver can also be built as a module. If so, the module
518+
will be called cros_ec_hwmon.
519+
509520
config SENSORS_DRIVETEMP
510521
tristate "Hard disk drives with temperature sensors"
511522
depends on SCSI && ATA

drivers/hwmon/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ obj-$(CONFIG_SENSORS_CHIPCAP2) += chipcap2.o
6464
obj-$(CONFIG_SENSORS_CORETEMP) += coretemp.o
6565
obj-$(CONFIG_SENSORS_CORSAIR_CPRO) += corsair-cpro.o
6666
obj-$(CONFIG_SENSORS_CORSAIR_PSU) += corsair-psu.o
67+
obj-$(CONFIG_SENSORS_CROS_EC) += cros_ec_hwmon.o
6768
obj-$(CONFIG_SENSORS_DA9052_ADC)+= da9052-hwmon.o
6869
obj-$(CONFIG_SENSORS_DA9055)+= da9055-hwmon.o
6970
obj-$(CONFIG_SENSORS_DELL_SMM) += dell-smm-hwmon.o

drivers/hwmon/cros_ec_hwmon.c

Lines changed: 283 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,283 @@
1+
// SPDX-License-Identifier: GPL-2.0-or-later
2+
/*
3+
* ChromeOS EC driver for hwmon
4+
*
5+
* Copyright (C) 2024 Thomas Weißschuh <[email protected]>
6+
*/
7+
8+
#include <linux/device.h>
9+
#include <linux/hwmon.h>
10+
#include <linux/mod_devicetable.h>
11+
#include <linux/module.h>
12+
#include <linux/platform_device.h>
13+
#include <linux/platform_data/cros_ec_commands.h>
14+
#include <linux/platform_data/cros_ec_proto.h>
15+
#include <linux/types.h>
16+
#include <linux/units.h>
17+
18+
#define DRV_NAME "cros-ec-hwmon"
19+
20+
struct cros_ec_hwmon_priv {
21+
struct cros_ec_device *cros_ec;
22+
const char *temp_sensor_names[EC_TEMP_SENSOR_ENTRIES + EC_TEMP_SENSOR_B_ENTRIES];
23+
u8 usable_fans;
24+
};
25+
26+
static int cros_ec_hwmon_read_fan_speed(struct cros_ec_device *cros_ec, u8 index, u16 *speed)
27+
{
28+
int ret;
29+
__le16 __speed;
30+
31+
ret = cros_ec_cmd_readmem(cros_ec, EC_MEMMAP_FAN + index * 2, 2, &__speed);
32+
if (ret < 0)
33+
return ret;
34+
35+
*speed = le16_to_cpu(__speed);
36+
return 0;
37+
}
38+
39+
static int cros_ec_hwmon_read_temp(struct cros_ec_device *cros_ec, u8 index, u8 *temp)
40+
{
41+
unsigned int offset;
42+
int ret;
43+
44+
if (index < EC_TEMP_SENSOR_ENTRIES)
45+
offset = EC_MEMMAP_TEMP_SENSOR + index;
46+
else
47+
offset = EC_MEMMAP_TEMP_SENSOR_B + index - EC_TEMP_SENSOR_ENTRIES;
48+
49+
ret = cros_ec_cmd_readmem(cros_ec, offset, 1, temp);
50+
if (ret < 0)
51+
return ret;
52+
return 0;
53+
}
54+
55+
static bool cros_ec_hwmon_is_error_fan(u16 speed)
56+
{
57+
return speed == EC_FAN_SPEED_NOT_PRESENT || speed == EC_FAN_SPEED_STALLED;
58+
}
59+
60+
static bool cros_ec_hwmon_is_error_temp(u8 temp)
61+
{
62+
return temp == EC_TEMP_SENSOR_NOT_PRESENT ||
63+
temp == EC_TEMP_SENSOR_ERROR ||
64+
temp == EC_TEMP_SENSOR_NOT_POWERED ||
65+
temp == EC_TEMP_SENSOR_NOT_CALIBRATED;
66+
}
67+
68+
static long cros_ec_hwmon_temp_to_millicelsius(u8 temp)
69+
{
70+
return kelvin_to_millicelsius((((long)temp) + EC_TEMP_SENSOR_OFFSET));
71+
}
72+
73+
static int cros_ec_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
74+
u32 attr, int channel, long *val)
75+
{
76+
struct cros_ec_hwmon_priv *priv = dev_get_drvdata(dev);
77+
int ret = -EOPNOTSUPP;
78+
u16 speed;
79+
u8 temp;
80+
81+
if (type == hwmon_fan) {
82+
if (attr == hwmon_fan_input) {
83+
ret = cros_ec_hwmon_read_fan_speed(priv->cros_ec, channel, &speed);
84+
if (ret == 0) {
85+
if (cros_ec_hwmon_is_error_fan(speed))
86+
ret = -ENODATA;
87+
else
88+
*val = speed;
89+
}
90+
} else if (attr == hwmon_fan_fault) {
91+
ret = cros_ec_hwmon_read_fan_speed(priv->cros_ec, channel, &speed);
92+
if (ret == 0)
93+
*val = cros_ec_hwmon_is_error_fan(speed);
94+
}
95+
} else if (type == hwmon_temp) {
96+
if (attr == hwmon_temp_input) {
97+
ret = cros_ec_hwmon_read_temp(priv->cros_ec, channel, &temp);
98+
if (ret == 0) {
99+
if (cros_ec_hwmon_is_error_temp(temp))
100+
ret = -ENODATA;
101+
else
102+
*val = cros_ec_hwmon_temp_to_millicelsius(temp);
103+
}
104+
} else if (attr == hwmon_temp_fault) {
105+
ret = cros_ec_hwmon_read_temp(priv->cros_ec, channel, &temp);
106+
if (ret == 0)
107+
*val = cros_ec_hwmon_is_error_temp(temp);
108+
}
109+
}
110+
111+
return ret;
112+
}
113+
114+
static int cros_ec_hwmon_read_string(struct device *dev, enum hwmon_sensor_types type,
115+
u32 attr, int channel, const char **str)
116+
{
117+
struct cros_ec_hwmon_priv *priv = dev_get_drvdata(dev);
118+
119+
if (type == hwmon_temp && attr == hwmon_temp_label) {
120+
*str = priv->temp_sensor_names[channel];
121+
return 0;
122+
}
123+
124+
return -EOPNOTSUPP;
125+
}
126+
127+
static umode_t cros_ec_hwmon_is_visible(const void *data, enum hwmon_sensor_types type,
128+
u32 attr, int channel)
129+
{
130+
const struct cros_ec_hwmon_priv *priv = data;
131+
132+
if (type == hwmon_fan) {
133+
if (priv->usable_fans & BIT(channel))
134+
return 0444;
135+
} else if (type == hwmon_temp) {
136+
if (priv->temp_sensor_names[channel])
137+
return 0444;
138+
}
139+
140+
return 0;
141+
}
142+
143+
static const struct hwmon_channel_info * const cros_ec_hwmon_info[] = {
144+
HWMON_CHANNEL_INFO(fan,
145+
HWMON_F_INPUT | HWMON_F_FAULT,
146+
HWMON_F_INPUT | HWMON_F_FAULT,
147+
HWMON_F_INPUT | HWMON_F_FAULT,
148+
HWMON_F_INPUT | HWMON_F_FAULT),
149+
HWMON_CHANNEL_INFO(temp,
150+
HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
151+
HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
152+
HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
153+
HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
154+
HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
155+
HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
156+
HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
157+
HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
158+
HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
159+
HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
160+
HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
161+
HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
162+
HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
163+
HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
164+
HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
165+
HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
166+
HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
167+
HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
168+
HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
169+
HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
170+
HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
171+
HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
172+
HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
173+
HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL),
174+
NULL
175+
};
176+
177+
static const struct hwmon_ops cros_ec_hwmon_ops = {
178+
.read = cros_ec_hwmon_read,
179+
.read_string = cros_ec_hwmon_read_string,
180+
.is_visible = cros_ec_hwmon_is_visible,
181+
};
182+
183+
static const struct hwmon_chip_info cros_ec_hwmon_chip_info = {
184+
.ops = &cros_ec_hwmon_ops,
185+
.info = cros_ec_hwmon_info,
186+
};
187+
188+
static void cros_ec_hwmon_probe_temp_sensors(struct device *dev, struct cros_ec_hwmon_priv *priv,
189+
u8 thermal_version)
190+
{
191+
struct ec_params_temp_sensor_get_info req = {};
192+
struct ec_response_temp_sensor_get_info resp;
193+
size_t candidates, i, sensor_name_size;
194+
int ret;
195+
u8 temp;
196+
197+
if (thermal_version < 2)
198+
candidates = EC_TEMP_SENSOR_ENTRIES;
199+
else
200+
candidates = ARRAY_SIZE(priv->temp_sensor_names);
201+
202+
for (i = 0; i < candidates; i++) {
203+
if (cros_ec_hwmon_read_temp(priv->cros_ec, i, &temp) < 0)
204+
continue;
205+
206+
if (temp == EC_TEMP_SENSOR_NOT_PRESENT)
207+
continue;
208+
209+
req.id = i;
210+
ret = cros_ec_cmd(priv->cros_ec, 0, EC_CMD_TEMP_SENSOR_GET_INFO,
211+
&req, sizeof(req), &resp, sizeof(resp));
212+
if (ret < 0)
213+
continue;
214+
215+
sensor_name_size = strnlen(resp.sensor_name, sizeof(resp.sensor_name));
216+
priv->temp_sensor_names[i] = devm_kasprintf(dev, GFP_KERNEL, "%.*s",
217+
(int)sensor_name_size,
218+
resp.sensor_name);
219+
}
220+
}
221+
222+
static void cros_ec_hwmon_probe_fans(struct cros_ec_hwmon_priv *priv)
223+
{
224+
u16 speed;
225+
size_t i;
226+
int ret;
227+
228+
for (i = 0; i < EC_FAN_SPEED_ENTRIES; i++) {
229+
ret = cros_ec_hwmon_read_fan_speed(priv->cros_ec, i, &speed);
230+
if (ret == 0 && speed != EC_FAN_SPEED_NOT_PRESENT)
231+
priv->usable_fans |= BIT(i);
232+
}
233+
}
234+
235+
static int cros_ec_hwmon_probe(struct platform_device *pdev)
236+
{
237+
struct device *dev = &pdev->dev;
238+
struct cros_ec_dev *ec_dev = dev_get_drvdata(dev->parent);
239+
struct cros_ec_device *cros_ec = ec_dev->ec_dev;
240+
struct cros_ec_hwmon_priv *priv;
241+
struct device *hwmon_dev;
242+
u8 thermal_version;
243+
int ret;
244+
245+
ret = cros_ec_cmd_readmem(cros_ec, EC_MEMMAP_THERMAL_VERSION, 1, &thermal_version);
246+
if (ret < 0)
247+
return ret;
248+
249+
/* Covers both fan and temp sensors */
250+
if (thermal_version == 0)
251+
return -ENODEV;
252+
253+
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
254+
if (!priv)
255+
return -ENOMEM;
256+
257+
priv->cros_ec = cros_ec;
258+
259+
cros_ec_hwmon_probe_temp_sensors(dev, priv, thermal_version);
260+
cros_ec_hwmon_probe_fans(priv);
261+
262+
hwmon_dev = devm_hwmon_device_register_with_info(dev, "cros_ec", priv,
263+
&cros_ec_hwmon_chip_info, NULL);
264+
265+
return PTR_ERR_OR_ZERO(hwmon_dev);
266+
}
267+
268+
static const struct platform_device_id cros_ec_hwmon_id[] = {
269+
{ DRV_NAME, 0 },
270+
{}
271+
};
272+
273+
static struct platform_driver cros_ec_hwmon_driver = {
274+
.driver.name = DRV_NAME,
275+
.probe = cros_ec_hwmon_probe,
276+
.id_table = cros_ec_hwmon_id,
277+
};
278+
module_platform_driver(cros_ec_hwmon_driver);
279+
280+
MODULE_DEVICE_TABLE(platform, cros_ec_hwmon_id);
281+
MODULE_DESCRIPTION("ChromeOS EC Hardware Monitoring Driver");
282+
MODULE_AUTHOR("Thomas Weißschuh <[email protected]");
283+
MODULE_LICENSE("GPL");

0 commit comments

Comments
 (0)