summaryrefslogtreecommitdiff
path: root/drivers/hwmon
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2024-07-16 03:25:38 +0300
committerLinus Torvalds <torvalds@linux-foundation.org>2024-07-16 03:25:38 +0300
commit89c491389331faea09a247da47ebd95982dae06e (patch)
treeac8101800b1f0ebd5579039a9be51717d274a3b1 /drivers/hwmon
parentd8764c1931a4c91b9b53ee183757f70999da2bb3 (diff)
parent4baf1cc54433ff7c6e5178517bc8768001416681 (diff)
downloadlinux-89c491389331faea09a247da47ebd95982dae06e.tar.xz
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 ...
Diffstat (limited to 'drivers/hwmon')
-rw-r--r--drivers/hwmon/Kconfig11
-rw-r--r--drivers/hwmon/Makefile1
-rw-r--r--drivers/hwmon/cros_ec_hwmon.c283
3 files changed, 295 insertions, 0 deletions
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index e14ae18a973b..702dc45ea405 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -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
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index e3f25475d1f0..4fb14dd1eafd 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -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
diff --git a/drivers/hwmon/cros_ec_hwmon.c b/drivers/hwmon/cros_ec_hwmon.c
new file mode 100644
index 000000000000..5514cf780b8b
--- /dev/null
+++ b/drivers/hwmon/cros_ec_hwmon.c
@@ -0,0 +1,283 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * ChromeOS EC driver for hwmon
+ *
+ * Copyright (C) 2024 Thomas Weißschuh <linux@weissschuh.net>
+ */
+
+#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 <linux@weissschuh.net");
+MODULE_LICENSE("GPL");