Skip to content
/ linux Public
forked from torvalds/linux

Commit

Permalink
Merge tag 'tag-chrome-platform-for-v6.11' of git://git.kernel.org/pub…
Browse files Browse the repository at this point in the history
…/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
  ...
  • Loading branch information
torvalds committed Jul 16, 2024
2 parents d8764c1 + 4baf1cc commit 89c4913
Show file tree
Hide file tree
Showing 22 changed files with 1,270 additions and 99 deletions.
26 changes: 26 additions & 0 deletions Documentation/hwmon/cros_ec_hwmon.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
.. SPDX-License-Identifier: GPL-2.0-or-later
Kernel driver cros_ec_hwmon
===========================

Supported chips:

* ChromeOS embedded controllers.

Prefix: 'cros_ec'

Addresses scanned: -

Author:

- Thomas Weißschuh <[email protected]>

Description
-----------

This driver implements support for hardware monitoring commands exposed by the
ChromeOS embedded controller used in Chromebooks and other devices.

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

Fan and temperature readings are supported.
1 change: 1 addition & 0 deletions Documentation/hwmon/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ Hardware Monitoring Kernel Drivers
coretemp
corsair-cpro
corsair-psu
cros_ec_hwmon
da9052
da9055
dell-smm-hwmon
Expand Down
14 changes: 14 additions & 0 deletions MAINTAINERS
Original file line number Diff line number Diff line change
Expand Up @@ -5128,11 +5128,25 @@ S: Maintained
F: Documentation/devicetree/bindings/sound/google,cros-ec-codec.yaml
F: sound/soc/codecs/cros_ec_codec.*

CHROMEOS EC CHARGE CONTROL
M: Thomas Weißschuh <[email protected]>
S: Maintained
F: drivers/power/supply/cros_charge-control.c

CHROMEOS EC HARDWARE MONITORING
M: Thomas Weißschuh <[email protected]>
L: [email protected]
L: [email protected]
S: Maintained
F: Documentation/hwmon/cros_ec_hwmon.rst
F: drivers/hwmon/cros_ec_hwmon.c

CHROMEOS EC SUBDRIVERS
M: Benson Leung <[email protected]>
R: Guenter Roeck <[email protected]>
L: [email protected]
S: Maintained
F: drivers/power/supply/cros_charge-control.c
F: drivers/power/supply/cros_usbpd-charger.c
N: cros_ec
N: cros-ec
Expand Down
15 changes: 15 additions & 0 deletions drivers/acpi/battery.c
Original file line number Diff line number Diff line change
Expand Up @@ -756,6 +756,21 @@ void battery_hook_register(struct acpi_battery_hook *hook)
}
EXPORT_SYMBOL_GPL(battery_hook_register);

static void devm_battery_hook_unregister(void *data)
{
struct acpi_battery_hook *hook = data;

battery_hook_unregister(hook);
}

int devm_battery_hook_register(struct device *dev, struct acpi_battery_hook *hook)
{
battery_hook_register(hook);

return devm_add_action_or_reset(dev, devm_battery_hook_unregister, hook);
}
EXPORT_SYMBOL_GPL(devm_battery_hook_register);

/*
* This function gets called right after the battery sysfs
* attributes have been added, so that the drivers that
Expand Down
11 changes: 11 additions & 0 deletions drivers/hwmon/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -506,6 +506,17 @@ config SENSORS_CORSAIR_PSU
This driver can also be built as a module. If so, the module
will be called corsair-psu.

config SENSORS_CROS_EC
tristate "ChromeOS Embedded Controller sensors"
depends on MFD_CROS_EC_DEV
default MFD_CROS_EC_DEV
help
If you say yes here you get support for ChromeOS Embedded Controller
sensors.

This driver can also be built as a module. If so, the module
will be called cros_ec_hwmon.

config SENSORS_DRIVETEMP
tristate "Hard disk drives with temperature sensors"
depends on SCSI && ATA
Expand Down
1 change: 1 addition & 0 deletions drivers/hwmon/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ obj-$(CONFIG_SENSORS_CHIPCAP2) += chipcap2.o
obj-$(CONFIG_SENSORS_CORETEMP) += coretemp.o
obj-$(CONFIG_SENSORS_CORSAIR_CPRO) += corsair-cpro.o
obj-$(CONFIG_SENSORS_CORSAIR_PSU) += corsair-psu.o
obj-$(CONFIG_SENSORS_CROS_EC) += cros_ec_hwmon.o
obj-$(CONFIG_SENSORS_DA9052_ADC)+= da9052-hwmon.o
obj-$(CONFIG_SENSORS_DA9055)+= da9055-hwmon.o
obj-$(CONFIG_SENSORS_DELL_SMM) += dell-smm-hwmon.o
Expand Down
283 changes: 283 additions & 0 deletions drivers/hwmon/cros_ec_hwmon.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,283 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* ChromeOS EC driver for hwmon
*
* Copyright (C) 2024 Thomas Weißschuh <[email protected]>
*/

#include <linux/device.h>
#include <linux/hwmon.h>
#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/platform_data/cros_ec_commands.h>
#include <linux/platform_data/cros_ec_proto.h>
#include <linux/types.h>
#include <linux/units.h>

#define DRV_NAME "cros-ec-hwmon"

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;
};

static int cros_ec_hwmon_read_fan_speed(struct cros_ec_device *cros_ec, u8 index, u16 *speed)
{
int ret;
__le16 __speed;

ret = cros_ec_cmd_readmem(cros_ec, EC_MEMMAP_FAN + index * 2, 2, &__speed);
if (ret < 0)
return ret;

*speed = le16_to_cpu(__speed);
return 0;
}

static int cros_ec_hwmon_read_temp(struct cros_ec_device *cros_ec, u8 index, u8 *temp)
{
unsigned int offset;
int ret;

if (index < EC_TEMP_SENSOR_ENTRIES)
offset = EC_MEMMAP_TEMP_SENSOR + index;
else
offset = EC_MEMMAP_TEMP_SENSOR_B + index - EC_TEMP_SENSOR_ENTRIES;

ret = cros_ec_cmd_readmem(cros_ec, offset, 1, temp);
if (ret < 0)
return ret;
return 0;
}

static bool cros_ec_hwmon_is_error_fan(u16 speed)
{
return speed == EC_FAN_SPEED_NOT_PRESENT || speed == EC_FAN_SPEED_STALLED;
}

static bool cros_ec_hwmon_is_error_temp(u8 temp)
{
return temp == EC_TEMP_SENSOR_NOT_PRESENT ||
temp == EC_TEMP_SENSOR_ERROR ||
temp == EC_TEMP_SENSOR_NOT_POWERED ||
temp == EC_TEMP_SENSOR_NOT_CALIBRATED;
}

static long cros_ec_hwmon_temp_to_millicelsius(u8 temp)
{
return kelvin_to_millicelsius((((long)temp) + EC_TEMP_SENSOR_OFFSET));
}

static int cros_ec_hwmon_read(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;
u16 speed;
u8 temp;

if (type == hwmon_fan) {
if (attr == hwmon_fan_input) {
ret = cros_ec_hwmon_read_fan_speed(priv->cros_ec, channel, &speed);
if (ret == 0) {
if (cros_ec_hwmon_is_error_fan(speed))
ret = -ENODATA;
else
*val = speed;
}
} else if (attr == hwmon_fan_fault) {
ret = cros_ec_hwmon_read_fan_speed(priv->cros_ec, channel, &speed);
if (ret == 0)
*val = cros_ec_hwmon_is_error_fan(speed);
}
} else if (type == hwmon_temp) {
if (attr == hwmon_temp_input) {
ret = cros_ec_hwmon_read_temp(priv->cros_ec, channel, &temp);
if (ret == 0) {
if (cros_ec_hwmon_is_error_temp(temp))
ret = -ENODATA;
else
*val = cros_ec_hwmon_temp_to_millicelsius(temp);
}
} else if (attr == hwmon_temp_fault) {
ret = cros_ec_hwmon_read_temp(priv->cros_ec, channel, &temp);
if (ret == 0)
*val = cros_ec_hwmon_is_error_temp(temp);
}
}

return ret;
}

static int cros_ec_hwmon_read_string(struct device *dev, enum hwmon_sensor_types type,
u32 attr, int channel, const char **str)
{
struct cros_ec_hwmon_priv *priv = dev_get_drvdata(dev);

if (type == hwmon_temp && attr == hwmon_temp_label) {
*str = priv->temp_sensor_names[channel];
return 0;
}

return -EOPNOTSUPP;
}

static umode_t cros_ec_hwmon_is_visible(const void *data, enum hwmon_sensor_types type,
u32 attr, int channel)
{
const struct cros_ec_hwmon_priv *priv = data;

if (type == hwmon_fan) {
if (priv->usable_fans & BIT(channel))
return 0444;
} else if (type == hwmon_temp) {
if (priv->temp_sensor_names[channel])
return 0444;
}

return 0;
}

static const struct hwmon_channel_info * const cros_ec_hwmon_info[] = {
HWMON_CHANNEL_INFO(fan,
HWMON_F_INPUT | HWMON_F_FAULT,
HWMON_F_INPUT | HWMON_F_FAULT,
HWMON_F_INPUT | HWMON_F_FAULT,
HWMON_F_INPUT | HWMON_F_FAULT),
HWMON_CHANNEL_INFO(temp,
HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL,
HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL),
NULL
};

static const struct hwmon_ops cros_ec_hwmon_ops = {
.read = cros_ec_hwmon_read,
.read_string = cros_ec_hwmon_read_string,
.is_visible = cros_ec_hwmon_is_visible,
};

static const struct hwmon_chip_info cros_ec_hwmon_chip_info = {
.ops = &cros_ec_hwmon_ops,
.info = cros_ec_hwmon_info,
};

static void cros_ec_hwmon_probe_temp_sensors(struct device *dev, struct cros_ec_hwmon_priv *priv,
u8 thermal_version)
{
struct ec_params_temp_sensor_get_info req = {};
struct ec_response_temp_sensor_get_info resp;
size_t candidates, i, sensor_name_size;
int ret;
u8 temp;

if (thermal_version < 2)
candidates = EC_TEMP_SENSOR_ENTRIES;
else
candidates = ARRAY_SIZE(priv->temp_sensor_names);

for (i = 0; i < candidates; i++) {
if (cros_ec_hwmon_read_temp(priv->cros_ec, i, &temp) < 0)
continue;

if (temp == EC_TEMP_SENSOR_NOT_PRESENT)
continue;

req.id = i;
ret = cros_ec_cmd(priv->cros_ec, 0, EC_CMD_TEMP_SENSOR_GET_INFO,
&req, sizeof(req), &resp, sizeof(resp));
if (ret < 0)
continue;

sensor_name_size = strnlen(resp.sensor_name, sizeof(resp.sensor_name));
priv->temp_sensor_names[i] = devm_kasprintf(dev, GFP_KERNEL, "%.*s",
(int)sensor_name_size,
resp.sensor_name);
}
}

static void cros_ec_hwmon_probe_fans(struct cros_ec_hwmon_priv *priv)
{
u16 speed;
size_t i;
int ret;

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);
}
}

static int cros_ec_hwmon_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct cros_ec_dev *ec_dev = dev_get_drvdata(dev->parent);
struct cros_ec_device *cros_ec = ec_dev->ec_dev;
struct cros_ec_hwmon_priv *priv;
struct device *hwmon_dev;
u8 thermal_version;
int ret;

ret = cros_ec_cmd_readmem(cros_ec, EC_MEMMAP_THERMAL_VERSION, 1, &thermal_version);
if (ret < 0)
return ret;

/* Covers both fan and temp sensors */
if (thermal_version == 0)
return -ENODEV;

priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;

priv->cros_ec = cros_ec;

cros_ec_hwmon_probe_temp_sensors(dev, priv, thermal_version);
cros_ec_hwmon_probe_fans(priv);

hwmon_dev = devm_hwmon_device_register_with_info(dev, "cros_ec", priv,
&cros_ec_hwmon_chip_info, NULL);

return PTR_ERR_OR_ZERO(hwmon_dev);
}

static const struct platform_device_id cros_ec_hwmon_id[] = {
{ DRV_NAME, 0 },
{}
};

static struct platform_driver cros_ec_hwmon_driver = {
.driver.name = DRV_NAME,
.probe = cros_ec_hwmon_probe,
.id_table = cros_ec_hwmon_id,
};
module_platform_driver(cros_ec_hwmon_driver);

MODULE_DEVICE_TABLE(platform, cros_ec_hwmon_id);
MODULE_DESCRIPTION("ChromeOS EC Hardware Monitoring Driver");
MODULE_AUTHOR("Thomas Weißschuh <[email protected]");
MODULE_LICENSE("GPL");
Loading

0 comments on commit 89c4913

Please sign in to comment.