From 4dfbbe3f8f723e5178404712b6b59d9c74250d93 Mon Sep 17 00:00:00 2001 From: Rong Zhang Date: Wed, 21 Jan 2026 02:20:02 +0800 Subject: [PATCH 01/13] [FROM-UPSTREAM] platform/x86: lenovo-wmi-helpers: Convert returned buffer into u32 The Windows WMI-ACPI driver converts all ACPI objects into a common buffer format, so returning a buffer with four bytes will look like an integer for WMI consumers under Windows. Therefore, some devices may simply implement the corresponding ACPI methods to always return a buffer. While lwmi_dev_evaluate_int() expects an integer (u32), convert returned >=4B buffer into u32 to support these devices. Suggested-by: Armin Wolf Link: https://lore.kernel.org/r/f1787927-b655-4321-b9d9-bc12353c72db@gmx.de/ Signed-off-by: Rong Zhang Reviewed-by: Derek J. Clark Tested-by: Derek J. Clark Reviewed-by: Armin Wolf Reviewed-by: Mark Pearson Signed-off-by: Derek J. Clark --- drivers/platform/x86/lenovo/wmi-helpers.c | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/drivers/platform/x86/lenovo/wmi-helpers.c b/drivers/platform/x86/lenovo/wmi-helpers.c index f6fef6296251e..7379defac5002 100644 --- a/drivers/platform/x86/lenovo/wmi-helpers.c +++ b/drivers/platform/x86/lenovo/wmi-helpers.c @@ -21,6 +21,7 @@ #include #include #include +#include #include #include "wmi-helpers.h" @@ -59,10 +60,24 @@ int lwmi_dev_evaluate_int(struct wmi_device *wdev, u8 instance, u32 method_id, if (!ret_obj) return -ENODATA; - if (ret_obj->type != ACPI_TYPE_INTEGER) - return -ENXIO; + switch (ret_obj->type) { + /* + * The ACPI method may simply return a buffer when a u32 + * is expected. This is valid on Windows as its WMI-ACPI + * driver converts everything to a common buffer. + */ + case ACPI_TYPE_BUFFER: + if (ret_obj->buffer.length < sizeof(u32)) + return -ENXIO; - *retval = (u32)ret_obj->integer.value; + *retval = get_unaligned_le32(ret_obj->buffer.pointer); + return 0; + case ACPI_TYPE_INTEGER: + *retval = (u32)ret_obj->integer.value; + return 0; + default: + return -ENXIO; + } } return 0; From bc7cde7279687da3e65ff651df72b959e9bf7227 Mon Sep 17 00:00:00 2001 From: Rong Zhang Date: Wed, 21 Jan 2026 02:20:03 +0800 Subject: [PATCH 02/13] [FROM-UPSTREAM] platform/x86: Rename lenovo-wmi-capdata01 to lenovo-wmi-capdata Prepare for the upcoming changes to make it suitable to retrieve and provide other Capability Data as well. Signed-off-by: Rong Zhang Reviewed-by: Derek J. Clark Tested-by: Derek J. Clark Reviewed-by: Mark Pearson Signed-off-by: Derek J. Clark --- drivers/platform/x86/lenovo/Kconfig | 4 +- drivers/platform/x86/lenovo/Makefile | 2 +- .../lenovo/{wmi-capdata01.c => wmi-capdata.c} | 124 +++++++++--------- .../lenovo/{wmi-capdata01.h => wmi-capdata.h} | 10 +- drivers/platform/x86/lenovo/wmi-other.c | 11 +- 5 files changed, 78 insertions(+), 73 deletions(-) rename drivers/platform/x86/lenovo/{wmi-capdata01.c => wmi-capdata.c} (60%) rename drivers/platform/x86/lenovo/{wmi-capdata01.h => wmi-capdata.h} (60%) diff --git a/drivers/platform/x86/lenovo/Kconfig b/drivers/platform/x86/lenovo/Kconfig index d22b774e0236f..587da1c602ca0 100644 --- a/drivers/platform/x86/lenovo/Kconfig +++ b/drivers/platform/x86/lenovo/Kconfig @@ -233,7 +233,7 @@ config YT2_1380 To compile this driver as a module, choose M here: the module will be called lenovo-yogabook. -config LENOVO_WMI_DATA01 +config LENOVO_WMI_CAPDATA tristate depends on ACPI_WMI @@ -264,7 +264,7 @@ config LENOVO_WMI_TUNING tristate "Lenovo Other Mode WMI Driver" depends on ACPI_WMI select FW_ATTR_CLASS - select LENOVO_WMI_DATA01 + select LENOVO_WMI_CAPDATA select LENOVO_WMI_EVENTS select LENOVO_WMI_HELPERS help diff --git a/drivers/platform/x86/lenovo/Makefile b/drivers/platform/x86/lenovo/Makefile index 7b2128e3a2142..91a9370f11b3a 100644 --- a/drivers/platform/x86/lenovo/Makefile +++ b/drivers/platform/x86/lenovo/Makefile @@ -12,7 +12,7 @@ lenovo-target-$(CONFIG_LENOVO_YMC) += ymc.o lenovo-target-$(CONFIG_YOGABOOK) += yogabook.o lenovo-target-$(CONFIG_YT2_1380) += yoga-tab2-pro-1380-fastcharger.o lenovo-target-$(CONFIG_LENOVO_WMI_CAMERA) += wmi-camera.o -lenovo-target-$(CONFIG_LENOVO_WMI_DATA01) += wmi-capdata01.o +lenovo-target-$(CONFIG_LENOVO_WMI_CAPDATA) += wmi-capdata.o lenovo-target-$(CONFIG_LENOVO_WMI_EVENTS) += wmi-events.o lenovo-target-$(CONFIG_LENOVO_WMI_HELPERS) += wmi-helpers.o lenovo-target-$(CONFIG_LENOVO_WMI_GAMEZONE) += wmi-gamezone.o diff --git a/drivers/platform/x86/lenovo/wmi-capdata01.c b/drivers/platform/x86/lenovo/wmi-capdata.c similarity index 60% rename from drivers/platform/x86/lenovo/wmi-capdata01.c rename to drivers/platform/x86/lenovo/wmi-capdata.c index fc7e3454e71dc..ba843b6604b06 100644 --- a/drivers/platform/x86/lenovo/wmi-capdata01.c +++ b/drivers/platform/x86/lenovo/wmi-capdata.c @@ -1,14 +1,17 @@ // SPDX-License-Identifier: GPL-2.0-or-later /* - * Lenovo Capability Data 01 WMI Data Block driver. + * Lenovo Capability Data WMI Data Block driver. * - * Lenovo Capability Data 01 provides information on tunable attributes used by - * the "Other Mode" WMI interface. The data includes if the attribute is - * supported by the hardware, the default_value, max_value, min_value, and step - * increment. Each attribute has multiple pages, one for each of the thermal - * modes managed by the Gamezone interface. + * Lenovo Capability Data provides information on tunable attributes used by + * the "Other Mode" WMI interface. + * + * Capability Data 01 includes if the attribute is supported by the hardware, + * and the default_value, max_value, min_value, and step increment. Each + * attribute has multiple pages, one for each of the thermal modes managed by + * the Gamezone interface. * * Copyright (C) 2025 Derek J. Clark + * - Initial implementation (formerly named lenovo-wmi-capdata01) */ #include @@ -26,55 +29,55 @@ #include #include -#include "wmi-capdata01.h" +#include "wmi-capdata.h" #define LENOVO_CAPABILITY_DATA_01_GUID "7A8F5407-CB67-4D6E-B547-39B3BE018154" #define ACPI_AC_CLASS "ac_adapter" #define ACPI_AC_NOTIFY_STATUS 0x80 -struct lwmi_cd01_priv { +struct lwmi_cd_priv { struct notifier_block acpi_nb; /* ACPI events */ struct wmi_device *wdev; - struct cd01_list *list; + struct cd_list *list; }; -struct cd01_list { +struct cd_list { struct mutex list_mutex; /* list R/W mutex */ u8 count; struct capdata01 data[]; }; /** - * lwmi_cd01_component_bind() - Bind component to master device. - * @cd01_dev: Pointer to the lenovo-wmi-capdata01 driver parent device. + * lwmi_cd_component_bind() - Bind component to master device. + * @cd_dev: Pointer to the lenovo-wmi-capdata driver parent device. * @om_dev: Pointer to the lenovo-wmi-other driver parent device. - * @data: capdata01_list object pointer used to return the capability data. + * @data: cd_list object pointer used to return the capability data. * - * On lenovo-wmi-other's master bind, provide a pointer to the local capdata01 - * list. This is used to call lwmi_cd01_get_data to look up attribute data + * On lenovo-wmi-other's master bind, provide a pointer to the local capdata + * list. This is used to call lwmi_cd*_get_data to look up attribute data * from the lenovo-wmi-other driver. * * Return: 0 */ -static int lwmi_cd01_component_bind(struct device *cd01_dev, - struct device *om_dev, void *data) +static int lwmi_cd_component_bind(struct device *cd_dev, + struct device *om_dev, void *data) { - struct lwmi_cd01_priv *priv = dev_get_drvdata(cd01_dev); - struct cd01_list **cd01_list = data; + struct lwmi_cd_priv *priv = dev_get_drvdata(cd_dev); + struct cd_list **cd_list = data; - *cd01_list = priv->list; + *cd_list = priv->list; return 0; } -static const struct component_ops lwmi_cd01_component_ops = { - .bind = lwmi_cd01_component_bind, +static const struct component_ops lwmi_cd_component_ops = { + .bind = lwmi_cd_component_bind, }; /** * lwmi_cd01_get_data - Get the data of the specified attribute - * @list: The lenovo-wmi-capdata01 pointer to its cd01_list struct. + * @list: The lenovo-wmi-capdata pointer to its cd_list struct. * @attribute_id: The capdata attribute ID to be found. * @output: Pointer to a capdata01 struct to return the data. * @@ -83,7 +86,7 @@ static const struct component_ops lwmi_cd01_component_ops = { * * Return: 0 on success, or -EINVAL. */ -int lwmi_cd01_get_data(struct cd01_list *list, u32 attribute_id, struct capdata01 *output) +int lwmi_cd01_get_data(struct cd_list *list, u32 attribute_id, struct capdata01 *output) { u8 idx; @@ -97,17 +100,17 @@ int lwmi_cd01_get_data(struct cd01_list *list, u32 attribute_id, struct capdata0 return -EINVAL; } -EXPORT_SYMBOL_NS_GPL(lwmi_cd01_get_data, "LENOVO_WMI_CD01"); +EXPORT_SYMBOL_NS_GPL(lwmi_cd01_get_data, "LENOVO_WMI_CAPDATA"); /** - * lwmi_cd01_cache() - Cache all WMI data block information - * @priv: lenovo-wmi-capdata01 driver data. + * lwmi_cd_cache() - Cache all WMI data block information + * @priv: lenovo-wmi-capdata driver data. * * Loop through each WMI data block and cache the data. * * Return: 0 on success, or an error. */ -static int lwmi_cd01_cache(struct lwmi_cd01_priv *priv) +static int lwmi_cd_cache(struct lwmi_cd_priv *priv) { int idx; @@ -131,17 +134,17 @@ static int lwmi_cd01_cache(struct lwmi_cd01_priv *priv) } /** - * lwmi_cd01_alloc() - Allocate a cd01_list struct in drvdata - * @priv: lenovo-wmi-capdata01 driver data. + * lwmi_cd_alloc() - Allocate a cd_list struct in drvdata + * @priv: lenovo-wmi-capdata driver data. * - * Allocate a cd01_list struct large enough to contain data from all WMI data + * Allocate a cd_list struct large enough to contain data from all WMI data * blocks provided by the interface. * * Return: 0 on success, or an error. */ -static int lwmi_cd01_alloc(struct lwmi_cd01_priv *priv) +static int lwmi_cd_alloc(struct lwmi_cd_priv *priv) { - struct cd01_list *list; + struct cd_list *list; size_t list_size; int count, ret; @@ -163,28 +166,28 @@ static int lwmi_cd01_alloc(struct lwmi_cd01_priv *priv) } /** - * lwmi_cd01_setup() - Cache all WMI data block information - * @priv: lenovo-wmi-capdata01 driver data. + * lwmi_cd_setup() - Cache all WMI data block information + * @priv: lenovo-wmi-capdata driver data. * - * Allocate a cd01_list struct large enough to contain data from all WMI data + * Allocate a cd_list struct large enough to contain data from all WMI data * blocks provided by the interface. Then loop through each data block and * cache the data. * * Return: 0 on success, or an error code. */ -static int lwmi_cd01_setup(struct lwmi_cd01_priv *priv) +static int lwmi_cd_setup(struct lwmi_cd_priv *priv) { int ret; - ret = lwmi_cd01_alloc(priv); + ret = lwmi_cd_alloc(priv); if (ret) return ret; - return lwmi_cd01_cache(priv); + return lwmi_cd_cache(priv); } /** - * lwmi_cd01_notifier_call() - Call method for lenovo-wmi-capdata01 driver notifier. + * lwmi_cd01_notifier_call() - Call method for cd01 notifier. * block call chain. * @nb: The notifier_block registered to lenovo-wmi-events driver. * @action: Unused. @@ -199,17 +202,17 @@ static int lwmi_cd01_notifier_call(struct notifier_block *nb, unsigned long acti void *data) { struct acpi_bus_event *event = data; - struct lwmi_cd01_priv *priv; + struct lwmi_cd_priv *priv; int ret; if (strcmp(event->device_class, ACPI_AC_CLASS) != 0) return NOTIFY_DONE; - priv = container_of(nb, struct lwmi_cd01_priv, acpi_nb); + priv = container_of(nb, struct lwmi_cd_priv, acpi_nb); switch (event->type) { case ACPI_AC_NOTIFY_STATUS: - ret = lwmi_cd01_cache(priv); + ret = lwmi_cd_cache(priv); if (ret) return NOTIFY_BAD; @@ -230,10 +233,9 @@ static void lwmi_cd01_unregister(void *data) unregister_acpi_notifier(acpi_nb); } -static int lwmi_cd01_probe(struct wmi_device *wdev, const void *context) - +static int lwmi_cd_probe(struct wmi_device *wdev, const void *context) { - struct lwmi_cd01_priv *priv; + struct lwmi_cd_priv *priv; int ret; priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL); @@ -243,7 +245,7 @@ static int lwmi_cd01_probe(struct wmi_device *wdev, const void *context) priv->wdev = wdev; dev_set_drvdata(&wdev->dev, priv); - ret = lwmi_cd01_setup(priv); + ret = lwmi_cd_setup(priv); if (ret) return ret; @@ -257,27 +259,27 @@ static int lwmi_cd01_probe(struct wmi_device *wdev, const void *context) if (ret) return ret; - return component_add(&wdev->dev, &lwmi_cd01_component_ops); + return component_add(&wdev->dev, &lwmi_cd_component_ops); } -static void lwmi_cd01_remove(struct wmi_device *wdev) +static void lwmi_cd_remove(struct wmi_device *wdev) { - component_del(&wdev->dev, &lwmi_cd01_component_ops); + component_del(&wdev->dev, &lwmi_cd_component_ops); } -static const struct wmi_device_id lwmi_cd01_id_table[] = { +static const struct wmi_device_id lwmi_cd_id_table[] = { { LENOVO_CAPABILITY_DATA_01_GUID, NULL }, {} }; -static struct wmi_driver lwmi_cd01_driver = { +static struct wmi_driver lwmi_cd_driver = { .driver = { - .name = "lenovo_wmi_cd01", + .name = "lenovo_wmi_capdata", .probe_type = PROBE_PREFER_ASYNCHRONOUS, }, - .id_table = lwmi_cd01_id_table, - .probe = lwmi_cd01_probe, - .remove = lwmi_cd01_remove, + .id_table = lwmi_cd_id_table, + .probe = lwmi_cd_probe, + .remove = lwmi_cd_remove, .no_singleton = true, }; @@ -290,13 +292,13 @@ static struct wmi_driver lwmi_cd01_driver = { */ int lwmi_cd01_match(struct device *dev, void *data) { - return dev->driver == &lwmi_cd01_driver.driver; + return dev->driver == &lwmi_cd_driver.driver; } -EXPORT_SYMBOL_NS_GPL(lwmi_cd01_match, "LENOVO_WMI_CD01"); +EXPORT_SYMBOL_NS_GPL(lwmi_cd01_match, "LENOVO_WMI_CAPDATA"); -module_wmi_driver(lwmi_cd01_driver); +module_wmi_driver(lwmi_cd_driver); -MODULE_DEVICE_TABLE(wmi, lwmi_cd01_id_table); +MODULE_DEVICE_TABLE(wmi, lwmi_cd_id_table); MODULE_AUTHOR("Derek J. Clark "); -MODULE_DESCRIPTION("Lenovo Capability Data 01 WMI Driver"); +MODULE_DESCRIPTION("Lenovo Capability Data WMI Driver"); MODULE_LICENSE("GPL"); diff --git a/drivers/platform/x86/lenovo/wmi-capdata01.h b/drivers/platform/x86/lenovo/wmi-capdata.h similarity index 60% rename from drivers/platform/x86/lenovo/wmi-capdata01.h rename to drivers/platform/x86/lenovo/wmi-capdata.h index bd06c5751f68b..2a4746e38ad43 100644 --- a/drivers/platform/x86/lenovo/wmi-capdata01.h +++ b/drivers/platform/x86/lenovo/wmi-capdata.h @@ -2,13 +2,13 @@ /* Copyright (C) 2025 Derek J. Clark */ -#ifndef _LENOVO_WMI_CAPDATA01_H_ -#define _LENOVO_WMI_CAPDATA01_H_ +#ifndef _LENOVO_WMI_CAPDATA_H_ +#define _LENOVO_WMI_CAPDATA_H_ #include struct device; -struct cd01_list; +struct cd_list; struct capdata01 { u32 id; @@ -19,7 +19,7 @@ struct capdata01 { u32 max_value; }; -int lwmi_cd01_get_data(struct cd01_list *list, u32 attribute_id, struct capdata01 *output); +int lwmi_cd01_get_data(struct cd_list *list, u32 attribute_id, struct capdata01 *output); int lwmi_cd01_match(struct device *dev, void *data); -#endif /* !_LENOVO_WMI_CAPDATA01_H_ */ +#endif /* !_LENOVO_WMI_CAPDATA_H_ */ diff --git a/drivers/platform/x86/lenovo/wmi-other.c b/drivers/platform/x86/lenovo/wmi-other.c index 2a960b278f117..ef34ea742d1ac 100644 --- a/drivers/platform/x86/lenovo/wmi-other.c +++ b/drivers/platform/x86/lenovo/wmi-other.c @@ -34,7 +34,7 @@ #include #include -#include "wmi-capdata01.h" +#include "wmi-capdata.h" #include "wmi-events.h" #include "wmi-gamezone.h" #include "wmi-helpers.h" @@ -74,7 +74,10 @@ enum attribute_property { struct lwmi_om_priv { struct component_master_ops *ops; - struct cd01_list *cd01_list; /* only valid after capdata01 bind */ + + /* only valid after capdata bind */ + struct cd_list *cd01_list; + struct device *fw_attr_dev; struct kset *fw_attr_kset; struct notifier_block nb; @@ -576,7 +579,7 @@ static void lwmi_om_fw_attr_remove(struct lwmi_om_priv *priv) static int lwmi_om_master_bind(struct device *dev) { struct lwmi_om_priv *priv = dev_get_drvdata(dev); - struct cd01_list *tmp_list; + struct cd_list *tmp_list; int ret; ret = component_bind_all(dev, &tmp_list); @@ -657,7 +660,7 @@ static struct wmi_driver lwmi_other_driver = { module_wmi_driver(lwmi_other_driver); -MODULE_IMPORT_NS("LENOVO_WMI_CD01"); +MODULE_IMPORT_NS("LENOVO_WMI_CAPDATA"); MODULE_IMPORT_NS("LENOVO_WMI_HELPERS"); MODULE_DEVICE_TABLE(wmi, lwmi_other_id_table); MODULE_AUTHOR("Derek J. Clark "); From 3a6b6dcef4e7a644965104dd022491dac1afce71 Mon Sep 17 00:00:00 2001 From: Rong Zhang Date: Wed, 21 Jan 2026 02:20:04 +0800 Subject: [PATCH 03/13] [FROM-UPSTREAM] platform/x86: lenovo-wmi-{capdata,other}: Support multiple Capability Data The current implementation are heavily bound to capdata01. Rewrite it so that it is suitable to utilize other Capability Data as well. No functional change intended. Signed-off-by: Rong Zhang Reviewed-by: Derek J. Clark Tested-by: Derek J. Clark Reviewed-by: Mark Pearson Signed-off-by: Derek J. Clark --- drivers/platform/x86/lenovo/wmi-capdata.c | 233 +++++++++++++++++----- drivers/platform/x86/lenovo/wmi-capdata.h | 7 +- drivers/platform/x86/lenovo/wmi-other.c | 16 +- 3 files changed, 196 insertions(+), 60 deletions(-) diff --git a/drivers/platform/x86/lenovo/wmi-capdata.c b/drivers/platform/x86/lenovo/wmi-capdata.c index ba843b6604b06..93ecb49c4c73a 100644 --- a/drivers/platform/x86/lenovo/wmi-capdata.c +++ b/drivers/platform/x86/lenovo/wmi-capdata.c @@ -12,13 +12,21 @@ * * Copyright (C) 2025 Derek J. Clark * - Initial implementation (formerly named lenovo-wmi-capdata01) + * + * Copyright (C) 2025 Rong Zhang + * - Unified implementation */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + #include +#include #include #include #include #include +#include +#include #include #include #include @@ -26,6 +34,7 @@ #include #include #include +#include #include #include @@ -36,6 +45,23 @@ #define ACPI_AC_CLASS "ac_adapter" #define ACPI_AC_NOTIFY_STATUS 0x80 +enum lwmi_cd_type { + LENOVO_CAPABILITY_DATA_01, +}; + +#define LWMI_CD_TABLE_ITEM(_type) \ + [_type] = { \ + .name = #_type, \ + .type = _type, \ + } + +static const struct lwmi_cd_info { + const char *name; + enum lwmi_cd_type type; +} lwmi_cd_table[] = { + LWMI_CD_TABLE_ITEM(LENOVO_CAPABILITY_DATA_01), +}; + struct lwmi_cd_priv { struct notifier_block acpi_nb; /* ACPI events */ struct wmi_device *wdev; @@ -44,15 +70,63 @@ struct lwmi_cd_priv { struct cd_list { struct mutex list_mutex; /* list R/W mutex */ + enum lwmi_cd_type type; u8 count; - struct capdata01 data[]; + + union { + DECLARE_FLEX_ARRAY(struct capdata01, cd01); + }; }; +static struct wmi_driver lwmi_cd_driver; + +/** + * lwmi_cd_match() - Match rule for the master driver. + * @dev: Pointer to the capability data parent device. + * @type: Pointer to capability data type (enum lwmi_cd_type *) to match. + * + * Return: int. + */ +static int lwmi_cd_match(struct device *dev, void *type) +{ + struct lwmi_cd_priv *priv; + + if (dev->driver != &lwmi_cd_driver.driver) + return false; + + priv = dev_get_drvdata(dev); + return priv->list->type == *(enum lwmi_cd_type *)type; +} + +/** + * lwmi_cd_match_add_all() - Add all match rule for the master driver. + * @master: Pointer to the master device. + * @matchptr: Pointer to the returned component_match pointer. + * + * Adds all component matches to the list stored in @matchptr for the @master + * device. @matchptr must be initialized to NULL. + */ +void lwmi_cd_match_add_all(struct device *master, struct component_match **matchptr) +{ + int i; + + if (WARN_ON(*matchptr)) + return; + + for (i = 0; i < ARRAY_SIZE(lwmi_cd_table); i++) { + component_match_add(master, matchptr, lwmi_cd_match, + (void *)&lwmi_cd_table[i].type); + if (IS_ERR(*matchptr)) + return; + } +} +EXPORT_SYMBOL_NS_GPL(lwmi_cd_match_add_all, "LENOVO_WMI_CAPDATA"); + /** * lwmi_cd_component_bind() - Bind component to master device. * @cd_dev: Pointer to the lenovo-wmi-capdata driver parent device. * @om_dev: Pointer to the lenovo-wmi-other driver parent device. - * @data: cd_list object pointer used to return the capability data. + * @data: lwmi_cd_binder object pointer used to return the capability data. * * On lenovo-wmi-other's master bind, provide a pointer to the local capdata * list. This is used to call lwmi_cd*_get_data to look up attribute data @@ -64,9 +138,15 @@ static int lwmi_cd_component_bind(struct device *cd_dev, struct device *om_dev, void *data) { struct lwmi_cd_priv *priv = dev_get_drvdata(cd_dev); - struct cd_list **cd_list = data; + struct lwmi_cd_binder *binder = data; - *cd_list = priv->list; + switch (priv->list->type) { + case LENOVO_CAPABILITY_DATA_01: + binder->cd01_list = priv->list; + break; + default: + return -EINVAL; + } return 0; } @@ -75,31 +155,36 @@ static const struct component_ops lwmi_cd_component_ops = { .bind = lwmi_cd_component_bind, }; -/** - * lwmi_cd01_get_data - Get the data of the specified attribute +/* + * lwmi_cd*_get_data - Get the data of the specified attribute * @list: The lenovo-wmi-capdata pointer to its cd_list struct. * @attribute_id: The capdata attribute ID to be found. - * @output: Pointer to a capdata01 struct to return the data. + * @output: Pointer to a capdata* struct to return the data. * - * Retrieves the capability data 01 struct pointer for the given - * attribute for its specified thermal mode. + * Retrieves the capability data struct pointer for the given + * attribute. * * Return: 0 on success, or -EINVAL. */ -int lwmi_cd01_get_data(struct cd_list *list, u32 attribute_id, struct capdata01 *output) -{ - u8 idx; - - guard(mutex)(&list->list_mutex); - for (idx = 0; idx < list->count; idx++) { - if (list->data[idx].id != attribute_id) - continue; - memcpy(output, &list->data[idx], sizeof(list->data[idx])); - return 0; +#define DEF_LWMI_CDXX_GET_DATA(_cdxx, _cd_type, _output_t) \ + int lwmi_##_cdxx##_get_data(struct cd_list *list, u32 attribute_id, _output_t *output) \ + { \ + u8 idx; \ + \ + if (WARN_ON(list->type != _cd_type)) \ + return -EINVAL; \ + \ + guard(mutex)(&list->list_mutex); \ + for (idx = 0; idx < list->count; idx++) { \ + if (list->_cdxx[idx].id != attribute_id) \ + continue; \ + memcpy(output, &list->_cdxx[idx], sizeof(list->_cdxx[idx])); \ + return 0; \ + } \ + return -EINVAL; \ } - return -EINVAL; -} +DEF_LWMI_CDXX_GET_DATA(cd01, LENOVO_CAPABILITY_DATA_01, struct capdata01); EXPORT_SYMBOL_NS_GPL(lwmi_cd01_get_data, "LENOVO_WMI_CAPDATA"); /** @@ -112,10 +197,21 @@ EXPORT_SYMBOL_NS_GPL(lwmi_cd01_get_data, "LENOVO_WMI_CAPDATA"); */ static int lwmi_cd_cache(struct lwmi_cd_priv *priv) { + size_t size; int idx; + void *p; + + switch (priv->list->type) { + case LENOVO_CAPABILITY_DATA_01: + p = &priv->list->cd01[0]; + size = sizeof(priv->list->cd01[0]); + break; + default: + return -EINVAL; + } guard(mutex)(&priv->list->list_mutex); - for (idx = 0; idx < priv->list->count; idx++) { + for (idx = 0; idx < priv->list->count; idx++, p += size) { union acpi_object *ret_obj __free(kfree) = NULL; ret_obj = wmidev_block_query(priv->wdev, idx); @@ -123,11 +219,10 @@ static int lwmi_cd_cache(struct lwmi_cd_priv *priv) return -ENODEV; if (ret_obj->type != ACPI_TYPE_BUFFER || - ret_obj->buffer.length < sizeof(priv->list->data[idx])) + ret_obj->buffer.length < size) continue; - memcpy(&priv->list->data[idx], ret_obj->buffer.pointer, - ret_obj->buffer.length); + memcpy(p, ret_obj->buffer.pointer, size); } return 0; @@ -136,20 +231,28 @@ static int lwmi_cd_cache(struct lwmi_cd_priv *priv) /** * lwmi_cd_alloc() - Allocate a cd_list struct in drvdata * @priv: lenovo-wmi-capdata driver data. + * @type: The type of capability data. * * Allocate a cd_list struct large enough to contain data from all WMI data * blocks provided by the interface. * * Return: 0 on success, or an error. */ -static int lwmi_cd_alloc(struct lwmi_cd_priv *priv) +static int lwmi_cd_alloc(struct lwmi_cd_priv *priv, enum lwmi_cd_type type) { struct cd_list *list; size_t list_size; int count, ret; count = wmidev_instance_count(priv->wdev); - list_size = struct_size(list, data, count); + + switch (type) { + case LENOVO_CAPABILITY_DATA_01: + list_size = struct_size(list, cd01, count); + break; + default: + return -EINVAL; + } list = devm_kzalloc(&priv->wdev->dev, list_size, GFP_KERNEL); if (!list) @@ -159,6 +262,7 @@ static int lwmi_cd_alloc(struct lwmi_cd_priv *priv) if (ret) return ret; + list->type = type; list->count = count; priv->list = list; @@ -168,6 +272,7 @@ static int lwmi_cd_alloc(struct lwmi_cd_priv *priv) /** * lwmi_cd_setup() - Cache all WMI data block information * @priv: lenovo-wmi-capdata driver data. + * @type: The type of capability data. * * Allocate a cd_list struct large enough to contain data from all WMI data * blocks provided by the interface. Then loop through each data block and @@ -175,11 +280,11 @@ static int lwmi_cd_alloc(struct lwmi_cd_priv *priv) * * Return: 0 on success, or an error code. */ -static int lwmi_cd_setup(struct lwmi_cd_priv *priv) +static int lwmi_cd_setup(struct lwmi_cd_priv *priv, enum lwmi_cd_type type) { int ret; - ret = lwmi_cd_alloc(priv); + ret = lwmi_cd_alloc(priv, type); if (ret) return ret; @@ -235,9 +340,13 @@ static void lwmi_cd01_unregister(void *data) static int lwmi_cd_probe(struct wmi_device *wdev, const void *context) { + const struct lwmi_cd_info *info = context; struct lwmi_cd_priv *priv; int ret; + if (!info) + return -EINVAL; + priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL); if (!priv) return -ENOMEM; @@ -245,30 +354,58 @@ static int lwmi_cd_probe(struct wmi_device *wdev, const void *context) priv->wdev = wdev; dev_set_drvdata(&wdev->dev, priv); - ret = lwmi_cd_setup(priv); + ret = lwmi_cd_setup(priv, info->type); if (ret) - return ret; + goto out; - priv->acpi_nb.notifier_call = lwmi_cd01_notifier_call; + switch (info->type) { + case LENOVO_CAPABILITY_DATA_01: + priv->acpi_nb.notifier_call = lwmi_cd01_notifier_call; - ret = register_acpi_notifier(&priv->acpi_nb); - if (ret) - return ret; + ret = register_acpi_notifier(&priv->acpi_nb); + if (ret) + goto out; - ret = devm_add_action_or_reset(&wdev->dev, lwmi_cd01_unregister, &priv->acpi_nb); - if (ret) - return ret; + ret = devm_add_action_or_reset(&wdev->dev, lwmi_cd01_unregister, + &priv->acpi_nb); + if (ret) + goto out; - return component_add(&wdev->dev, &lwmi_cd_component_ops); + ret = component_add(&wdev->dev, &lwmi_cd_component_ops); + goto out; + default: + return -EINVAL; + } +out: + if (ret) { + dev_err(&wdev->dev, "failed to register %s: %d\n", + info->name, ret); + } else { + dev_dbg(&wdev->dev, "registered %s with %u items\n", + info->name, priv->list->count); + } + return ret; } static void lwmi_cd_remove(struct wmi_device *wdev) { - component_del(&wdev->dev, &lwmi_cd_component_ops); + struct lwmi_cd_priv *priv = dev_get_drvdata(&wdev->dev); + + switch (priv->list->type) { + case LENOVO_CAPABILITY_DATA_01: + component_del(&wdev->dev, &lwmi_cd_component_ops); + break; + default: + WARN_ON(1); + } } +#define LWMI_CD_WDEV_ID(_type) \ + .guid_string = _type##_GUID, \ + .context = &lwmi_cd_table[_type], + static const struct wmi_device_id lwmi_cd_id_table[] = { - { LENOVO_CAPABILITY_DATA_01_GUID, NULL }, + { LWMI_CD_WDEV_ID(LENOVO_CAPABILITY_DATA_01) }, {} }; @@ -283,22 +420,10 @@ static struct wmi_driver lwmi_cd_driver = { .no_singleton = true, }; -/** - * lwmi_cd01_match() - Match rule for the master driver. - * @dev: Pointer to the capability data 01 parent device. - * @data: Unused void pointer for passing match criteria. - * - * Return: int. - */ -int lwmi_cd01_match(struct device *dev, void *data) -{ - return dev->driver == &lwmi_cd_driver.driver; -} -EXPORT_SYMBOL_NS_GPL(lwmi_cd01_match, "LENOVO_WMI_CAPDATA"); - module_wmi_driver(lwmi_cd_driver); MODULE_DEVICE_TABLE(wmi, lwmi_cd_id_table); MODULE_AUTHOR("Derek J. Clark "); +MODULE_AUTHOR("Rong Zhang "); MODULE_DESCRIPTION("Lenovo Capability Data WMI Driver"); MODULE_LICENSE("GPL"); diff --git a/drivers/platform/x86/lenovo/wmi-capdata.h b/drivers/platform/x86/lenovo/wmi-capdata.h index 2a4746e38ad43..d326f9d2d1659 100644 --- a/drivers/platform/x86/lenovo/wmi-capdata.h +++ b/drivers/platform/x86/lenovo/wmi-capdata.h @@ -7,6 +7,7 @@ #include +struct component_match; struct device; struct cd_list; @@ -19,7 +20,11 @@ struct capdata01 { u32 max_value; }; +struct lwmi_cd_binder { + struct cd_list *cd01_list; +}; + +void lwmi_cd_match_add_all(struct device *master, struct component_match **matchptr); int lwmi_cd01_get_data(struct cd_list *list, u32 attribute_id, struct capdata01 *output); -int lwmi_cd01_match(struct device *dev, void *data); #endif /* !_LENOVO_WMI_CAPDATA_H_ */ diff --git a/drivers/platform/x86/lenovo/wmi-other.c b/drivers/platform/x86/lenovo/wmi-other.c index ef34ea742d1ac..73191dedc029f 100644 --- a/drivers/platform/x86/lenovo/wmi-other.c +++ b/drivers/platform/x86/lenovo/wmi-other.c @@ -579,14 +579,14 @@ static void lwmi_om_fw_attr_remove(struct lwmi_om_priv *priv) static int lwmi_om_master_bind(struct device *dev) { struct lwmi_om_priv *priv = dev_get_drvdata(dev); - struct cd_list *tmp_list; + struct lwmi_cd_binder binder = {}; int ret; - ret = component_bind_all(dev, &tmp_list); + ret = component_bind_all(dev, &binder); if (ret) return ret; - priv->cd01_list = tmp_list; + priv->cd01_list = binder.cd01_list; if (!priv->cd01_list) return -ENODEV; @@ -623,10 +623,13 @@ static int lwmi_other_probe(struct wmi_device *wdev, const void *context) if (!priv) return -ENOMEM; + /* Sentinel for on-demand ida_free(). */ + priv->ida_id = -EIDRM; + priv->wdev = wdev; dev_set_drvdata(&wdev->dev, priv); - component_match_add(&wdev->dev, &master_match, lwmi_cd01_match, NULL); + lwmi_cd_match_add_all(&wdev->dev, &master_match); if (IS_ERR(master_match)) return PTR_ERR(master_match); @@ -639,7 +642,10 @@ static void lwmi_other_remove(struct wmi_device *wdev) struct lwmi_om_priv *priv = dev_get_drvdata(&wdev->dev); component_master_del(&wdev->dev, &lwmi_om_master_ops); - ida_free(&lwmi_om_ida, priv->ida_id); + + /* No IDA to free if the driver is never bound to its components. */ + if (priv->ida_id >= 0) + ida_free(&lwmi_om_ida, priv->ida_id); } static const struct wmi_device_id lwmi_other_id_table[] = { From 0a354a33e01957be5e1350dc9ebf753672a664b7 Mon Sep 17 00:00:00 2001 From: Rong Zhang Date: Wed, 21 Jan 2026 02:20:05 +0800 Subject: [PATCH 04/13] [FROM-UPSTREAM] platform/x86: lenovo-wmi-capdata: Add support for Capability Data 00 Add support for LENOVO_CAPABILITY_DATA_00 WMI data block that comes on "Other Mode" enabled hardware. Provides an interface for querying if a given attribute is supported by the hardware, as well as its default value. capdata00 always presents on devices with capdata01. lenovo-wmi-other now binds to both (no functional change intended). Signed-off-by: Rong Zhang Reviewed-by: Derek J. Clark Tested-by: Derek J. Clark Reviewed-by: Mark Pearson Signed-off-by: Derek J. Clark --- .../wmi/devices/lenovo-wmi-other.rst | 15 ++++++++--- drivers/platform/x86/lenovo/wmi-capdata.c | 25 +++++++++++++++++++ drivers/platform/x86/lenovo/wmi-capdata.h | 8 ++++++ 3 files changed, 45 insertions(+), 3 deletions(-) diff --git a/Documentation/wmi/devices/lenovo-wmi-other.rst b/Documentation/wmi/devices/lenovo-wmi-other.rst index d7928b8dfb4b5..fcad595d49af2 100644 --- a/Documentation/wmi/devices/lenovo-wmi-other.rst +++ b/Documentation/wmi/devices/lenovo-wmi-other.rst @@ -31,13 +31,22 @@ under the following path: /sys/class/firmware-attributes/lenovo-wmi-other/attributes// +LENOVO_CAPABILITY_DATA_00 +------------------------- + +WMI GUID ``362A3AFE-3D96-4665-8530-96DAD5BB300E`` + +The LENOVO_CAPABILITY_DATA_00 interface provides various information that +does not rely on the gamezone thermal mode. + LENOVO_CAPABILITY_DATA_01 ------------------------- WMI GUID ``7A8F5407-CB67-4D6E-B547-39B3BE018154`` -The LENOVO_CAPABILITY_DATA_01 interface provides information on various -power limits of integrated CPU and GPU components. +The LENOVO_CAPABILITY_DATA_01 interface provides various information that +relies on the gamezone thermal mode, including power limits of integrated +CPU and GPU components. Each attribute has the following properties: - current_value @@ -48,7 +57,7 @@ Each attribute has the following properties: - scalar_increment - type -The following attributes are implemented: +The following firmware-attributes are implemented: - ppt_pl1_spl: Platform Profile Tracking Sustained Power Limit - ppt_pl2_sppt: Platform Profile Tracking Slow Package Power Tracking - ppt_pl3_fppt: Platform Profile Tracking Fast Package Power Tracking diff --git a/drivers/platform/x86/lenovo/wmi-capdata.c b/drivers/platform/x86/lenovo/wmi-capdata.c index 93ecb49c4c73a..4ed5b73d430dc 100644 --- a/drivers/platform/x86/lenovo/wmi-capdata.c +++ b/drivers/platform/x86/lenovo/wmi-capdata.c @@ -5,6 +5,9 @@ * Lenovo Capability Data provides information on tunable attributes used by * the "Other Mode" WMI interface. * + * Capability Data 00 includes if the attribute is supported by the hardware, + * and the default_value. All attributes are independent of thermal modes. + * * Capability Data 01 includes if the attribute is supported by the hardware, * and the default_value, max_value, min_value, and step increment. Each * attribute has multiple pages, one for each of the thermal modes managed by @@ -40,12 +43,14 @@ #include "wmi-capdata.h" +#define LENOVO_CAPABILITY_DATA_00_GUID "362A3AFE-3D96-4665-8530-96DAD5BB300E" #define LENOVO_CAPABILITY_DATA_01_GUID "7A8F5407-CB67-4D6E-B547-39B3BE018154" #define ACPI_AC_CLASS "ac_adapter" #define ACPI_AC_NOTIFY_STATUS 0x80 enum lwmi_cd_type { + LENOVO_CAPABILITY_DATA_00, LENOVO_CAPABILITY_DATA_01, }; @@ -59,6 +64,7 @@ static const struct lwmi_cd_info { const char *name; enum lwmi_cd_type type; } lwmi_cd_table[] = { + LWMI_CD_TABLE_ITEM(LENOVO_CAPABILITY_DATA_00), LWMI_CD_TABLE_ITEM(LENOVO_CAPABILITY_DATA_01), }; @@ -74,6 +80,7 @@ struct cd_list { u8 count; union { + DECLARE_FLEX_ARRAY(struct capdata00, cd00); DECLARE_FLEX_ARRAY(struct capdata01, cd01); }; }; @@ -141,6 +148,9 @@ static int lwmi_cd_component_bind(struct device *cd_dev, struct lwmi_cd_binder *binder = data; switch (priv->list->type) { + case LENOVO_CAPABILITY_DATA_00: + binder->cd00_list = priv->list; + break; case LENOVO_CAPABILITY_DATA_01: binder->cd01_list = priv->list; break; @@ -184,6 +194,9 @@ static const struct component_ops lwmi_cd_component_ops = { return -EINVAL; \ } +DEF_LWMI_CDXX_GET_DATA(cd00, LENOVO_CAPABILITY_DATA_00, struct capdata00); +EXPORT_SYMBOL_NS_GPL(lwmi_cd00_get_data, "LENOVO_WMI_CAPDATA"); + DEF_LWMI_CDXX_GET_DATA(cd01, LENOVO_CAPABILITY_DATA_01, struct capdata01); EXPORT_SYMBOL_NS_GPL(lwmi_cd01_get_data, "LENOVO_WMI_CAPDATA"); @@ -202,6 +215,10 @@ static int lwmi_cd_cache(struct lwmi_cd_priv *priv) void *p; switch (priv->list->type) { + case LENOVO_CAPABILITY_DATA_00: + p = &priv->list->cd00[0]; + size = sizeof(priv->list->cd00[0]); + break; case LENOVO_CAPABILITY_DATA_01: p = &priv->list->cd01[0]; size = sizeof(priv->list->cd01[0]); @@ -247,6 +264,9 @@ static int lwmi_cd_alloc(struct lwmi_cd_priv *priv, enum lwmi_cd_type type) count = wmidev_instance_count(priv->wdev); switch (type) { + case LENOVO_CAPABILITY_DATA_00: + list_size = struct_size(list, cd00, count); + break; case LENOVO_CAPABILITY_DATA_01: list_size = struct_size(list, cd01, count); break; @@ -359,6 +379,9 @@ static int lwmi_cd_probe(struct wmi_device *wdev, const void *context) goto out; switch (info->type) { + case LENOVO_CAPABILITY_DATA_00: + ret = component_add(&wdev->dev, &lwmi_cd_component_ops); + goto out; case LENOVO_CAPABILITY_DATA_01: priv->acpi_nb.notifier_call = lwmi_cd01_notifier_call; @@ -392,6 +415,7 @@ static void lwmi_cd_remove(struct wmi_device *wdev) struct lwmi_cd_priv *priv = dev_get_drvdata(&wdev->dev); switch (priv->list->type) { + case LENOVO_CAPABILITY_DATA_00: case LENOVO_CAPABILITY_DATA_01: component_del(&wdev->dev, &lwmi_cd_component_ops); break; @@ -405,6 +429,7 @@ static void lwmi_cd_remove(struct wmi_device *wdev) .context = &lwmi_cd_table[_type], static const struct wmi_device_id lwmi_cd_id_table[] = { + { LWMI_CD_WDEV_ID(LENOVO_CAPABILITY_DATA_00) }, { LWMI_CD_WDEV_ID(LENOVO_CAPABILITY_DATA_01) }, {} }; diff --git a/drivers/platform/x86/lenovo/wmi-capdata.h b/drivers/platform/x86/lenovo/wmi-capdata.h index d326f9d2d1659..a6d006ef458f6 100644 --- a/drivers/platform/x86/lenovo/wmi-capdata.h +++ b/drivers/platform/x86/lenovo/wmi-capdata.h @@ -11,6 +11,12 @@ struct component_match; struct device; struct cd_list; +struct capdata00 { + u32 id; + u32 supported; + u32 default_value; +}; + struct capdata01 { u32 id; u32 supported; @@ -21,10 +27,12 @@ struct capdata01 { }; struct lwmi_cd_binder { + struct cd_list *cd00_list; struct cd_list *cd01_list; }; void lwmi_cd_match_add_all(struct device *master, struct component_match **matchptr); +int lwmi_cd00_get_data(struct cd_list *list, u32 attribute_id, struct capdata00 *output); int lwmi_cd01_get_data(struct cd_list *list, u32 attribute_id, struct capdata01 *output); #endif /* !_LENOVO_WMI_CAPDATA_H_ */ From d784403fa986006c7cd239ae20fd0e3a3e7ff325 Mon Sep 17 00:00:00 2001 From: Rong Zhang Date: Wed, 21 Jan 2026 02:20:06 +0800 Subject: [PATCH 05/13] [FROM-UPSTREAM] platform/x86: lenovo-wmi-capdata: Add support for Fan Test Data Add support for LENOVO_FAN_TEST_DATA WMI data block. Provides an interface for querying the min/max fan speed RPM (reference data) of a given fan ID. This interface is optional. Hence, it does not bind to lenovo-wmi-other and is not registered as a component for the moment. Appropriate binding will be implemented in the subsequent patch. Signed-off-by: Rong Zhang Reviewed-by: Derek J. Clark Tested-by: Derek J. Clark Reviewed-by: Mark Pearson Signed-off-by: Derek J. Clark --- .../wmi/devices/lenovo-wmi-other.rst | 17 ++++ drivers/platform/x86/lenovo/wmi-capdata.c | 97 +++++++++++++++++++ drivers/platform/x86/lenovo/wmi-capdata.h | 7 ++ 3 files changed, 121 insertions(+) diff --git a/Documentation/wmi/devices/lenovo-wmi-other.rst b/Documentation/wmi/devices/lenovo-wmi-other.rst index fcad595d49af2..821282e07d93c 100644 --- a/Documentation/wmi/devices/lenovo-wmi-other.rst +++ b/Documentation/wmi/devices/lenovo-wmi-other.rst @@ -62,6 +62,13 @@ The following firmware-attributes are implemented: - ppt_pl2_sppt: Platform Profile Tracking Slow Package Power Tracking - ppt_pl3_fppt: Platform Profile Tracking Fast Package Power Tracking +LENOVO_FAN_TEST_DATA +------------------------- + +WMI GUID ``B642801B-3D21-45DE-90AE-6E86F164FB21`` + +The LENOVO_FAN_TEST_DATA interface provides reference data for self-test of +cooling fans. WMI interface description ========================= @@ -115,3 +122,13 @@ data using the `bmfdec `_ utility: [WmiDataId(3), read, Description("Data Size.")] uint32 DataSize; [WmiDataId(4), read, Description("Default Value"), WmiSizeIs("DataSize")] uint8 DefaultValue[]; }; + + [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("Definition of Fan Test Data"), guid("{B642801B-3D21-45DE-90AE-6E86F164FB21}")] + class LENOVO_FAN_TEST_DATA { + [key, read] string InstanceName; + [read] boolean Active; + [WmiDataId(1), read, Description("Mode.")] uint32 NumOfFans; + [WmiDataId(2), read, Description("Fan ID."), WmiSizeIs("NumOfFans")] uint32 FanId[]; + [WmiDataId(3), read, Description("Maximum Fan Speed."), WmiSizeIs("NumOfFans")] uint32 FanMaxSpeed[]; + [WmiDataId(4), read, Description("Minumum Fan Speed."), WmiSizeIs("NumOfFans")] uint32 FanMinSpeed[]; + }; diff --git a/drivers/platform/x86/lenovo/wmi-capdata.c b/drivers/platform/x86/lenovo/wmi-capdata.c index 4ed5b73d430dc..478b00bc66c4c 100644 --- a/drivers/platform/x86/lenovo/wmi-capdata.c +++ b/drivers/platform/x86/lenovo/wmi-capdata.c @@ -13,6 +13,10 @@ * attribute has multiple pages, one for each of the thermal modes managed by * the Gamezone interface. * + * Fan Test Data includes the max/min fan speed RPM for each fan. This is + * reference data for self-test. If the fan is in good condition, it is capable + * to spin faster than max RPM or slower than min RPM. + * * Copyright (C) 2025 Derek J. Clark * - Initial implementation (formerly named lenovo-wmi-capdata01) * @@ -32,6 +36,7 @@ #include #include #include +#include #include #include #include @@ -45,6 +50,7 @@ #define LENOVO_CAPABILITY_DATA_00_GUID "362A3AFE-3D96-4665-8530-96DAD5BB300E" #define LENOVO_CAPABILITY_DATA_01_GUID "7A8F5407-CB67-4D6E-B547-39B3BE018154" +#define LENOVO_FAN_TEST_DATA_GUID "B642801B-3D21-45DE-90AE-6E86F164FB21" #define ACPI_AC_CLASS "ac_adapter" #define ACPI_AC_NOTIFY_STATUS 0x80 @@ -52,6 +58,7 @@ enum lwmi_cd_type { LENOVO_CAPABILITY_DATA_00, LENOVO_CAPABILITY_DATA_01, + LENOVO_FAN_TEST_DATA, }; #define LWMI_CD_TABLE_ITEM(_type) \ @@ -66,6 +73,7 @@ static const struct lwmi_cd_info { } lwmi_cd_table[] = { LWMI_CD_TABLE_ITEM(LENOVO_CAPABILITY_DATA_00), LWMI_CD_TABLE_ITEM(LENOVO_CAPABILITY_DATA_01), + LWMI_CD_TABLE_ITEM(LENOVO_FAN_TEST_DATA), }; struct lwmi_cd_priv { @@ -82,6 +90,7 @@ struct cd_list { union { DECLARE_FLEX_ARRAY(struct capdata00, cd00); DECLARE_FLEX_ARRAY(struct capdata01, cd01); + DECLARE_FLEX_ARRAY(struct capdata_fan, cd_fan); }; }; @@ -121,6 +130,10 @@ void lwmi_cd_match_add_all(struct device *master, struct component_match **match return; for (i = 0; i < ARRAY_SIZE(lwmi_cd_table); i++) { + /* Skip sub-components. */ + if (lwmi_cd_table[i].type == LENOVO_FAN_TEST_DATA) + continue; + component_match_add(master, matchptr, lwmi_cd_match, (void *)&lwmi_cd_table[i].type); if (IS_ERR(*matchptr)) @@ -200,6 +213,9 @@ EXPORT_SYMBOL_NS_GPL(lwmi_cd00_get_data, "LENOVO_WMI_CAPDATA"); DEF_LWMI_CDXX_GET_DATA(cd01, LENOVO_CAPABILITY_DATA_01, struct capdata01); EXPORT_SYMBOL_NS_GPL(lwmi_cd01_get_data, "LENOVO_WMI_CAPDATA"); +DEF_LWMI_CDXX_GET_DATA(cd_fan, LENOVO_FAN_TEST_DATA, struct capdata_fan); +EXPORT_SYMBOL_NS_GPL(lwmi_cd_fan_get_data, "LENOVO_WMI_CAPDATA"); + /** * lwmi_cd_cache() - Cache all WMI data block information * @priv: lenovo-wmi-capdata driver data. @@ -223,6 +239,9 @@ static int lwmi_cd_cache(struct lwmi_cd_priv *priv) p = &priv->list->cd01[0]; size = sizeof(priv->list->cd01[0]); break; + case LENOVO_FAN_TEST_DATA: + /* Done by lwmi_cd_alloc() => lwmi_cd_fan_list_alloc_cache(). */ + return 0; default: return -EINVAL; } @@ -245,6 +264,72 @@ static int lwmi_cd_cache(struct lwmi_cd_priv *priv) return 0; } +/** + * lwmi_cd_fan_list_alloc_cache() - Alloc and cache Fan Test Data list + * @priv: lenovo-wmi-capdata driver data. + * @listptr: Pointer to returned cd_list pointer. + * + * Return: count of fans found, or an error. + */ +static int lwmi_cd_fan_list_alloc_cache(struct lwmi_cd_priv *priv, struct cd_list **listptr) +{ + struct cd_list *list; + size_t size; + u32 count; + int idx; + + /* Emit unaligned access to u8 buffer with __packed. */ + struct cd_fan_block { + u32 nr; + u32 data[]; /* id[nr], max_rpm[nr], min_rpm[nr] */ + } __packed * block; + + union acpi_object *ret_obj __free(kfree) = wmidev_block_query(priv->wdev, 0); + if (!ret_obj) + return -ENODEV; + + if (ret_obj->type == ACPI_TYPE_BUFFER) { + block = (struct cd_fan_block *)ret_obj->buffer.pointer; + size = ret_obj->buffer.length; + + count = size >= sizeof(*block) ? block->nr : 0; + if (size < struct_size(block, data, count * 3)) { + dev_warn(&priv->wdev->dev, + "incomplete fan test data block: %zu < %zu, ignoring\n", + size, struct_size(block, data, count * 3)); + count = 0; + } else if (count > U8_MAX) { + dev_warn(&priv->wdev->dev, + "too many fans reported: %u > %u, truncating\n", + count, U8_MAX); + count = U8_MAX; + } + } else { + /* + * This is usually caused by a dummy ACPI method. Do not return an error + * as failing to probe this device will result in sub-master device being + * unbound. This behavior aligns with lwmi_cd_cache(). + */ + count = 0; + } + + list = devm_kzalloc(&priv->wdev->dev, struct_size(list, cd_fan, count), GFP_KERNEL); + if (!list) + return -ENOMEM; + + for (idx = 0; idx < count; idx++) { + /* Do not calculate array index using count, as it may be truncated. */ + list->cd_fan[idx] = (struct capdata_fan) { + .id = block->data[idx], + .max_rpm = block->data[idx + block->nr], + .min_rpm = block->data[idx + (2 * block->nr)], + }; + } + + *listptr = list; + return count; +} + /** * lwmi_cd_alloc() - Allocate a cd_list struct in drvdata * @priv: lenovo-wmi-capdata driver data. @@ -270,6 +355,12 @@ static int lwmi_cd_alloc(struct lwmi_cd_priv *priv, enum lwmi_cd_type type) case LENOVO_CAPABILITY_DATA_01: list_size = struct_size(list, cd01, count); break; + case LENOVO_FAN_TEST_DATA: + count = lwmi_cd_fan_list_alloc_cache(priv, &list); + if (count < 0) + return count; + + goto got_list; default: return -EINVAL; } @@ -278,6 +369,7 @@ static int lwmi_cd_alloc(struct lwmi_cd_priv *priv, enum lwmi_cd_type type) if (!list) return -ENOMEM; +got_list: ret = devm_mutex_init(&priv->wdev->dev, &list->list_mutex); if (ret) return ret; @@ -396,6 +488,8 @@ static int lwmi_cd_probe(struct wmi_device *wdev, const void *context) ret = component_add(&wdev->dev, &lwmi_cd_component_ops); goto out; + case LENOVO_FAN_TEST_DATA: + goto out; default: return -EINVAL; } @@ -419,6 +513,8 @@ static void lwmi_cd_remove(struct wmi_device *wdev) case LENOVO_CAPABILITY_DATA_01: component_del(&wdev->dev, &lwmi_cd_component_ops); break; + case LENOVO_FAN_TEST_DATA: + break; default: WARN_ON(1); } @@ -431,6 +527,7 @@ static void lwmi_cd_remove(struct wmi_device *wdev) static const struct wmi_device_id lwmi_cd_id_table[] = { { LWMI_CD_WDEV_ID(LENOVO_CAPABILITY_DATA_00) }, { LWMI_CD_WDEV_ID(LENOVO_CAPABILITY_DATA_01) }, + { LWMI_CD_WDEV_ID(LENOVO_FAN_TEST_DATA) }, {} }; diff --git a/drivers/platform/x86/lenovo/wmi-capdata.h b/drivers/platform/x86/lenovo/wmi-capdata.h index a6d006ef458f6..38af4c4e4ef4b 100644 --- a/drivers/platform/x86/lenovo/wmi-capdata.h +++ b/drivers/platform/x86/lenovo/wmi-capdata.h @@ -26,6 +26,12 @@ struct capdata01 { u32 max_value; }; +struct capdata_fan { + u32 id; + u32 min_rpm; + u32 max_rpm; +}; + struct lwmi_cd_binder { struct cd_list *cd00_list; struct cd_list *cd01_list; @@ -34,5 +40,6 @@ struct lwmi_cd_binder { void lwmi_cd_match_add_all(struct device *master, struct component_match **matchptr); int lwmi_cd00_get_data(struct cd_list *list, u32 attribute_id, struct capdata00 *output); int lwmi_cd01_get_data(struct cd_list *list, u32 attribute_id, struct capdata01 *output); +int lwmi_cd_fan_get_data(struct cd_list *list, u32 attribute_id, struct capdata_fan *output); #endif /* !_LENOVO_WMI_CAPDATA_H_ */ From aef83f373c7d4052d7d8ac72f1d4fed959ae271a Mon Sep 17 00:00:00 2001 From: Rong Zhang Date: Wed, 21 Jan 2026 02:20:07 +0800 Subject: [PATCH 06/13] [FROM-UPSTREAM] platform/x86: lenovo-wmi-capdata: Wire up Fan Test Data A capdata00 attribute (0x04050000) describes the presence of Fan Test Data. Query it, and bind Fan Test Data as a component of capdata00 accordingly. The component master of capdata00 may pass a callback while binding to retrieve fan info from Fan Test Data. Summarizing this scheme: lenovo-wmi-other <-> capdata00 <-> capdata_fan |- master |- component | |- sub-master |- sub-component The callback will be called once both the master and the sub-component are bound to the sub-master (component). This scheme is essential to solve these issues: - The component framework only supports one aggregation per master - A binding is only established until all components are found - The Fan Test Data interface may be missing on some devices - To get rid of queries for the presence of WMI GUIDs - The notifier framework cannot cleanly connect capdata_fan to lenovo-wmi-other without introducing assumptions on probing sequence capdata00 is registered as a component and a sub-master on probe, instead of chaining the registrations in one's bind callback. This is because calling (un)registration methods of the component framework causes deadlock in (un)bind callbacks, i.e., it's impossible to register capdata00 as a sub-master/component in its component/sub-master bind callback, and vice versa. Signed-off-by: Rong Zhang Reviewed-by: Derek J. Clark Tested-by: Derek J. Clark Signed-off-by: Derek J. Clark --- drivers/platform/x86/lenovo/wmi-capdata.c | 280 +++++++++++++++++++++- drivers/platform/x86/lenovo/wmi-capdata.h | 20 ++ drivers/platform/x86/lenovo/wmi-other.c | 5 - 3 files changed, 299 insertions(+), 6 deletions(-) diff --git a/drivers/platform/x86/lenovo/wmi-capdata.c b/drivers/platform/x86/lenovo/wmi-capdata.c index 478b00bc66c4c..ee1fb02d8e31e 100644 --- a/drivers/platform/x86/lenovo/wmi-capdata.c +++ b/drivers/platform/x86/lenovo/wmi-capdata.c @@ -27,6 +27,7 @@ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include +#include #include #include #include @@ -55,10 +56,17 @@ #define ACPI_AC_CLASS "ac_adapter" #define ACPI_AC_NOTIFY_STATUS 0x80 +#define LWMI_FEATURE_ID_FAN_TEST 0x05 + +#define LWMI_ATTR_ID_FAN_TEST \ + (FIELD_PREP(LWMI_ATTR_DEV_ID_MASK, LWMI_DEVICE_ID_FAN) | \ + FIELD_PREP(LWMI_ATTR_FEAT_ID_MASK, LWMI_FEATURE_ID_FAN_TEST)) + enum lwmi_cd_type { LENOVO_CAPABILITY_DATA_00, LENOVO_CAPABILITY_DATA_01, LENOVO_FAN_TEST_DATA, + CD_TYPE_NONE = -1, }; #define LWMI_CD_TABLE_ITEM(_type) \ @@ -80,6 +88,20 @@ struct lwmi_cd_priv { struct notifier_block acpi_nb; /* ACPI events */ struct wmi_device *wdev; struct cd_list *list; + + /* + * A capdata device may be a component master of another capdata device. + * E.g., lenovo-wmi-other <-> capdata00 <-> capdata_fan + * |- master |- component + * |- sub-master + * |- sub-component + */ + struct lwmi_cd_sub_master_priv { + struct device *master_dev; + cd_list_cb_t master_cb; + struct cd_list *sub_component_list; /* ERR_PTR(-ENODEV) implies no sub-component. */ + bool registered; /* Has the sub-master been registered? */ + } *sub_master; }; struct cd_list { @@ -142,6 +164,72 @@ void lwmi_cd_match_add_all(struct device *master, struct component_match **match } EXPORT_SYMBOL_NS_GPL(lwmi_cd_match_add_all, "LENOVO_WMI_CAPDATA"); +/** + * lwmi_cd_call_master_cb() - Call the master callback for the sub-component. + * @priv: Pointer to the capability data private data. + * + * Call the master callback and pass the sub-component list to it if the + * dependency chain (master <-> sub-master <-> sub-component) is complete. + */ +static void lwmi_cd_call_master_cb(struct lwmi_cd_priv *priv) +{ + struct cd_list *sub_component_list = priv->sub_master->sub_component_list; + + /* + * Call the callback only if the dependency chain is ready: + * - Binding between master and sub-master: fills master_dev and master_cb + * - Binding between sub-master and sub-component: fills sub_component_list + * + * If a binding has been unbound before the other binding is bound, the + * corresponding members filled by the former are guaranteed to be cleared. + * + * This function is only called in bind callbacks, and the component + * framework guarantees bind/unbind callbacks may never execute + * simultaneously, which implies that it's impossible to have a race + * condition. + * + * Hence, this check is sufficient to ensure that the callback is called + * at most once and with the correct state, without relying on a specific + * sequence of binding establishment. + */ + if (!sub_component_list || + !priv->sub_master->master_dev || + !priv->sub_master->master_cb) + return; + + if (PTR_ERR(sub_component_list) == -ENODEV) + sub_component_list = NULL; + else if (WARN_ON(IS_ERR(sub_component_list))) + return; + + priv->sub_master->master_cb(priv->sub_master->master_dev, + sub_component_list); + + /* + * Userspace may unbind a device from its driver and bind it again + * through sysfs. Let's call this operation "reprobe" to distinguish it + * from component "rebind". + * + * When reprobing capdata00/01 or the master device, the master device + * is unbound from us with appropriate cleanup before we bind to it and + * call master_cb. Everything is fine in this case. + * + * When reprobing capdata_fan, the master device has never been unbound + * from us (hence no cleanup is done)[1], but we call master_cb the + * second time. To solve this issue, we clear master_cb and master_dev + * so we won't call master_cb twice while a binding is still complete. + * + * Note that we can't clear sub_component_list, otherwise reprobing + * capdata01 or the master device causes master_cb to be never called + * after we rebind to the master device. + * + * [1]: The master device does not need capdata_fan in run time, so + * losing capdata_fan will not break the binding to the master device. + */ + priv->sub_master->master_cb = NULL; + priv->sub_master->master_dev = NULL; +} + /** * lwmi_cd_component_bind() - Bind component to master device. * @cd_dev: Pointer to the lenovo-wmi-capdata driver parent device. @@ -152,6 +240,8 @@ EXPORT_SYMBOL_NS_GPL(lwmi_cd_match_add_all, "LENOVO_WMI_CAPDATA"); * list. This is used to call lwmi_cd*_get_data to look up attribute data * from the lenovo-wmi-other driver. * + * If cd_dev is a sub-master, try to call the master callback. + * * Return: 0 */ static int lwmi_cd_component_bind(struct device *cd_dev, @@ -163,6 +253,11 @@ static int lwmi_cd_component_bind(struct device *cd_dev, switch (priv->list->type) { case LENOVO_CAPABILITY_DATA_00: binder->cd00_list = priv->list; + + priv->sub_master->master_dev = om_dev; + priv->sub_master->master_cb = binder->cd_fan_list_cb; + lwmi_cd_call_master_cb(priv); + break; case LENOVO_CAPABILITY_DATA_01: binder->cd01_list = priv->list; @@ -174,8 +269,168 @@ static int lwmi_cd_component_bind(struct device *cd_dev, return 0; } +/** + * lwmi_cd_component_unbind() - Unbind component to master device. + * @cd_dev: Pointer to the lenovo-wmi-capdata driver parent device. + * @om_dev: Pointer to the lenovo-wmi-other driver parent device. + * @data: Unused. + * + * If cd_dev is a sub-master, clear the collected data from the master device to + * prevent the binding establishment between the sub-master and the sub- + * component (if it's about to happen) from calling the master callback. + */ +static void lwmi_cd_component_unbind(struct device *cd_dev, + struct device *om_dev, void *data) +{ + struct lwmi_cd_priv *priv = dev_get_drvdata(cd_dev); + + switch (priv->list->type) { + case LENOVO_CAPABILITY_DATA_00: + priv->sub_master->master_dev = NULL; + priv->sub_master->master_cb = NULL; + return; + default: + return; + } +} + static const struct component_ops lwmi_cd_component_ops = { .bind = lwmi_cd_component_bind, + .unbind = lwmi_cd_component_unbind, +}; + +/** + * lwmi_cd_sub_master_bind() - Bind sub-component of sub-master device + * @dev: The sub-master capdata basic device. + * + * Call component_bind_all to bind the sub-component device to the sub-master + * device. On success, collect the pointer to the sub-component list and try + * to call the master callback. + * + * Return: 0 on success, or an error code. + */ +static int lwmi_cd_sub_master_bind(struct device *dev) +{ + struct lwmi_cd_priv *priv = dev_get_drvdata(dev); + struct cd_list *sub_component_list; + int ret; + + ret = component_bind_all(dev, &sub_component_list); + if (ret) + return ret; + + priv->sub_master->sub_component_list = sub_component_list; + lwmi_cd_call_master_cb(priv); + + return 0; +} + +/** + * lwmi_cd_sub_master_unbind() - Unbind sub-component of sub-master device + * @dev: The sub-master capdata basic device + * + * Clear the collected pointer to the sub-component list to prevent the binding + * establishment between the sub-master and the sub-component (if it's about to + * happen) from calling the master callback. Then, call component_unbind_all to + * unbind the sub-component device from the sub-master device. + */ +static void lwmi_cd_sub_master_unbind(struct device *dev) +{ + struct lwmi_cd_priv *priv = dev_get_drvdata(dev); + + priv->sub_master->sub_component_list = NULL; + + component_unbind_all(dev, NULL); +} + +static const struct component_master_ops lwmi_cd_sub_master_ops = { + .bind = lwmi_cd_sub_master_bind, + .unbind = lwmi_cd_sub_master_unbind, +}; + +/** + * lwmi_cd_sub_master_add() - Register a sub-master with its sub-component + * @priv: Pointer to the sub-master capdata device private data. + * @sub_component_type: Type of the sub-component. + * + * Match the sub-component type and register the current capdata device as a + * sub-master. If the given sub-component type is CD_TYPE_NONE, mark the sub- + * component as non-existent without registering sub-master. + * + * Return: 0 on success, or an error code. + */ +static int lwmi_cd_sub_master_add(struct lwmi_cd_priv *priv, + enum lwmi_cd_type sub_component_type) +{ + struct component_match *master_match = NULL; + int ret; + + priv->sub_master = devm_kzalloc(&priv->wdev->dev, sizeof(*priv->sub_master), GFP_KERNEL); + if (!priv->sub_master) + return -ENOMEM; + + if (sub_component_type == CD_TYPE_NONE) { + /* The master callback will be called with NULL on bind. */ + priv->sub_master->sub_component_list = ERR_PTR(-ENODEV); + priv->sub_master->registered = false; + return 0; + } + + /* + * lwmi_cd_match() needs a pointer to enum lwmi_cd_type, but on-stack + * data cannot be used here. Steal one from lwmi_cd_table. + */ + component_match_add(&priv->wdev->dev, &master_match, lwmi_cd_match, + (void *)&lwmi_cd_table[sub_component_type].type); + if (IS_ERR(master_match)) + return PTR_ERR(master_match); + + ret = component_master_add_with_match(&priv->wdev->dev, &lwmi_cd_sub_master_ops, + master_match); + if (ret) + return ret; + + priv->sub_master->registered = true; + return 0; +} + +/** + * lwmi_cd_sub_master_del() - Unregister a sub-master if it's registered + * @priv: Pointer to the sub-master capdata device private data. + */ +static void lwmi_cd_sub_master_del(struct lwmi_cd_priv *priv) +{ + if (!priv->sub_master->registered) + return; + + component_master_del(&priv->wdev->dev, &lwmi_cd_sub_master_ops); + priv->sub_master->registered = false; +} + +/** + * lwmi_cd_sub_component_bind() - Bind sub-component to sub-master device. + * @sc_dev: Pointer to the sub-component capdata parent device. + * @sm_dev: Pointer to the sub-master capdata parent device. + * @data: Pointer used to return the capability data list pointer. + * + * On sub-master's bind, provide a pointer to the local capdata list. + * This is used by the sub-master to call the master callback. + * + * Return: 0 + */ +static int lwmi_cd_sub_component_bind(struct device *sc_dev, + struct device *sm_dev, void *data) +{ + struct lwmi_cd_priv *priv = dev_get_drvdata(sc_dev); + struct cd_list **listp = data; + + *listp = priv->list; + + return 0; +} + +static const struct component_ops lwmi_cd_sub_component_ops = { + .bind = lwmi_cd_sub_component_bind, }; /* @@ -471,9 +726,28 @@ static int lwmi_cd_probe(struct wmi_device *wdev, const void *context) goto out; switch (info->type) { - case LENOVO_CAPABILITY_DATA_00: + case LENOVO_CAPABILITY_DATA_00: { + enum lwmi_cd_type sub_component_type = LENOVO_FAN_TEST_DATA; + struct capdata00 capdata00; + + ret = lwmi_cd00_get_data(priv->list, LWMI_ATTR_ID_FAN_TEST, &capdata00); + if (ret || !(capdata00.supported & LWMI_SUPP_VALID)) { + dev_dbg(&wdev->dev, "capdata00 declares no fan test support\n"); + sub_component_type = CD_TYPE_NONE; + } + + /* Sub-master (capdata00) <-> sub-component (capdata_fan) */ + ret = lwmi_cd_sub_master_add(priv, sub_component_type); + if (ret) + goto out; + + /* Master (lenovo-wmi-other) <-> sub-master (capdata00) */ ret = component_add(&wdev->dev, &lwmi_cd_component_ops); + if (ret) + lwmi_cd_sub_master_del(priv); + goto out; + } case LENOVO_CAPABILITY_DATA_01: priv->acpi_nb.notifier_call = lwmi_cd01_notifier_call; @@ -489,6 +763,7 @@ static int lwmi_cd_probe(struct wmi_device *wdev, const void *context) ret = component_add(&wdev->dev, &lwmi_cd_component_ops); goto out; case LENOVO_FAN_TEST_DATA: + ret = component_add(&wdev->dev, &lwmi_cd_sub_component_ops); goto out; default: return -EINVAL; @@ -510,10 +785,13 @@ static void lwmi_cd_remove(struct wmi_device *wdev) switch (priv->list->type) { case LENOVO_CAPABILITY_DATA_00: + lwmi_cd_sub_master_del(priv); + fallthrough; case LENOVO_CAPABILITY_DATA_01: component_del(&wdev->dev, &lwmi_cd_component_ops); break; case LENOVO_FAN_TEST_DATA: + component_del(&wdev->dev, &lwmi_cd_sub_component_ops); break; default: WARN_ON(1); diff --git a/drivers/platform/x86/lenovo/wmi-capdata.h b/drivers/platform/x86/lenovo/wmi-capdata.h index 38af4c4e4ef4b..59ca3b3e5760b 100644 --- a/drivers/platform/x86/lenovo/wmi-capdata.h +++ b/drivers/platform/x86/lenovo/wmi-capdata.h @@ -5,8 +5,20 @@ #ifndef _LENOVO_WMI_CAPDATA_H_ #define _LENOVO_WMI_CAPDATA_H_ +#include #include +#define LWMI_SUPP_VALID BIT(0) +#define LWMI_SUPP_MAY_GET (LWMI_SUPP_VALID | BIT(1)) +#define LWMI_SUPP_MAY_SET (LWMI_SUPP_VALID | BIT(2)) + +#define LWMI_ATTR_DEV_ID_MASK GENMASK(31, 24) +#define LWMI_ATTR_FEAT_ID_MASK GENMASK(23, 16) +#define LWMI_ATTR_MODE_ID_MASK GENMASK(15, 8) +#define LWMI_ATTR_TYPE_ID_MASK GENMASK(7, 0) + +#define LWMI_DEVICE_ID_FAN 0x04 + struct component_match; struct device; struct cd_list; @@ -32,9 +44,17 @@ struct capdata_fan { u32 max_rpm; }; +typedef void (*cd_list_cb_t)(struct device *master_dev, struct cd_list *cd_list); + struct lwmi_cd_binder { struct cd_list *cd00_list; struct cd_list *cd01_list; + /* + * May be called during or after the bind callback. + * Will be called with NULL if capdata_fan does not exist. + * The pointer is only valid in the callback; never keep it for later use! + */ + cd_list_cb_t cd_fan_list_cb; }; void lwmi_cd_match_add_all(struct device *master, struct component_match **matchptr); diff --git a/drivers/platform/x86/lenovo/wmi-other.c b/drivers/platform/x86/lenovo/wmi-other.c index 73191dedc029f..373390459cbf4 100644 --- a/drivers/platform/x86/lenovo/wmi-other.c +++ b/drivers/platform/x86/lenovo/wmi-other.c @@ -54,11 +54,6 @@ #define LWMI_FEATURE_VALUE_GET 17 #define LWMI_FEATURE_VALUE_SET 18 -#define LWMI_ATTR_DEV_ID_MASK GENMASK(31, 24) -#define LWMI_ATTR_FEAT_ID_MASK GENMASK(23, 16) -#define LWMI_ATTR_MODE_ID_MASK GENMASK(15, 8) -#define LWMI_ATTR_TYPE_ID_MASK GENMASK(7, 0) - #define LWMI_OM_FW_ATTR_BASE_PATH "lenovo-wmi-other" static BLOCKING_NOTIFIER_HEAD(om_chain_head); From 4a6a93afd2202eb08b286dd3d9b36565ce4937fb Mon Sep 17 00:00:00 2001 From: Rong Zhang Date: Wed, 21 Jan 2026 02:20:08 +0800 Subject: [PATCH 07/13] [FROM-UPSTREAM] platform/x86: lenovo-wmi-other: Add HWMON for fan reporting/tuning Register an HWMON device for fan reporting/tuning according to Capability Data 00 (capdata00) and Fan Test Data (capdata_fan) provided by lenovo-wmi-capdata. The corresponding HWMON nodes are: - fanX_div: internal RPM divisor - fanX_input: current RPM - fanX_max: maximum RPM - fanX_min: minimum RPM - fanX_target: target RPM (tunable, 0=auto) Information from capdata00 and capdata_fan are used to control the visibility and constraints of HWMON attributes. Fan info from capdata00 is collected on bind, while fan info from capdata_fan is collected in a callback. Once all fan info is collected, register the HWMON device. Signed-off-by: Rong Zhang Reviewed-by: Derek J. Clark Tested-by: Kurt Borja Signed-off-by: Derek J. Clark --- .../wmi/devices/lenovo-wmi-other.rst | 14 + drivers/platform/x86/lenovo/Kconfig | 1 + drivers/platform/x86/lenovo/wmi-other.c | 502 +++++++++++++++++- 3 files changed, 507 insertions(+), 10 deletions(-) diff --git a/Documentation/wmi/devices/lenovo-wmi-other.rst b/Documentation/wmi/devices/lenovo-wmi-other.rst index 821282e07d93c..01d4711567380 100644 --- a/Documentation/wmi/devices/lenovo-wmi-other.rst +++ b/Documentation/wmi/devices/lenovo-wmi-other.rst @@ -31,6 +31,8 @@ under the following path: /sys/class/firmware-attributes/lenovo-wmi-other/attributes// +Additionally, this driver also exports attributes to HWMON. + LENOVO_CAPABILITY_DATA_00 ------------------------- @@ -39,6 +41,14 @@ WMI GUID ``362A3AFE-3D96-4665-8530-96DAD5BB300E`` The LENOVO_CAPABILITY_DATA_00 interface provides various information that does not rely on the gamezone thermal mode. +The following HWMON attributes are implemented: + - fanX_div: internal RPM divisor + - fanX_input: current RPM + - fanX_target: target RPM (tunable, 0=auto) + +Due to the internal RPM divisor, the current/target RPMs are rounded down to +its nearest multiple. The divisor itself is not necessary to be a power of two. + LENOVO_CAPABILITY_DATA_01 ------------------------- @@ -70,6 +80,10 @@ WMI GUID ``B642801B-3D21-45DE-90AE-6E86F164FB21`` The LENOVO_FAN_TEST_DATA interface provides reference data for self-test of cooling fans. +The following HWMON attributes are implemented: + - fanX_max: maximum RPM + - fanX_min: minimum RPM + WMI interface description ========================= diff --git a/drivers/platform/x86/lenovo/Kconfig b/drivers/platform/x86/lenovo/Kconfig index 587da1c602ca0..f885127b007f1 100644 --- a/drivers/platform/x86/lenovo/Kconfig +++ b/drivers/platform/x86/lenovo/Kconfig @@ -263,6 +263,7 @@ config LENOVO_WMI_GAMEZONE config LENOVO_WMI_TUNING tristate "Lenovo Other Mode WMI Driver" depends on ACPI_WMI + select HWMON select FW_ATTR_CLASS select LENOVO_WMI_CAPDATA select LENOVO_WMI_EVENTS diff --git a/drivers/platform/x86/lenovo/wmi-other.c b/drivers/platform/x86/lenovo/wmi-other.c index 373390459cbf4..58a9166a88f61 100644 --- a/drivers/platform/x86/lenovo/wmi-other.c +++ b/drivers/platform/x86/lenovo/wmi-other.c @@ -14,7 +14,16 @@ * These attributes typically don't fit anywhere else in the sysfs and are set * in Windows using one of Lenovo's multiple user applications. * + * Additionally, this driver also exports tunable fan speed RPM to HWMON. + * Min/max RPM are also provided for reference. + * * Copyright (C) 2025 Derek J. Clark + * - fw_attributes + * - binding to Capability Data 01 + * + * Copyright (C) 2025 Rong Zhang + * - HWMON + * - binding to Capability Data 00 and Fan */ #include @@ -25,9 +34,11 @@ #include #include #include +#include #include #include #include +#include #include #include #include @@ -49,12 +60,26 @@ #define LWMI_FEATURE_ID_CPU_SPL 0x02 #define LWMI_FEATURE_ID_CPU_FPPT 0x03 +#define LWMI_FEATURE_ID_FAN_RPM 0x03 + #define LWMI_TYPE_ID_NONE 0x00 #define LWMI_FEATURE_VALUE_GET 17 #define LWMI_FEATURE_VALUE_SET 18 +#define LWMI_FAN_ID_BASE 1 +#define LWMI_FAN_NR 4 +#define LWMI_FAN_ID(x) ((x) + LWMI_FAN_ID_BASE) + +#define LWMI_ATTR_ID_FAN_RPM(x) \ + (FIELD_PREP(LWMI_ATTR_DEV_ID_MASK, LWMI_DEVICE_ID_FAN) | \ + FIELD_PREP(LWMI_ATTR_FEAT_ID_MASK, LWMI_FEATURE_ID_FAN_RPM) | \ + FIELD_PREP(LWMI_ATTR_TYPE_ID_MASK, LWMI_FAN_ID(x))) + +#define LWMI_FAN_DIV 100 + #define LWMI_OM_FW_ATTR_BASE_PATH "lenovo-wmi-other" +#define LWMI_OM_HWMON_NAME "lenovo_wmi_other" static BLOCKING_NOTIFIER_HEAD(om_chain_head); static DEFINE_IDA(lwmi_om_ida); @@ -67,19 +92,459 @@ enum attribute_property { SUPPORTED, }; +struct lwmi_fan_info { + u32 supported; + u32 last_target; + long min_rpm; + long max_rpm; +}; + struct lwmi_om_priv { struct component_master_ops *ops; /* only valid after capdata bind */ + struct cd_list *cd00_list; struct cd_list *cd01_list; + struct device *hwmon_dev; struct device *fw_attr_dev; struct kset *fw_attr_kset; struct notifier_block nb; struct wmi_device *wdev; int ida_id; + + struct lwmi_fan_info fan_info[LWMI_FAN_NR]; + + struct { + bool capdata00_collected : 1; + bool capdata_fan_collected : 1; + } fan_flags; +}; + +/* + * Visibility of fan channels: + * + * +-------------------+---------+------------------+-----------------------+------------+ + * | | default | +expose_all_fans | +relax_fan_constraint | +both | + * +-------------------+---------+------------------+-----------------------+------------+ + * | canonical | RW | RW | RW+relaxed | RW+relaxed | + * +-------------------+---------+------------------+-----------------------+------------+ + * | -capdata_fan[idx] | N | RO | N | RW+relaxed | + * +-------------------+---------+------------------+-----------------------+------------+ + * + * Note: + * 1. LWMI_ATTR_ID_FAN_RPM[idx].supported is always checked before exposing a channel. + * 2. -capdata_fan implies -capdata_fan[idx]. + */ +static bool expose_all_fans; +module_param(expose_all_fans, bool, 0444); +MODULE_PARM_DESC(expose_all_fans, + "This option skips some capability checks and solely relies on per-channel ones " + "to expose fan attributes. Use with caution."); + +static bool relax_fan_constraint; +module_param(relax_fan_constraint, bool, 0444); +MODULE_PARM_DESC(relax_fan_constraint, + "Do not enforce fan RPM constraint (div/min/max) " + "and enables fan tuning when such data is missing. " + "Enabling this may results in HWMON attributes being out-of-sync, " + "and setting a too low RPM stops the fan. Use with caution."); + +/* ======== HWMON (component: lenovo-wmi-capdata 00 & fan) ======== */ + +/** + * lwmi_om_fan_get_set() - Get or set fan RPM value of specified fan + * @priv: Driver private data structure + * @channel: Fan channel index (0-based) + * @val: Pointer to value (input for set, output for get) + * @set: True to set value, false to get value + * + * Communicates with WMI interface to either retrieve current fan RPM + * or set target fan RPM. + * + * Return: 0 on success, or an error code. + */ +static int lwmi_om_fan_get_set(struct lwmi_om_priv *priv, int channel, u32 *val, bool set) +{ + struct wmi_method_args_32 args; + u32 method_id, retval; + int err; + + method_id = set ? LWMI_FEATURE_VALUE_SET : LWMI_FEATURE_VALUE_GET; + args.arg0 = LWMI_ATTR_ID_FAN_RPM(channel); + args.arg1 = set ? *val : 0; + + err = lwmi_dev_evaluate_int(priv->wdev, 0x0, method_id, + (unsigned char *)&args, sizeof(args), &retval); + if (err) + return err; + + if (!set) { + *val = retval; + return 0; + } + + /* + * It seems that 0 means "no error" and 1 means "done". Apparently + * different firmware teams have different thoughts on indicating + * success, so we accepts both. + */ + return (retval == 0 || retval == 1) ? 0 : -EIO; +} + +/** + * lwmi_om_hwmon_is_visible() - Determine visibility of HWMON attributes + * @drvdata: Driver private data + * @type: Sensor type + * @attr: Attribute identifier + * @channel: Channel index + * + * Determines whether an HWMON attribute should be visible in sysfs + * based on hardware capabilities and current configuration. + * + * Return: permission mode, or 0 if invisible. + */ +static umode_t lwmi_om_hwmon_is_visible(const void *drvdata, enum hwmon_sensor_types type, + u32 attr, int channel) +{ + struct lwmi_om_priv *priv = (struct lwmi_om_priv *)drvdata; + bool visible = false; + + if (type == hwmon_fan) { + if (!(priv->fan_info[channel].supported & LWMI_SUPP_VALID)) + return 0; + + switch (attr) { + case hwmon_fan_target: + if (!(priv->fan_info[channel].supported & LWMI_SUPP_MAY_SET)) + return 0; + + if (relax_fan_constraint || + (priv->fan_info[channel].min_rpm >= 0 && + priv->fan_info[channel].max_rpm >= 0)) + return 0644; + + /* + * Reaching here implies expose_all_fans is set. + * See lwmi_om_hwmon_add(). + */ + dev_warn_once(&priv->wdev->dev, + "fan tuning disabled due to missing RPM constraint\n"); + return 0; + case hwmon_fan_div: + case hwmon_fan_input: + visible = priv->fan_info[channel].supported & LWMI_SUPP_MAY_GET; + break; + case hwmon_fan_min: + visible = priv->fan_info[channel].min_rpm >= 0; + break; + case hwmon_fan_max: + visible = priv->fan_info[channel].max_rpm >= 0; + break; + } + } + + return visible ? 0444 : 0; +} + +/** + * lwmi_om_hwmon_read() - Read HWMON sensor data + * @dev: Device pointer + * @type: Sensor type + * @attr: Attribute identifier + * @channel: Channel index + * @val: Pointer to store value + * + * Reads current sensor values from hardware through WMI interface. + * + * Return: 0 on success, or an error code. + */ +static int lwmi_om_hwmon_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + struct lwmi_om_priv *priv = dev_get_drvdata(dev); + u32 retval = 0; + int err; + + if (type == hwmon_fan) { + switch (attr) { + /* + * The EC has an internal RPM divisor (i.e., the raw register value is + * RPM / fanY_div). For fanY_input, the WMI moethod reads the register + * value and returns raw * fanY_div. For fanY_target, the WMI method + * divides the written value by fanY_div before writing it to the EC. + * + * As a result, reading fanY_input always returns a multiple of fanY_div, + * while writing to fanY_target loses the remainder. + */ + case hwmon_fan_div: + *val = LWMI_FAN_DIV; + return 0; + case hwmon_fan_input: + err = lwmi_om_fan_get_set(priv, channel, &retval, false); + if (err) + return err; + + *val = retval; + return 0; + case hwmon_fan_target: + *val = priv->fan_info[channel].last_target; + return 0; + case hwmon_fan_min: + *val = priv->fan_info[channel].min_rpm; + return 0; + case hwmon_fan_max: + *val = priv->fan_info[channel].max_rpm; + return 0; + } + } + + return -EOPNOTSUPP; +} + +/** + * lwmi_om_hwmon_write() - Write HWMON sensor data + * @dev: Device pointer + * @type: Sensor type + * @attr: Attribute identifier + * @channel: Channel index + * @val: Value to write + * + * Writes configuration values to hardware through WMI interface. + * + * Return: 0 on success, or an error code. + */ +static int lwmi_om_hwmon_write(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long val) +{ + struct lwmi_om_priv *priv = dev_get_drvdata(dev); + u32 raw, min_rpm, max_rpm; + int err; + + if (type == hwmon_fan) { + switch (attr) { + case hwmon_fan_target: + if (relax_fan_constraint) { + min_rpm = 1; + max_rpm = U16_MAX; + } else { + min_rpm = priv->fan_info[channel].min_rpm; + max_rpm = priv->fan_info[channel].max_rpm; + } + + /* 0 means "auto". */ + if (val != 0 && (val < min_rpm || val > max_rpm)) + return -EINVAL; + + /* + * The effective fanY_target is always a multiple of fanY_div + * due to the EC's internal RPM divisor (see lwmi_om_hwmon_read). + * + * Round down the written value to the nearest multiple of fanY_div + * to prevent mismatch between the effective value and last_target. + * + * For relax_fan_constraint, skip this conversion as setting a + * sub-fanY_div value is necessary to completely stop the fan on + * some devices. + */ + if (!relax_fan_constraint) + raw = val / LWMI_FAN_DIV * LWMI_FAN_DIV; + + err = lwmi_om_fan_get_set(priv, channel, &raw, true); + if (err) + return err; + + priv->fan_info[channel].last_target = raw; + return 0; + } + } + + return -EOPNOTSUPP; +} + +static const struct hwmon_channel_info * const lwmi_om_hwmon_info[] = { + /* Must match LWMI_FAN_NR. */ + HWMON_CHANNEL_INFO(fan, + HWMON_F_INPUT | HWMON_F_TARGET | HWMON_F_DIV | + HWMON_F_MIN | HWMON_F_MAX, + HWMON_F_INPUT | HWMON_F_TARGET | HWMON_F_DIV | + HWMON_F_MIN | HWMON_F_MAX, + HWMON_F_INPUT | HWMON_F_TARGET | HWMON_F_DIV | + HWMON_F_MIN | HWMON_F_MAX, + HWMON_F_INPUT | HWMON_F_TARGET | HWMON_F_DIV | + HWMON_F_MIN | HWMON_F_MAX), + NULL }; +static const struct hwmon_ops lwmi_om_hwmon_ops = { + .is_visible = lwmi_om_hwmon_is_visible, + .read = lwmi_om_hwmon_read, + .write = lwmi_om_hwmon_write, +}; + +static const struct hwmon_chip_info lwmi_om_hwmon_chip_info = { + .ops = &lwmi_om_hwmon_ops, + .info = lwmi_om_hwmon_info, +}; + +/** + * lwmi_om_hwmon_add() - Register HWMON device if all info is collected + * @priv: Driver private data + */ +static void lwmi_om_hwmon_add(struct lwmi_om_priv *priv) +{ + int i, valid; + + if (WARN_ON(priv->hwmon_dev)) + return; + + if (!priv->fan_flags.capdata00_collected || !priv->fan_flags.capdata_fan_collected) { + dev_dbg(&priv->wdev->dev, "HWMON registration pending (00: %d, fan: %d)\n", + priv->fan_flags.capdata00_collected, + priv->fan_flags.capdata_fan_collected); + return; + } + + if (expose_all_fans) + dev_warn(&priv->wdev->dev, "all fans exposed. Use with caution\n"); + + if (relax_fan_constraint) + dev_warn(&priv->wdev->dev, "fan RPM constraint relaxed. Use with caution\n"); + + valid = 0; + for (i = 0; i < LWMI_FAN_NR; i++) { + if (!(priv->fan_info[i].supported & LWMI_SUPP_VALID)) + continue; + + valid++; + + if (!expose_all_fans && + (priv->fan_info[i].min_rpm < 0 || priv->fan_info[i].max_rpm < 0)) { + dev_dbg(&priv->wdev->dev, "missing RPM constraint for fan%d, hiding\n", + LWMI_FAN_ID(i)); + priv->fan_info[i].supported = 0; + valid--; + } + } + + if (valid == 0) { + dev_warn(&priv->wdev->dev, + "fan reporting/tuning is unsupported on this device\n"); + return; + } + + priv->hwmon_dev = hwmon_device_register_with_info(&priv->wdev->dev, + LWMI_OM_HWMON_NAME, priv, + &lwmi_om_hwmon_chip_info, + NULL); + if (IS_ERR(priv->hwmon_dev)) { + dev_warn(&priv->wdev->dev, "failed to register HWMON device: %ld\n", + PTR_ERR(priv->hwmon_dev)); + priv->hwmon_dev = NULL; + return; + } + + dev_dbg(&priv->wdev->dev, "registered HWMON device\n"); +} + +/** + * lwmi_om_hwmon_remove() - Unregister HWMON device + * @priv: Driver private data + * + * Unregisters the HWMON device if applicable. + */ +static void lwmi_om_hwmon_remove(struct lwmi_om_priv *priv) +{ + if (!priv->hwmon_dev) + return; + + hwmon_device_unregister(priv->hwmon_dev); + priv->hwmon_dev = NULL; +} + +/** + * lwmi_om_fan_info_init() - Initialzie fan info + * @priv: Driver private data + * + * lwmi_om_fan_info_collect_cd00() and lwmi_om_fan_info_collect_cd_fan() may be + * called in an arbitrary order. Hence, initializion must be done before. + */ +static void lwmi_om_fan_info_init(struct lwmi_om_priv *priv) +{ + int i; + + for (i = 0; i < LWMI_FAN_NR; i++) { + priv->fan_info[i] = (struct lwmi_fan_info) { + .supported = 0, + /* + * Assume 0 on probe as the EC resets all fans to auto mode on (re)boot. + * + * Note that S0ix (s2idle) preserves the RPM target, so we don't need + * suspend/resume callbacks. This behavior has not been tested on S3- + * capable devices, but I doubt if such devices even have this interface. + */ + .last_target = 0, + .min_rpm = -ENODATA, + .max_rpm = -ENODATA, + }; + } + + priv->fan_flags.capdata00_collected = false; + priv->fan_flags.capdata_fan_collected = false; +} + +/** + * lwmi_om_fan_info_collect_cd00() - Collect fan info from capdata 00 + * @priv: Driver private data + */ +static void lwmi_om_fan_info_collect_cd00(struct lwmi_om_priv *priv) +{ + struct capdata00 capdata00; + int i, err; + + dev_dbg(&priv->wdev->dev, "Collecting fan info from capdata00\n"); + + for (i = 0; i < LWMI_FAN_NR; i++) { + err = lwmi_cd00_get_data(priv->cd00_list, LWMI_ATTR_ID_FAN_RPM(i), &capdata00); + priv->fan_info[i].supported = err ? 0 : capdata00.supported; + } + + priv->fan_flags.capdata00_collected = true; + lwmi_om_hwmon_add(priv); +} + +/** + * lwmi_om_fan_info_collect_cd_fan() - Collect fan info from capdata fan + * @dev: Pointer to the lenovo-wmi-other device + * @cd_fan_list: Pointer to the capdata fan list + */ +static void lwmi_om_fan_info_collect_cd_fan(struct device *dev, struct cd_list *cd_fan_list) +{ + struct lwmi_om_priv *priv = dev_get_drvdata(dev); + struct capdata_fan capdata_fan; + int i, err; + + dev_dbg(dev, "Collecting fan info from capdata_fan\n"); + + if (!cd_fan_list) + goto out; + + for (i = 0; i < LWMI_FAN_NR; i++) { + err = lwmi_cd_fan_get_data(cd_fan_list, LWMI_FAN_ID(i), &capdata_fan); + if (err) + continue; + + priv->fan_info[i].min_rpm = capdata_fan.min_rpm; + priv->fan_info[i].max_rpm = capdata_fan.max_rpm; + } + +out: + priv->fan_flags.capdata_fan_collected = true; + lwmi_om_hwmon_add(priv); +} + +/* ======== fw_attributes (component: lenovo-wmi-capdata 01) ======== */ + struct tunable_attr_01 { struct capdata01 *capdata; struct device *dev; @@ -559,32 +1024,45 @@ static void lwmi_om_fw_attr_remove(struct lwmi_om_priv *priv) device_unregister(priv->fw_attr_dev); } +/* ======== Self (master: lenovo-wmi-other) ======== */ + /** * lwmi_om_master_bind() - Bind all components of the other mode driver * @dev: The lenovo-wmi-other driver basic device. * - * Call component_bind_all to bind the lenovo-wmi-capdata01 driver to the - * lenovo-wmi-other master driver. On success, assign the capability data 01 - * list pointer to the driver data struct for later access. This pointer - * is only valid while the capdata01 interface exists. Finally, register all - * firmware attribute groups. + * Call component_bind_all to bind the lenovo-wmi-capdata devices to the + * lenovo-wmi-other master driver, with a callback to collect fan info from + * capdata_fan. On success, assign the capability data list pointers to the + * driver data struct for later access. These pointers are only valid while the + * capdata interfaces exist. Finally, collect fan info from capdata00 and + * register all firmware attribute groups. Note that the HWMON device is + * registered only if all fan info is collected. Hence, it is not registered + * here. See lwmi_om_fan_info_collect_cd00() and + * lwmi_om_fan_info_collect_cd_fan(). * * Return: 0 on success, or an error code. */ static int lwmi_om_master_bind(struct device *dev) { struct lwmi_om_priv *priv = dev_get_drvdata(dev); - struct lwmi_cd_binder binder = {}; + struct lwmi_cd_binder binder = { + .cd_fan_list_cb = lwmi_om_fan_info_collect_cd_fan, + }; int ret; + lwmi_om_fan_info_init(priv); + ret = component_bind_all(dev, &binder); if (ret) return ret; + priv->cd00_list = binder.cd00_list; priv->cd01_list = binder.cd01_list; - if (!priv->cd01_list) + if (!priv->cd00_list || !priv->cd01_list) return -ENODEV; + lwmi_om_fan_info_collect_cd00(priv); + return lwmi_om_fw_attr_add(priv); } @@ -592,15 +1070,18 @@ static int lwmi_om_master_bind(struct device *dev) * lwmi_om_master_unbind() - Unbind all components of the other mode driver * @dev: The lenovo-wmi-other driver basic device * - * Unregister all capability data attribute groups. Then call - * component_unbind_all to unbind the lenovo-wmi-capdata01 driver from the - * lenovo-wmi-other master driver. Finally, free the IDA for this device. + * Unregister all firmware attribute groups and the HWMON device. Then call + * component_unbind_all to unbind lenovo-wmi-capdata devices from the + * lenovo-wmi-other master driver. */ static void lwmi_om_master_unbind(struct device *dev) { struct lwmi_om_priv *priv = dev_get_drvdata(dev); lwmi_om_fw_attr_remove(priv); + + lwmi_om_hwmon_remove(priv); + component_unbind_all(dev, NULL); } @@ -665,5 +1146,6 @@ MODULE_IMPORT_NS("LENOVO_WMI_CAPDATA"); MODULE_IMPORT_NS("LENOVO_WMI_HELPERS"); MODULE_DEVICE_TABLE(wmi, lwmi_other_id_table); MODULE_AUTHOR("Derek J. Clark "); +MODULE_AUTHOR("Rong Zhang "); MODULE_DESCRIPTION("Lenovo Other Mode WMI Driver"); MODULE_LICENSE("GPL"); From 894056076137efea2766f63b9b536943677982cf Mon Sep 17 00:00:00 2001 From: Rong Zhang Date: Sun, 8 Feb 2026 01:23:27 +0800 Subject: [PATCH 08/13] [FROM-UPSTREAM] platform/x86: lenovo-wmi-{capdata,other}: Fix HWMON channel visibility The LWMI_SUPP_MAY_{GET,SET} macros are fundamentally broken. When I introduced them, I meant to check LWMI_SUPP_VALID *and* the corresponding bits for get/set capabilities. However, `supported & LWMI_SUPP_MAY_{GET,SET}' means *or*, so it accidentally passes the check when LWMI_SUPP_VALID is set. Fix them by only including the corresponding get/set bit without LWMI_SUPP_VALID. Meanwhile, rename them to LWMI_SUPP_{GET,SET} to make them less confusing. Fixes: 67d9a39ce85f ("platform/x86: lenovo-wmi-capdata: Wire up Fan Test Data") Signed-off-by: Rong Zhang Signed-off-by: Derek J. Clark --- drivers/platform/x86/lenovo/wmi-capdata.h | 4 ++-- drivers/platform/x86/lenovo/wmi-other.c | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/platform/x86/lenovo/wmi-capdata.h b/drivers/platform/x86/lenovo/wmi-capdata.h index 59ca3b3e5760b..8c1df3efcc553 100644 --- a/drivers/platform/x86/lenovo/wmi-capdata.h +++ b/drivers/platform/x86/lenovo/wmi-capdata.h @@ -9,8 +9,8 @@ #include #define LWMI_SUPP_VALID BIT(0) -#define LWMI_SUPP_MAY_GET (LWMI_SUPP_VALID | BIT(1)) -#define LWMI_SUPP_MAY_SET (LWMI_SUPP_VALID | BIT(2)) +#define LWMI_SUPP_GET BIT(1) +#define LWMI_SUPP_SET BIT(2) #define LWMI_ATTR_DEV_ID_MASK GENMASK(31, 24) #define LWMI_ATTR_FEAT_ID_MASK GENMASK(23, 16) diff --git a/drivers/platform/x86/lenovo/wmi-other.c b/drivers/platform/x86/lenovo/wmi-other.c index 58a9166a88f61..2ab505e92b9ee 100644 --- a/drivers/platform/x86/lenovo/wmi-other.c +++ b/drivers/platform/x86/lenovo/wmi-other.c @@ -216,7 +216,7 @@ static umode_t lwmi_om_hwmon_is_visible(const void *drvdata, enum hwmon_sensor_t switch (attr) { case hwmon_fan_target: - if (!(priv->fan_info[channel].supported & LWMI_SUPP_MAY_SET)) + if (!(priv->fan_info[channel].supported & LWMI_SUPP_SET)) return 0; if (relax_fan_constraint || @@ -233,7 +233,7 @@ static umode_t lwmi_om_hwmon_is_visible(const void *drvdata, enum hwmon_sensor_t return 0; case hwmon_fan_div: case hwmon_fan_input: - visible = priv->fan_info[channel].supported & LWMI_SUPP_MAY_GET; + visible = priv->fan_info[channel].supported & LWMI_SUPP_GET; break; case hwmon_fan_min: visible = priv->fan_info[channel].min_rpm >= 0; From e541a9c0701fce341acfdf78be46de19dfbc2740 Mon Sep 17 00:00:00 2001 From: "Derek J. Clark" Date: Fri, 13 Feb 2026 08:02:53 +0000 Subject: [PATCH 09/13] [FROM-ML] platform/x86: lenovo-wmi-other: Add LWMI_ATTR_ID Macro Adds LWMI_ATTR_ID macro. In the same vein as LWMI_ATTR_ID_FAN_RPM, but as a generic, to de-duplicate attribute_id assignment biolerplate. Signed-off-by: Derek J. Clark --- drivers/platform/x86/lenovo/wmi-capdata.h | 6 ++++ drivers/platform/x86/lenovo/wmi-gamezone.h | 1 + drivers/platform/x86/lenovo/wmi-other.c | 32 ++++++---------------- 3 files changed, 16 insertions(+), 23 deletions(-) diff --git a/drivers/platform/x86/lenovo/wmi-capdata.h b/drivers/platform/x86/lenovo/wmi-capdata.h index 8c1df3efcc553..27202e2dc8a59 100644 --- a/drivers/platform/x86/lenovo/wmi-capdata.h +++ b/drivers/platform/x86/lenovo/wmi-capdata.h @@ -17,6 +17,12 @@ #define LWMI_ATTR_MODE_ID_MASK GENMASK(15, 8) #define LWMI_ATTR_TYPE_ID_MASK GENMASK(7, 0) +#define LWMI_ATTR_ID(dev, feat, mode, type) \ + (FIELD_PREP(LWMI_ATTR_DEV_ID_MASK, dev) | \ + FIELD_PREP(LWMI_ATTR_FEAT_ID_MASK, feat) | \ + FIELD_PREP(LWMI_ATTR_MODE_ID_MASK, mode) | \ + FIELD_PREP(LWMI_ATTR_TYPE_ID_MASK, type)) + #define LWMI_DEVICE_ID_FAN 0x04 struct component_match; diff --git a/drivers/platform/x86/lenovo/wmi-gamezone.h b/drivers/platform/x86/lenovo/wmi-gamezone.h index 6b163a5eeb959..ddb919cf6c36d 100644 --- a/drivers/platform/x86/lenovo/wmi-gamezone.h +++ b/drivers/platform/x86/lenovo/wmi-gamezone.h @@ -10,6 +10,7 @@ enum gamezone_events_type { }; enum thermal_mode { + LWMI_GZ_THERMAL_MODE_NONE = 0x00, LWMI_GZ_THERMAL_MODE_QUIET = 0x01, LWMI_GZ_THERMAL_MODE_BALANCED = 0x02, LWMI_GZ_THERMAL_MODE_PERFORMANCE = 0x03, diff --git a/drivers/platform/x86/lenovo/wmi-other.c b/drivers/platform/x86/lenovo/wmi-other.c index 2ab505e92b9ee..c2112f8dbd58a 100644 --- a/drivers/platform/x86/lenovo/wmi-other.c +++ b/drivers/platform/x86/lenovo/wmi-other.c @@ -71,10 +71,9 @@ #define LWMI_FAN_NR 4 #define LWMI_FAN_ID(x) ((x) + LWMI_FAN_ID_BASE) -#define LWMI_ATTR_ID_FAN_RPM(x) \ - (FIELD_PREP(LWMI_ATTR_DEV_ID_MASK, LWMI_DEVICE_ID_FAN) | \ - FIELD_PREP(LWMI_ATTR_FEAT_ID_MASK, LWMI_FEATURE_ID_FAN_RPM) | \ - FIELD_PREP(LWMI_ATTR_TYPE_ID_MASK, LWMI_FAN_ID(x))) +#define LWMI_ATTR_ID_FAN_RPM(x) \ + LWMI_ATTR_ID(LWMI_DEVICE_ID_FAN, LWMI_FEATURE_ID_FAN_RPM, \ + LWMI_GZ_THERMAL_MODE_NONE, LWMI_FAN_ID(x)) #define LWMI_FAN_DIV 100 @@ -716,12 +715,8 @@ static ssize_t attr_capdata01_show(struct kobject *kobj, u32 attribute_id; int value, ret; - attribute_id = - FIELD_PREP(LWMI_ATTR_DEV_ID_MASK, tunable_attr->device_id) | - FIELD_PREP(LWMI_ATTR_FEAT_ID_MASK, tunable_attr->feature_id) | - FIELD_PREP(LWMI_ATTR_MODE_ID_MASK, - LWMI_GZ_THERMAL_MODE_CUSTOM) | - FIELD_PREP(LWMI_ATTR_TYPE_ID_MASK, tunable_attr->type_id); + attribute_id = LWMI_ATTR_ID(tunable_attr->device_id, tunable_attr->feature_id, + LWMI_GZ_THERMAL_MODE_CUSTOM, tunable_attr->type_id); ret = lwmi_cd01_get_data(priv->cd01_list, attribute_id, &capdata); if (ret) @@ -787,11 +782,8 @@ static ssize_t attr_current_value_store(struct kobject *kobj, if (mode != LWMI_GZ_THERMAL_MODE_CUSTOM) return -EBUSY; - attribute_id = - FIELD_PREP(LWMI_ATTR_DEV_ID_MASK, tunable_attr->device_id) | - FIELD_PREP(LWMI_ATTR_FEAT_ID_MASK, tunable_attr->feature_id) | - FIELD_PREP(LWMI_ATTR_MODE_ID_MASK, mode) | - FIELD_PREP(LWMI_ATTR_TYPE_ID_MASK, tunable_attr->type_id); + attribute_id = LWMI_ATTR_ID(tunable_attr->device_id, tunable_attr->feature_id, + mode, tunable_attr->type_id); ret = lwmi_cd01_get_data(priv->cd01_list, attribute_id, &capdata); if (ret) @@ -838,7 +830,6 @@ static ssize_t attr_current_value_show(struct kobject *kobj, struct lwmi_om_priv *priv = dev_get_drvdata(tunable_attr->dev); struct wmi_method_args_32 args; enum thermal_mode mode; - u32 attribute_id; int retval; int ret; @@ -846,13 +837,8 @@ static ssize_t attr_current_value_show(struct kobject *kobj, if (ret) return ret; - attribute_id = - FIELD_PREP(LWMI_ATTR_DEV_ID_MASK, tunable_attr->device_id) | - FIELD_PREP(LWMI_ATTR_FEAT_ID_MASK, tunable_attr->feature_id) | - FIELD_PREP(LWMI_ATTR_MODE_ID_MASK, mode) | - FIELD_PREP(LWMI_ATTR_TYPE_ID_MASK, tunable_attr->type_id); - - args.arg0 = attribute_id; + args.arg0 = LWMI_ATTR_ID(tunable_attr->device_id, tunable_attr->feature_id, + mode, tunable_attr->type_id); ret = lwmi_dev_evaluate_int(priv->wdev, 0x0, LWMI_FEATURE_VALUE_GET, (unsigned char *)&args, sizeof(args), From dcdf945f8ee1fa21c5b3cc8c3bee7e59eb90cfb7 Mon Sep 17 00:00:00 2001 From: "Derek J. Clark" Date: Fri, 13 Feb 2026 08:02:54 +0000 Subject: [PATCH 10/13] [FROM-ML] platform/x86: lenovo-wmi-other: Limit adding attributes to supported devices Adds lwmi_is_attr_01_supported, and only creates the attribute subfolder if the attribute is supported by the hardware. Due to some poorly implemented BIOS, this is a multi-step sequence of events. This is because: - Some BIOS support getting the capability data from custom mode (0xff), while others only support it in no-mode (0x00). - Similarly, some BIOS support get/set for the current value from custom mode (0xff), while others only support it in no-mode (0x00). - Some BIOS report capability data for a method that is not fully implemented. - Some BIOS have methods fully implemented, but no complimentary capability data. To ensure we only expose fully implemented methods with corresponding capability data, we check each outcome before reporting that an attribute can be supported. Checking for lwmi_is_attr_01_supported during remove is not done to ensure that we don't attempt to call cd01 or send WMI events if one of the interfaces being removed was the cause of the driver unloading. While adding members to tunable_attr_01, remove unused capdata pointer and limit size of all ID's to the appropriate size. Reported-by: Kurt Borja Closes: https://lore.kernel.org/platform-driver-x86/DG60P3SHXR8H.3NSEHMZ6J7XRC@gmail.com/ Signed-off-by: Derek J. Clark --- drivers/platform/x86/lenovo/wmi-other.c | 123 ++++++++++++++++++++---- 1 file changed, 104 insertions(+), 19 deletions(-) diff --git a/drivers/platform/x86/lenovo/wmi-other.c b/drivers/platform/x86/lenovo/wmi-other.c index c2112f8dbd58a..47f9c34611321 100644 --- a/drivers/platform/x86/lenovo/wmi-other.c +++ b/drivers/platform/x86/lenovo/wmi-other.c @@ -545,11 +545,12 @@ static void lwmi_om_fan_info_collect_cd_fan(struct device *dev, struct cd_list * /* ======== fw_attributes (component: lenovo-wmi-capdata 01) ======== */ struct tunable_attr_01 { - struct capdata01 *capdata; struct device *dev; - u32 feature_id; - u32 device_id; - u32 type_id; + u8 feature_id; + u8 device_id; + u8 type_id; + u8 cd_mode_id; /* mode arg for searching capdata */ + u8 cv_mode_id; /* mode arg for set/get current_value */ }; static struct tunable_attr_01 ppt_pl1_spl = { @@ -716,7 +717,7 @@ static ssize_t attr_capdata01_show(struct kobject *kobj, int value, ret; attribute_id = LWMI_ATTR_ID(tunable_attr->device_id, tunable_attr->feature_id, - LWMI_GZ_THERMAL_MODE_CUSTOM, tunable_attr->type_id); + tunable_attr->cd_mode_id, tunable_attr->type_id); ret = lwmi_cd01_get_data(priv->cd01_list, attribute_id, &capdata); if (ret) @@ -771,7 +772,6 @@ static ssize_t attr_current_value_store(struct kobject *kobj, struct wmi_method_args_32 args; struct capdata01 capdata; enum thermal_mode mode; - u32 attribute_id; u32 value; int ret; @@ -782,10 +782,10 @@ static ssize_t attr_current_value_store(struct kobject *kobj, if (mode != LWMI_GZ_THERMAL_MODE_CUSTOM) return -EBUSY; - attribute_id = LWMI_ATTR_ID(tunable_attr->device_id, tunable_attr->feature_id, - mode, tunable_attr->type_id); + args.arg0 = LWMI_ATTR_ID(tunable_attr->device_id, tunable_attr->feature_id, + tunable_attr->cd_mode_id, tunable_attr->type_id); - ret = lwmi_cd01_get_data(priv->cd01_list, attribute_id, &capdata); + ret = lwmi_cd01_get_data(priv->cd01_list, args.arg0, &capdata); if (ret) return ret; @@ -796,7 +796,8 @@ static ssize_t attr_current_value_store(struct kobject *kobj, if (value < capdata.min_value || value > capdata.max_value) return -EINVAL; - args.arg0 = attribute_id; + args.arg0 = LWMI_ATTR_ID(tunable_attr->device_id, tunable_attr->feature_id, + tunable_attr->cv_mode_id, tunable_attr->type_id); args.arg1 = value; ret = lwmi_dev_evaluate_int(priv->wdev, 0x0, LWMI_FEATURE_VALUE_SET, @@ -830,13 +831,16 @@ static ssize_t attr_current_value_show(struct kobject *kobj, struct lwmi_om_priv *priv = dev_get_drvdata(tunable_attr->dev); struct wmi_method_args_32 args; enum thermal_mode mode; - int retval; - int ret; + int retval, ret; ret = lwmi_om_notifier_call(&mode); if (ret) return ret; + /* If "no-mode" is the supported mode, ensure we never send current mode */ + if (tunable_attr->cv_mode_id == LWMI_GZ_THERMAL_MODE_NONE) + mode = tunable_attr->cv_mode_id; + args.arg0 = LWMI_ATTR_ID(tunable_attr->device_id, tunable_attr->feature_id, mode, tunable_attr->type_id); @@ -849,6 +853,85 @@ static ssize_t attr_current_value_show(struct kobject *kobj, return sysfs_emit(buf, "%d\n", retval); } +/** + * lwmi_attr_01_is_supported() - Determine if the given attribute is supported. + * @tunable_attr: The attribute to verify. + * + * First check if the attribute has a corresponding capdata01 table in the cd01 + * module under the "custom" mode (0xff). If that is not present then check if + * there is a corresponding "no-mode" (0x00) entry. If either of those passes, + * check capdata->supported for values > 0. If capdata is available, attempt to + * determine the set/get mode for the current value property using a similar + * pattern. If the value returned by either custom or no-mode is 0, or we get + * an error, we assume that mode is not supported. If any of the above checks + * fail then the attribute is not fully supported. + * + * The probed cd_mode_id/cv_mode_id are stored on the tunable_attr for later + * reference. + * + * Return: Support level, or an error code. + */ +static int lwmi_attr_01_is_supported(struct tunable_attr_01 *tunable_attr) +{ + struct lwmi_om_priv *priv = dev_get_drvdata(tunable_attr->dev); + u8 mode = LWMI_GZ_THERMAL_MODE_CUSTOM; + struct wmi_method_args_32 args; + struct capdata01 capdata; + int retval, ret; + + /* Determine tunable_attr->cd_mode_id */ +no_mode_fallback_1: + args.arg0 = LWMI_ATTR_ID(tunable_attr->device_id, tunable_attr->feature_id, + mode, tunable_attr->type_id); + + ret = lwmi_cd01_get_data(priv->cd01_list, args.arg0, &capdata); + if (ret && mode) { + dev_dbg(tunable_attr->dev, "Attribute id %x not supported\n", args.arg0); + mode = LWMI_GZ_THERMAL_MODE_NONE; + goto no_mode_fallback_1; + } + if (ret) + goto not_supported; + if (!capdata.supported) { + ret = -EOPNOTSUPP; + goto not_supported; + } + + tunable_attr->cd_mode_id = mode; + + /* Determine tunable_attr->cv_mode_id */ + mode = LWMI_GZ_THERMAL_MODE_CUSTOM; +no_mode_fallback_2: + args.arg0 = LWMI_ATTR_ID(tunable_attr->device_id, tunable_attr->feature_id, + mode, tunable_attr->type_id); + + ret = lwmi_dev_evaluate_int(priv->wdev, 0x0, LWMI_FEATURE_VALUE_GET, + (unsigned char *)&args, sizeof(args), + &retval); + if ((ret && mode) || (!retval && mode)) { + dev_dbg(tunable_attr->dev, "Attribute id %x not supported\n", args.arg0); + mode = LWMI_GZ_THERMAL_MODE_NONE; + goto no_mode_fallback_2; + } + if (ret) + goto not_supported; + if (retval == 0) { + ret = -EOPNOTSUPP; + goto not_supported; + } + + tunable_attr->cv_mode_id = mode; + dev_dbg(tunable_attr->dev, "cd_mode_id: %02x%02x%02x%02x, cv_mode_id: %#08x attribute support level: %x\n", + tunable_attr->device_id, tunable_attr->feature_id, tunable_attr->cd_mode_id, + tunable_attr->type_id, args.arg0, capdata.supported); + + return capdata.supported; + +not_supported: + dev_dbg(tunable_attr->dev, "Attribute id %x not supported\n", args.arg0); + return ret; +} + /* Lenovo WMI Other Mode Attribute macros */ #define __LWMI_ATTR_RO(_func, _name) \ { \ @@ -972,19 +1055,21 @@ static int lwmi_om_fw_attr_add(struct lwmi_om_priv *priv) } for (i = 0; i < ARRAY_SIZE(cd01_attr_groups) - 1; i++) { - err = sysfs_create_group(&priv->fw_attr_kset->kobj, - cd01_attr_groups[i].attr_group); - if (err) - goto err_remove_groups; - cd01_attr_groups[i].tunable_attr->dev = &priv->wdev->dev; + if (lwmi_attr_01_is_supported(cd01_attr_groups[i].tunable_attr) > 0) { + err = sysfs_create_group(&priv->fw_attr_kset->kobj, + cd01_attr_groups[i].attr_group); + if (err) + goto err_remove_groups; + } } return 0; err_remove_groups: while (i--) - sysfs_remove_group(&priv->fw_attr_kset->kobj, - cd01_attr_groups[i].attr_group); + if (lwmi_attr_01_is_supported(cd01_attr_groups[i].tunable_attr) > 0) + sysfs_remove_group(&priv->fw_attr_kset->kobj, + cd01_attr_groups[i].attr_group); kset_unregister(priv->fw_attr_kset); From 1325145b3cb235fbb1d108fd30547d25318d90f0 Mon Sep 17 00:00:00 2001 From: "Derek J. Clark" Date: Fri, 13 Feb 2026 08:02:55 +0000 Subject: [PATCH 11/13] [FROM-ML] platform/x86: lenovo-wmi-other: Add missing CPU tunable attributes Use an enum for all device Id's and CPU attribute feature ID's, add missing CPU attributes. Signed-off-by: Derek J. Clark --- .../wmi/devices/lenovo-wmi-other.rst | 10 ++ drivers/platform/x86/lenovo/wmi-capdata.h | 5 +- drivers/platform/x86/lenovo/wmi-other.c | 108 +++++++++++++++++- 3 files changed, 117 insertions(+), 6 deletions(-) diff --git a/Documentation/wmi/devices/lenovo-wmi-other.rst b/Documentation/wmi/devices/lenovo-wmi-other.rst index 01d4711567380..f4763ed66cc6d 100644 --- a/Documentation/wmi/devices/lenovo-wmi-other.rst +++ b/Documentation/wmi/devices/lenovo-wmi-other.rst @@ -68,9 +68,19 @@ Each attribute has the following properties: - type The following firmware-attributes are implemented: + - cpu_oc_stat: CPU Overlocking Status + - cpu_temp: CPU Thermal Load Limit + - ppt_cpu_cl: CPU Cross Loading Power Limit + - ppt_pl1_apu_spl: Platform Profile Tracking APU Sustained Power Limit - ppt_pl1_spl: Platform Profile Tracking Sustained Power Limit + - ppt_pl1_spl_cl: Platform Profile Tracking Cross Loading Sustained Power Limit + - ppt_pl1_tau: Exceed Duration for Platform Profile Tracking Sustained Power Limit - ppt_pl2_sppt: Platform Profile Tracking Slow Package Power Tracking + - ppt_pl2_sppt_cl: Platform Profile Tracking Cross Loading Slow Package Tracking - ppt_pl3_fppt: Platform Profile Tracking Fast Package Power Tracking + - ppt_pl3_fppt_cl: Platform Profile Tracking Cross Loading Fast Package Power Tracking + - ppt_pl4_ipl: Platform Profile Trakcing Instantaneous Power Limit + - ppt_pl4_ipl_cl: Platform Profile Tracking Cross Loading Instantaneous Power Limit LENOVO_FAN_TEST_DATA ------------------------- diff --git a/drivers/platform/x86/lenovo/wmi-capdata.h b/drivers/platform/x86/lenovo/wmi-capdata.h index 27202e2dc8a59..aa48f43cbb43b 100644 --- a/drivers/platform/x86/lenovo/wmi-capdata.h +++ b/drivers/platform/x86/lenovo/wmi-capdata.h @@ -23,7 +23,10 @@ FIELD_PREP(LWMI_ATTR_MODE_ID_MASK, mode) | \ FIELD_PREP(LWMI_ATTR_TYPE_ID_MASK, type)) -#define LWMI_DEVICE_ID_FAN 0x04 +enum lwmi_device_id { + LWMI_DEVICE_ID_CPU = 0x01, + LWMI_DEVICE_ID_FAN = 0x04, +}; struct component_match; struct device; diff --git a/drivers/platform/x86/lenovo/wmi-other.c b/drivers/platform/x86/lenovo/wmi-other.c index 47f9c34611321..8d473bc5354ff 100644 --- a/drivers/platform/x86/lenovo/wmi-other.c +++ b/drivers/platform/x86/lenovo/wmi-other.c @@ -54,15 +54,22 @@ #define LENOVO_OTHER_MODE_GUID "DC2A8805-3A8C-41BA-A6F7-092E0089CD3B" -#define LWMI_DEVICE_ID_CPU 0x01 - -#define LWMI_FEATURE_ID_CPU_SPPT 0x01 -#define LWMI_FEATURE_ID_CPU_SPL 0x02 -#define LWMI_FEATURE_ID_CPU_FPPT 0x03 +enum lwmi_feature_id_cpu { + LWMI_FEATURE_ID_CPU_SPPT = 0x01, + LWMI_FEATURE_ID_CPU_SPL = 0x02, + LWMI_FEATURE_ID_CPU_FPPT = 0x03, + LWMI_FEATURE_ID_CPU_TEMP = 0x04, + LWMI_FEATURE_ID_CPU_APU = 0x05, + LWMI_FEATURE_ID_CPU_CL = 0x06, + LWMI_FEATURE_ID_CPU_TAU = 0x07, + LWMI_FEATURE_ID_CPU_OC = 0x08, + LWMI_FEATURE_ID_CPU_IPL = 0x09, +}; #define LWMI_FEATURE_ID_FAN_RPM 0x03 #define LWMI_TYPE_ID_NONE 0x00 +#define LWMI_TYPE_ID_CROSSLOAD 0x01 #define LWMI_FEATURE_VALUE_GET 17 #define LWMI_FEATURE_VALUE_SET 18 @@ -559,18 +566,78 @@ static struct tunable_attr_01 ppt_pl1_spl = { .type_id = LWMI_TYPE_ID_NONE, }; +static struct tunable_attr_01 ppt_pl1_spl_cl = { + .device_id = LWMI_DEVICE_ID_CPU, + .feature_id = LWMI_FEATURE_ID_CPU_SPL, + .type_id = LWMI_TYPE_ID_CROSSLOAD, +}; + static struct tunable_attr_01 ppt_pl2_sppt = { .device_id = LWMI_DEVICE_ID_CPU, .feature_id = LWMI_FEATURE_ID_CPU_SPPT, .type_id = LWMI_TYPE_ID_NONE, }; +static struct tunable_attr_01 ppt_pl2_sppt_cl = { + .device_id = LWMI_DEVICE_ID_CPU, + .feature_id = LWMI_FEATURE_ID_CPU_SPPT, + .type_id = LWMI_TYPE_ID_CROSSLOAD, +}; + static struct tunable_attr_01 ppt_pl3_fppt = { .device_id = LWMI_DEVICE_ID_CPU, .feature_id = LWMI_FEATURE_ID_CPU_FPPT, .type_id = LWMI_TYPE_ID_NONE, }; +static struct tunable_attr_01 ppt_pl3_fppt_cl = { + .device_id = LWMI_DEVICE_ID_CPU, + .feature_id = LWMI_FEATURE_ID_CPU_FPPT, + .type_id = LWMI_TYPE_ID_CROSSLOAD, +}; + +static struct tunable_attr_01 cpu_temp = { + .device_id = LWMI_DEVICE_ID_CPU, + .feature_id = LWMI_FEATURE_ID_CPU_TEMP, + .type_id = LWMI_TYPE_ID_NONE, +}; + +static struct tunable_attr_01 ppt_pl1_apu_spl = { + .device_id = LWMI_DEVICE_ID_CPU, + .feature_id = LWMI_FEATURE_ID_CPU_APU, + .type_id = LWMI_TYPE_ID_NONE, +}; + +static struct tunable_attr_01 ppt_cpu_cl = { + .device_id = LWMI_DEVICE_ID_CPU, + .feature_id = LWMI_FEATURE_ID_CPU_CL, + .type_id = LWMI_TYPE_ID_NONE, +}; + +static struct tunable_attr_01 ppt_pl1_tau = { + .device_id = LWMI_DEVICE_ID_CPU, + .feature_id = LWMI_FEATURE_ID_CPU_TAU, + .type_id = LWMI_TYPE_ID_NONE, +}; + +static struct tunable_attr_01 cpu_oc_stat = { + .device_id = LWMI_DEVICE_ID_CPU, + .feature_id = LWMI_FEATURE_ID_CPU_OC, + .type_id = LWMI_TYPE_ID_NONE, +}; + +static struct tunable_attr_01 ppt_pl4_ipl = { + .device_id = LWMI_DEVICE_ID_CPU, + .feature_id = LWMI_FEATURE_ID_CPU_IPL, + .type_id = LWMI_TYPE_ID_NONE, +}; + +static struct tunable_attr_01 ppt_pl4_ipl_cl = { + .device_id = LWMI_DEVICE_ID_CPU, + .feature_id = LWMI_FEATURE_ID_CPU_IPL, + .type_id = LWMI_TYPE_ID_CROSSLOAD, +}; + struct capdata01_attr_group { const struct attribute_group *attr_group; struct tunable_attr_01 *tunable_attr; @@ -1011,15 +1078,46 @@ static int lwmi_attr_01_is_supported(struct tunable_attr_01 *tunable_attr) LWMI_ATTR_GROUP_TUNABLE_CAP01(ppt_pl1_spl, "ppt_pl1_spl", "Set the CPU sustained power limit"); +LWMI_ATTR_GROUP_TUNABLE_CAP01(ppt_pl1_spl_cl, "ppt_pl1_spl_cl", + "Set the CPU cross loading sustained power limit"); LWMI_ATTR_GROUP_TUNABLE_CAP01(ppt_pl2_sppt, "ppt_pl2_sppt", "Set the CPU slow package power tracking limit"); +LWMI_ATTR_GROUP_TUNABLE_CAP01(ppt_pl2_sppt_cl, "ppt_pl2_sppt_cl", + "Set the CPU cross loading slow package power tracking limit"); LWMI_ATTR_GROUP_TUNABLE_CAP01(ppt_pl3_fppt, "ppt_pl3_fppt", "Set the CPU fast package power tracking limit"); +LWMI_ATTR_GROUP_TUNABLE_CAP01(ppt_pl3_fppt_cl, "ppt_pl3_fppt_cl", + "Set the CPU cross loading fast package power tracking limit"); +LWMI_ATTR_GROUP_TUNABLE_CAP01(cpu_temp, "cpu_temp", + "Set the CPU thermal load limit"); +LWMI_ATTR_GROUP_TUNABLE_CAP01(ppt_pl1_apu_spl, "ppt_pl1_apu_spl", + "Set the APU sustained power limit"); +LWMI_ATTR_GROUP_TUNABLE_CAP01(ppt_cpu_cl, "ppt_cpu_cl", + "Set the CPU cross loading power limit"); +LWMI_ATTR_GROUP_TUNABLE_CAP01(ppt_pl1_tau, "ppt_pl1_tau", + "Set the CPU sustained power limit exceed duration"); +LWMI_ATTR_GROUP_TUNABLE_CAP01(cpu_oc_stat, "cpu_oc_stat", + "Set the CPU overclocking status"); +LWMI_ATTR_GROUP_TUNABLE_CAP01(ppt_pl4_ipl, "ppt_pl4_ipl", + "Set the CPU instantaneous power limit"); +LWMI_ATTR_GROUP_TUNABLE_CAP01(ppt_pl4_ipl_cl, "ppt_pl4_ipl_cl", + "Set the CPU cross loading instantaneous power limit"); + static struct capdata01_attr_group cd01_attr_groups[] = { { &ppt_pl1_spl_attr_group, &ppt_pl1_spl }, + { &ppt_pl1_spl_cl_attr_group, &ppt_pl1_spl_cl }, { &ppt_pl2_sppt_attr_group, &ppt_pl2_sppt }, + { &ppt_pl2_sppt_cl_attr_group, &ppt_pl2_sppt_cl }, { &ppt_pl3_fppt_attr_group, &ppt_pl3_fppt }, + { &ppt_pl3_fppt_cl_attr_group, &ppt_pl3_fppt_cl }, + { &cpu_temp_attr_group, &cpu_temp }, + { &ppt_pl1_apu_spl_attr_group, &ppt_pl1_apu_spl }, + { &ppt_cpu_cl_attr_group, &ppt_cpu_cl }, + { &ppt_pl1_tau_attr_group, &ppt_pl1_tau }, + { &cpu_oc_stat_attr_group, &cpu_oc_stat }, + { &ppt_pl4_ipl_attr_group, &ppt_pl4_ipl }, + { &ppt_pl4_ipl_cl_attr_group, &ppt_pl4_ipl_cl }, {}, }; From 3e7822c08c431ae36ea99a1c2a79468fa8a0f0ad Mon Sep 17 00:00:00 2001 From: "Derek J. Clark" Date: Fri, 13 Feb 2026 08:02:56 +0000 Subject: [PATCH 12/13] [FROM-ML] platform/x86: lenovo-wmi-other: Add GPU tunable attributes Use an enum for all GPU attribute feature ID's and add GPU attributes. Signed-off-by: Derek J. Clark --- .../wmi/devices/lenovo-wmi-other.rst | 11 ++ drivers/platform/x86/lenovo/wmi-capdata.h | 1 + drivers/platform/x86/lenovo/wmi-other.c | 114 ++++++++++++++++++ 3 files changed, 126 insertions(+) diff --git a/Documentation/wmi/devices/lenovo-wmi-other.rst b/Documentation/wmi/devices/lenovo-wmi-other.rst index f4763ed66cc6d..f7564b23bb7f0 100644 --- a/Documentation/wmi/devices/lenovo-wmi-other.rst +++ b/Documentation/wmi/devices/lenovo-wmi-other.rst @@ -70,6 +70,17 @@ Each attribute has the following properties: The following firmware-attributes are implemented: - cpu_oc_stat: CPU Overlocking Status - cpu_temp: CPU Thermal Load Limit + - dgpu_boost_clk: Dedicated GPU Boost Clock + - dgpu_enable: Dedicated GPU Enabled Status + - gpu_didvid: GPU Device Identifier and Vendor Identifier + - gpu_mode: GPU Mode by Power Limit + - gpu_nv_ac_offset: Nvidia GPU AC Total Processing Power Baseline Offset + - gpu_nv_bpl: Nvidia GPU Base Power Limit + - gpu_nv_cpu_boost: Nvidia GPU to CPU Dynamic Boost Limit + - gpu_nv_ctgp: Nvidia GPU Configurable Total Graphics Power + - gpu_nv_ppab: Nvidia GPU Power Performance Aware Boost Limit + - gpu_oc_stat: GPU Overclocking Status + - gpu_temp: GPU Thermal Load Limit - ppt_cpu_cl: CPU Cross Loading Power Limit - ppt_pl1_apu_spl: Platform Profile Tracking APU Sustained Power Limit - ppt_pl1_spl: Platform Profile Tracking Sustained Power Limit diff --git a/drivers/platform/x86/lenovo/wmi-capdata.h b/drivers/platform/x86/lenovo/wmi-capdata.h index aa48f43cbb43b..b7f9ee7b301a5 100644 --- a/drivers/platform/x86/lenovo/wmi-capdata.h +++ b/drivers/platform/x86/lenovo/wmi-capdata.h @@ -25,6 +25,7 @@ enum lwmi_device_id { LWMI_DEVICE_ID_CPU = 0x01, + LWMI_DEVICE_ID_GPU = 0x02, LWMI_DEVICE_ID_FAN = 0x04, }; diff --git a/drivers/platform/x86/lenovo/wmi-other.c b/drivers/platform/x86/lenovo/wmi-other.c index 8d473bc5354ff..07a654b6c6742 100644 --- a/drivers/platform/x86/lenovo/wmi-other.c +++ b/drivers/platform/x86/lenovo/wmi-other.c @@ -66,6 +66,20 @@ enum lwmi_feature_id_cpu { LWMI_FEATURE_ID_CPU_IPL = 0x09, }; +enum lwmi_feature_id_gpu { + LWMI_FEATURE_ID_GPU_NV_PPAB = 0x01, + LWMI_FEATURE_ID_GPU_NV_CTGP = 0x02, + LWMI_FEATURE_ID_GPU_TEMP = 0x03, + LWMI_FEATURE_ID_GPU_AC_OFFSET = 0x04, + LWMI_FEATURE_ID_GPU_OC = 0x05, + LWMI_FEATURE_ID_DGPU_BOOST_CLK = 0x06, + LWMI_FEATURE_ID_DGPU_EN = 0x07, + LWMI_FEATURE_ID_GPU_MODE = 0x08, + LWMI_FEATURE_ID_DGPU_DIDVID = 0x09, + LWMI_FEATURE_ID_GPU_NV_BPL = 0x0a, + LWMI_FEATURE_ID_GPU_NV_CPU_BOOST = 0x0b, +}; + #define LWMI_FEATURE_ID_FAN_RPM 0x03 #define LWMI_TYPE_ID_NONE 0x00 @@ -638,6 +652,72 @@ static struct tunable_attr_01 ppt_pl4_ipl_cl = { .type_id = LWMI_TYPE_ID_CROSSLOAD, }; +static struct tunable_attr_01 gpu_nv_ppab = { + .device_id = LWMI_DEVICE_ID_GPU, + .feature_id = LWMI_FEATURE_ID_GPU_NV_PPAB, + .type_id = LWMI_TYPE_ID_NONE, +}; + +static struct tunable_attr_01 gpu_nv_ctgp = { + .device_id = LWMI_DEVICE_ID_GPU, + .feature_id = LWMI_FEATURE_ID_GPU_NV_CTGP, + .type_id = LWMI_TYPE_ID_NONE, +}; + +static struct tunable_attr_01 gpu_temp = { + .device_id = LWMI_DEVICE_ID_GPU, + .feature_id = LWMI_FEATURE_ID_GPU_TEMP, + .type_id = LWMI_TYPE_ID_NONE, +}; + +static struct tunable_attr_01 gpu_nv_ac_offset = { + .device_id = LWMI_DEVICE_ID_GPU, + .feature_id = LWMI_FEATURE_ID_GPU_AC_OFFSET, + .type_id = LWMI_TYPE_ID_NONE, +}; + +static struct tunable_attr_01 gpu_oc_stat = { + .device_id = LWMI_DEVICE_ID_GPU, + .feature_id = LWMI_FEATURE_ID_GPU_OC, + .type_id = LWMI_TYPE_ID_NONE, +}; + +static struct tunable_attr_01 dgpu_boost_clk = { + .device_id = LWMI_DEVICE_ID_GPU, + .feature_id = LWMI_FEATURE_ID_DGPU_BOOST_CLK, + .type_id = LWMI_TYPE_ID_NONE, +}; + +static struct tunable_attr_01 dgpu_enable = { + .device_id = LWMI_DEVICE_ID_GPU, + .feature_id = LWMI_FEATURE_ID_DGPU_EN, + .type_id = LWMI_TYPE_ID_NONE, +}; + +static struct tunable_attr_01 gpu_mode = { + .device_id = LWMI_DEVICE_ID_GPU, + .feature_id = LWMI_FEATURE_ID_GPU_MODE, + .type_id = LWMI_TYPE_ID_NONE, +}; + +static struct tunable_attr_01 dgpu_didvid = { + .device_id = LWMI_DEVICE_ID_GPU, + .feature_id = LWMI_FEATURE_ID_DGPU_DIDVID, + .type_id = LWMI_TYPE_ID_NONE, +}; + +static struct tunable_attr_01 gpu_nv_bpl = { + .device_id = LWMI_DEVICE_ID_GPU, + .feature_id = LWMI_FEATURE_ID_GPU_NV_BPL, + .type_id = LWMI_TYPE_ID_NONE, +}; + +static struct tunable_attr_01 gpu_nv_cpu_boost = { + .device_id = LWMI_DEVICE_ID_GPU, + .feature_id = LWMI_FEATURE_ID_GPU_NV_CPU_BOOST, + .type_id = LWMI_TYPE_ID_NONE, +}; + struct capdata01_attr_group { const struct attribute_group *attr_group; struct tunable_attr_01 *tunable_attr; @@ -1076,6 +1156,7 @@ static int lwmi_attr_01_is_supported(struct tunable_attr_01 *tunable_attr) .name = _fsname, .attrs = _attrname##_attrs \ } +/* CPU tunable attributes */ LWMI_ATTR_GROUP_TUNABLE_CAP01(ppt_pl1_spl, "ppt_pl1_spl", "Set the CPU sustained power limit"); LWMI_ATTR_GROUP_TUNABLE_CAP01(ppt_pl1_spl_cl, "ppt_pl1_spl_cl", @@ -1103,6 +1184,29 @@ LWMI_ATTR_GROUP_TUNABLE_CAP01(ppt_pl4_ipl, "ppt_pl4_ipl", LWMI_ATTR_GROUP_TUNABLE_CAP01(ppt_pl4_ipl_cl, "ppt_pl4_ipl_cl", "Set the CPU cross loading instantaneous power limit"); +/* GPU tunable attributes */ +LWMI_ATTR_GROUP_TUNABLE_CAP01(gpu_nv_ppab, "gpu_nv_ppab", + "Set the Nvidia GPU power performance aware boost limit"); +LWMI_ATTR_GROUP_TUNABLE_CAP01(gpu_nv_ctgp, "gpu_nv_ctgp", + "Set the GPU configurable total graphics power"); +LWMI_ATTR_GROUP_TUNABLE_CAP01(gpu_temp, "gpu_temp", + "Set the GPU thermal load limit"); +LWMI_ATTR_GROUP_TUNABLE_CAP01(gpu_nv_ac_offset, "gpu_nv_ac_offset", + "Set the Nvidia GPU AC total processing power baseline offset"); +LWMI_ATTR_GROUP_TUNABLE_CAP01(gpu_oc_stat, "gpu_oc_stat", + "Set the GPU overclocking status"); +LWMI_ATTR_GROUP_TUNABLE_CAP01(dgpu_boost_clk, "gpu_boost_clk", + "Set the dedicated GPU boost clock"); +LWMI_ATTR_GROUP_TUNABLE_CAP01(dgpu_enable, "dgpu_enable", + "Set the dedicated Nvidia GPU enabled status"); +LWMI_ATTR_GROUP_TUNABLE_CAP01(gpu_mode, "gpu_mode", + "Set the GPU mode by power limit"); +LWMI_ATTR_GROUP_TUNABLE_CAP01(dgpu_didvid, "gpu_didvid", + "Get the GPU device identifier and vendor identifier"); +LWMI_ATTR_GROUP_TUNABLE_CAP01(gpu_nv_bpl, "gpu_nv_bpl", + "Set the Nvidia GPU base power limit"); +LWMI_ATTR_GROUP_TUNABLE_CAP01(gpu_nv_cpu_boost, "gpu_nv_cpu_boost", + "Set the Nvidia GPU to CPU dynamic boost limit"); static struct capdata01_attr_group cd01_attr_groups[] = { { &ppt_pl1_spl_attr_group, &ppt_pl1_spl }, @@ -1118,6 +1222,16 @@ static struct capdata01_attr_group cd01_attr_groups[] = { { &cpu_oc_stat_attr_group, &cpu_oc_stat }, { &ppt_pl4_ipl_attr_group, &ppt_pl4_ipl }, { &ppt_pl4_ipl_cl_attr_group, &ppt_pl4_ipl_cl }, + { &gpu_nv_ppab_attr_group, &gpu_nv_ppab }, + { &gpu_nv_ctgp_attr_group, &gpu_nv_ctgp }, + { &gpu_temp_attr_group, &gpu_temp }, + { &gpu_nv_ac_offset_attr_group, &gpu_nv_ac_offset }, + { &gpu_oc_stat_attr_group, &gpu_oc_stat }, + { &dgpu_boost_clk_attr_group, &dgpu_boost_clk }, + { &dgpu_enable_attr_group, &dgpu_enable }, + { &dgpu_didvid_attr_group, &dgpu_didvid }, + { &gpu_nv_bpl_attr_group, &gpu_nv_bpl }, + { &gpu_nv_cpu_boost_attr_group, &gpu_nv_cpu_boost }, {}, }; From 6e887985b8a95342fd3a63a9bf8a3d515df5873b Mon Sep 17 00:00:00 2001 From: "Derek J. Clark" Date: Fri, 13 Feb 2026 08:02:57 +0000 Subject: [PATCH 13/13] [FROM-ML] platform/x86: lenovo-wmi-other: Add WMI battery charge limiting. Add charge-type power supply extension for devices that support WMI based charge enable/disable. Lenovo Legion devices that implement function ID and capdata 00 ID 0x03010001 are able to enable or disable charging through the lenovo-wmi-other interface. The ideapad_laptop driver conflicts with this if it can also provide the attribute, so we have to get the acpi_handle and check for the same ACPI methods that enable the feature in that driver. The ACPI method is more reliable from my testing when both are present, so there is no need to modify the ideapad_laptop driver instead. The power supply extension requires a name. Instead of adding a third const macro with the same information, replace LWMI_OM_FW_ATTR_BASE_PATH and LWMI_OM_HWMON_NAME with LWMI_OM_NAME and use that everywhere. Signed-off-by: Derek J. Clark --- drivers/platform/x86/lenovo/wmi-capdata.h | 1 + drivers/platform/x86/lenovo/wmi-other.c | 238 +++++++++++++++++++++- 2 files changed, 233 insertions(+), 6 deletions(-) diff --git a/drivers/platform/x86/lenovo/wmi-capdata.h b/drivers/platform/x86/lenovo/wmi-capdata.h index b7f9ee7b301a5..00471551e7d60 100644 --- a/drivers/platform/x86/lenovo/wmi-capdata.h +++ b/drivers/platform/x86/lenovo/wmi-capdata.h @@ -26,6 +26,7 @@ enum lwmi_device_id { LWMI_DEVICE_ID_CPU = 0x01, LWMI_DEVICE_ID_GPU = 0x02, + LWMI_DEVICE_ID_PSU = 0x03, LWMI_DEVICE_ID_FAN = 0x04, }; diff --git a/drivers/platform/x86/lenovo/wmi-other.c b/drivers/platform/x86/lenovo/wmi-other.c index 07a654b6c6742..dc128fa9f99cd 100644 --- a/drivers/platform/x86/lenovo/wmi-other.c +++ b/drivers/platform/x86/lenovo/wmi-other.c @@ -26,6 +26,7 @@ * - binding to Capability Data 00 and Fan */ +#include #include #include #include @@ -42,6 +43,7 @@ #include #include #include +#include #include #include @@ -80,10 +82,17 @@ enum lwmi_feature_id_gpu { LWMI_FEATURE_ID_GPU_NV_CPU_BOOST = 0x0b, }; +enum lwmi_feature_id_psu { + LWMI_FEATURE_ID_PSU_INSTANT_MODE = 0x01, + LWMI_FEATURE_ID_PSU_CHARGE_MODE = 0x02, +}; + #define LWMI_FEATURE_ID_FAN_RPM 0x03 #define LWMI_TYPE_ID_NONE 0x00 #define LWMI_TYPE_ID_CROSSLOAD 0x01 +#define LWMI_TYPE_ID_PSU_AC 0x01 +#define LWMI_TYPE_ID_PSU_PD 0x02 #define LWMI_FEATURE_VALUE_GET 17 #define LWMI_FEATURE_VALUE_SET 18 @@ -91,15 +100,20 @@ enum lwmi_feature_id_gpu { #define LWMI_FAN_ID_BASE 1 #define LWMI_FAN_NR 4 #define LWMI_FAN_ID(x) ((x) + LWMI_FAN_ID_BASE) +#define LWMI_FAN_DIV 100 + +#define LWMI_CHARGE_MODE_ENABLED 0x00 +#define LWMI_CHARGE_MODE_DISABLED 0x01 #define LWMI_ATTR_ID_FAN_RPM(x) \ LWMI_ATTR_ID(LWMI_DEVICE_ID_FAN, LWMI_FEATURE_ID_FAN_RPM, \ LWMI_GZ_THERMAL_MODE_NONE, LWMI_FAN_ID(x)) -#define LWMI_FAN_DIV 100 +#define LWMI_ATTR_ID_PSU(feat, type) \ + LWMI_ATTR_ID(LWMI_DEVICE_ID_PSU, feat, \ + LWMI_GZ_THERMAL_MODE_NONE, type) -#define LWMI_OM_FW_ATTR_BASE_PATH "lenovo-wmi-other" -#define LWMI_OM_HWMON_NAME "lenovo_wmi_other" +#define LWMI_OM_NAME "lenovo-wmi-other" static BLOCKING_NOTIFIER_HEAD(om_chain_head); static DEFINE_IDA(lwmi_om_ida); @@ -139,6 +153,8 @@ struct lwmi_om_priv { bool capdata00_collected : 1; bool capdata_fan_collected : 1; } fan_flags; + + struct acpi_battery_hook battery_hook; }; /* @@ -454,7 +470,7 @@ static void lwmi_om_hwmon_add(struct lwmi_om_priv *priv) } priv->hwmon_dev = hwmon_device_register_with_info(&priv->wdev->dev, - LWMI_OM_HWMON_NAME, priv, + LWMI_OM_NAME, priv, &lwmi_om_hwmon_chip_info, NULL); if (IS_ERR(priv->hwmon_dev)) { @@ -563,6 +579,216 @@ static void lwmi_om_fan_info_collect_cd_fan(struct device *dev, struct cd_list * lwmi_om_hwmon_add(priv); } +/* ======== Power Supply Extension (component: lenovo-wmi-capdata 00) ======== */ + +/** + * lwmi_psy_prop_is_writeable() - Get a power_supply_ext property + * @ps: The battery that was extended + * @ext: The extension + * @ext_data: Pointer the lwmi_om_priv drvdata + * @prop: The property to read + * @val: The value to return + * + * Writes the given value to the power_supply_ext property + * + * Return: 0 on success, or an error + */ +static int lwmi_psy_ext_get_prop(struct power_supply *ps, + const struct power_supply_ext *ext, + void *data, + enum power_supply_property prop, + union power_supply_propval *val) +{ + struct lwmi_om_priv *priv = data; + struct wmi_method_args_32 args; + u32 retval; + int ret; + + args.arg0 = LWMI_ATTR_ID_PSU(LWMI_FEATURE_ID_PSU_INSTANT_MODE, LWMI_TYPE_ID_PSU_AC); + + ret = lwmi_dev_evaluate_int(priv->wdev, 0x0, LWMI_FEATURE_VALUE_GET, + (unsigned char *)&args, sizeof(args), + &retval); + if (ret) + return ret; + + dev_dbg(&priv->wdev->dev, "Got return value %x for property %x\n", retval, prop); + + if (retval == LWMI_CHARGE_MODE_DISABLED) + val->intval = POWER_SUPPLY_CHARGE_TYPE_LONGLIFE; + else + val->intval = POWER_SUPPLY_CHARGE_TYPE_STANDARD; + + return 0; +} + +/** + * lwmi_psy_prop_is_writeable() - Set a power_supply_ext property + * @ps: The battery that was extended + * @ext: The extension + * @ext_data: Pointer the lwmi_om_priv drvdata + * @prop: The property to write + * @val: The value to write + * + * Writes the given value to the power_supply_ext property + * + * Return: 0 on success, or an error + */ +static int lwmi_psy_ext_set_prop(struct power_supply *ps, + const struct power_supply_ext *ext, + void *ext_data, + enum power_supply_property prop, + const union power_supply_propval *val) +{ + struct lwmi_om_priv *priv = ext_data; + struct wmi_method_args_32 args; + + args.arg0 = LWMI_ATTR_ID_PSU(LWMI_FEATURE_ID_PSU_INSTANT_MODE, LWMI_TYPE_ID_PSU_AC); + if (val->intval == POWER_SUPPLY_CHARGE_TYPE_LONGLIFE) + args.arg1 = LWMI_CHARGE_MODE_DISABLED; + else + args.arg1 = LWMI_CHARGE_MODE_ENABLED; + + dev_dbg(&priv->wdev->dev, "Attempting to set %#08x for property %x to %x\n", + args.arg0, prop, args.arg1); + + return lwmi_dev_evaluate_int(priv->wdev, 0x0, LWMI_FEATURE_VALUE_SET, + (unsigned char *)&args, sizeof(args), NULL); +} + +/** + * lwmi_psy_prop_is_writeable() - Determine if the property is supported + * @ps: The battery that was extended + * @ext: The extension + * @ext_data: Pointer the lwmi_om_priv drvdata + * @prop: The property to check + * + * Checks capdata 00 to determine if the property is supported. + * + * Return: Support level, or false + */ +static int lwmi_psy_prop_is_writeable(struct power_supply *ps, + const struct power_supply_ext *ext, + void *ext_data, + enum power_supply_property prop) +{ + struct lwmi_om_priv *priv = ext_data; + struct capdata00 capdata; + u32 attribute_id = LWMI_ATTR_ID_PSU(LWMI_FEATURE_ID_PSU_INSTANT_MODE, LWMI_TYPE_ID_PSU_AC); + int ret; + + ret = lwmi_cd00_get_data(priv->cd00_list, attribute_id, &capdata); + if (ret) + return false; + + dev_dbg(&priv->wdev->dev, "Battery charge mode (%#08x) support level: %x\n", + attribute_id, capdata.supported); + + return capdata.supported; +} + +static const enum power_supply_property lwmi_psy_ext_props[] = { + POWER_SUPPLY_PROP_CHARGE_TYPES, +}; + +static const struct power_supply_ext lwmi_psy_ext = { + .name = LWMI_OM_NAME, + .properties = lwmi_psy_ext_props, + .num_properties = ARRAY_SIZE(lwmi_psy_ext_props), + .charge_types = (BIT(POWER_SUPPLY_CHARGE_TYPE_STANDARD) | + BIT(POWER_SUPPLY_CHARGE_TYPE_LONGLIFE)), + .get_property = lwmi_psy_ext_get_prop, + .set_property = lwmi_psy_ext_set_prop, + .property_is_writeable = lwmi_psy_prop_is_writeable, +}; + +/** + * lwmi_add_battery() - Connect the power_supply_ext + * @battery: The battery to extend + * @hook: The driver hook used to extend the battery + * + * Return: 0 on success, or an error. + */ +static int lwmi_add_battery(struct power_supply *battery, struct acpi_battery_hook *hook) +{ + struct lwmi_om_priv *priv = container_of(hook, struct lwmi_om_priv, battery_hook); + + return power_supply_register_extension(battery, &lwmi_psy_ext, &priv->wdev->dev, priv); +} + +/** + * lwmi_remove_battery() - Disconnect the power_supply_ext + * @battery: The battery that was extended + * @hook: The driver hook used to extend the battery + * + * Return: 0 on success, or an error. + */ +static int lwmi_remove_battery(struct power_supply *battery, struct acpi_battery_hook *hook) +{ + power_supply_unregister_extension(battery, &lwmi_psy_ext); + return 0; +} + +/** + * lwmi_acpi_match() - Attempts to return the ideapad acpi handle + * @acpi_handle: The ACPI handle that manages battery charging + * @lvl: Unused + * @context: Void pointer to the acpi_handle object to return + * @retval: Unused + * + * Checks if the ideapad_laptop driver is going to manage charge_type first, + * thenm if not, hooks the battery to our WMI methods. + * + * Return: AE_CTRL_TERMINATE if found, AE_OK if not found. + */ +static acpi_status lwmi_acpi_match(acpi_handle handle, u32 lvl, + void *context, void **retval) +{ + if (!handle) + return AE_OK; + + acpi_handle *ahand = context; + *ahand = handle; + + return AE_CTRL_TERMINATE; +} + +/** + * lwmi_om_ps_ext_init() - Hooks power supply extension to device battery + * @priv: Driver private data + * + * Checks if the ideapad_laptop driver is going to manage charge_type first, + * thenm if not, hooks the battery to our WMI methods. + */ +static void lwmi_om_ps_ext_init(struct lwmi_om_priv *priv) +{ + static const char * const ideapad_hid = "VPC2004"; + acpi_handle handle = NULL; + int ret; + + /* Deconflict ideapad_laptop driver */ + ret = acpi_get_devices(ideapad_hid, lwmi_acpi_match, &handle, NULL); + if (ret) + return; + + if (!handle) + return; + + if (acpi_has_method(handle, "GBMD") && acpi_has_method(handle, "SBMC")) { + dev_dbg(&priv->wdev->dev, "ideapad_laptop driver manages battery for device.\n"); + return; + } + + /* Add battery hooks */ + priv->battery_hook.add_battery = lwmi_add_battery, + priv->battery_hook.remove_battery = lwmi_remove_battery, + priv->battery_hook.name = "Lenovo WMI Other Battery Extension", + + ret = devm_battery_hook_register(&priv->wdev->dev, &priv->battery_hook); + if (ret) + dev_err(&priv->wdev->dev, "Error during battery hook: %i\n", ret); +} + /* ======== fw_attributes (component: lenovo-wmi-capdata 01) ======== */ struct tunable_attr_01 { @@ -1252,8 +1478,7 @@ static int lwmi_om_fw_attr_add(struct lwmi_om_priv *priv) priv->fw_attr_dev = device_create(&firmware_attributes_class, NULL, MKDEV(0, 0), NULL, "%s-%u", - LWMI_OM_FW_ATTR_BASE_PATH, - priv->ida_id); + LWMI_OM_NAME, priv->ida_id); if (IS_ERR(priv->fw_attr_dev)) { err = PTR_ERR(priv->fw_attr_dev); goto err_free_ida; @@ -1345,6 +1570,7 @@ static int lwmi_om_master_bind(struct device *dev) return -ENODEV; lwmi_om_fan_info_collect_cd00(priv); + lwmi_om_ps_ext_init(priv); return lwmi_om_fw_attr_add(priv); }