* [PATCH 0/6] platform/x86: lenovo-wmi-{capdata,other}: Add HWMON for fan speed
@ 2025-10-19 21:04 Rong Zhang
2025-10-19 21:04 ` [PATCH 1/6] platform/x86: Rename lenovo-wmi-capdata01 to lenovo-wmi-capdata Rong Zhang
` (6 more replies)
0 siblings, 7 replies; 20+ messages in thread
From: Rong Zhang @ 2025-10-19 21:04 UTC (permalink / raw)
To: Mark Pearson, Derek J. Clark, Armin Wolf, Hans de Goede,
Ilpo Järvinen
Cc: Rong Zhang, Guenter Roeck, platform-driver-x86, linux-kernel,
linux-hwmon
Lenovo WMI Other Mode interface also supports querying or setting fan
speed RPM. This capability is decribed by LENOVO_CAPABILITY_DATA_00.
Besides, LENOVO_FAN_TEST_DATA provides reference data for self-test of
cooling fans, including minimum and maximum fan speed RPM.
This patchset turns lenovo-wmi-capdata01 into a unified driver (now
named lenovo-wmi-capdata) for LENOVO_CAPABILITY_DATA_{00,01} and
LENOVO_FAN_TEST_DATA; then adds HWMON support for lenovo-wmi-other:
- fanX_enable: enable/disable the fan (tunable)
- fanX_input: current RPM
- fanX_max: maximum RPM
- fanX_min: minimum RPM
- fanX_target: target RPM (tunable)
This implementation doesn't require all capability data to be available,
and is capable to expose interfaces accordingly:
- Having LENOVO_CAPABILITY_DATA_00: exposes fanX_{enable,input,target}
- Having LENOVO_CAPABILITY_DATA_01: exposes firmware_attributes
- Having LENOVO_FAN_TEST_DATA: exposes fanX_{max,min}
Rong Zhang (6):
platform/x86: Rename lenovo-wmi-capdata01 to lenovo-wmi-capdata
platform/x86: lenovo-wmi-{capdata,other}: Support multiple Capability
Data
platform/x86: lenovo-wmi-capdata: Add support for Capability Data 00
platform/x86: lenovo-wmi-other: Add HWMON for fan speed RPM
platform/x86: lenovo-wmi-capdata: Add support for Fan Test Data
platform/x86: lenovo-wmi-other: Report min/max RPM and hide dummy fans
.../wmi/devices/lenovo-wmi-other.rst | 32 +
drivers/platform/x86/lenovo/Kconfig | 5 +-
drivers/platform/x86/lenovo/Makefile | 2 +-
drivers/platform/x86/lenovo/wmi-capdata.c | 545 ++++++++++++++++++
drivers/platform/x86/lenovo/wmi-capdata.h | 46 ++
drivers/platform/x86/lenovo/wmi-capdata01.c | 302 ----------
drivers/platform/x86/lenovo/wmi-capdata01.h | 25 -
drivers/platform/x86/lenovo/wmi-other.c | 422 +++++++++++++-
8 files changed, 1028 insertions(+), 351 deletions(-)
create mode 100644 drivers/platform/x86/lenovo/wmi-capdata.c
create mode 100644 drivers/platform/x86/lenovo/wmi-capdata.h
delete mode 100644 drivers/platform/x86/lenovo/wmi-capdata01.c
delete mode 100644 drivers/platform/x86/lenovo/wmi-capdata01.h
base-commit: 3a8660878839faadb4f1a6dd72c3179c1df56787
--
2.51.0
^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH 1/6] platform/x86: Rename lenovo-wmi-capdata01 to lenovo-wmi-capdata
2025-10-19 21:04 [PATCH 0/6] platform/x86: lenovo-wmi-{capdata,other}: Add HWMON for fan speed Rong Zhang
@ 2025-10-19 21:04 ` Rong Zhang
2025-10-26 4:41 ` Derek John Clark
2025-10-19 21:04 ` [PATCH 2/6] platform/x86: lenovo-wmi-{capdata,other}: Support multiple Capability Data Rong Zhang
` (5 subsequent siblings)
6 siblings, 1 reply; 20+ messages in thread
From: Rong Zhang @ 2025-10-19 21:04 UTC (permalink / raw)
To: Mark Pearson, Derek J. Clark, Armin Wolf, Hans de Goede,
Ilpo Järvinen
Cc: Rong Zhang, Guenter Roeck, platform-driver-x86, linux-kernel,
linux-hwmon
Prepare for the upcoming changes to make it suitable to retrieve
and provide other Capability Data as well.
Signed-off-by: Rong Zhang <i@rong.moe>
---
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..fb96a0f908f03 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_DATA
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_DATA
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..29014d8c1376d 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_DATA) += 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..c5e74b2bfeb36 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 <derekjohn.clark@gmail.com>
+ * - Initial implementation (formerly named lenovo-wmi-capdata01)
*/
#include <linux/acpi.h>
@@ -26,55 +29,55 @@
#include <linux/types.h>
#include <linux/wmi.h>
-#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_CD");
/**
- * 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_cd",
.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_CD");
-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 <derekjohn.clark@gmail.com>");
-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 <derekjohn.clark@gmail.com> */
-#ifndef _LENOVO_WMI_CAPDATA01_H_
-#define _LENOVO_WMI_CAPDATA01_H_
+#ifndef _LENOVO_WMI_CAPDATA_H_
+#define _LENOVO_WMI_CAPDATA_H_
#include <linux/types.h>
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..c6dc1b4cff841 100644
--- a/drivers/platform/x86/lenovo/wmi-other.c
+++ b/drivers/platform/x86/lenovo/wmi-other.c
@@ -34,7 +34,7 @@
#include <linux/types.h>
#include <linux/wmi.h>
-#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_CD");
MODULE_IMPORT_NS("LENOVO_WMI_HELPERS");
MODULE_DEVICE_TABLE(wmi, lwmi_other_id_table);
MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
--
2.51.0
^ permalink raw reply related [flat|nested] 20+ messages in thread
* [PATCH 2/6] platform/x86: lenovo-wmi-{capdata,other}: Support multiple Capability Data
2025-10-19 21:04 [PATCH 0/6] platform/x86: lenovo-wmi-{capdata,other}: Add HWMON for fan speed Rong Zhang
2025-10-19 21:04 ` [PATCH 1/6] platform/x86: Rename lenovo-wmi-capdata01 to lenovo-wmi-capdata Rong Zhang
@ 2025-10-19 21:04 ` Rong Zhang
2025-10-20 1:03 ` kernel test robot
2025-10-26 4:43 ` Derek John Clark
2025-10-19 21:04 ` [PATCH 3/6] platform/x86: lenovo-wmi-capdata: Add support for Capability Data 00 Rong Zhang
` (4 subsequent siblings)
6 siblings, 2 replies; 20+ messages in thread
From: Rong Zhang @ 2025-10-19 21:04 UTC (permalink / raw)
To: Mark Pearson, Derek J. Clark, Armin Wolf, Hans de Goede,
Ilpo Järvinen
Cc: Rong Zhang, Guenter Roeck, platform-driver-x86, linux-kernel,
linux-hwmon
The current inplementation are heavily bound to capdata01. Rewrite it so
that it is suitable to utilize other Capability Data as well.
No functional changes are introduced.
Signed-off-by: Rong Zhang <i@rong.moe>
---
drivers/platform/x86/lenovo/wmi-capdata.c | 208 +++++++++++++++++-----
drivers/platform/x86/lenovo/wmi-capdata.h | 7 +-
drivers/platform/x86/lenovo/wmi-other.c | 27 ++-
3 files changed, 190 insertions(+), 52 deletions(-)
diff --git a/drivers/platform/x86/lenovo/wmi-capdata.c b/drivers/platform/x86/lenovo/wmi-capdata.c
index c5e74b2bfeb36..14175fe19247e 100644
--- a/drivers/platform/x86/lenovo/wmi-capdata.c
+++ b/drivers/platform/x86/lenovo/wmi-capdata.c
@@ -12,8 +12,13 @@
*
* Copyright (C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
* - Initial implementation (formerly named lenovo-wmi-capdata01)
+ *
+ * Copyright (C) 2025 Rong Zhang <i@rong.moe>
+ * - Unified implementation
*/
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
#include <linux/acpi.h>
#include <linux/cleanup.h>
#include <linux/component.h>
@@ -36,6 +41,25 @@
#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] = { \
+ .guid_string = _type##_GUID, \
+ .name = #_type, \
+ .type = _type, \
+ }
+
+static const struct lwmi_cd_info {
+ const char *guid_string;
+ 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 +68,19 @@ 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);
+ };
};
/**
* 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 +92,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;
}
@@ -76,30 +110,33 @@ static const struct component_ops lwmi_cd_component_ops = {
};
/**
- * 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_CD");
/**
@@ -112,10 +149,21 @@ EXPORT_SYMBOL_NS_GPL(lwmi_cd01_get_data, "LENOVO_WMI_CD");
*/
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 +171,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 +183,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 +214,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 +224,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 +232,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 +292,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,21 +306,34 @@ 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;
+ if (info->type == 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;
+ }
+
+ ret = component_add(&wdev->dev, &lwmi_cd_component_ops);
- return component_add(&wdev->dev, &lwmi_cd_component_ops);
+out:
+ if (ret) {
+ dev_err(&wdev->dev, "failed to register %s: %d\n",
+ info->name, ret);
+ } else {
+ dev_info(&wdev->dev, "registered %s with %u items\n",
+ info->name, priv->list->count);
+ }
+ return ret;
}
static void lwmi_cd_remove(struct wmi_device *wdev)
@@ -267,8 +341,12 @@ static void lwmi_cd_remove(struct wmi_device *wdev)
component_del(&wdev->dev, &lwmi_cd_component_ops);
}
+#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) },
{}
};
@@ -284,21 +362,61 @@ static struct wmi_driver lwmi_cd_driver = {
};
/**
- * 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.
+ * lwmi_cd_match() - Match rule for the master driver.
+ * @dev: Pointer to the capability data parent device.
+ * @data: Pointer to capability data type (enum lwmi_cd_type *) to match.
*
* Return: int.
*/
-int lwmi_cd01_match(struct device *dev, void *data)
+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. This matches all available
+ * capdata types on the machine.
+ */
+void lwmi_cd_match_add_all(struct device *master, struct component_match **matchptr)
{
- return dev->driver == &lwmi_cd_driver.driver;
+ int i;
+
+ if (WARN_ON(*matchptr))
+ return;
+
+ for (i = 0; i < ARRAY_SIZE(lwmi_cd_table); i++) {
+ if (!lwmi_cd_table[i].guid_string ||
+ !wmi_has_guid(lwmi_cd_table[i].guid_string))
+ continue;
+
+ component_match_add(master, matchptr, lwmi_cd_match,
+ (void *)&lwmi_cd_table[i].type);
+ if (IS_ERR(matchptr))
+ return;
+ }
+
+ if (!*matchptr) {
+ pr_warn("a master driver requested capability data, but nothing is available\n");
+ *matchptr = ERR_PTR(-ENODEV);
+ }
}
-EXPORT_SYMBOL_NS_GPL(lwmi_cd01_match, "LENOVO_WMI_CD");
+EXPORT_SYMBOL_NS_GPL(lwmi_cd_match_add_all, "LENOVO_WMI_CD");
module_wmi_driver(lwmi_cd_driver);
MODULE_DEVICE_TABLE(wmi, lwmi_cd_id_table);
MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
+MODULE_AUTHOR("Rong Zhang <i@rong.moe>");
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..1e5fce7836cbf 100644
--- a/drivers/platform/x86/lenovo/wmi-capdata.h
+++ b/drivers/platform/x86/lenovo/wmi-capdata.h
@@ -7,6 +7,7 @@
#include <linux/types.h>
+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;
+};
+
int lwmi_cd01_get_data(struct cd_list *list, u32 attribute_id, struct capdata01 *output);
-int lwmi_cd01_match(struct device *dev, void *data);
+void lwmi_cd_match_add_all(struct device *master, struct component_match **matchptr);
#endif /* !_LENOVO_WMI_CAPDATA_H_ */
diff --git a/drivers/platform/x86/lenovo/wmi-other.c b/drivers/platform/x86/lenovo/wmi-other.c
index c6dc1b4cff841..20c6ff0be37a1 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 = { 0 };
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;
@@ -618,6 +618,7 @@ static int lwmi_other_probe(struct wmi_device *wdev, const void *context)
{
struct component_match *master_match = NULL;
struct lwmi_om_priv *priv;
+ int ret;
priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
@@ -626,12 +627,26 @@ static int lwmi_other_probe(struct wmi_device *wdev, const void *context)
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);
- return component_master_add_with_match(&wdev->dev, &lwmi_om_master_ops,
- master_match);
+ ret = component_master_add_with_match(&wdev->dev, &lwmi_om_master_ops,
+ master_match);
+ if (ret)
+ return ret;
+
+ if (likely(component_master_is_bound(&wdev->dev, &lwmi_om_master_ops)))
+ return 0;
+
+ /*
+ * The bind callbacks of both master and components were never called in
+ * this case - this driver won't work at all. Failing...
+ */
+ dev_err(&wdev->dev, "unbound master; is any component failing to be probed?");
+
+ component_master_del(&wdev->dev, &lwmi_om_master_ops);
+ return -EXDEV;
}
static void lwmi_other_remove(struct wmi_device *wdev)
--
2.51.0
^ permalink raw reply related [flat|nested] 20+ messages in thread
* [PATCH 3/6] platform/x86: lenovo-wmi-capdata: Add support for Capability Data 00
2025-10-19 21:04 [PATCH 0/6] platform/x86: lenovo-wmi-{capdata,other}: Add HWMON for fan speed Rong Zhang
2025-10-19 21:04 ` [PATCH 1/6] platform/x86: Rename lenovo-wmi-capdata01 to lenovo-wmi-capdata Rong Zhang
2025-10-19 21:04 ` [PATCH 2/6] platform/x86: lenovo-wmi-{capdata,other}: Support multiple Capability Data Rong Zhang
@ 2025-10-19 21:04 ` Rong Zhang
2025-10-26 4:55 ` Derek John Clark
2025-10-19 21:04 ` [PATCH 4/6] platform/x86: lenovo-wmi-other: Add HWMON for fan speed RPM Rong Zhang
` (3 subsequent siblings)
6 siblings, 1 reply; 20+ messages in thread
From: Rong Zhang @ 2025-10-19 21:04 UTC (permalink / raw)
To: Mark Pearson, Derek J. Clark, Armin Wolf, Hans de Goede,
Ilpo Järvinen
Cc: Rong Zhang, Guenter Roeck, platform-driver-x86, linux-kernel,
linux-hwmon
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.
Signed-off-by: Rong Zhang <i@rong.moe>
---
.../wmi/devices/lenovo-wmi-other.rst | 8 +++++++
drivers/platform/x86/lenovo/wmi-capdata.c | 23 ++++++++++++++++++-
drivers/platform/x86/lenovo/wmi-capdata.h | 8 +++++++
3 files changed, 38 insertions(+), 1 deletion(-)
diff --git a/Documentation/wmi/devices/lenovo-wmi-other.rst b/Documentation/wmi/devices/lenovo-wmi-other.rst
index d7928b8dfb4b5..adbd7943c6756 100644
--- a/Documentation/wmi/devices/lenovo-wmi-other.rst
+++ b/Documentation/wmi/devices/lenovo-wmi-other.rst
@@ -31,6 +31,14 @@ under the following path:
/sys/class/firmware-attributes/lenovo-wmi-other/attributes/<attribute>/
+LENOVO_CAPABILITY_DATA_00
+-------------------------
+
+WMI GUID ``362A3AFE-3D96-4665-8530-96DAD5BB300E``
+
+The LENOVO-CAPABILITD_DATA_00 interface provides information on whether the
+device supports querying or setting fan speed.
+
LENOVO_CAPABILITY_DATA_01
-------------------------
diff --git a/drivers/platform/x86/lenovo/wmi-capdata.c b/drivers/platform/x86/lenovo/wmi-capdata.c
index 14175fe19247e..6927de409b09d 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
@@ -14,7 +17,7 @@
* - Initial implementation (formerly named lenovo-wmi-capdata01)
*
* Copyright (C) 2025 Rong Zhang <i@rong.moe>
- * - Unified implementation
+ * - Unified implementation for Capability Data 00 and 01
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
@@ -36,12 +39,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,
};
@@ -57,6 +62,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),
};
@@ -72,6 +78,7 @@ struct cd_list {
u8 count;
union {
+ DECLARE_FLEX_ARRAY(struct capdata00, cd00);
DECLARE_FLEX_ARRAY(struct capdata01, cd01);
};
};
@@ -95,6 +102,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;
@@ -136,6 +146,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_CD");
+
DEF_LWMI_CDXX_GET_DATA(cd01, LENOVO_CAPABILITY_DATA_01, struct capdata01);
EXPORT_SYMBOL_NS_GPL(lwmi_cd01_get_data, "LENOVO_WMI_CD");
@@ -154,6 +167,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]);
@@ -199,6 +216,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;
@@ -346,6 +366,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 1e5fce7836cbf..a6f0cb006e745 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,9 +27,11 @@ struct capdata01 {
};
struct lwmi_cd_binder {
+ struct cd_list *cd00_list;
struct cd_list *cd01_list;
};
+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);
void lwmi_cd_match_add_all(struct device *master, struct component_match **matchptr);
--
2.51.0
^ permalink raw reply related [flat|nested] 20+ messages in thread
* [PATCH 4/6] platform/x86: lenovo-wmi-other: Add HWMON for fan speed RPM
2025-10-19 21:04 [PATCH 0/6] platform/x86: lenovo-wmi-{capdata,other}: Add HWMON for fan speed Rong Zhang
` (2 preceding siblings ...)
2025-10-19 21:04 ` [PATCH 3/6] platform/x86: lenovo-wmi-capdata: Add support for Capability Data 00 Rong Zhang
@ 2025-10-19 21:04 ` Rong Zhang
2025-10-26 5:23 ` Derek John Clark
2025-10-19 21:04 ` [PATCH 5/6] platform/x86: lenovo-wmi-capdata: Add support for Fan Test Data Rong Zhang
` (2 subsequent siblings)
6 siblings, 1 reply; 20+ messages in thread
From: Rong Zhang @ 2025-10-19 21:04 UTC (permalink / raw)
To: Mark Pearson, Derek J. Clark, Armin Wolf, Hans de Goede,
Ilpo Järvinen
Cc: Rong Zhang, Guenter Roeck, platform-driver-x86, linux-kernel,
linux-hwmon
Register an HWMON device for fan spped RPM according to Capability Data
00 provided by lenovo-wmi-capdata. The corresponding HWMON nodes are:
- fanX_enable: enable/disable the fan (tunable)
- fanX_input: current RPM
- fanX_target: target RPM (tunable)
Signed-off-by: Rong Zhang <i@rong.moe>
---
.../wmi/devices/lenovo-wmi-other.rst | 5 +
drivers/platform/x86/lenovo/Kconfig | 1 +
drivers/platform/x86/lenovo/wmi-other.c | 324 +++++++++++++++++-
3 files changed, 317 insertions(+), 13 deletions(-)
diff --git a/Documentation/wmi/devices/lenovo-wmi-other.rst b/Documentation/wmi/devices/lenovo-wmi-other.rst
index adbd7943c6756..cb6a9bfe5a79e 100644
--- a/Documentation/wmi/devices/lenovo-wmi-other.rst
+++ b/Documentation/wmi/devices/lenovo-wmi-other.rst
@@ -31,6 +31,11 @@ under the following path:
/sys/class/firmware-attributes/lenovo-wmi-other/attributes/<attribute>/
+Besides, this driver also exports fan speed RPM to HWMON:
+ - fanX_enable: enable/disable the fan (tunable)
+ - fanX_input: current RPM
+ - fanX_target: target RPM (tunable)
+
LENOVO_CAPABILITY_DATA_00
-------------------------
diff --git a/drivers/platform/x86/lenovo/Kconfig b/drivers/platform/x86/lenovo/Kconfig
index fb96a0f908f03..be9af04511462 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_DATA
select LENOVO_WMI_EVENTS
diff --git a/drivers/platform/x86/lenovo/wmi-other.c b/drivers/platform/x86/lenovo/wmi-other.c
index 20c6ff0be37a1..f8771ed3c6642 100644
--- a/drivers/platform/x86/lenovo/wmi-other.c
+++ b/drivers/platform/x86/lenovo/wmi-other.c
@@ -14,7 +14,15 @@
* These attributes typically don't fit anywhere else in the sysfs and are set
* in Windows using one of Lenovo's multiple user applications.
*
+ * Besides, this driver also exports tunable fan speed RPM to HWMON.
+ *
* Copyright (C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
+ * - fw_attributes
+ * - binding to Capability Data 01
+ *
+ * Copyright (C) 2025 Rong Zhang <i@rong.moe>
+ * - HWMON
+ * - binding to Capability Data 00
*/
#include <linux/acpi.h>
@@ -25,6 +33,7 @@
#include <linux/device.h>
#include <linux/export.h>
#include <linux/gfp_types.h>
+#include <linux/hwmon.h>
#include <linux/idr.h>
#include <linux/kdev_t.h>
#include <linux/kobject.h>
@@ -43,12 +52,20 @@
#define LENOVO_OTHER_MODE_GUID "DC2A8805-3A8C-41BA-A6F7-092E0089CD3B"
+#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_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
+#define LWMI_DEVICE_ID_FAN 0x04
+
+#define LWMI_FEATURE_ID_FAN_RPM 0x03
+
#define LWMI_TYPE_ID_NONE 0x00
#define LWMI_FEATURE_VALUE_GET 17
@@ -59,7 +76,18 @@
#define LWMI_ATTR_MODE_ID_MASK GENMASK(15, 8)
#define LWMI_ATTR_TYPE_ID_MASK GENMASK(7, 0)
+/* Only fan1 and fan2 are present on supported devices. */
+#define LWMI_FAN_ID_BASE 1
+#define LWMI_FAN_NR 2
+#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_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);
@@ -76,15 +104,256 @@ 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 fan_info {
+ u32 supported;
+ long target;
+ } fan_info[LWMI_FAN_NR];
};
+/* ======== HWMON (component: lenovo-wmi-capdata 00) ======== */
+
+/**
+ * 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 speed.
+ *
+ * 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;
+ else if (retval != 1)
+ return -EIO;
+
+ return 0;
+}
+
+/**
+ * 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 a 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 r = false, w = false;
+
+ if (type == hwmon_fan) {
+ switch (attr) {
+ case hwmon_fan_enable:
+ case hwmon_fan_target:
+ r = w = priv->fan_info[channel].supported & LWMI_SUPP_MAY_SET;
+ break;
+ case hwmon_fan_input:
+ r = priv->fan_info[channel].supported & LWMI_SUPP_MAY_GET;
+ break;
+ }
+ }
+
+ if (!r)
+ return 0;
+
+ return w ? 0644 : 0444;
+}
+
+/**
+ * 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) {
+ 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_enable:
+ case hwmon_fan_target:
+ /* -ENODATA before first set. */
+ err = (int)priv->fan_info[channel].target;
+ if (err < 0)
+ return err;
+
+ if (attr == hwmon_fan_enable)
+ *val = priv->fan_info[channel].target != 1;
+ else
+ *val = priv->fan_info[channel].target;
+ 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;
+ int err;
+
+ if (type == hwmon_fan) {
+ switch (attr) {
+ case hwmon_fan_enable:
+ case hwmon_fan_target:
+ if (attr == hwmon_fan_enable) {
+ if (val == 0)
+ raw = 1; /* stop */
+ else if (val == 1)
+ raw = 0; /* auto */
+ else
+ return -EINVAL;
+ } else {
+ /*
+ * val > U16_MAX seems safe but meaningless.
+ */
+ if (val < 0 || val > U16_MAX)
+ return -EINVAL;
+ raw = val;
+ }
+
+ err = lwmi_om_fan_get_set(priv, channel, &raw, true);
+ if (err)
+ return err;
+
+ priv->fan_info[channel].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_ENABLE | HWMON_F_INPUT | HWMON_F_TARGET,
+ HWMON_F_ENABLE | HWMON_F_INPUT | HWMON_F_TARGET),
+ 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
+ * @priv: Driver private data
+ *
+ * Initializes capability data and registers the HWMON device.
+ *
+ * Return: 0 on success, or an error code.
+ */
+static int lwmi_om_hwmon_add(struct lwmi_om_priv *priv)
+{
+ struct capdata00 capdata00;
+ int i, err;
+
+ for (i = 0; i < LWMI_FAN_NR; i++) {
+ err = lwmi_cd00_get_data(priv->cd00_list, LWMI_ATTR_ID_FAN_RPM(i),
+ &capdata00);
+ if (err)
+ continue;
+
+ priv->fan_info[i] = (struct fan_info) {
+ .supported = capdata00.supported,
+ .target = -ENODATA,
+ };
+ }
+
+ priv->hwmon_dev = hwmon_device_register_with_info(&priv->wdev->dev, LWMI_OM_HWMON_NAME,
+ priv, &lwmi_om_hwmon_chip_info, NULL);
+
+ return PTR_ERR_OR_ZERO(priv->hwmon_dev);
+}
+
+/**
+ * lwmi_om_hwmon_remove() - Unregister HWMON device
+ * @priv: Driver private data
+ *
+ * Unregisters the HWMON device and resets all fans to automatic mode.
+ * Ensures hardware doesn't remain in manual mode after driver removal.
+ */
+static void lwmi_om_hwmon_remove(struct lwmi_om_priv *priv)
+{
+ hwmon_device_unregister(priv->hwmon_dev);
+}
+
+/* ======== fw_attributes (component: lenovo-wmi-capdata 01) ======== */
+
struct tunable_attr_01 {
struct capdata01 *capdata;
struct device *dev;
@@ -564,15 +833,17 @@ 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. 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, register the
+ * HWMON device and all firmware attribute groups.
*
* Return: 0 on success, or an error code.
*/
@@ -586,26 +857,47 @@ static int lwmi_om_master_bind(struct device *dev)
if (ret)
return ret;
- priv->cd01_list = binder.cd01_list;
- if (!priv->cd01_list)
+ if (!binder.cd00_list && !binder.cd01_list)
return -ENODEV;
- return lwmi_om_fw_attr_add(priv);
+ priv->cd00_list = binder.cd00_list;
+ if (priv->cd00_list) {
+ ret = lwmi_om_hwmon_add(priv);
+ if (ret)
+ return ret;
+ }
+
+ priv->cd01_list = binder.cd01_list;
+ if (priv->cd01_list) {
+ ret = lwmi_om_fw_attr_add(priv);
+ if (ret) {
+ if (priv->cd00_list)
+ lwmi_om_hwmon_remove(priv);
+ return ret;
+ }
+ }
+
+ return 0;
}
/**
* 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 the HWMON device and all capability data attribute groups. Then
+ * call component_unbind_all to unbind the lenovo-wmi-capdata driver 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);
+ if (priv->cd00_list)
+ lwmi_om_hwmon_remove(priv);
+
+ if (priv->cd01_list)
+ lwmi_om_fw_attr_remove(priv);
+
component_unbind_all(dev, NULL);
}
@@ -624,6 +916,9 @@ 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);
@@ -654,7 +949,9 @@ 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);
+
+ if (priv->ida_id >= 0)
+ ida_free(&lwmi_om_ida, priv->ida_id);
}
static const struct wmi_device_id lwmi_other_id_table[] = {
@@ -679,5 +976,6 @@ MODULE_IMPORT_NS("LENOVO_WMI_CD");
MODULE_IMPORT_NS("LENOVO_WMI_HELPERS");
MODULE_DEVICE_TABLE(wmi, lwmi_other_id_table);
MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
+MODULE_AUTHOR("Rong Zhang <i@rong.moe>");
MODULE_DESCRIPTION("Lenovo Other Mode WMI Driver");
MODULE_LICENSE("GPL");
--
2.51.0
^ permalink raw reply related [flat|nested] 20+ messages in thread
* [PATCH 5/6] platform/x86: lenovo-wmi-capdata: Add support for Fan Test Data
2025-10-19 21:04 [PATCH 0/6] platform/x86: lenovo-wmi-{capdata,other}: Add HWMON for fan speed Rong Zhang
` (3 preceding siblings ...)
2025-10-19 21:04 ` [PATCH 4/6] platform/x86: lenovo-wmi-other: Add HWMON for fan speed RPM Rong Zhang
@ 2025-10-19 21:04 ` Rong Zhang
2025-10-19 21:04 ` [PATCH 6/6] platform/x86: lenovo-wmi-other: Report min/max RPM and hide dummy fans Rong Zhang
2025-10-26 4:39 ` [PATCH 0/6] platform/x86: lenovo-wmi-{capdata,other}: Add HWMON for fan speed Derek John Clark
6 siblings, 0 replies; 20+ messages in thread
From: Rong Zhang @ 2025-10-19 21:04 UTC (permalink / raw)
To: Mark Pearson, Derek J. Clark, Armin Wolf, Hans de Goede,
Ilpo Järvinen
Cc: Rong Zhang, Guenter Roeck, platform-driver-x86, linux-kernel,
linux-hwmon
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.
Signed-off-by: Rong Zhang <i@rong.moe>
---
.../wmi/devices/lenovo-wmi-other.rst | 17 +++
drivers/platform/x86/lenovo/wmi-capdata.c | 104 +++++++++++++++++-
drivers/platform/x86/lenovo/wmi-capdata.h | 8 ++
3 files changed, 128 insertions(+), 1 deletion(-)
diff --git a/Documentation/wmi/devices/lenovo-wmi-other.rst b/Documentation/wmi/devices/lenovo-wmi-other.rst
index cb6a9bfe5a79e..cca96862ae9c4 100644
--- a/Documentation/wmi/devices/lenovo-wmi-other.rst
+++ b/Documentation/wmi/devices/lenovo-wmi-other.rst
@@ -66,6 +66,13 @@ The following 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-LENOVO_FAN_TEST_DATA interface provides reference data for self-test
+of cooling fans, including minimum and maximum fan speed RPM.
WMI interface description
=========================
@@ -119,3 +126,13 @@ data using the `bmfdec <https://github.com/pali/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 6927de409b09d..c861f2dd75c32 100644
--- a/drivers/platform/x86/lenovo/wmi-capdata.c
+++ b/drivers/platform/x86/lenovo/wmi-capdata.c
@@ -13,11 +13,15 @@
* 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 <derekjohn.clark@gmail.com>
* - Initial implementation (formerly named lenovo-wmi-capdata01)
*
* Copyright (C) 2025 Rong Zhang <i@rong.moe>
- * - Unified implementation for Capability Data 00 and 01
+ * - Unified implementation for Capability Data 00/01 and Fan Test Data
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
@@ -41,6 +45,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
@@ -48,6 +53,7 @@
enum lwmi_cd_type {
LENOVO_CAPABILITY_DATA_00,
LENOVO_CAPABILITY_DATA_01,
+ LENOVO_FAN_TEST_DATA,
};
#define LWMI_CD_TABLE_ITEM(_type) \
@@ -64,6 +70,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 {
@@ -80,6 +87,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);
};
};
@@ -108,6 +116,14 @@ static int lwmi_cd_component_bind(struct device *cd_dev,
case LENOVO_CAPABILITY_DATA_01:
binder->cd01_list = priv->list;
break;
+ case LENOVO_FAN_TEST_DATA:
+ /*
+ * Do not expose dummy data.
+ * See also lwmi_cd_fan_list_alloc_cache().
+ */
+ if (priv->list->count)
+ binder->cd_fan_list = priv->list;
+ break;
default:
return -EINVAL;
}
@@ -152,6 +168,9 @@ EXPORT_SYMBOL_NS_GPL(lwmi_cd00_get_data, "LENOVO_WMI_CD");
DEF_LWMI_CDXX_GET_DATA(cd01, LENOVO_CAPABILITY_DATA_01, struct capdata01);
EXPORT_SYMBOL_NS_GPL(lwmi_cd01_get_data, "LENOVO_WMI_CD");
+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_CD");
+
/**
* lwmi_cd_cache() - Cache all WMI data block information
* @priv: lenovo-wmi-capdata driver data.
@@ -175,6 +194,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;
}
@@ -197,6 +219,78 @@ 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)
+{
+ u32 count, *fan_ids, *fan_min_rpms, *fan_max_rpms;
+ union acpi_object *ret_obj __free(kfree) = NULL;
+ struct block { u32 nr; u32 data[]; } *block;
+ struct cd_list *list;
+ size_t size;
+ int idx;
+
+ ret_obj = wmidev_block_query(priv->wdev, 0);
+ if (!ret_obj)
+ return -ENODEV;
+
+ /*
+ * This is usually caused by a dummy ACPI method. Do not return an error
+ * as failing to probe this device will result in master driver being
+ * unbound - this behavior aligns with lwmi_cd_cache().
+ */
+ if (ret_obj->type != ACPI_TYPE_BUFFER) {
+ count = 0;
+ goto alloc;
+ }
+
+ size = ret_obj->buffer.length;
+ block = (struct block *)ret_obj->buffer.pointer;
+
+ 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;
+ }
+
+ if (count == 0)
+ goto alloc;
+
+ if (count > U8_MAX) {
+ dev_warn(&priv->wdev->dev,
+ "too many fans reported: %u > %u, truncating\n",
+ count, U8_MAX);
+ count = U8_MAX;
+ }
+
+ fan_ids = &block->data[0];
+ fan_max_rpms = &block->data[count];
+ fan_min_rpms = &block->data[count * 2];
+
+alloc:
+ list = devm_kzalloc(&priv->wdev->dev, struct_size(list, cd_fan, count), GFP_KERNEL);
+ if (!list)
+ return -ENOMEM;
+
+ for (idx = 0; idx < count; idx++) {
+ list->cd_fan[idx] = (struct capdata_fan) {
+ .id = fan_ids[idx],
+ .min_rpm = fan_min_rpms[idx],
+ .max_rpm = fan_max_rpms[idx],
+ };
+ }
+
+ *listptr = list;
+ return count;
+}
+
/**
* lwmi_cd_alloc() - Allocate a cd_list struct in drvdata
* @priv: lenovo-wmi-capdata driver data.
@@ -222,6 +316,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;
}
@@ -230,6 +330,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;
@@ -368,6 +469,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 a6f0cb006e745..52bc215ac43d8 100644
--- a/drivers/platform/x86/lenovo/wmi-capdata.h
+++ b/drivers/platform/x86/lenovo/wmi-capdata.h
@@ -26,13 +26,21 @@ 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;
+ struct cd_list *cd_fan_list;
};
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);
void lwmi_cd_match_add_all(struct device *master, struct component_match **matchptr);
#endif /* !_LENOVO_WMI_CAPDATA_H_ */
--
2.51.0
^ permalink raw reply related [flat|nested] 20+ messages in thread
* [PATCH 6/6] platform/x86: lenovo-wmi-other: Report min/max RPM and hide dummy fans
2025-10-19 21:04 [PATCH 0/6] platform/x86: lenovo-wmi-{capdata,other}: Add HWMON for fan speed Rong Zhang
` (4 preceding siblings ...)
2025-10-19 21:04 ` [PATCH 5/6] platform/x86: lenovo-wmi-capdata: Add support for Fan Test Data Rong Zhang
@ 2025-10-19 21:04 ` Rong Zhang
2025-10-26 4:39 ` [PATCH 0/6] platform/x86: lenovo-wmi-{capdata,other}: Add HWMON for fan speed Derek John Clark
6 siblings, 0 replies; 20+ messages in thread
From: Rong Zhang @ 2025-10-19 21:04 UTC (permalink / raw)
To: Mark Pearson, Derek J. Clark, Armin Wolf, Hans de Goede,
Ilpo Järvinen
Cc: Rong Zhang, Guenter Roeck, platform-driver-x86, linux-kernel,
linux-hwmon
When Fan Test Data is available, make fans without such data invisible
by default (opt-out with option ignore_fan_cap). Besides, report extra
data for reference:
- fanX_max: maximum RPM
- fanX_min: minimum RPM
Signed-off-by: Rong Zhang <i@rong.moe>
---
.../wmi/devices/lenovo-wmi-other.rst | 2 +
drivers/platform/x86/lenovo/wmi-other.c | 74 +++++++++++++++++--
2 files changed, 71 insertions(+), 5 deletions(-)
diff --git a/Documentation/wmi/devices/lenovo-wmi-other.rst b/Documentation/wmi/devices/lenovo-wmi-other.rst
index cca96862ae9c4..06ee7fe77e6ef 100644
--- a/Documentation/wmi/devices/lenovo-wmi-other.rst
+++ b/Documentation/wmi/devices/lenovo-wmi-other.rst
@@ -34,6 +34,8 @@ under the following path:
Besides, this driver also exports fan speed RPM to HWMON:
- fanX_enable: enable/disable the fan (tunable)
- fanX_input: current RPM
+ - fanX_max: maximum RPM
+ - fanX_min: minimum RPM
- fanX_target: target RPM (tunable)
LENOVO_CAPABILITY_DATA_00
diff --git a/drivers/platform/x86/lenovo/wmi-other.c b/drivers/platform/x86/lenovo/wmi-other.c
index f8771ed3c6642..aded709c1ba4a 100644
--- a/drivers/platform/x86/lenovo/wmi-other.c
+++ b/drivers/platform/x86/lenovo/wmi-other.c
@@ -14,7 +14,8 @@
* These attributes typically don't fit anywhere else in the sysfs and are set
* in Windows using one of Lenovo's multiple user applications.
*
- * Besides, this driver also exports tunable fan speed RPM to HWMON.
+ * Besides, this driver also exports tunable fan speed RPM to HWMON. Min/max RPM
+ * are also provided for reference when available.
*
* Copyright (C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
* - fw_attributes
@@ -22,7 +23,7 @@
*
* Copyright (C) 2025 Rong Zhang <i@rong.moe>
* - HWMON
- * - binding to Capability Data 00
+ * - binding to Capability Data 00 and Fan
*/
#include <linux/acpi.h>
@@ -106,6 +107,7 @@ struct lwmi_om_priv {
/* only valid after capdata bind */
struct cd_list *cd00_list;
struct cd_list *cd01_list;
+ struct cd_list *cd_fan_list;
struct device *hwmon_dev;
struct device *fw_attr_dev;
@@ -116,11 +118,20 @@ struct lwmi_om_priv {
struct fan_info {
u32 supported;
+ long min_rpm;
+ long max_rpm;
long target;
} fan_info[LWMI_FAN_NR];
};
-/* ======== HWMON (component: lenovo-wmi-capdata 00) ======== */
+static bool ignore_fan_cap;
+module_param(ignore_fan_cap, bool, 0444);
+MODULE_PARM_DESC(ignore_fan_cap,
+ "Ignore fan capdata. "
+ "Results in fans missing from capdata keep visible. "
+ "Effectively disables mix/max fan speed RPM reporting.");
+
+/* ======== HWMON (component: lenovo-wmi-capdata 00 & fan) ======== */
/**
* lwmi_om_fan_get_set() - Get or set fan RPM value of specified fan
@@ -184,6 +195,12 @@ static umode_t lwmi_om_hwmon_is_visible(const void *drvdata, enum hwmon_sensor_t
case hwmon_fan_input:
r = priv->fan_info[channel].supported & LWMI_SUPP_MAY_GET;
break;
+ case hwmon_fan_min:
+ r = priv->fan_info[channel].min_rpm >= 0;
+ break;
+ case hwmon_fan_max:
+ r = priv->fan_info[channel].max_rpm >= 0;
+ break;
}
}
@@ -233,6 +250,12 @@ static int lwmi_om_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
else
*val = priv->fan_info[channel].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;
}
}
@@ -271,6 +294,9 @@ static int lwmi_om_hwmon_write(struct device *dev, enum hwmon_sensor_types type,
return -EINVAL;
} else {
/*
+ * We don't check fan capdata here as it is just
+ * reference value for self-test.
+ *
* val > U16_MAX seems safe but meaningless.
*/
if (val < 0 || val > U16_MAX)
@@ -293,8 +319,10 @@ static int lwmi_om_hwmon_write(struct device *dev, enum hwmon_sensor_types type,
static const struct hwmon_channel_info * const lwmi_om_hwmon_info[] = {
/* Must match LWMI_FAN_NR. */
HWMON_CHANNEL_INFO(fan,
- HWMON_F_ENABLE | HWMON_F_INPUT | HWMON_F_TARGET,
- HWMON_F_ENABLE | HWMON_F_INPUT | HWMON_F_TARGET),
+ HWMON_F_ENABLE | HWMON_F_INPUT | HWMON_F_TARGET |
+ HWMON_F_MIN | HWMON_F_MAX,
+ HWMON_F_ENABLE | HWMON_F_INPUT | HWMON_F_TARGET |
+ HWMON_F_MIN | HWMON_F_MAX),
NULL
};
@@ -319,6 +347,7 @@ static const struct hwmon_chip_info lwmi_om_hwmon_chip_info = {
*/
static int lwmi_om_hwmon_add(struct lwmi_om_priv *priv)
{
+ struct capdata_fan capdata_fan;
struct capdata00 capdata00;
int i, err;
@@ -330,8 +359,42 @@ static int lwmi_om_hwmon_add(struct lwmi_om_priv *priv)
priv->fan_info[i] = (struct fan_info) {
.supported = capdata00.supported,
+ .min_rpm = -ENODATA,
+ .max_rpm = -ENODATA,
.target = -ENODATA,
};
+
+ if (!(priv->fan_info[i].supported & LWMI_SUPP_VALID))
+ continue;
+
+ if (ignore_fan_cap) {
+ dev_notice_once(&priv->wdev->dev, "fan capdata ignored\n");
+ continue;
+ }
+
+ if (!priv->cd_fan_list) {
+ dev_notice_once(&priv->wdev->dev, "fan capdata unavailable\n");
+ continue;
+ }
+
+ /*
+ * Fan attribute from capdata00 may be dummy (i.e.,
+ * get: constant dummy RPM, set: no-op with retval == 0).
+ *
+ * If fan capdata is available and a fan is missing from it,
+ * make the fan invisible.
+ */
+ err = lwmi_cd_fan_get_data(priv->cd_fan_list, LWMI_FAN_ID(i), &capdata_fan);
+ if (err) {
+ dev_dbg(&priv->wdev->dev,
+ "fan %d missing from fan capdata, hiding\n",
+ LWMI_FAN_ID(i));
+ priv->fan_info[i].supported = 0;
+ continue;
+ }
+
+ priv->fan_info[i].min_rpm = capdata_fan.min_rpm;
+ priv->fan_info[i].max_rpm = capdata_fan.max_rpm;
}
priv->hwmon_dev = hwmon_device_register_with_info(&priv->wdev->dev, LWMI_OM_HWMON_NAME,
@@ -862,6 +925,7 @@ static int lwmi_om_master_bind(struct device *dev)
priv->cd00_list = binder.cd00_list;
if (priv->cd00_list) {
+ priv->cd_fan_list = binder.cd_fan_list;
ret = lwmi_om_hwmon_add(priv);
if (ret)
return ret;
--
2.51.0
^ permalink raw reply related [flat|nested] 20+ messages in thread
* Re: [PATCH 2/6] platform/x86: lenovo-wmi-{capdata,other}: Support multiple Capability Data
2025-10-19 21:04 ` [PATCH 2/6] platform/x86: lenovo-wmi-{capdata,other}: Support multiple Capability Data Rong Zhang
@ 2025-10-20 1:03 ` kernel test robot
2025-10-26 4:43 ` Derek John Clark
1 sibling, 0 replies; 20+ messages in thread
From: kernel test robot @ 2025-10-20 1:03 UTC (permalink / raw)
To: Rong Zhang, Mark Pearson, Derek J. Clark, Armin Wolf,
Hans de Goede, Ilpo Järvinen
Cc: llvm, oe-kbuild-all, Rong Zhang, Guenter Roeck,
platform-driver-x86, linux-kernel, linux-hwmon
Hi Rong,
kernel test robot noticed the following build warnings:
[auto build test WARNING on 3a8660878839faadb4f1a6dd72c3179c1df56787]
url: https://github.com/intel-lab-lkp/linux/commits/Rong-Zhang/platform-x86-Rename-lenovo-wmi-capdata01-to-lenovo-wmi-capdata/20251020-050849
base: 3a8660878839faadb4f1a6dd72c3179c1df56787
patch link: https://lore.kernel.org/r/20251019210450.88830-3-i%40rong.moe
patch subject: [PATCH 2/6] platform/x86: lenovo-wmi-{capdata,other}: Support multiple Capability Data
config: i386-buildonly-randconfig-006-20251020 (https://download.01.org/0day-ci/archive/20251020/202510200717.19aOSEUP-lkp@intel.com/config)
compiler: clang version 20.1.8 (https://github.com/llvm/llvm-project 87f0227cb60147a26a1eeb4fb06e3b505e9c7261)
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20251020/202510200717.19aOSEUP-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202510200717.19aOSEUP-lkp@intel.com/
All warnings (new ones prefixed by >>):
>> Warning: drivers/platform/x86/lenovo/wmi-capdata.c:371 function parameter 'type' not described in 'lwmi_cd_match'
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH 0/6] platform/x86: lenovo-wmi-{capdata,other}: Add HWMON for fan speed
2025-10-19 21:04 [PATCH 0/6] platform/x86: lenovo-wmi-{capdata,other}: Add HWMON for fan speed Rong Zhang
` (5 preceding siblings ...)
2025-10-19 21:04 ` [PATCH 6/6] platform/x86: lenovo-wmi-other: Report min/max RPM and hide dummy fans Rong Zhang
@ 2025-10-26 4:39 ` Derek John Clark
2025-10-26 17:11 ` Rong Zhang
6 siblings, 1 reply; 20+ messages in thread
From: Derek John Clark @ 2025-10-26 4:39 UTC (permalink / raw)
To: Rong Zhang
Cc: Mark Pearson, Armin Wolf, Hans de Goede, Ilpo Järvinen,
Guenter Roeck, platform-driver-x86, linux-kernel, linux-hwmon
On Sun, Oct 19, 2025 at 2:05 PM Rong Zhang <i@rong.moe> wrote:
>
> Lenovo WMI Other Mode interface also supports querying or setting fan
> speed RPM. This capability is decribed by LENOVO_CAPABILITY_DATA_00.
> Besides, LENOVO_FAN_TEST_DATA provides reference data for self-test of
> cooling fans, including minimum and maximum fan speed RPM.
>
> This patchset turns lenovo-wmi-capdata01 into a unified driver (now
> named lenovo-wmi-capdata) for LENOVO_CAPABILITY_DATA_{00,01} and
> LENOVO_FAN_TEST_DATA; then adds HWMON support for lenovo-wmi-other:
>
> - fanX_enable: enable/disable the fan (tunable)
> - fanX_input: current RPM
> - fanX_max: maximum RPM
> - fanX_min: minimum RPM
> - fanX_target: target RPM (tunable)
>
> This implementation doesn't require all capability data to be available,
> and is capable to expose interfaces accordingly:
>
> - Having LENOVO_CAPABILITY_DATA_00: exposes fanX_{enable,input,target}
> - Having LENOVO_CAPABILITY_DATA_01: exposes firmware_attributes
> - Having LENOVO_FAN_TEST_DATA: exposes fanX_{max,min}
>
> Rong Zhang (6):
> platform/x86: Rename lenovo-wmi-capdata01 to lenovo-wmi-capdata
> platform/x86: lenovo-wmi-{capdata,other}: Support multiple Capability
> Data
> platform/x86: lenovo-wmi-capdata: Add support for Capability Data 00
> platform/x86: lenovo-wmi-other: Add HWMON for fan speed RPM
> platform/x86: lenovo-wmi-capdata: Add support for Fan Test Data
> platform/x86: lenovo-wmi-other: Report min/max RPM and hide dummy fans
>
> .../wmi/devices/lenovo-wmi-other.rst | 32 +
> drivers/platform/x86/lenovo/Kconfig | 5 +-
> drivers/platform/x86/lenovo/Makefile | 2 +-
> drivers/platform/x86/lenovo/wmi-capdata.c | 545 ++++++++++++++++++
> drivers/platform/x86/lenovo/wmi-capdata.h | 46 ++
> drivers/platform/x86/lenovo/wmi-capdata01.c | 302 ----------
> drivers/platform/x86/lenovo/wmi-capdata01.h | 25 -
> drivers/platform/x86/lenovo/wmi-other.c | 422 +++++++++++++-
> 8 files changed, 1028 insertions(+), 351 deletions(-)
> create mode 100644 drivers/platform/x86/lenovo/wmi-capdata.c
> create mode 100644 drivers/platform/x86/lenovo/wmi-capdata.h
> delete mode 100644 drivers/platform/x86/lenovo/wmi-capdata01.c
> delete mode 100644 drivers/platform/x86/lenovo/wmi-capdata01.h
>
>
> base-commit: 3a8660878839faadb4f1a6dd72c3179c1df56787
> --
> 2.51.0
>
The series' intention looks good overall. The composable methods for
additional capdata interfaces is a welcome change. I have a few
comments I'll add for a couple of the patches. My apologies for the
slow review timeline, I've been on travel and wanted to test the
changes before submitting a review.
For testing I'm using my Legion Go 2. It apparently doesn't have the
FAN_TEST_DATA GUID, and the hwmon interface errors on all inputs
despite being visible. I know for the Legion Go series they use a fan
table with 10 auto_set points in the Other Method interface tied to
the platform profile, but the documentation I have says the methods
you're adding here should be available on all models, so that is a bit
strange.
dmesg output:
[ 3.995549] lenovo_wmi_cd 362A3AFE-3D96-4665-8530-96DAD5BB300E-13:
registered LENOVO_CAPABILITY_DATA_00 with 33 items
[ 4.000266] lenovo_wmi_cd 7A8F5407-CB67-4D6E-B547-39B3BE018154-9:
registered LENOVO_CAPABILITY_DATA_01 with 80 items
[ 4.005603] lenovo_wmi_other
DC2A8805-3A8C-41BA-A6F7-092E0089CD3B-3: bound
362A3AFE-3D96-4665-8530-96DAD5BB300E-13 (ops lwmi_cd_component_ops
[lenovo_wmi_capdata])
[ 4.005611] lenovo_wmi_other
DC2A8805-3A8C-41BA-A6F7-092E0089CD3B-3: bound
7A8F5407-CB67-4D6E-B547-39B3BE018154-9 (ops lwmi_cd_component_ops
[lenovo_wmi_capdata])
[ 4.005614] lenovo_wmi_other
DC2A8805-3A8C-41BA-A6F7-092E0089CD3B-3: fan capdata unavailable
Testing results:
(deck@lego2 hwmon5)$ ls
device fan1_enable fan1_input fan1_target name power subsystem uevent
(deck@lego2 hwmon5)$ cat fan1_enable
cat: fan1_enable: No data available
(1)(deck@lego2 hwmon5)$ echo 1 | sudo tee fan1_enable
[sudo] password for deck:
1
tee: fan1_enable: Input/output error
(1)(deck@lego2 hwmon5)$ echo 0 | sudo tee fan1_enable
0
tee: fan1_enable: Input/output error
(1)(deck@lego2 hwmon5)$ echo 3000 | sudo tee fan1_target
3000
tee: fan1_target: Input/output error
(1)(deck@lego2 hwmon5)$ cat fan1_input
cat: fan1_input: No such device or address
Thanks,
Derek
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH 1/6] platform/x86: Rename lenovo-wmi-capdata01 to lenovo-wmi-capdata
2025-10-19 21:04 ` [PATCH 1/6] platform/x86: Rename lenovo-wmi-capdata01 to lenovo-wmi-capdata Rong Zhang
@ 2025-10-26 4:41 ` Derek John Clark
0 siblings, 0 replies; 20+ messages in thread
From: Derek John Clark @ 2025-10-26 4:41 UTC (permalink / raw)
To: Rong Zhang
Cc: Mark Pearson, Armin Wolf, Hans de Goede, Ilpo Järvinen,
Guenter Roeck, platform-driver-x86, linux-kernel, linux-hwmon
On Sun, Oct 19, 2025 at 2:05 PM Rong Zhang <i@rong.moe> wrote:
>
> Prepare for the upcoming changes to make it suitable to retrieve
> and provide other Capability Data as well.
>
> Signed-off-by: Rong Zhang <i@rong.moe>
> ---
> 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..fb96a0f908f03 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_DATA
> 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_DATA
> 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..29014d8c1376d 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_DATA) += 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..c5e74b2bfeb36 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 <derekjohn.clark@gmail.com>
> + * - Initial implementation (formerly named lenovo-wmi-capdata01)
> */
>
> #include <linux/acpi.h>
> @@ -26,55 +29,55 @@
> #include <linux/types.h>
> #include <linux/wmi.h>
>
> -#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_CD");
>
> /**
> - * 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_cd",
> .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_CD");
>
> -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 <derekjohn.clark@gmail.com>");
> -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 <derekjohn.clark@gmail.com> */
>
> -#ifndef _LENOVO_WMI_CAPDATA01_H_
> -#define _LENOVO_WMI_CAPDATA01_H_
> +#ifndef _LENOVO_WMI_CAPDATA_H_
> +#define _LENOVO_WMI_CAPDATA_H_
>
> #include <linux/types.h>
>
> 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..c6dc1b4cff841 100644
> --- a/drivers/platform/x86/lenovo/wmi-other.c
> +++ b/drivers/platform/x86/lenovo/wmi-other.c
> @@ -34,7 +34,7 @@
> #include <linux/types.h>
> #include <linux/wmi.h>
>
> -#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_CD");
> MODULE_IMPORT_NS("LENOVO_WMI_HELPERS");
> MODULE_DEVICE_TABLE(wmi, lwmi_other_id_table);
> MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
> --
> 2.51.0
>
Looks good.
Reviewed-by: Derek J. Clark <derekjohn.clark@gmail.com>
Cheers,
Derek
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH 2/6] platform/x86: lenovo-wmi-{capdata,other}: Support multiple Capability Data
2025-10-19 21:04 ` [PATCH 2/6] platform/x86: lenovo-wmi-{capdata,other}: Support multiple Capability Data Rong Zhang
2025-10-20 1:03 ` kernel test robot
@ 2025-10-26 4:43 ` Derek John Clark
1 sibling, 0 replies; 20+ messages in thread
From: Derek John Clark @ 2025-10-26 4:43 UTC (permalink / raw)
To: Rong Zhang
Cc: Mark Pearson, Armin Wolf, Hans de Goede, Ilpo Järvinen,
Guenter Roeck, platform-driver-x86, linux-kernel, linux-hwmon
On Sun, Oct 19, 2025 at 2:05 PM Rong Zhang <i@rong.moe> wrote:
>
> The current inplementation are heavily bound to capdata01. Rewrite it so
> that it is suitable to utilize other Capability Data as well.
>
> No functional changes are introduced.
>
> Signed-off-by: Rong Zhang <i@rong.moe>
> ---
> drivers/platform/x86/lenovo/wmi-capdata.c | 208 +++++++++++++++++-----
> drivers/platform/x86/lenovo/wmi-capdata.h | 7 +-
> drivers/platform/x86/lenovo/wmi-other.c | 27 ++-
> 3 files changed, 190 insertions(+), 52 deletions(-)
>
> diff --git a/drivers/platform/x86/lenovo/wmi-capdata.c b/drivers/platform/x86/lenovo/wmi-capdata.c
> index c5e74b2bfeb36..14175fe19247e 100644
> --- a/drivers/platform/x86/lenovo/wmi-capdata.c
> +++ b/drivers/platform/x86/lenovo/wmi-capdata.c
> @@ -12,8 +12,13 @@
> *
> * Copyright (C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
> * - Initial implementation (formerly named lenovo-wmi-capdata01)
> + *
> + * Copyright (C) 2025 Rong Zhang <i@rong.moe>
> + * - Unified implementation
> */
>
> +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
> +
> #include <linux/acpi.h>
> #include <linux/cleanup.h>
> #include <linux/component.h>
> @@ -36,6 +41,25 @@
> #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] = { \
> + .guid_string = _type##_GUID, \
> + .name = #_type, \
> + .type = _type, \
> + }
> +
> +static const struct lwmi_cd_info {
> + const char *guid_string;
> + 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 +68,19 @@ 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);
> + };
> };
>
> /**
> * 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 +92,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;
> }
> @@ -76,30 +110,33 @@ static const struct component_ops lwmi_cd_component_ops = {
> };
>
> /**
> - * 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_CD");
>
> /**
> @@ -112,10 +149,21 @@ EXPORT_SYMBOL_NS_GPL(lwmi_cd01_get_data, "LENOVO_WMI_CD");
> */
> 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 +171,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 +183,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 +214,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 +224,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 +232,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 +292,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,21 +306,34 @@ 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;
> + if (info->type == 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;
> + }
> +
> + ret = component_add(&wdev->dev, &lwmi_cd_component_ops);
>
> - return component_add(&wdev->dev, &lwmi_cd_component_ops);
> +out:
> + if (ret) {
> + dev_err(&wdev->dev, "failed to register %s: %d\n",
> + info->name, ret);
> + } else {
> + dev_info(&wdev->dev, "registered %s with %u items\n",
> + info->name, priv->list->count);
> + }
> + return ret;
> }
>
> static void lwmi_cd_remove(struct wmi_device *wdev)
> @@ -267,8 +341,12 @@ static void lwmi_cd_remove(struct wmi_device *wdev)
> component_del(&wdev->dev, &lwmi_cd_component_ops);
> }
>
> +#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) },
> {}
> };
>
> @@ -284,21 +362,61 @@ static struct wmi_driver lwmi_cd_driver = {
> };
>
> /**
> - * 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.
> + * lwmi_cd_match() - Match rule for the master driver.
> + * @dev: Pointer to the capability data parent device.
> + * @data: Pointer to capability data type (enum lwmi_cd_type *) to match.
> *
> * Return: int.
> */
> -int lwmi_cd01_match(struct device *dev, void *data)
> +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. This matches all available
> + * capdata types on the machine.
> + */
> +void lwmi_cd_match_add_all(struct device *master, struct component_match **matchptr)
> {
> - return dev->driver == &lwmi_cd_driver.driver;
> + int i;
> +
> + if (WARN_ON(*matchptr))
> + return;
> +
> + for (i = 0; i < ARRAY_SIZE(lwmi_cd_table); i++) {
> + if (!lwmi_cd_table[i].guid_string ||
> + !wmi_has_guid(lwmi_cd_table[i].guid_string))
> + continue;
> +
> + component_match_add(master, matchptr, lwmi_cd_match,
> + (void *)&lwmi_cd_table[i].type);
> + if (IS_ERR(matchptr))
> + return;
> + }
> +
> + if (!*matchptr) {
> + pr_warn("a master driver requested capability data, but nothing is available\n");
> + *matchptr = ERR_PTR(-ENODEV);
> + }
> }
> -EXPORT_SYMBOL_NS_GPL(lwmi_cd01_match, "LENOVO_WMI_CD");
> +EXPORT_SYMBOL_NS_GPL(lwmi_cd_match_add_all, "LENOVO_WMI_CD");
>
> module_wmi_driver(lwmi_cd_driver);
>
> MODULE_DEVICE_TABLE(wmi, lwmi_cd_id_table);
> MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
> +MODULE_AUTHOR("Rong Zhang <i@rong.moe>");
> 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..1e5fce7836cbf 100644
> --- a/drivers/platform/x86/lenovo/wmi-capdata.h
> +++ b/drivers/platform/x86/lenovo/wmi-capdata.h
> @@ -7,6 +7,7 @@
>
> #include <linux/types.h>
>
> +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;
> +};
> +
> int lwmi_cd01_get_data(struct cd_list *list, u32 attribute_id, struct capdata01 *output);
> -int lwmi_cd01_match(struct device *dev, void *data);
> +void lwmi_cd_match_add_all(struct device *master, struct component_match **matchptr);
>
> #endif /* !_LENOVO_WMI_CAPDATA_H_ */
> diff --git a/drivers/platform/x86/lenovo/wmi-other.c b/drivers/platform/x86/lenovo/wmi-other.c
> index c6dc1b4cff841..20c6ff0be37a1 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 = { 0 };
> 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;
>
> @@ -618,6 +618,7 @@ static int lwmi_other_probe(struct wmi_device *wdev, const void *context)
> {
> struct component_match *master_match = NULL;
> struct lwmi_om_priv *priv;
> + int ret;
>
> priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
> if (!priv)
> @@ -626,12 +627,26 @@ static int lwmi_other_probe(struct wmi_device *wdev, const void *context)
> 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);
>
> - return component_master_add_with_match(&wdev->dev, &lwmi_om_master_ops,
> - master_match);
> + ret = component_master_add_with_match(&wdev->dev, &lwmi_om_master_ops,
> + master_match);
> + if (ret)
> + return ret;
> +
> + if (likely(component_master_is_bound(&wdev->dev, &lwmi_om_master_ops)))
> + return 0;
> +
> + /*
> + * The bind callbacks of both master and components were never called in
> + * this case - this driver won't work at all. Failing...
> + */
> + dev_err(&wdev->dev, "unbound master; is any component failing to be probed?");
> +
> + component_master_del(&wdev->dev, &lwmi_om_master_ops);
> + return -EXDEV;
> }
>
> static void lwmi_other_remove(struct wmi_device *wdev)
> --
> 2.51.0
>
After fixing the kernel bot warning, I have no further comments on this one.
Reviewed-by: Derek J. Clark <derekjohn.clark@gmail.com>
Cheers,
- Derek
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH 3/6] platform/x86: lenovo-wmi-capdata: Add support for Capability Data 00
2025-10-19 21:04 ` [PATCH 3/6] platform/x86: lenovo-wmi-capdata: Add support for Capability Data 00 Rong Zhang
@ 2025-10-26 4:55 ` Derek John Clark
2025-10-26 17:18 ` Rong Zhang
0 siblings, 1 reply; 20+ messages in thread
From: Derek John Clark @ 2025-10-26 4:55 UTC (permalink / raw)
To: Rong Zhang
Cc: Mark Pearson, Armin Wolf, Hans de Goede, Ilpo Järvinen,
Guenter Roeck, platform-driver-x86, linux-kernel, linux-hwmon
On Sun, Oct 19, 2025 at 2:05 PM Rong Zhang <i@rong.moe> wrote:
>
> 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.
>
> Signed-off-by: Rong Zhang <i@rong.moe>
> ---
> .../wmi/devices/lenovo-wmi-other.rst | 8 +++++++
> drivers/platform/x86/lenovo/wmi-capdata.c | 23 ++++++++++++++++++-
> drivers/platform/x86/lenovo/wmi-capdata.h | 8 +++++++
> 3 files changed, 38 insertions(+), 1 deletion(-)
>
> diff --git a/Documentation/wmi/devices/lenovo-wmi-other.rst b/Documentation/wmi/devices/lenovo-wmi-other.rst
> index d7928b8dfb4b5..adbd7943c6756 100644
> --- a/Documentation/wmi/devices/lenovo-wmi-other.rst
> +++ b/Documentation/wmi/devices/lenovo-wmi-other.rst
> @@ -31,6 +31,14 @@ under the following path:
>
> /sys/class/firmware-attributes/lenovo-wmi-other/attributes/<attribute>/
>
> +LENOVO_CAPABILITY_DATA_00
> +-------------------------
> +
> +WMI GUID ``362A3AFE-3D96-4665-8530-96DAD5BB300E``
> +
> +The LENOVO-CAPABILITD_DATA_00 interface provides information on whether the
> +device supports querying or setting fan speed.
> +
There is a lot more data provided by this interface that hasn't been
implemented yet. To avoid having to touch this too often I'd prefer if
it were formatted similarly to the 01 interface where the opening
paragraph is generic for the interface and the specific features that
have been implemented in the driver are listed below that. From
documentation, the 00 interface seems to deal with enabling or
disabling various hardware features that don't rely on the gamezone
thermal mode. I'd also be okay with specifying in the change that 01
features do rely on the gamezone thermal mode.
> LENOVO_CAPABILITY_DATA_01
> -------------------------
>
> diff --git a/drivers/platform/x86/lenovo/wmi-capdata.c b/drivers/platform/x86/lenovo/wmi-capdata.c
> index 14175fe19247e..6927de409b09d 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
> @@ -14,7 +17,7 @@
> * - Initial implementation (formerly named lenovo-wmi-capdata01)
> *
> * Copyright (C) 2025 Rong Zhang <i@rong.moe>
> - * - Unified implementation
> + * - Unified implementation for Capability Data 00 and 01
> */
This might be a bit verbose considering the changes are all part of
the same series.
Thanks,
Derek
>
> #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
> @@ -36,12 +39,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,
> };
>
> @@ -57,6 +62,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),
> };
>
> @@ -72,6 +78,7 @@ struct cd_list {
> u8 count;
>
> union {
> + DECLARE_FLEX_ARRAY(struct capdata00, cd00);
> DECLARE_FLEX_ARRAY(struct capdata01, cd01);
> };
> };
> @@ -95,6 +102,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;
> @@ -136,6 +146,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_CD");
> +
> DEF_LWMI_CDXX_GET_DATA(cd01, LENOVO_CAPABILITY_DATA_01, struct capdata01);
> EXPORT_SYMBOL_NS_GPL(lwmi_cd01_get_data, "LENOVO_WMI_CD");
>
> @@ -154,6 +167,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]);
> @@ -199,6 +216,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;
> @@ -346,6 +366,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 1e5fce7836cbf..a6f0cb006e745 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,9 +27,11 @@ struct capdata01 {
> };
>
> struct lwmi_cd_binder {
> + struct cd_list *cd00_list;
> struct cd_list *cd01_list;
> };
>
> +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);
> void lwmi_cd_match_add_all(struct device *master, struct component_match **matchptr);
>
> --
> 2.51.0
>
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH 4/6] platform/x86: lenovo-wmi-other: Add HWMON for fan speed RPM
2025-10-19 21:04 ` [PATCH 4/6] platform/x86: lenovo-wmi-other: Add HWMON for fan speed RPM Rong Zhang
@ 2025-10-26 5:23 ` Derek John Clark
2025-10-26 19:42 ` Rong Zhang
0 siblings, 1 reply; 20+ messages in thread
From: Derek John Clark @ 2025-10-26 5:23 UTC (permalink / raw)
To: Rong Zhang
Cc: Mark Pearson, Armin Wolf, Hans de Goede, Ilpo Järvinen,
Guenter Roeck, platform-driver-x86, linux-kernel, linux-hwmon
On Sun, Oct 19, 2025 at 2:05 PM Rong Zhang <i@rong.moe> wrote:
>
> Register an HWMON device for fan spped RPM according to Capability Data
> 00 provided by lenovo-wmi-capdata. The corresponding HWMON nodes are:
>
> - fanX_enable: enable/disable the fan (tunable)
> - fanX_input: current RPM
> - fanX_target: target RPM (tunable)
>
> Signed-off-by: Rong Zhang <i@rong.moe>
> ---
> .../wmi/devices/lenovo-wmi-other.rst | 5 +
> drivers/platform/x86/lenovo/Kconfig | 1 +
> drivers/platform/x86/lenovo/wmi-other.c | 324 +++++++++++++++++-
> 3 files changed, 317 insertions(+), 13 deletions(-)
>
> diff --git a/Documentation/wmi/devices/lenovo-wmi-other.rst b/Documentation/wmi/devices/lenovo-wmi-other.rst
> index adbd7943c6756..cb6a9bfe5a79e 100644
> --- a/Documentation/wmi/devices/lenovo-wmi-other.rst
> +++ b/Documentation/wmi/devices/lenovo-wmi-other.rst
> @@ -31,6 +31,11 @@ under the following path:
>
> /sys/class/firmware-attributes/lenovo-wmi-other/attributes/<attribute>/
>
> +Besides, this driver also exports fan speed RPM to HWMON:
> + - fanX_enable: enable/disable the fan (tunable)
> + - fanX_input: current RPM
> + - fanX_target: target RPM (tunable)
> +
> LENOVO_CAPABILITY_DATA_00
> -------------------------
>
> diff --git a/drivers/platform/x86/lenovo/Kconfig b/drivers/platform/x86/lenovo/Kconfig
> index fb96a0f908f03..be9af04511462 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_DATA
> select LENOVO_WMI_EVENTS
> diff --git a/drivers/platform/x86/lenovo/wmi-other.c b/drivers/platform/x86/lenovo/wmi-other.c
> index 20c6ff0be37a1..f8771ed3c6642 100644
> --- a/drivers/platform/x86/lenovo/wmi-other.c
> +++ b/drivers/platform/x86/lenovo/wmi-other.c
> @@ -14,7 +14,15 @@
> * These attributes typically don't fit anywhere else in the sysfs and are set
> * in Windows using one of Lenovo's multiple user applications.
> *
> + * Besides, this driver also exports tunable fan speed RPM to HWMON.
> + *
> * Copyright (C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
> + * - fw_attributes
> + * - binding to Capability Data 01
> + *
> + * Copyright (C) 2025 Rong Zhang <i@rong.moe>
> + * - HWMON
> + * - binding to Capability Data 00
> */
>
> #include <linux/acpi.h>
> @@ -25,6 +33,7 @@
> #include <linux/device.h>
> #include <linux/export.h>
> #include <linux/gfp_types.h>
> +#include <linux/hwmon.h>
> #include <linux/idr.h>
> #include <linux/kdev_t.h>
> #include <linux/kobject.h>
> @@ -43,12 +52,20 @@
>
> #define LENOVO_OTHER_MODE_GUID "DC2A8805-3A8C-41BA-A6F7-092E0089CD3B"
>
> +#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_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
>
> +#define LWMI_DEVICE_ID_FAN 0x04
> +
> +#define LWMI_FEATURE_ID_FAN_RPM 0x03
> +
> #define LWMI_TYPE_ID_NONE 0x00
>
> #define LWMI_FEATURE_VALUE_GET 17
> @@ -59,7 +76,18 @@
> #define LWMI_ATTR_MODE_ID_MASK GENMASK(15, 8)
> #define LWMI_ATTR_TYPE_ID_MASK GENMASK(7, 0)
>
> +/* Only fan1 and fan2 are present on supported devices. */
> +#define LWMI_FAN_ID_BASE 1
> +#define LWMI_FAN_NR 2
> +#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_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);
> @@ -76,15 +104,256 @@ 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 fan_info {
> + u32 supported;
> + long target;
> + } fan_info[LWMI_FAN_NR];
> };
>
> +/* ======== HWMON (component: lenovo-wmi-capdata 00) ======== */
> +
> +/**
> + * 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 speed.
> + *
> + * 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;
> + else if (retval != 1)
> + return -EIO;
> +
> + return 0;
> +}
> +
> +/**
> + * 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 a 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 r = false, w = false;
> +
> + if (type == hwmon_fan) {
> + switch (attr) {
> + case hwmon_fan_enable:
> + case hwmon_fan_target:
> + r = w = priv->fan_info[channel].supported & LWMI_SUPP_MAY_SET;
> + break;
> + case hwmon_fan_input:
> + r = priv->fan_info[channel].supported & LWMI_SUPP_MAY_GET;
> + break;
> + }
> + }
> +
There is another method in capdata00 that could be useful here
Fan Test For Diagnostic Software
uint32 IDs //0x04050000
uint32 Capability //9:by project
bit 3: 0: not support LENOVO_FAN_TEST_DATA, 1 support LENOVO_FAN_TEST_DATA
bit 2: 0: not support SetFeatureValue(), 1: support SetFeatureValue()
bit 1: 0: not support GetFeatureValue(), 1: support GetFeatureValue()
bit 0: 0: not support fan test for diagnostic software, 1: support an
test for diagnostic software
I'll discuss below, but it seems like knowing min/max is a good idea
before making the sysfs visible.
> + if (!r)
> + return 0;
> +
> + return w ? 0644 : 0444;
> +}
> +
> +/**
> + * 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) {
> + 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_enable:
> + case hwmon_fan_target:
> + /* -ENODATA before first set. */
Why not query the interface in realtime to know the system state? That
would avoid this problem.
> + err = (int)priv->fan_info[channel].target;
> + if (err < 0)
> + return err;
> +
> + if (attr == hwmon_fan_enable)
> + *val = priv->fan_info[channel].target != 1;
> + else
> + *val = priv->fan_info[channel].target;
> + 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;
> + int err;
> +
> + if (type == hwmon_fan) {
> + switch (attr) {
> + case hwmon_fan_enable:
> + case hwmon_fan_target:
> + if (attr == hwmon_fan_enable) {
> + if (val == 0)
> + raw = 1; /* stop */
> + else if (val == 1)
> + raw = 0; /* auto */
> + else
> + return -EINVAL;
> + } else {
> + /*
> + * val > U16_MAX seems safe but meaningless.
> + */
> + if (val < 0 || val > U16_MAX)
I think it might be prudent to only permit these settings if fan speed
params can't be known. Pragmatically it ensures userspace is aware of
the range of the interface. Per the documentation it should be "safe"
as is, but setting below the min fan speed will return it to "auto"
mode and the hwmon will be out of sync. Anything above should just be
set to the max, if the BIOS is working properly.
IMO the fan speed data is essential to ensuring the hwmon interface is
usable and synced. I'd move that patch before this one in the series
and make the 0x04050000 method reporting IsSupported required for any
of the attributes to be visible, with value checks against the min/max
when setting a given fan.
> + return -EINVAL;
> + raw = val;
> + }
> +
> + err = lwmi_om_fan_get_set(priv, channel, &raw, true);
> + if (err)
> + return err;
> +
> + priv->fan_info[channel].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_ENABLE | HWMON_F_INPUT | HWMON_F_TARGET,
> + HWMON_F_ENABLE | HWMON_F_INPUT | HWMON_F_TARGET),
> + 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
> + * @priv: Driver private data
> + *
> + * Initializes capability data and registers the HWMON device.
> + *
> + * Return: 0 on success, or an error code.
> + */
> +static int lwmi_om_hwmon_add(struct lwmi_om_priv *priv)
> +{
> + struct capdata00 capdata00;
> + int i, err;
> +
> + for (i = 0; i < LWMI_FAN_NR; i++) {
There is an assumption here that isn't accurate. Each fan ID
corresponds to a specific fan functionality. 01 is CPU Fan, 02 is GPU
Fan, 02 is GPU Power Fan, and 04 is System Fan. Not every fan needs to
exist, so an ID table might look like this (example from docs):
illustrate:
UINT32 NumOfFans = 3;
NoteBook:
1: CPU Fan ID
2: GPU Fan ID
3: GPU Power Fan ID
4: System Fan ID
UINT32 FanId [1,2,4]
UINT32 FanMaxSpeed[5400, 5400, 9000];
UINT32 FanMinSpeed[1900, 1900, 2000];
In such a case, "count" would be 3, but the idx should be 4 going to
the hardware because the GPU Power Fan isn't present, while the case
fan is.
Thanks,
Derek
> + err = lwmi_cd00_get_data(priv->cd00_list, LWMI_ATTR_ID_FAN_RPM(i),
> + &capdata00);
> + if (err)
> + continue;
> +
> + priv->fan_info[i] = (struct fan_info) {
> + .supported = capdata00.supported,
> + .target = -ENODATA,
> + };
> + }
> +
> + priv->hwmon_dev = hwmon_device_register_with_info(&priv->wdev->dev, LWMI_OM_HWMON_NAME,
> + priv, &lwmi_om_hwmon_chip_info, NULL);
> +
> + return PTR_ERR_OR_ZERO(priv->hwmon_dev);
> +}
> +
> +/**
> + * lwmi_om_hwmon_remove() - Unregister HWMON device
> + * @priv: Driver private data
> + *
> + * Unregisters the HWMON device and resets all fans to automatic mode.
> + * Ensures hardware doesn't remain in manual mode after driver removal.
> + */
> +static void lwmi_om_hwmon_remove(struct lwmi_om_priv *priv)
> +{
> + hwmon_device_unregister(priv->hwmon_dev);
> +}
> +
> +/* ======== fw_attributes (component: lenovo-wmi-capdata 01) ======== */
> +
> struct tunable_attr_01 {
> struct capdata01 *capdata;
> struct device *dev;
> @@ -564,15 +833,17 @@ 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. 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, register the
> + * HWMON device and all firmware attribute groups.
> *
> * Return: 0 on success, or an error code.
> */
> @@ -586,26 +857,47 @@ static int lwmi_om_master_bind(struct device *dev)
> if (ret)
> return ret;
>
> - priv->cd01_list = binder.cd01_list;
> - if (!priv->cd01_list)
> + if (!binder.cd00_list && !binder.cd01_list)
> return -ENODEV;
>
> - return lwmi_om_fw_attr_add(priv);
> + priv->cd00_list = binder.cd00_list;
> + if (priv->cd00_list) {
> + ret = lwmi_om_hwmon_add(priv);
> + if (ret)
> + return ret;
> + }
> +
> + priv->cd01_list = binder.cd01_list;
> + if (priv->cd01_list) {
> + ret = lwmi_om_fw_attr_add(priv);
> + if (ret) {
> + if (priv->cd00_list)
> + lwmi_om_hwmon_remove(priv);
> + return ret;
> + }
> + }
> +
> + return 0;
> }
>
> /**
> * 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 the HWMON device and all capability data attribute groups. Then
> + * call component_unbind_all to unbind the lenovo-wmi-capdata driver 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);
> + if (priv->cd00_list)
> + lwmi_om_hwmon_remove(priv);
> +
> + if (priv->cd01_list)
> + lwmi_om_fw_attr_remove(priv);
> +
> component_unbind_all(dev, NULL);
> }
>
> @@ -624,6 +916,9 @@ 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);
>
> @@ -654,7 +949,9 @@ 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);
> +
> + if (priv->ida_id >= 0)
> + ida_free(&lwmi_om_ida, priv->ida_id);
> }
>
> static const struct wmi_device_id lwmi_other_id_table[] = {
> @@ -679,5 +976,6 @@ MODULE_IMPORT_NS("LENOVO_WMI_CD");
> MODULE_IMPORT_NS("LENOVO_WMI_HELPERS");
> MODULE_DEVICE_TABLE(wmi, lwmi_other_id_table);
> MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
> +MODULE_AUTHOR("Rong Zhang <i@rong.moe>");
> MODULE_DESCRIPTION("Lenovo Other Mode WMI Driver");
> MODULE_LICENSE("GPL");
> --
> 2.51.0
>
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH 0/6] platform/x86: lenovo-wmi-{capdata,other}: Add HWMON for fan speed
2025-10-26 4:39 ` [PATCH 0/6] platform/x86: lenovo-wmi-{capdata,other}: Add HWMON for fan speed Derek John Clark
@ 2025-10-26 17:11 ` Rong Zhang
2025-10-26 22:59 ` Armin Wolf
0 siblings, 1 reply; 20+ messages in thread
From: Rong Zhang @ 2025-10-26 17:11 UTC (permalink / raw)
To: Derek John Clark
Cc: Mark Pearson, Armin Wolf, Hans de Goede, Ilpo Järvinen,
Guenter Roeck, platform-driver-x86, linux-kernel, linux-hwmon
Hi Derek,
On Sat, 2025-10-25 at 21:39 -0700, Derek John Clark wrote:
> On Sun, Oct 19, 2025 at 2:05 PM Rong Zhang <i@rong.moe> wrote:
> >
> > Lenovo WMI Other Mode interface also supports querying or setting fan
> > speed RPM. This capability is decribed by LENOVO_CAPABILITY_DATA_00.
> > Besides, LENOVO_FAN_TEST_DATA provides reference data for self-test of
> > cooling fans, including minimum and maximum fan speed RPM.
> >
> > This patchset turns lenovo-wmi-capdata01 into a unified driver (now
> > named lenovo-wmi-capdata) for LENOVO_CAPABILITY_DATA_{00,01} and
> > LENOVO_FAN_TEST_DATA; then adds HWMON support for lenovo-wmi-other:
> >
> > - fanX_enable: enable/disable the fan (tunable)
> > - fanX_input: current RPM
> > - fanX_max: maximum RPM
> > - fanX_min: minimum RPM
> > - fanX_target: target RPM (tunable)
> >
> > This implementation doesn't require all capability data to be available,
> > and is capable to expose interfaces accordingly:
> >
> > - Having LENOVO_CAPABILITY_DATA_00: exposes fanX_{enable,input,target}
> > - Having LENOVO_CAPABILITY_DATA_01: exposes firmware_attributes
> > - Having LENOVO_FAN_TEST_DATA: exposes fanX_{max,min}
> >
> > Rong Zhang (6):
> > platform/x86: Rename lenovo-wmi-capdata01 to lenovo-wmi-capdata
> > platform/x86: lenovo-wmi-{capdata,other}: Support multiple Capability
> > Data
> > platform/x86: lenovo-wmi-capdata: Add support for Capability Data 00
> > platform/x86: lenovo-wmi-other: Add HWMON for fan speed RPM
> > platform/x86: lenovo-wmi-capdata: Add support for Fan Test Data
> > platform/x86: lenovo-wmi-other: Report min/max RPM and hide dummy fans
> >
> > .../wmi/devices/lenovo-wmi-other.rst | 32 +
> > drivers/platform/x86/lenovo/Kconfig | 5 +-
> > drivers/platform/x86/lenovo/Makefile | 2 +-
> > drivers/platform/x86/lenovo/wmi-capdata.c | 545 ++++++++++++++++++
> > drivers/platform/x86/lenovo/wmi-capdata.h | 46 ++
> > drivers/platform/x86/lenovo/wmi-capdata01.c | 302 ----------
> > drivers/platform/x86/lenovo/wmi-capdata01.h | 25 -
> > drivers/platform/x86/lenovo/wmi-other.c | 422 +++++++++++++-
> > 8 files changed, 1028 insertions(+), 351 deletions(-)
> > create mode 100644 drivers/platform/x86/lenovo/wmi-capdata.c
> > create mode 100644 drivers/platform/x86/lenovo/wmi-capdata.h
> > delete mode 100644 drivers/platform/x86/lenovo/wmi-capdata01.c
> > delete mode 100644 drivers/platform/x86/lenovo/wmi-capdata01.h
> >
> >
> > base-commit: 3a8660878839faadb4f1a6dd72c3179c1df56787
> > --
> > 2.51.0
> >
>
> The series' intention looks good overall. The composable methods for
> additional capdata interfaces is a welcome change. I have a few
> comments I'll add for a couple of the patches. My apologies for the
> slow review timeline, I've been on travel and wanted to test the
> changes before submitting a review.
Thanks for you review and testing! Hope you have/had a nice trip ;)
> For testing I'm using my Legion Go 2. It apparently doesn't have the
> FAN_TEST_DATA GUID, and the hwmon interface errors on all inputs
> despite being visible. I know for the Legion Go series they use a fan
> table with 10 auto_set points in the Other Method interface tied to
> the platform profile, but the documentation I have says the methods
> you're adding here should be available on all models, so that is a bit
> strange.
Yeah, that sounds weird.
As for the fan table on your device, did you mean
LENOVO_FAN_TABLE_DATA/LENOVO_FAN_METHOD? My device doesn't use a fan
table, the corresponding ACPI methods are dummy (see below).
My device is ThinkBook 14 G7+ ASP (forgot to mention when submitting,
sorry). I don't have any documentation and I finished the patchset
according to the MOF as well as the decompiled ASL code of its ACPI
tables. The information from the documentation (including those in your
following replies) is very useful, thanks for that!
As it's branded as ThinkBook, most GAMEZONE/WMI_OTHER interfaces on my
device may differ from Legion devices. To summerize:
- LENOVO_GAMEZONE_DATA: dummy ACPI method.
- LENOVO_GAMEZONE_CPU_OC_DATA: presents in MOF; missing ACPI method.
- LENOVO_GAMEZONE_GPU_OC_DATA: dummy ACPI method.
- LENOVO_CAPABILITY_DATA_00: works fine.
- LENOVO_CAPABILITY_DATA_01: dummy ACPI method, data still presents
(\_SB.GZFD.CD01).
- LENOVO_FAN_TEST_DATA: works fine.
- LENOVO_FAN_TABLE_DATA: dummy ACPI method.
- LENOVO_FAN_METHOD: dummy ACPI method.
- LENOVO_OTHER_METHOD:
* Despite missing LENOVO_CAPABILITY_DATA_01, SPPT/SPL/FPPT can still
be get/set. There is also CHTC (FEATURE_ID=4, get/set) which I am
not sure what it means.
* FAN1/2: get method reads data from the EC; set method for FAN1
updates the EC, for FAN2 is dummy (no-op, returns 0).
> dmesg output:
> [ 3.995549] lenovo_wmi_cd 362A3AFE-3D96-4665-8530-96DAD5BB300E-13:
> registered LENOVO_CAPABILITY_DATA_00 with 33 items
> [ 4.000266] lenovo_wmi_cd 7A8F5407-CB67-4D6E-B547-39B3BE018154-9:
> registered LENOVO_CAPABILITY_DATA_01 with 80 items
> [ 4.005603] lenovo_wmi_other
> DC2A8805-3A8C-41BA-A6F7-092E0089CD3B-3: bound
> 362A3AFE-3D96-4665-8530-96DAD5BB300E-13 (ops lwmi_cd_component_ops
> [lenovo_wmi_capdata])
> [ 4.005611] lenovo_wmi_other
> DC2A8805-3A8C-41BA-A6F7-092E0089CD3B-3: bound
> 7A8F5407-CB67-4D6E-B547-39B3BE018154-9 (ops lwmi_cd_component_ops
> [lenovo_wmi_capdata])
> [ 4.005614] lenovo_wmi_other
> DC2A8805-3A8C-41BA-A6F7-092E0089CD3B-3: fan capdata unavailable
>
> Testing results:
> (deck@lego2 hwmon5)$ ls
> device fan1_enable fan1_input fan1_target name power subsystem uevent
> (deck@lego2 hwmon5)$ cat fan1_enable
> cat: fan1_enable: No data available
> (1)(deck@lego2 hwmon5)$ echo 1 | sudo tee fan1_enable
> [sudo] password for deck:
> 1
> tee: fan1_enable: Input/output error
> (1)(deck@lego2 hwmon5)$ echo 0 | sudo tee fan1_enable
> 0
> tee: fan1_enable: Input/output error
> (1)(deck@lego2 hwmon5)$ echo 3000 | sudo tee fan1_target
> 3000
> tee: fan1_target: Input/output error
-EIO was returned when the set method didn't return 1 (as long as
lwmi_dev_evaluate_int() didn't return this due to ACPI_FAILURE).
Despite the return value, did the fan speed change after writing?
Otherwise the method might be dummy and LENOVO_CAPABILITY_DATA_00
simply returned mistaken data :(
> (1)(deck@lego2 hwmon5)$ cat fan1_input
> cat: fan1_input: No such device or address
-ENXIO was returned by lwmi_dev_evaluate_int() as the return value was
not an integer. It's really weird. Could you check the type of the
return value? Some clues may also lie in the ASL code of the ACPI
method.
> Thanks,
> Derek
Thanks,
Rong
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH 3/6] platform/x86: lenovo-wmi-capdata: Add support for Capability Data 00
2025-10-26 4:55 ` Derek John Clark
@ 2025-10-26 17:18 ` Rong Zhang
0 siblings, 0 replies; 20+ messages in thread
From: Rong Zhang @ 2025-10-26 17:18 UTC (permalink / raw)
To: Derek John Clark
Cc: Mark Pearson, Armin Wolf, Hans de Goede, Ilpo Järvinen,
Guenter Roeck, platform-driver-x86, linux-kernel, linux-hwmon
Hi Derek,
On Sat, 2025-10-25 at 21:55 -0700, Derek John Clark wrote:
> On Sun, Oct 19, 2025 at 2:05 PM Rong Zhang <i@rong.moe> wrote:
> >
> > 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.
> >
> > Signed-off-by: Rong Zhang <i@rong.moe>
> > ---
> > .../wmi/devices/lenovo-wmi-other.rst | 8 +++++++
> > drivers/platform/x86/lenovo/wmi-capdata.c | 23 ++++++++++++++++++-
> > drivers/platform/x86/lenovo/wmi-capdata.h | 8 +++++++
> > 3 files changed, 38 insertions(+), 1 deletion(-)
> >
> > diff --git a/Documentation/wmi/devices/lenovo-wmi-other.rst b/Documentation/wmi/devices/lenovo-wmi-other.rst
> > index d7928b8dfb4b5..adbd7943c6756 100644
> > --- a/Documentation/wmi/devices/lenovo-wmi-other.rst
> > +++ b/Documentation/wmi/devices/lenovo-wmi-other.rst
> > @@ -31,6 +31,14 @@ under the following path:
> >
> > /sys/class/firmware-attributes/lenovo-wmi-other/attributes/<attribute>/
> >
> > +LENOVO_CAPABILITY_DATA_00
> > +-------------------------
> > +
> > +WMI GUID ``362A3AFE-3D96-4665-8530-96DAD5BB300E``
> > +
> > +The LENOVO-CAPABILITD_DATA_00 interface provides information on whether the
> > +device supports querying or setting fan speed.
> > +
>
> There is a lot more data provided by this interface that hasn't been
> implemented yet. To avoid having to touch this too often I'd prefer if
> it were formatted similarly to the 01 interface where the opening
> paragraph is generic for the interface and the specific features that
> have been implemented in the driver are listed below that. From
> documentation, the 00 interface seems to deal with enabling or
> disabling various hardware features that don't rely on the gamezone
> thermal mode. I'd also be okay with specifying in the change that 01
> features do rely on the gamezone thermal mode.
Makes sense. Will reword it in v2. Thanks for the suggestion and
information.
> > LENOVO_CAPABILITY_DATA_01
> > -------------------------
> >
> > diff --git a/drivers/platform/x86/lenovo/wmi-capdata.c b/drivers/platform/x86/lenovo/wmi-capdata.c
> > index 14175fe19247e..6927de409b09d 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
> > @@ -14,7 +17,7 @@
> > * - Initial implementation (formerly named lenovo-wmi-capdata01)
> > *
> > * Copyright (C) 2025 Rong Zhang <i@rong.moe>
> > - * - Unified implementation
> > + * - Unified implementation for Capability Data 00 and 01
> > */
>
> This might be a bit verbose considering the changes are all part of
> the same series.
ACK. Will drop "for ..." in v2.
> Thanks,
> Derek
Thanks,
Rong
> >
> > #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
> > @@ -36,12 +39,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,
> > };
> >
> > @@ -57,6 +62,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),
> > };
> >
> > @@ -72,6 +78,7 @@ struct cd_list {
> > u8 count;
> >
> > union {
> > + DECLARE_FLEX_ARRAY(struct capdata00, cd00);
> > DECLARE_FLEX_ARRAY(struct capdata01, cd01);
> > };
> > };
> > @@ -95,6 +102,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;
> > @@ -136,6 +146,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_CD");
> > +
> > DEF_LWMI_CDXX_GET_DATA(cd01, LENOVO_CAPABILITY_DATA_01, struct capdata01);
> > EXPORT_SYMBOL_NS_GPL(lwmi_cd01_get_data, "LENOVO_WMI_CD");
> >
> > @@ -154,6 +167,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]);
> > @@ -199,6 +216,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;
> > @@ -346,6 +366,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 1e5fce7836cbf..a6f0cb006e745 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,9 +27,11 @@ struct capdata01 {
> > };
> >
> > struct lwmi_cd_binder {
> > + struct cd_list *cd00_list;
> > struct cd_list *cd01_list;
> > };
> >
> > +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);
> > void lwmi_cd_match_add_all(struct device *master, struct component_match **matchptr);
> >
> > --
> > 2.51.0
> >
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH 4/6] platform/x86: lenovo-wmi-other: Add HWMON for fan speed RPM
2025-10-26 5:23 ` Derek John Clark
@ 2025-10-26 19:42 ` Rong Zhang
2025-10-26 20:19 ` Derek J. Clark
2025-10-26 23:04 ` Armin Wolf
0 siblings, 2 replies; 20+ messages in thread
From: Rong Zhang @ 2025-10-26 19:42 UTC (permalink / raw)
To: Derek John Clark
Cc: Mark Pearson, Armin Wolf, Hans de Goede, Ilpo Järvinen,
Guenter Roeck, platform-driver-x86, linux-kernel, linux-hwmon
Hi Derek,
On Sat, 2025-10-25 at 22:23 -0700, Derek John Clark wrote:
> On Sun, Oct 19, 2025 at 2:05 PM Rong Zhang <i@rong.moe> wrote:
> >
> > Register an HWMON device for fan spped RPM according to Capability Data
> > 00 provided by lenovo-wmi-capdata. The corresponding HWMON nodes are:
> >
> > - fanX_enable: enable/disable the fan (tunable)
> > - fanX_input: current RPM
> > - fanX_target: target RPM (tunable)
> >
> > Signed-off-by: Rong Zhang <i@rong.moe>
> > ---
> > .../wmi/devices/lenovo-wmi-other.rst | 5 +
> > drivers/platform/x86/lenovo/Kconfig | 1 +
> > drivers/platform/x86/lenovo/wmi-other.c | 324 +++++++++++++++++-
> > 3 files changed, 317 insertions(+), 13 deletions(-)
> >
> > diff --git a/Documentation/wmi/devices/lenovo-wmi-other.rst b/Documentation/wmi/devices/lenovo-wmi-other.rst
> > index adbd7943c6756..cb6a9bfe5a79e 100644
> > --- a/Documentation/wmi/devices/lenovo-wmi-other.rst
> > +++ b/Documentation/wmi/devices/lenovo-wmi-other.rst
> > @@ -31,6 +31,11 @@ under the following path:
> >
> > /sys/class/firmware-attributes/lenovo-wmi-other/attributes/<attribute>/
> >
> > +Besides, this driver also exports fan speed RPM to HWMON:
> > + - fanX_enable: enable/disable the fan (tunable)
> > + - fanX_input: current RPM
> > + - fanX_target: target RPM (tunable)
> > +
> > LENOVO_CAPABILITY_DATA_00
> > -------------------------
> >
> > diff --git a/drivers/platform/x86/lenovo/Kconfig b/drivers/platform/x86/lenovo/Kconfig
> > index fb96a0f908f03..be9af04511462 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_DATA
> > select LENOVO_WMI_EVENTS
> > diff --git a/drivers/platform/x86/lenovo/wmi-other.c b/drivers/platform/x86/lenovo/wmi-other.c
> > index 20c6ff0be37a1..f8771ed3c6642 100644
> > --- a/drivers/platform/x86/lenovo/wmi-other.c
> > +++ b/drivers/platform/x86/lenovo/wmi-other.c
> > @@ -14,7 +14,15 @@
> > * These attributes typically don't fit anywhere else in the sysfs and are set
> > * in Windows using one of Lenovo's multiple user applications.
> > *
> > + * Besides, this driver also exports tunable fan speed RPM to HWMON.
> > + *
> > * Copyright (C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
> > + * - fw_attributes
> > + * - binding to Capability Data 01
> > + *
> > + * Copyright (C) 2025 Rong Zhang <i@rong.moe>
> > + * - HWMON
> > + * - binding to Capability Data 00
> > */
> >
> > #include <linux/acpi.h>
> > @@ -25,6 +33,7 @@
> > #include <linux/device.h>
> > #include <linux/export.h>
> > #include <linux/gfp_types.h>
> > +#include <linux/hwmon.h>
> > #include <linux/idr.h>
> > #include <linux/kdev_t.h>
> > #include <linux/kobject.h>
> > @@ -43,12 +52,20 @@
> >
> > #define LENOVO_OTHER_MODE_GUID "DC2A8805-3A8C-41BA-A6F7-092E0089CD3B"
> >
> > +#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_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
> >
> > +#define LWMI_DEVICE_ID_FAN 0x04
> > +
> > +#define LWMI_FEATURE_ID_FAN_RPM 0x03
> > +
> > #define LWMI_TYPE_ID_NONE 0x00
> >
> > #define LWMI_FEATURE_VALUE_GET 17
> > @@ -59,7 +76,18 @@
> > #define LWMI_ATTR_MODE_ID_MASK GENMASK(15, 8)
> > #define LWMI_ATTR_TYPE_ID_MASK GENMASK(7, 0)
> >
> > +/* Only fan1 and fan2 are present on supported devices. */
> > +#define LWMI_FAN_ID_BASE 1
> > +#define LWMI_FAN_NR 2
> > +#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_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);
> > @@ -76,15 +104,256 @@ 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 fan_info {
> > + u32 supported;
> > + long target;
> > + } fan_info[LWMI_FAN_NR];
> > };
> >
> > +/* ======== HWMON (component: lenovo-wmi-capdata 00) ======== */
> > +
> > +/**
> > + * 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 speed.
> > + *
> > + * 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;
> > + else if (retval != 1)
> > + return -EIO;
> > +
> > + return 0;
> > +}
> > +
> > +/**
> > + * 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 a 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 r = false, w = false;
> > +
> > + if (type == hwmon_fan) {
> > + switch (attr) {
> > + case hwmon_fan_enable:
> > + case hwmon_fan_target:
> > + r = w = priv->fan_info[channel].supported & LWMI_SUPP_MAY_SET;
> > + break;
> > + case hwmon_fan_input:
> > + r = priv->fan_info[channel].supported & LWMI_SUPP_MAY_GET;
> > + break;
> > + }
> > + }
> > +
>
> There is another method in capdata00 that could be useful here
>
> Fan Test For Diagnostic Software
> uint32 IDs //0x04050000
> uint32 Capability //9:by project
> bit 3: 0: not support LENOVO_FAN_TEST_DATA, 1 support LENOVO_FAN_TEST_DATA
> bit 2: 0: not support SetFeatureValue(), 1: support SetFeatureValue()
> bit 1: 0: not support GetFeatureValue(), 1: support GetFeatureValue()
> bit 0: 0: not support fan test for diagnostic software, 1: support an
> test for diagnostic software
The information is useful, thanks for that!
A quick look at the decompiled ASL code of my device's ACPI tables:
Package (0x03)
{
0x04050000,
0x07,
One
},
I've confirmed that the corresponding ACPI method didn't modify the
return value of 0x04050000.
0x07 means my device supports this interface, GetFeatureValue() and
SetFeatureValue(); but does not support LENOVO_FAN_TEST_DATA. Is BIT(3)
only defined in some models (but not on my device)? The data returned
by LENOVO_FAN_TEST_DATA seems correct and is probably the min/max auto
points.
My device didn't implement {Get,Set}FeatureValue(0x04050000). What will
it do when it's implemented?
> I'll discuss below, but it seems like knowing min/max is a good idea
> before making the sysfs visible.
>
> > + if (!r)
> > + return 0;
> > +
> > + return w ? 0644 : 0444;
> > +}
> > +
> > +/**
> > + * 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) {
> > + 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_enable:
> > + case hwmon_fan_target:
> > + /* -ENODATA before first set. */
>
> Why not query the interface in realtime to know the system state? That
> would avoid this problem.
My implementation follows the approach of corsair-cpro
(drivers/hwmon/corsair-cpro.c). hwmon_fan_target is about "how much RPM
*should* the fan reach?", while hwmon_fan_input is about "how much RPM
does the fan *really* reach?"
Calling SetFeatureValue(0x040300*) sets the former, while calling
GetFeatureValue(0x040300*) queries the latter. IIUC, using
GetFeatureValue(0x040300*) for hwmon_fan_target violates the
definition, especially when the fan is in auto mode.
> > + err = (int)priv->fan_info[channel].target;
> > + if (err < 0)
> > + return err;
> > +
> > + if (attr == hwmon_fan_enable)
> > + *val = priv->fan_info[channel].target != 1;
> > + else
> > + *val = priv->fan_info[channel].target;
> > + 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;
> > + int err;
> > +
> > + if (type == hwmon_fan) {
> > + switch (attr) {
> > + case hwmon_fan_enable:
> > + case hwmon_fan_target:
> > + if (attr == hwmon_fan_enable) {
> > + if (val == 0)
> > + raw = 1; /* stop */
> > + else if (val == 1)
> > + raw = 0; /* auto */
> > + else
> > + return -EINVAL;
> > + } else {
> > + /*
> > + * val > U16_MAX seems safe but meaningless.
> > + */
> > + if (val < 0 || val > U16_MAX)
>
> I think it might be prudent to only permit these settings if fan speed
> params can't be known. Pragmatically it ensures userspace is aware of
> the range of the interface. Per the documentation it should be "safe"
> as is, but setting below the min fan speed will return it to "auto"
> mode and the hwmon will be out of sync. Anything above should just be
> set to the max, if the BIOS is working properly.
On my device, the data returned by LENOVO_FAN_TEST_DATA seems to be the
min/max auto points. The fan can spin much slower/faster than the
min/max RPM. Setting below the "real" min RPM stops the fan - setting 0
is the only way to return it to auto mode.
# grep . fan1_*
grep: fan1_enable: No data available
fan1_input:2200
fan1_max:5000
fan1_min:2200
grep: fan1_target: No data available
# echo 800 >fan1_target
# cat fan1_input
800
# echo 700 >fan1_target
# cat fan1_input
700
# echo 10000 >fan1_target
# cat fan1_input
6500
# echo 100 >fan1_target
# cat fan1_input
0
# taskset -c 2 stress-ng -c 1 >/dev/null &
[1] 37967
# cat fan1_input
0
# echo 0 >fan1_target
# cat fan1_input
2200
# cat fan1_input
2600
> IMO the fan speed data is essential to ensuring the hwmon interface is
> usable and synced. I'd move that patch before this one in the series
> and make the 0x04050000 method reporting IsSupported required for any
> of the attributes to be visible, with value checks against the min/max
> when setting a given fan.
I agree that setting the RPM too low/high may results in HWMON being
out of sync, which is usually not desired. Will do these in v2.
My extra idea:
- drop the parameter "ignore_fan_cap".
- new parameter "expose_all_fans": does not hide fans when missing from
LENOVO_FAN_TEST_DATA or when 0x04050000 reports unsupported.
0x040300* is always checked to hide missing fans.
- new parameter "enforce_fan_rpm_range": defaults to true, checks
against the min/max RPM from LENOVO_FAN_TEST_DATA while setting
target RPM. dev_warn_once() when it exceeds min/max RPM.
> > + return -EINVAL;
> > + raw = val;
> > + }
> > +
> > + err = lwmi_om_fan_get_set(priv, channel, &raw, true);
> > + if (err)
> > + return err;
> > +
> > + priv->fan_info[channel].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_ENABLE | HWMON_F_INPUT | HWMON_F_TARGET,
> > + HWMON_F_ENABLE | HWMON_F_INPUT | HWMON_F_TARGET),
> > + 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
> > + * @priv: Driver private data
> > + *
> > + * Initializes capability data and registers the HWMON device.
> > + *
> > + * Return: 0 on success, or an error code.
> > + */
> > +static int lwmi_om_hwmon_add(struct lwmi_om_priv *priv)
> > +{
> > + struct capdata00 capdata00;
> > + int i, err;
> > +
> > + for (i = 0; i < LWMI_FAN_NR; i++) {
>
> There is an assumption here that isn't accurate. Each fan ID
> corresponds to a specific fan functionality. 01 is CPU Fan, 02 is GPU
> Fan, 02 is GPU Power Fan, and 04 is System Fan. Not every fan needs to
> exist, so an ID table might look like this (example from docs):
>
> illustrate:
> UINT32 NumOfFans = 3;
> NoteBook:
> 1: CPU Fan ID
> 2: GPU Fan ID
> 3: GPU Power Fan ID
> 4: System Fan ID
> UINT32 FanId [1,2,4]
> UINT32 FanMaxSpeed[5400, 5400, 9000];
> UINT32 FanMinSpeed[1900, 1900, 2000];
Thanks for the information. My device only defines 0x0403000{1,2} in
LENOVO_CAPABILITY_DATA_00, so I assumed LWMI_FAN_NR == 2.
> In such a case, "count" would be 3, but the idx should be 4 going to
> the hardware because the GPU Power Fan isn't present, while the case
> fan is.
LWMI_FAN_NR has nothing to do with the actual "count". It is about "how
many HWMON fan channels are defined?" It exists because HWMON channels
are defined statically - we hide defined channels when they are missing
from LENOVO_CAPABILITY_DATA_00 (and LENOVO_FAN_TEST_DATA, if
available).
The implementation of lenovo-wmi-other doesn't use NumOfFans either -
it queries LENOVO_FAN_TEST_DATA using fan ID directly. NumOfFans is
only used when lenovo-wmi-capdata retrieves the data.
This implementation has another advantage: the X in fanX_* is always
the same as the fan ID in
LENOVO_CAPABILITY_DATA_00/LENOVO_FAN_TEST_DATA even in your example
where fan 3 is missing - fan3_* is invisible, fan{1,2,4}_* are exposed.
Given the information, I will define 4 fan channels in v2.
> Thanks,
> Derek
Thanks,
Rong
> > + err = lwmi_cd00_get_data(priv->cd00_list, LWMI_ATTR_ID_FAN_RPM(i),
> > + &capdata00);
> > + if (err)
> > + continue;
> > +
> > + priv->fan_info[i] = (struct fan_info) {
> > + .supported = capdata00.supported,
> > + .target = -ENODATA,
> > + };
> > + }
> > +
> > + priv->hwmon_dev = hwmon_device_register_with_info(&priv->wdev->dev, LWMI_OM_HWMON_NAME,
> > + priv, &lwmi_om_hwmon_chip_info, NULL);
> > +
> > + return PTR_ERR_OR_ZERO(priv->hwmon_dev);
> > +}
> > +
> > +/**
> > + * lwmi_om_hwmon_remove() - Unregister HWMON device
> > + * @priv: Driver private data
> > + *
> > + * Unregisters the HWMON device and resets all fans to automatic mode.
> > + * Ensures hardware doesn't remain in manual mode after driver removal.
> > + */
> > +static void lwmi_om_hwmon_remove(struct lwmi_om_priv *priv)
> > +{
> > + hwmon_device_unregister(priv->hwmon_dev);
> > +}
> > +
> > +/* ======== fw_attributes (component: lenovo-wmi-capdata 01) ======== */
> > +
> > struct tunable_attr_01 {
> > struct capdata01 *capdata;
> > struct device *dev;
> > @@ -564,15 +833,17 @@ 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. 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, register the
> > + * HWMON device and all firmware attribute groups.
> > *
> > * Return: 0 on success, or an error code.
> > */
> > @@ -586,26 +857,47 @@ static int lwmi_om_master_bind(struct device *dev)
> > if (ret)
> > return ret;
> >
> > - priv->cd01_list = binder.cd01_list;
> > - if (!priv->cd01_list)
> > + if (!binder.cd00_list && !binder.cd01_list)
> > return -ENODEV;
> >
> > - return lwmi_om_fw_attr_add(priv);
> > + priv->cd00_list = binder.cd00_list;
> > + if (priv->cd00_list) {
> > + ret = lwmi_om_hwmon_add(priv);
> > + if (ret)
> > + return ret;
> > + }
> > +
> > + priv->cd01_list = binder.cd01_list;
> > + if (priv->cd01_list) {
> > + ret = lwmi_om_fw_attr_add(priv);
> > + if (ret) {
> > + if (priv->cd00_list)
> > + lwmi_om_hwmon_remove(priv);
> > + return ret;
> > + }
> > + }
> > +
> > + return 0;
> > }
> >
> > /**
> > * 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 the HWMON device and all capability data attribute groups. Then
> > + * call component_unbind_all to unbind the lenovo-wmi-capdata driver 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);
> > + if (priv->cd00_list)
> > + lwmi_om_hwmon_remove(priv);
> > +
> > + if (priv->cd01_list)
> > + lwmi_om_fw_attr_remove(priv);
> > +
> > component_unbind_all(dev, NULL);
> > }
> >
> > @@ -624,6 +916,9 @@ 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);
> >
> > @@ -654,7 +949,9 @@ 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);
> > +
> > + if (priv->ida_id >= 0)
> > + ida_free(&lwmi_om_ida, priv->ida_id);
> > }
> >
> > static const struct wmi_device_id lwmi_other_id_table[] = {
> > @@ -679,5 +976,6 @@ MODULE_IMPORT_NS("LENOVO_WMI_CD");
> > MODULE_IMPORT_NS("LENOVO_WMI_HELPERS");
> > MODULE_DEVICE_TABLE(wmi, lwmi_other_id_table);
> > MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
> > +MODULE_AUTHOR("Rong Zhang <i@rong.moe>");
> > MODULE_DESCRIPTION("Lenovo Other Mode WMI Driver");
> > MODULE_LICENSE("GPL");
> > --
> > 2.51.0
> >
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH 4/6] platform/x86: lenovo-wmi-other: Add HWMON for fan speed RPM
2025-10-26 19:42 ` Rong Zhang
@ 2025-10-26 20:19 ` Derek J. Clark
2025-10-26 23:04 ` Armin Wolf
1 sibling, 0 replies; 20+ messages in thread
From: Derek J. Clark @ 2025-10-26 20:19 UTC (permalink / raw)
To: Rong Zhang
Cc: Mark Pearson, Armin Wolf, Hans de Goede, Ilpo Järvinen,
Guenter Roeck, platform-driver-x86, linux-kernel, linux-hwmon
On October 26, 2025 12:42:22 PM PDT, Rong Zhang <i@rong.moe> wrote:
>Hi Derek,
>
>On Sat, 2025-10-25 at 22:23 -0700, Derek John Clark wrote:
>> On Sun, Oct 19, 2025 at 2:05 PM Rong Zhang <i@rong.moe> wrote:
>> >
>> > Register an HWMON device for fan spped RPM according to Capability Data
>> > 00 provided by lenovo-wmi-capdata. The corresponding HWMON nodes are:
>> >
>> > - fanX_enable: enable/disable the fan (tunable)
>> > - fanX_input: current RPM
>> > - fanX_target: target RPM (tunable)
>> >
>> > Signed-off-by: Rong Zhang <i@rong.moe>
>> > ---
>> > .../wmi/devices/lenovo-wmi-other.rst | 5 +
>> > drivers/platform/x86/lenovo/Kconfig | 1 +
>> > drivers/platform/x86/lenovo/wmi-other.c | 324 +++++++++++++++++-
>> > 3 files changed, 317 insertions(+), 13 deletions(-)
>> >
>> > diff --git a/Documentation/wmi/devices/lenovo-wmi-other.rst b/Documentation/wmi/devices/lenovo-wmi-other.rst
>> > index adbd7943c6756..cb6a9bfe5a79e 100644
>> > --- a/Documentation/wmi/devices/lenovo-wmi-other.rst
>> > +++ b/Documentation/wmi/devices/lenovo-wmi-other.rst
>> > @@ -31,6 +31,11 @@ under the following path:
>> >
>> > /sys/class/firmware-attributes/lenovo-wmi-other/attributes/<attribute>/
>> >
>> > +Besides, this driver also exports fan speed RPM to HWMON:
>> > + - fanX_enable: enable/disable the fan (tunable)
>> > + - fanX_input: current RPM
>> > + - fanX_target: target RPM (tunable)
>> > +
>> > LENOVO_CAPABILITY_DATA_00
>> > -------------------------
>> >
>> > diff --git a/drivers/platform/x86/lenovo/Kconfig b/drivers/platform/x86/lenovo/Kconfig
>> > index fb96a0f908f03..be9af04511462 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_DATA
>> > select LENOVO_WMI_EVENTS
>> > diff --git a/drivers/platform/x86/lenovo/wmi-other.c b/drivers/platform/x86/lenovo/wmi-other.c
>> > index 20c6ff0be37a1..f8771ed3c6642 100644
>> > --- a/drivers/platform/x86/lenovo/wmi-other.c
>> > +++ b/drivers/platform/x86/lenovo/wmi-other.c
>> > @@ -14,7 +14,15 @@
>> > * These attributes typically don't fit anywhere else in the sysfs and are set
>> > * in Windows using one of Lenovo's multiple user applications.
>> > *
>> > + * Besides, this driver also exports tunable fan speed RPM to HWMON.
>> > + *
>> > * Copyright (C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
>> > + * - fw_attributes
>> > + * - binding to Capability Data 01
>> > + *
>> > + * Copyright (C) 2025 Rong Zhang <i@rong.moe>
>> > + * - HWMON
>> > + * - binding to Capability Data 00
>> > */
>> >
>> > #include <linux/acpi.h>
>> > @@ -25,6 +33,7 @@
>> > #include <linux/device.h>
>> > #include <linux/export.h>
>> > #include <linux/gfp_types.h>
>> > +#include <linux/hwmon.h>
>> > #include <linux/idr.h>
>> > #include <linux/kdev_t.h>
>> > #include <linux/kobject.h>
>> > @@ -43,12 +52,20 @@
>> >
>> > #define LENOVO_OTHER_MODE_GUID "DC2A8805-3A8C-41BA-A6F7-092E0089CD3B"
>> >
>> > +#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_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
>> >
>> > +#define LWMI_DEVICE_ID_FAN 0x04
>> > +
>> > +#define LWMI_FEATURE_ID_FAN_RPM 0x03
>> > +
>> > #define LWMI_TYPE_ID_NONE 0x00
>> >
>> > #define LWMI_FEATURE_VALUE_GET 17
>> > @@ -59,7 +76,18 @@
>> > #define LWMI_ATTR_MODE_ID_MASK GENMASK(15, 8)
>> > #define LWMI_ATTR_TYPE_ID_MASK GENMASK(7, 0)
>> >
>> > +/* Only fan1 and fan2 are present on supported devices. */
>> > +#define LWMI_FAN_ID_BASE 1
>> > +#define LWMI_FAN_NR 2
>> > +#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_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);
>> > @@ -76,15 +104,256 @@ 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 fan_info {
>> > + u32 supported;
>> > + long target;
>> > + } fan_info[LWMI_FAN_NR];
>> > };
>> >
>> > +/* ======== HWMON (component: lenovo-wmi-capdata 00) ======== */
>> > +
>> > +/**
>> > + * 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 speed.
>> > + *
>> > + * 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;
>> > + else if (retval != 1)
>> > + return -EIO;
>> > +
>> > + return 0;
>> > +}
>> > +
>> > +/**
>> > + * 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 a 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 r = false, w = false;
>> > +
>> > + if (type == hwmon_fan) {
>> > + switch (attr) {
>> > + case hwmon_fan_enable:
>> > + case hwmon_fan_target:
>> > + r = w = priv->fan_info[channel].supported & LWMI_SUPP_MAY_SET;
>> > + break;
>> > + case hwmon_fan_input:
>> > + r = priv->fan_info[channel].supported & LWMI_SUPP_MAY_GET;
>> > + break;
>> > + }
>> > + }
>> > +
>>
>> There is another method in capdata00 that could be useful here
>>
>> Fan Test For Diagnostic Software
>> uint32 IDs //0x04050000
>> uint32 Capability //9:by project
>> bit 3: 0: not support LENOVO_FAN_TEST_DATA, 1 support LENOVO_FAN_TEST_DATA
>> bit 2: 0: not support SetFeatureValue(), 1: support SetFeatureValue()
>> bit 1: 0: not support GetFeatureValue(), 1: support GetFeatureValue()
>> bit 0: 0: not support fan test for diagnostic software, 1: support an
>> test for diagnostic software
>
>The information is useful, thanks for that!
>
>A quick look at the decompiled ASL code of my device's ACPI tables:
>
> Package (0x03)
> {
> 0x04050000,
> 0x07,
> One
> },
>
>I've confirmed that the corresponding ACPI method didn't modify the
>return value of 0x04050000.
>
>0x07 means my device supports this interface, GetFeatureValue() and
>SetFeatureValue(); but does not support LENOVO_FAN_TEST_DATA. Is BIT(3)
>only defined in some models (but not on my device)? The data returned
>by LENOVO_FAN_TEST_DATA seems correct and is probably the min/max auto
>points.
>
Bah, of course not. I suppose it wouldn't be a Lenovo BIOS if everything was consistent with their spec...
>My device didn't implement {Get,Set}FeatureValue(0x04050000). What will
>it do when it's implemented?
I doubt anything. It seems like get/set is generally enabled for most attributes even if they are stubbed. It's probably why I get the attributes are available with v1 even when my device has stubbed methods.
>> I'll discuss below, but it seems like knowing min/max is a good idea
>> before making the sysfs visible.
>>
>> > + if (!r)
>> > + return 0;
>> > +
>> > + return w ? 0644 : 0444;
>> > +}
>> > +
>> > +/**
>> > + * 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) {
>> > + 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_enable:
>> > + case hwmon_fan_target:
>> > + /* -ENODATA before first set. */
>>
>> Why not query the interface in realtime to know the system state? That
>> would avoid this problem.
>
>My implementation follows the approach of corsair-cpro
>(drivers/hwmon/corsair-cpro.c). hwmon_fan_target is about "how much RPM
>*should* the fan reach?", while hwmon_fan_input is about "how much RPM
>does the fan *really* reach?"
>
>Calling SetFeatureValue(0x040300*) sets the former, while calling
>GetFeatureValue(0x040300*) queries the latter. IIUC, using
>GetFeatureValue(0x040300*) for hwmon_fan_target violates the
>definition, especially when the fan is in auto mode.
>
My interpretation was that we could at least determine enable. I'm not so worried about determining target as that makes sense it wouldn't be anything. Based on your below data it seems the documentation is not consistent with real world data so it might not be possible even for enable, which is annoying.
Is the last setting retained on boot, or does it reset after restarting (or suspend/resume for that matter)? If it's reset, we can probably safely assume auto mode on init? I'll defer to your judgement here since you have a device in hand.
>> > + err = (int)priv->fan_info[channel].target;
>> > + if (err < 0)
>> > + return err;
>> > +
>> > + if (attr == hwmon_fan_enable)
>> > + *val = priv->fan_info[channel].target != 1;
>> > + else
>> > + *val = priv->fan_info[channel].target;
>> > + 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;
>> > + int err;
>> > +
>> > + if (type == hwmon_fan) {
>> > + switch (attr) {
>> > + case hwmon_fan_enable:
>> > + case hwmon_fan_target:
>> > + if (attr == hwmon_fan_enable) {
>> > + if (val == 0)
>> > + raw = 1; /* stop */
>> > + else if (val == 1)
>> > + raw = 0; /* auto */
>> > + else
>> > + return -EINVAL;
>> > + } else {
>> > + /*
>> > + * val > U16_MAX seems safe but meaningless.
>> > + */
>> > + if (val < 0 || val > U16_MAX)
>>
>> I think it might be prudent to only permit these settings if fan speed
>> params can't be known. Pragmatically it ensures userspace is aware of
>> the range of the interface. Per the documentation it should be "safe"
>> as is, but setting below the min fan speed will return it to "auto"
>> mode and the hwmon will be out of sync. Anything above should just be
>> set to the max, if the BIOS is working properly.
>
>On my device, the data returned by LENOVO_FAN_TEST_DATA seems to be the
>min/max auto points. The fan can spin much slower/faster than the
>min/max RPM. Setting below the "real" min RPM stops the fan - setting 0
>is the only way to return it to auto mode.
>
Okay. I'm not surprised at this point when real world data contradicts the spec.
Thanks,
Derek
> # grep . fan1_*
> grep: fan1_enable: No data available
> fan1_input:2200
> fan1_max:5000
> fan1_min:2200
> grep: fan1_target: No data available
> # echo 800 >fan1_target
> # cat fan1_input
> 800
> # echo 700 >fan1_target
> # cat fan1_input
> 700
> # echo 10000 >fan1_target
> # cat fan1_input
> 6500
> # echo 100 >fan1_target
> # cat fan1_input
> 0
> # taskset -c 2 stress-ng -c 1 >/dev/null &
> [1] 37967
> # cat fan1_input
> 0
> # echo 0 >fan1_target
> # cat fan1_input
> 2200
> # cat fan1_input
> 2600
>
>> IMO the fan speed data is essential to ensuring the hwmon interface is
>> usable and synced. I'd move that patch before this one in the series
>> and make the 0x04050000 method reporting IsSupported required for any
>> of the attributes to be visible, with value checks against the min/max
>> when setting a given fan.
>
>I agree that setting the RPM too low/high may results in HWMON being
>out of sync, which is usually not desired. Will do these in v2.
>
>My extra idea:
>- drop the parameter "ignore_fan_cap".
>- new parameter "expose_all_fans": does not hide fans when missing from
> LENOVO_FAN_TEST_DATA or when 0x04050000 reports unsupported.
> 0x040300* is always checked to hide missing fans.
>- new parameter "enforce_fan_rpm_range": defaults to true, checks
> against the min/max RPM from LENOVO_FAN_TEST_DATA while setting
> target RPM. dev_warn_once() when it exceeds min/max RPM.
>
>> > + return -EINVAL;
>> > + raw = val;
>> > + }
>> > +
>> > + err = lwmi_om_fan_get_set(priv, channel, &raw, true);
>> > + if (err)
>> > + return err;
>> > +
>> > + priv->fan_info[channel].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_ENABLE | HWMON_F_INPUT | HWMON_F_TARGET,
>> > + HWMON_F_ENABLE | HWMON_F_INPUT | HWMON_F_TARGET),
>> > + 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
>> > + * @priv: Driver private data
>> > + *
>> > + * Initializes capability data and registers the HWMON device.
>> > + *
>> > + * Return: 0 on success, or an error code.
>> > + */
>> > +static int lwmi_om_hwmon_add(struct lwmi_om_priv *priv)
>> > +{
>> > + struct capdata00 capdata00;
>> > + int i, err;
>> > +
>> > + for (i = 0; i < LWMI_FAN_NR; i++) {
>>
>> There is an assumption here that isn't accurate. Each fan ID
>> corresponds to a specific fan functionality. 01 is CPU Fan, 02 is GPU
>> Fan, 02 is GPU Power Fan, and 04 is System Fan. Not every fan needs to
>> exist, so an ID table might look like this (example from docs):
>>
>> illustrate:
>> UINT32 NumOfFans = 3;
>> NoteBook:
>> 1: CPU Fan ID
>> 2: GPU Fan ID
>> 3: GPU Power Fan ID
>> 4: System Fan ID
>> UINT32 FanId [1,2,4]
>> UINT32 FanMaxSpeed[5400, 5400, 9000];
>> UINT32 FanMinSpeed[1900, 1900, 2000];
>
>Thanks for the information. My device only defines 0x0403000{1,2} in
>LENOVO_CAPABILITY_DATA_00, so I assumed LWMI_FAN_NR == 2.
>
>> In such a case, "count" would be 3, but the idx should be 4 going to
>> the hardware because the GPU Power Fan isn't present, while the case
>> fan is.
>
>LWMI_FAN_NR has nothing to do with the actual "count". It is about "how
>many HWMON fan channels are defined?" It exists because HWMON channels
>are defined statically - we hide defined channels when they are missing
>from LENOVO_CAPABILITY_DATA_00 (and LENOVO_FAN_TEST_DATA, if
>available).
>
>The implementation of lenovo-wmi-other doesn't use NumOfFans either -
>it queries LENOVO_FAN_TEST_DATA using fan ID directly. NumOfFans is
>only used when lenovo-wmi-capdata retrieves the data.
>
>This implementation has another advantage: the X in fanX_* is always
>the same as the fan ID in
>LENOVO_CAPABILITY_DATA_00/LENOVO_FAN_TEST_DATA even in your example
>where fan 3 is missing - fan3_* is invisible, fan{1,2,4}_* are exposed.
>
>Given the information, I will define 4 fan channels in v2.
>
>> Thanks,
>> Derek
>
>Thanks,
>Rong
>
>> > + err = lwmi_cd00_get_data(priv->cd00_list, LWMI_ATTR_ID_FAN_RPM(i),
>> > + &capdata00);
>> > + if (err)
>> > + continue;
>> > +
>> > + priv->fan_info[i] = (struct fan_info) {
>> > + .supported = capdata00.supported,
>> > + .target = -ENODATA,
>> > + };
>> > + }
>> > +
>> > + priv->hwmon_dev = hwmon_device_register_with_info(&priv->wdev->dev, LWMI_OM_HWMON_NAME,
>> > + priv, &lwmi_om_hwmon_chip_info, NULL);
>> > +
>> > + return PTR_ERR_OR_ZERO(priv->hwmon_dev);
>> > +}
>> > +
>> > +/**
>> > + * lwmi_om_hwmon_remove() - Unregister HWMON device
>> > + * @priv: Driver private data
>> > + *
>> > + * Unregisters the HWMON device and resets all fans to automatic mode.
>> > + * Ensures hardware doesn't remain in manual mode after driver removal.
>> > + */
>> > +static void lwmi_om_hwmon_remove(struct lwmi_om_priv *priv)
>> > +{
>> > + hwmon_device_unregister(priv->hwmon_dev);
>> > +}
>> > +
>> > +/* ======== fw_attributes (component: lenovo-wmi-capdata 01) ======== */
>> > +
>> > struct tunable_attr_01 {
>> > struct capdata01 *capdata;
>> > struct device *dev;
>> > @@ -564,15 +833,17 @@ 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. 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, register the
>> > + * HWMON device and all firmware attribute groups.
>> > *
>> > * Return: 0 on success, or an error code.
>> > */
>> > @@ -586,26 +857,47 @@ static int lwmi_om_master_bind(struct device *dev)
>> > if (ret)
>> > return ret;
>> >
>> > - priv->cd01_list = binder.cd01_list;
>> > - if (!priv->cd01_list)
>> > + if (!binder.cd00_list && !binder.cd01_list)
>> > return -ENODEV;
>> >
>> > - return lwmi_om_fw_attr_add(priv);
>> > + priv->cd00_list = binder.cd00_list;
>> > + if (priv->cd00_list) {
>> > + ret = lwmi_om_hwmon_add(priv);
>> > + if (ret)
>> > + return ret;
>> > + }
>> > +
>> > + priv->cd01_list = binder.cd01_list;
>> > + if (priv->cd01_list) {
>> > + ret = lwmi_om_fw_attr_add(priv);
>> > + if (ret) {
>> > + if (priv->cd00_list)
>> > + lwmi_om_hwmon_remove(priv);
>> > + return ret;
>> > + }
>> > + }
>> > +
>> > + return 0;
>> > }
>> >
>> > /**
>> > * 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 the HWMON device and all capability data attribute groups. Then
>> > + * call component_unbind_all to unbind the lenovo-wmi-capdata driver 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);
>> > + if (priv->cd00_list)
>> > + lwmi_om_hwmon_remove(priv);
>> > +
>> > + if (priv->cd01_list)
>> > + lwmi_om_fw_attr_remove(priv);
>> > +
>> > component_unbind_all(dev, NULL);
>> > }
>> >
>> > @@ -624,6 +916,9 @@ 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);
>> >
>> > @@ -654,7 +949,9 @@ 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);
>> > +
>> > + if (priv->ida_id >= 0)
>> > + ida_free(&lwmi_om_ida, priv->ida_id);
>> > }
>> >
>> > static const struct wmi_device_id lwmi_other_id_table[] = {
>> > @@ -679,5 +976,6 @@ MODULE_IMPORT_NS("LENOVO_WMI_CD");
>> > MODULE_IMPORT_NS("LENOVO_WMI_HELPERS");
>> > MODULE_DEVICE_TABLE(wmi, lwmi_other_id_table);
>> > MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
>> > +MODULE_AUTHOR("Rong Zhang <i@rong.moe>");
>> > MODULE_DESCRIPTION("Lenovo Other Mode WMI Driver");
>> > MODULE_LICENSE("GPL");
>> > --
>> > 2.51.0
>> >
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH 0/6] platform/x86: lenovo-wmi-{capdata,other}: Add HWMON for fan speed
2025-10-26 17:11 ` Rong Zhang
@ 2025-10-26 22:59 ` Armin Wolf
0 siblings, 0 replies; 20+ messages in thread
From: Armin Wolf @ 2025-10-26 22:59 UTC (permalink / raw)
To: Rong Zhang, Derek John Clark
Cc: Mark Pearson, Hans de Goede, Ilpo Järvinen, Guenter Roeck,
platform-driver-x86, linux-kernel, linux-hwmon
Am 26.10.25 um 18:11 schrieb Rong Zhang:
> Hi Derek,
>
> On Sat, 2025-10-25 at 21:39 -0700, Derek John Clark wrote:
>> On Sun, Oct 19, 2025 at 2:05 PM Rong Zhang <i@rong.moe> wrote:
>>> Lenovo WMI Other Mode interface also supports querying or setting fan
>>> speed RPM. This capability is decribed by LENOVO_CAPABILITY_DATA_00.
>>> Besides, LENOVO_FAN_TEST_DATA provides reference data for self-test of
>>> cooling fans, including minimum and maximum fan speed RPM.
>>>
>>> This patchset turns lenovo-wmi-capdata01 into a unified driver (now
>>> named lenovo-wmi-capdata) for LENOVO_CAPABILITY_DATA_{00,01} and
>>> LENOVO_FAN_TEST_DATA; then adds HWMON support for lenovo-wmi-other:
>>>
>>> - fanX_enable: enable/disable the fan (tunable)
>>> - fanX_input: current RPM
>>> - fanX_max: maximum RPM
>>> - fanX_min: minimum RPM
>>> - fanX_target: target RPM (tunable)
>>>
>>> This implementation doesn't require all capability data to be available,
>>> and is capable to expose interfaces accordingly:
>>>
>>> - Having LENOVO_CAPABILITY_DATA_00: exposes fanX_{enable,input,target}
>>> - Having LENOVO_CAPABILITY_DATA_01: exposes firmware_attributes
>>> - Having LENOVO_FAN_TEST_DATA: exposes fanX_{max,min}
>>>
>>> Rong Zhang (6):
>>> platform/x86: Rename lenovo-wmi-capdata01 to lenovo-wmi-capdata
>>> platform/x86: lenovo-wmi-{capdata,other}: Support multiple Capability
>>> Data
>>> platform/x86: lenovo-wmi-capdata: Add support for Capability Data 00
>>> platform/x86: lenovo-wmi-other: Add HWMON for fan speed RPM
>>> platform/x86: lenovo-wmi-capdata: Add support for Fan Test Data
>>> platform/x86: lenovo-wmi-other: Report min/max RPM and hide dummy fans
>>>
>>> .../wmi/devices/lenovo-wmi-other.rst | 32 +
>>> drivers/platform/x86/lenovo/Kconfig | 5 +-
>>> drivers/platform/x86/lenovo/Makefile | 2 +-
>>> drivers/platform/x86/lenovo/wmi-capdata.c | 545 ++++++++++++++++++
>>> drivers/platform/x86/lenovo/wmi-capdata.h | 46 ++
>>> drivers/platform/x86/lenovo/wmi-capdata01.c | 302 ----------
>>> drivers/platform/x86/lenovo/wmi-capdata01.h | 25 -
>>> drivers/platform/x86/lenovo/wmi-other.c | 422 +++++++++++++-
>>> 8 files changed, 1028 insertions(+), 351 deletions(-)
>>> create mode 100644 drivers/platform/x86/lenovo/wmi-capdata.c
>>> create mode 100644 drivers/platform/x86/lenovo/wmi-capdata.h
>>> delete mode 100644 drivers/platform/x86/lenovo/wmi-capdata01.c
>>> delete mode 100644 drivers/platform/x86/lenovo/wmi-capdata01.h
>>>
>>>
>>> base-commit: 3a8660878839faadb4f1a6dd72c3179c1df56787
>>> --
>>> 2.51.0
>>>
>> The series' intention looks good overall. The composable methods for
>> additional capdata interfaces is a welcome change. I have a few
>> comments I'll add for a couple of the patches. My apologies for the
>> slow review timeline, I've been on travel and wanted to test the
>> changes before submitting a review.
> Thanks for you review and testing! Hope you have/had a nice trip ;)
>
>> For testing I'm using my Legion Go 2. It apparently doesn't have the
>> FAN_TEST_DATA GUID, and the hwmon interface errors on all inputs
>> despite being visible. I know for the Legion Go series they use a fan
>> table with 10 auto_set points in the Other Method interface tied to
>> the platform profile, but the documentation I have says the methods
>> you're adding here should be available on all models, so that is a bit
>> strange.
> Yeah, that sounds weird.
>
> As for the fan table on your device, did you mean
> LENOVO_FAN_TABLE_DATA/LENOVO_FAN_METHOD? My device doesn't use a fan
> table, the corresponding ACPI methods are dummy (see below).
>
> My device is ThinkBook 14 G7+ ASP (forgot to mention when submitting,
> sorry). I don't have any documentation and I finished the patchset
> according to the MOF as well as the decompiled ASL code of its ACPI
> tables. The information from the documentation (including those in your
> following replies) is very useful, thanks for that!
>
> As it's branded as ThinkBook, most GAMEZONE/WMI_OTHER interfaces on my
> device may differ from Legion devices. To summerize:
>
> - LENOVO_GAMEZONE_DATA: dummy ACPI method.
> - LENOVO_GAMEZONE_CPU_OC_DATA: presents in MOF; missing ACPI method.
> - LENOVO_GAMEZONE_GPU_OC_DATA: dummy ACPI method.
> - LENOVO_CAPABILITY_DATA_00: works fine.
> - LENOVO_CAPABILITY_DATA_01: dummy ACPI method, data still presents
> (\_SB.GZFD.CD01).
> - LENOVO_FAN_TEST_DATA: works fine.
> - LENOVO_FAN_TABLE_DATA: dummy ACPI method.
> - LENOVO_FAN_METHOD: dummy ACPI method.
> - LENOVO_OTHER_METHOD:
> * Despite missing LENOVO_CAPABILITY_DATA_01, SPPT/SPL/FPPT can still
> be get/set. There is also CHTC (FEATURE_ID=4, get/set) which I am
> not sure what it means.
> * FAN1/2: get method reads data from the EC; set method for FAN1
> updates the EC, for FAN2 is dummy (no-op, returns 0).
>
>> dmesg output:
>> [ 3.995549] lenovo_wmi_cd 362A3AFE-3D96-4665-8530-96DAD5BB300E-13:
>> registered LENOVO_CAPABILITY_DATA_00 with 33 items
>> [ 4.000266] lenovo_wmi_cd 7A8F5407-CB67-4D6E-B547-39B3BE018154-9:
>> registered LENOVO_CAPABILITY_DATA_01 with 80 items
>> [ 4.005603] lenovo_wmi_other
>> DC2A8805-3A8C-41BA-A6F7-092E0089CD3B-3: bound
>> 362A3AFE-3D96-4665-8530-96DAD5BB300E-13 (ops lwmi_cd_component_ops
>> [lenovo_wmi_capdata])
>> [ 4.005611] lenovo_wmi_other
>> DC2A8805-3A8C-41BA-A6F7-092E0089CD3B-3: bound
>> 7A8F5407-CB67-4D6E-B547-39B3BE018154-9 (ops lwmi_cd_component_ops
>> [lenovo_wmi_capdata])
>> [ 4.005614] lenovo_wmi_other
>> DC2A8805-3A8C-41BA-A6F7-092E0089CD3B-3: fan capdata unavailable
>>
>> Testing results:
>> (deck@lego2 hwmon5)$ ls
>> device fan1_enable fan1_input fan1_target name power subsystem uevent
>> (deck@lego2 hwmon5)$ cat fan1_enable
>> cat: fan1_enable: No data available
>> (1)(deck@lego2 hwmon5)$ echo 1 | sudo tee fan1_enable
>> [sudo] password for deck:
>> 1
>> tee: fan1_enable: Input/output error
>> (1)(deck@lego2 hwmon5)$ echo 0 | sudo tee fan1_enable
>> 0
>> tee: fan1_enable: Input/output error
>> (1)(deck@lego2 hwmon5)$ echo 3000 | sudo tee fan1_target
>> 3000
>> tee: fan1_target: Input/output error
> -EIO was returned when the set method didn't return 1 (as long as
> lwmi_dev_evaluate_int() didn't return this due to ACPI_FAILURE).
> Despite the return value, did the fan speed change after writing?
> Otherwise the method might be dummy and LENOVO_CAPABILITY_DATA_00
> simply returned mistaken data :(
>
>> (1)(deck@lego2 hwmon5)$ cat fan1_input
>> cat: fan1_input: No such device or address
> -ENXIO was returned by lwmi_dev_evaluate_int() as the return value was
> not an integer. It's really weird. Could you check the type of the
> return value? Some clues may also lie in the ASL code of the ACPI
> method.
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.
I already have patches for that, but for now i suggest that you handle
this inside lwmi_dev_evaluate_int() yourself.
Thanks,
Armin Wolf
>> Thanks,
>> Derek
> Thanks,
> Rong
>
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH 4/6] platform/x86: lenovo-wmi-other: Add HWMON for fan speed RPM
2025-10-26 19:42 ` Rong Zhang
2025-10-26 20:19 ` Derek J. Clark
@ 2025-10-26 23:04 ` Armin Wolf
2025-10-27 12:15 ` Rong Zhang
1 sibling, 1 reply; 20+ messages in thread
From: Armin Wolf @ 2025-10-26 23:04 UTC (permalink / raw)
To: Rong Zhang, Derek John Clark
Cc: Mark Pearson, Hans de Goede, Ilpo Järvinen, Guenter Roeck,
platform-driver-x86, linux-kernel, linux-hwmon
Am 26.10.25 um 20:42 schrieb Rong Zhang:
> Hi Derek,
>
> On Sat, 2025-10-25 at 22:23 -0700, Derek John Clark wrote:
>> On Sun, Oct 19, 2025 at 2:05 PM Rong Zhang <i@rong.moe> wrote:
>>> Register an HWMON device for fan spped RPM according to Capability Data
>>> 00 provided by lenovo-wmi-capdata. The corresponding HWMON nodes are:
>>>
>>> - fanX_enable: enable/disable the fan (tunable)
>>> - fanX_input: current RPM
>>> - fanX_target: target RPM (tunable)
>>>
>>> Signed-off-by: Rong Zhang <i@rong.moe>
>>> ---
>>> .../wmi/devices/lenovo-wmi-other.rst | 5 +
>>> drivers/platform/x86/lenovo/Kconfig | 1 +
>>> drivers/platform/x86/lenovo/wmi-other.c | 324 +++++++++++++++++-
>>> 3 files changed, 317 insertions(+), 13 deletions(-)
>>>
>>> diff --git a/Documentation/wmi/devices/lenovo-wmi-other.rst b/Documentation/wmi/devices/lenovo-wmi-other.rst
>>> index adbd7943c6756..cb6a9bfe5a79e 100644
>>> --- a/Documentation/wmi/devices/lenovo-wmi-other.rst
>>> +++ b/Documentation/wmi/devices/lenovo-wmi-other.rst
>>> @@ -31,6 +31,11 @@ under the following path:
>>>
>>> /sys/class/firmware-attributes/lenovo-wmi-other/attributes/<attribute>/
>>>
>>> +Besides, this driver also exports fan speed RPM to HWMON:
>>> + - fanX_enable: enable/disable the fan (tunable)
>>> + - fanX_input: current RPM
>>> + - fanX_target: target RPM (tunable)
>>> +
>>> LENOVO_CAPABILITY_DATA_00
>>> -------------------------
>>>
>>> diff --git a/drivers/platform/x86/lenovo/Kconfig b/drivers/platform/x86/lenovo/Kconfig
>>> index fb96a0f908f03..be9af04511462 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_DATA
>>> select LENOVO_WMI_EVENTS
>>> diff --git a/drivers/platform/x86/lenovo/wmi-other.c b/drivers/platform/x86/lenovo/wmi-other.c
>>> index 20c6ff0be37a1..f8771ed3c6642 100644
>>> --- a/drivers/platform/x86/lenovo/wmi-other.c
>>> +++ b/drivers/platform/x86/lenovo/wmi-other.c
>>> @@ -14,7 +14,15 @@
>>> * These attributes typically don't fit anywhere else in the sysfs and are set
>>> * in Windows using one of Lenovo's multiple user applications.
>>> *
>>> + * Besides, this driver also exports tunable fan speed RPM to HWMON.
>>> + *
>>> * Copyright (C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
>>> + * - fw_attributes
>>> + * - binding to Capability Data 01
>>> + *
>>> + * Copyright (C) 2025 Rong Zhang <i@rong.moe>
>>> + * - HWMON
>>> + * - binding to Capability Data 00
>>> */
>>>
>>> #include <linux/acpi.h>
>>> @@ -25,6 +33,7 @@
>>> #include <linux/device.h>
>>> #include <linux/export.h>
>>> #include <linux/gfp_types.h>
>>> +#include <linux/hwmon.h>
>>> #include <linux/idr.h>
>>> #include <linux/kdev_t.h>
>>> #include <linux/kobject.h>
>>> @@ -43,12 +52,20 @@
>>>
>>> #define LENOVO_OTHER_MODE_GUID "DC2A8805-3A8C-41BA-A6F7-092E0089CD3B"
>>>
>>> +#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_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
>>>
>>> +#define LWMI_DEVICE_ID_FAN 0x04
>>> +
>>> +#define LWMI_FEATURE_ID_FAN_RPM 0x03
>>> +
>>> #define LWMI_TYPE_ID_NONE 0x00
>>>
>>> #define LWMI_FEATURE_VALUE_GET 17
>>> @@ -59,7 +76,18 @@
>>> #define LWMI_ATTR_MODE_ID_MASK GENMASK(15, 8)
>>> #define LWMI_ATTR_TYPE_ID_MASK GENMASK(7, 0)
>>>
>>> +/* Only fan1 and fan2 are present on supported devices. */
>>> +#define LWMI_FAN_ID_BASE 1
>>> +#define LWMI_FAN_NR 2
>>> +#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_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);
>>> @@ -76,15 +104,256 @@ 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 fan_info {
>>> + u32 supported;
>>> + long target;
>>> + } fan_info[LWMI_FAN_NR];
>>> };
>>>
>>> +/* ======== HWMON (component: lenovo-wmi-capdata 00) ======== */
>>> +
>>> +/**
>>> + * 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 speed.
>>> + *
>>> + * 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;
>>> + else if (retval != 1)
>>> + return -EIO;
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +/**
>>> + * 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 a 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 r = false, w = false;
>>> +
>>> + if (type == hwmon_fan) {
>>> + switch (attr) {
>>> + case hwmon_fan_enable:
>>> + case hwmon_fan_target:
>>> + r = w = priv->fan_info[channel].supported & LWMI_SUPP_MAY_SET;
>>> + break;
>>> + case hwmon_fan_input:
>>> + r = priv->fan_info[channel].supported & LWMI_SUPP_MAY_GET;
>>> + break;
>>> + }
>>> + }
>>> +
>> There is another method in capdata00 that could be useful here
>>
>> Fan Test For Diagnostic Software
>> uint32 IDs //0x04050000
>> uint32 Capability //9:by project
>> bit 3: 0: not support LENOVO_FAN_TEST_DATA, 1 support LENOVO_FAN_TEST_DATA
>> bit 2: 0: not support SetFeatureValue(), 1: support SetFeatureValue()
>> bit 1: 0: not support GetFeatureValue(), 1: support GetFeatureValue()
>> bit 0: 0: not support fan test for diagnostic software, 1: support an
>> test for diagnostic software
> The information is useful, thanks for that!
>
> A quick look at the decompiled ASL code of my device's ACPI tables:
>
> Package (0x03)
> {
> 0x04050000,
> 0x07,
> One
> },
>
> I've confirmed that the corresponding ACPI method didn't modify the
> return value of 0x04050000.
>
> 0x07 means my device supports this interface, GetFeatureValue() and
> SetFeatureValue(); but does not support LENOVO_FAN_TEST_DATA. Is BIT(3)
> only defined in some models (but not on my device)? The data returned
> by LENOVO_FAN_TEST_DATA seems correct and is probably the min/max auto
> points.
Can you please use this information instead of wmi_has_guid() when matching the
components? I would prefer if we can phase out wmi_has_guid() eventually.
Thanks,
Armin Wolf
>
> My device didn't implement {Get,Set}FeatureValue(0x04050000). What will
> it do when it's implemented?
>
>> I'll discuss below, but it seems like knowing min/max is a good idea
>> before making the sysfs visible.
>>
>>> + if (!r)
>>> + return 0;
>>> +
>>> + return w ? 0644 : 0444;
>>> +}
>>> +
>>> +/**
>>> + * 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) {
>>> + 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_enable:
>>> + case hwmon_fan_target:
>>> + /* -ENODATA before first set. */
>> Why not query the interface in realtime to know the system state? That
>> would avoid this problem.
> My implementation follows the approach of corsair-cpro
> (drivers/hwmon/corsair-cpro.c). hwmon_fan_target is about "how much RPM
> *should* the fan reach?", while hwmon_fan_input is about "how much RPM
> does the fan *really* reach?"
>
> Calling SetFeatureValue(0x040300*) sets the former, while calling
> GetFeatureValue(0x040300*) queries the latter. IIUC, using
> GetFeatureValue(0x040300*) for hwmon_fan_target violates the
> definition, especially when the fan is in auto mode.
>
>>> + err = (int)priv->fan_info[channel].target;
>>> + if (err < 0)
>>> + return err;
>>> +
>>> + if (attr == hwmon_fan_enable)
>>> + *val = priv->fan_info[channel].target != 1;
>>> + else
>>> + *val = priv->fan_info[channel].target;
>>> + 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;
>>> + int err;
>>> +
>>> + if (type == hwmon_fan) {
>>> + switch (attr) {
>>> + case hwmon_fan_enable:
>>> + case hwmon_fan_target:
>>> + if (attr == hwmon_fan_enable) {
>>> + if (val == 0)
>>> + raw = 1; /* stop */
>>> + else if (val == 1)
>>> + raw = 0; /* auto */
>>> + else
>>> + return -EINVAL;
>>> + } else {
>>> + /*
>>> + * val > U16_MAX seems safe but meaningless.
>>> + */
>>> + if (val < 0 || val > U16_MAX)
>> I think it might be prudent to only permit these settings if fan speed
>> params can't be known. Pragmatically it ensures userspace is aware of
>> the range of the interface. Per the documentation it should be "safe"
>> as is, but setting below the min fan speed will return it to "auto"
>> mode and the hwmon will be out of sync. Anything above should just be
>> set to the max, if the BIOS is working properly.
> On my device, the data returned by LENOVO_FAN_TEST_DATA seems to be the
> min/max auto points. The fan can spin much slower/faster than the
> min/max RPM. Setting below the "real" min RPM stops the fan - setting 0
> is the only way to return it to auto mode.
>
> # grep . fan1_*
> grep: fan1_enable: No data available
> fan1_input:2200
> fan1_max:5000
> fan1_min:2200
> grep: fan1_target: No data available
> # echo 800 >fan1_target
> # cat fan1_input
> 800
> # echo 700 >fan1_target
> # cat fan1_input
> 700
> # echo 10000 >fan1_target
> # cat fan1_input
> 6500
> # echo 100 >fan1_target
> # cat fan1_input
> 0
> # taskset -c 2 stress-ng -c 1 >/dev/null &
> [1] 37967
> # cat fan1_input
> 0
> # echo 0 >fan1_target
> # cat fan1_input
> 2200
> # cat fan1_input
> 2600
>
>> IMO the fan speed data is essential to ensuring the hwmon interface is
>> usable and synced. I'd move that patch before this one in the series
>> and make the 0x04050000 method reporting IsSupported required for any
>> of the attributes to be visible, with value checks against the min/max
>> when setting a given fan.
> I agree that setting the RPM too low/high may results in HWMON being
> out of sync, which is usually not desired. Will do these in v2.
>
> My extra idea:
> - drop the parameter "ignore_fan_cap".
> - new parameter "expose_all_fans": does not hide fans when missing from
> LENOVO_FAN_TEST_DATA or when 0x04050000 reports unsupported.
> 0x040300* is always checked to hide missing fans.
> - new parameter "enforce_fan_rpm_range": defaults to true, checks
> against the min/max RPM from LENOVO_FAN_TEST_DATA while setting
> target RPM. dev_warn_once() when it exceeds min/max RPM.
>
>>> + return -EINVAL;
>>> + raw = val;
>>> + }
>>> +
>>> + err = lwmi_om_fan_get_set(priv, channel, &raw, true);
>>> + if (err)
>>> + return err;
>>> +
>>> + priv->fan_info[channel].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_ENABLE | HWMON_F_INPUT | HWMON_F_TARGET,
>>> + HWMON_F_ENABLE | HWMON_F_INPUT | HWMON_F_TARGET),
>>> + 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
>>> + * @priv: Driver private data
>>> + *
>>> + * Initializes capability data and registers the HWMON device.
>>> + *
>>> + * Return: 0 on success, or an error code.
>>> + */
>>> +static int lwmi_om_hwmon_add(struct lwmi_om_priv *priv)
>>> +{
>>> + struct capdata00 capdata00;
>>> + int i, err;
>>> +
>>> + for (i = 0; i < LWMI_FAN_NR; i++) {
>> There is an assumption here that isn't accurate. Each fan ID
>> corresponds to a specific fan functionality. 01 is CPU Fan, 02 is GPU
>> Fan, 02 is GPU Power Fan, and 04 is System Fan. Not every fan needs to
>> exist, so an ID table might look like this (example from docs):
>>
>> illustrate:
>> UINT32 NumOfFans = 3;
>> NoteBook:
>> 1: CPU Fan ID
>> 2: GPU Fan ID
>> 3: GPU Power Fan ID
>> 4: System Fan ID
>> UINT32 FanId [1,2,4]
>> UINT32 FanMaxSpeed[5400, 5400, 9000];
>> UINT32 FanMinSpeed[1900, 1900, 2000];
> Thanks for the information. My device only defines 0x0403000{1,2} in
> LENOVO_CAPABILITY_DATA_00, so I assumed LWMI_FAN_NR == 2.
>
>> In such a case, "count" would be 3, but the idx should be 4 going to
>> the hardware because the GPU Power Fan isn't present, while the case
>> fan is.
> LWMI_FAN_NR has nothing to do with the actual "count". It is about "how
> many HWMON fan channels are defined?" It exists because HWMON channels
> are defined statically - we hide defined channels when they are missing
> from LENOVO_CAPABILITY_DATA_00 (and LENOVO_FAN_TEST_DATA, if
> available).
>
> The implementation of lenovo-wmi-other doesn't use NumOfFans either -
> it queries LENOVO_FAN_TEST_DATA using fan ID directly. NumOfFans is
> only used when lenovo-wmi-capdata retrieves the data.
>
> This implementation has another advantage: the X in fanX_* is always
> the same as the fan ID in
> LENOVO_CAPABILITY_DATA_00/LENOVO_FAN_TEST_DATA even in your example
> where fan 3 is missing - fan3_* is invisible, fan{1,2,4}_* are exposed.
>
> Given the information, I will define 4 fan channels in v2.
>
>> Thanks,
>> Derek
> Thanks,
> Rong
>
>>> + err = lwmi_cd00_get_data(priv->cd00_list, LWMI_ATTR_ID_FAN_RPM(i),
>>> + &capdata00);
>>> + if (err)
>>> + continue;
>>> +
>>> + priv->fan_info[i] = (struct fan_info) {
>>> + .supported = capdata00.supported,
>>> + .target = -ENODATA,
>>> + };
>>> + }
>>> +
>>> + priv->hwmon_dev = hwmon_device_register_with_info(&priv->wdev->dev, LWMI_OM_HWMON_NAME,
>>> + priv, &lwmi_om_hwmon_chip_info, NULL);
>>> +
>>> + return PTR_ERR_OR_ZERO(priv->hwmon_dev);
>>> +}
>>> +
>>> +/**
>>> + * lwmi_om_hwmon_remove() - Unregister HWMON device
>>> + * @priv: Driver private data
>>> + *
>>> + * Unregisters the HWMON device and resets all fans to automatic mode.
>>> + * Ensures hardware doesn't remain in manual mode after driver removal.
>>> + */
>>> +static void lwmi_om_hwmon_remove(struct lwmi_om_priv *priv)
>>> +{
>>> + hwmon_device_unregister(priv->hwmon_dev);
>>> +}
>>> +
>>> +/* ======== fw_attributes (component: lenovo-wmi-capdata 01) ======== */
>>> +
>>> struct tunable_attr_01 {
>>> struct capdata01 *capdata;
>>> struct device *dev;
>>> @@ -564,15 +833,17 @@ 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. 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, register the
>>> + * HWMON device and all firmware attribute groups.
>>> *
>>> * Return: 0 on success, or an error code.
>>> */
>>> @@ -586,26 +857,47 @@ static int lwmi_om_master_bind(struct device *dev)
>>> if (ret)
>>> return ret;
>>>
>>> - priv->cd01_list = binder.cd01_list;
>>> - if (!priv->cd01_list)
>>> + if (!binder.cd00_list && !binder.cd01_list)
>>> return -ENODEV;
>>>
>>> - return lwmi_om_fw_attr_add(priv);
>>> + priv->cd00_list = binder.cd00_list;
>>> + if (priv->cd00_list) {
>>> + ret = lwmi_om_hwmon_add(priv);
>>> + if (ret)
>>> + return ret;
>>> + }
>>> +
>>> + priv->cd01_list = binder.cd01_list;
>>> + if (priv->cd01_list) {
>>> + ret = lwmi_om_fw_attr_add(priv);
>>> + if (ret) {
>>> + if (priv->cd00_list)
>>> + lwmi_om_hwmon_remove(priv);
>>> + return ret;
>>> + }
>>> + }
>>> +
>>> + return 0;
>>> }
>>>
>>> /**
>>> * 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 the HWMON device and all capability data attribute groups. Then
>>> + * call component_unbind_all to unbind the lenovo-wmi-capdata driver 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);
>>> + if (priv->cd00_list)
>>> + lwmi_om_hwmon_remove(priv);
>>> +
>>> + if (priv->cd01_list)
>>> + lwmi_om_fw_attr_remove(priv);
>>> +
>>> component_unbind_all(dev, NULL);
>>> }
>>>
>>> @@ -624,6 +916,9 @@ 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);
>>>
>>> @@ -654,7 +949,9 @@ 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);
>>> +
>>> + if (priv->ida_id >= 0)
>>> + ida_free(&lwmi_om_ida, priv->ida_id);
>>> }
>>>
>>> static const struct wmi_device_id lwmi_other_id_table[] = {
>>> @@ -679,5 +976,6 @@ MODULE_IMPORT_NS("LENOVO_WMI_CD");
>>> MODULE_IMPORT_NS("LENOVO_WMI_HELPERS");
>>> MODULE_DEVICE_TABLE(wmi, lwmi_other_id_table);
>>> MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
>>> +MODULE_AUTHOR("Rong Zhang <i@rong.moe>");
>>> MODULE_DESCRIPTION("Lenovo Other Mode WMI Driver");
>>> MODULE_LICENSE("GPL");
>>> --
>>> 2.51.0
>>>
^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH 4/6] platform/x86: lenovo-wmi-other: Add HWMON for fan speed RPM
2025-10-26 23:04 ` Armin Wolf
@ 2025-10-27 12:15 ` Rong Zhang
0 siblings, 0 replies; 20+ messages in thread
From: Rong Zhang @ 2025-10-27 12:15 UTC (permalink / raw)
To: Armin Wolf, Derek John Clark
Cc: Mark Pearson, Hans de Goede, Ilpo Järvinen, Guenter Roeck,
platform-driver-x86, linux-kernel, linux-hwmon
Hi Armin and Derek,
On Mon, 2025-10-27 at 00:04 +0100, Armin Wolf wrote:
> Am 26.10.25 um 20:42 schrieb Rong Zhang:
>
> > Hi Derek,
> >
> > On Sat, 2025-10-25 at 22:23 -0700, Derek John Clark wrote:
> > > On Sun, Oct 19, 2025 at 2:05 PM Rong Zhang <i@rong.moe> wrote:
> > > > Register an HWMON device for fan spped RPM according to Capability Data
> > > > 00 provided by lenovo-wmi-capdata. The corresponding HWMON nodes are:
> > > >
> > > > - fanX_enable: enable/disable the fan (tunable)
> > > > - fanX_input: current RPM
> > > > - fanX_target: target RPM (tunable)
> > > >
> > > > Signed-off-by: Rong Zhang <i@rong.moe>
> > > > ---
> > > > .../wmi/devices/lenovo-wmi-other.rst | 5 +
> > > > drivers/platform/x86/lenovo/Kconfig | 1 +
> > > > drivers/platform/x86/lenovo/wmi-other.c | 324 +++++++++++++++++-
> > > > 3 files changed, 317 insertions(+), 13 deletions(-)
> > > >
> > > > diff --git a/Documentation/wmi/devices/lenovo-wmi-other.rst b/Documentation/wmi/devices/lenovo-wmi-other.rst
> > > > index adbd7943c6756..cb6a9bfe5a79e 100644
> > > > --- a/Documentation/wmi/devices/lenovo-wmi-other.rst
> > > > +++ b/Documentation/wmi/devices/lenovo-wmi-other.rst
> > > > @@ -31,6 +31,11 @@ under the following path:
> > > >
> > > > /sys/class/firmware-attributes/lenovo-wmi-other/attributes/<attribute>/
> > > >
> > > > +Besides, this driver also exports fan speed RPM to HWMON:
> > > > + - fanX_enable: enable/disable the fan (tunable)
> > > > + - fanX_input: current RPM
> > > > + - fanX_target: target RPM (tunable)
> > > > +
> > > > LENOVO_CAPABILITY_DATA_00
> > > > -------------------------
> > > >
> > > > diff --git a/drivers/platform/x86/lenovo/Kconfig b/drivers/platform/x86/lenovo/Kconfig
> > > > index fb96a0f908f03..be9af04511462 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_DATA
> > > > select LENOVO_WMI_EVENTS
> > > > diff --git a/drivers/platform/x86/lenovo/wmi-other.c b/drivers/platform/x86/lenovo/wmi-other.c
> > > > index 20c6ff0be37a1..f8771ed3c6642 100644
> > > > --- a/drivers/platform/x86/lenovo/wmi-other.c
> > > > +++ b/drivers/platform/x86/lenovo/wmi-other.c
> > > > @@ -14,7 +14,15 @@
> > > > * These attributes typically don't fit anywhere else in the sysfs and are set
> > > > * in Windows using one of Lenovo's multiple user applications.
> > > > *
> > > > + * Besides, this driver also exports tunable fan speed RPM to HWMON.
> > > > + *
> > > > * Copyright (C) 2025 Derek J. Clark <derekjohn.clark@gmail.com>
> > > > + * - fw_attributes
> > > > + * - binding to Capability Data 01
> > > > + *
> > > > + * Copyright (C) 2025 Rong Zhang <i@rong.moe>
> > > > + * - HWMON
> > > > + * - binding to Capability Data 00
> > > > */
> > > >
> > > > #include <linux/acpi.h>
> > > > @@ -25,6 +33,7 @@
> > > > #include <linux/device.h>
> > > > #include <linux/export.h>
> > > > #include <linux/gfp_types.h>
> > > > +#include <linux/hwmon.h>
> > > > #include <linux/idr.h>
> > > > #include <linux/kdev_t.h>
> > > > #include <linux/kobject.h>
> > > > @@ -43,12 +52,20 @@
> > > >
> > > > #define LENOVO_OTHER_MODE_GUID "DC2A8805-3A8C-41BA-A6F7-092E0089CD3B"
> > > >
> > > > +#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_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
> > > >
> > > > +#define LWMI_DEVICE_ID_FAN 0x04
> > > > +
> > > > +#define LWMI_FEATURE_ID_FAN_RPM 0x03
> > > > +
> > > > #define LWMI_TYPE_ID_NONE 0x00
> > > >
> > > > #define LWMI_FEATURE_VALUE_GET 17
> > > > @@ -59,7 +76,18 @@
> > > > #define LWMI_ATTR_MODE_ID_MASK GENMASK(15, 8)
> > > > #define LWMI_ATTR_TYPE_ID_MASK GENMASK(7, 0)
> > > >
> > > > +/* Only fan1 and fan2 are present on supported devices. */
> > > > +#define LWMI_FAN_ID_BASE 1
> > > > +#define LWMI_FAN_NR 2
> > > > +#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_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);
> > > > @@ -76,15 +104,256 @@ 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 fan_info {
> > > > + u32 supported;
> > > > + long target;
> > > > + } fan_info[LWMI_FAN_NR];
> > > > };
[...snip...]
> > > There is another method in capdata00 that could be useful here
> > >
> > > Fan Test For Diagnostic Software
> > > uint32 IDs //0x04050000
> > > uint32 Capability //9:by project
> > > bit 3: 0: not support LENOVO_FAN_TEST_DATA, 1 support LENOVO_FAN_TEST_DATA
> > > bit 2: 0: not support SetFeatureValue(), 1: support SetFeatureValue()
> > > bit 1: 0: not support GetFeatureValue(), 1: support GetFeatureValue()
> > > bit 0: 0: not support fan test for diagnostic software, 1: support an
> > > test for diagnostic software
> > The information is useful, thanks for that!
> >
> > A quick look at the decompiled ASL code of my device's ACPI tables:
> >
> > Package (0x03)
> > {
> > 0x04050000,
> > 0x07,
> > One
> > },
> >
> > I've confirmed that the corresponding ACPI method didn't modify the
> > return value of 0x04050000.
> >
> > 0x07 means my device supports this interface, GetFeatureValue() and
> > SetFeatureValue(); but does not support LENOVO_FAN_TEST_DATA. Is BIT(3)
> > only defined in some models (but not on my device)? The data returned
> > by LENOVO_FAN_TEST_DATA seems correct and is probably the min/max auto
> > points.
>
> Can you please use this information instead of wmi_has_guid() when matching the
> components? I would prefer if we can phase out wmi_has_guid() eventually.
Doing so leads to a chicken-or-the-egg paradox as long as we use the
component helper:
- Calling lwmi_cd00_get_data() from wmi-other requires wmi-capdata
being bound to get a reference to cd00_list. Binding wmi-capdata again
to get a reference to cd_fan_list implies that the HWMON device can
only be registered in the driver probe callback instead of the master
bind callback, but the unbind callback still needs to check and
unregister it, which is really ugly.
- Calling lwmi_cd00_get_data() from wmi-capdata requires variables in
the module-level global scope to store references to cd_list. Doing so
completely makes the component helper meaningless for our use case.
Meanwhile, if we did't use the component helper at all, we would need
neither this information nor wmi_has_guid().
This information itself may also be inconsistent, e.g., it says my
device does not support LENOVO_FAN_TEST_DATA, but the GUID actually
exists and its data makes sense.
Moreover, capdata00 and capdata01 are irrelevant to each other. My
implementation is capable to work properly on devices with only one of
them (I am not sure if such devices exist, though). This again requires
wmi_has_guid() as it's the only way to determine their existence.
Do you think it's a good idea to drop the component approach? If so, I
will implement this in v2 (or v3, if I finish and send out v2 soon).
Thanks,
Rong
> Thanks,
> Armin Wolf
>
> >
> > My device didn't implement {Get,Set}FeatureValue(0x04050000). What will
> > it do when it's implemented?
[...snip...]
> > > >
> > > > +/* ======== 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. 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, register the
> > > > + * HWMON device and all firmware attribute groups.
> > > > *
> > > > * Return: 0 on success, or an error code.
> > > > */
> > > > @@ -586,26 +857,47 @@ static int lwmi_om_master_bind(struct device *dev)
> > > > if (ret)
> > > > return ret;
> > > >
> > > > - priv->cd01_list = binder.cd01_list;
> > > > - if (!priv->cd01_list)
> > > > + if (!binder.cd00_list && !binder.cd01_list)
> > > > return -ENODEV;
> > > >
> > > > - return lwmi_om_fw_attr_add(priv);
> > > > + priv->cd00_list = binder.cd00_list;
> > > > + if (priv->cd00_list) {
> > > > + ret = lwmi_om_hwmon_add(priv);
> > > > + if (ret)
> > > > + return ret;
> > > > + }
> > > > +
> > > > + priv->cd01_list = binder.cd01_list;
> > > > + if (priv->cd01_list) {
> > > > + ret = lwmi_om_fw_attr_add(priv);
> > > > + if (ret) {
> > > > + if (priv->cd00_list)
> > > > + lwmi_om_hwmon_remove(priv);
> > > > + return ret;
> > > > + }
> > > > + }
> > > > +
> > > > + return 0;
> > > > }
> > > >
> > > > /**
> > > > * 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 the HWMON device and all capability data attribute groups. Then
> > > > + * call component_unbind_all to unbind the lenovo-wmi-capdata driver 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);
> > > > + if (priv->cd00_list)
> > > > + lwmi_om_hwmon_remove(priv);
> > > > +
> > > > + if (priv->cd01_list)
> > > > + lwmi_om_fw_attr_remove(priv);
> > > > +
> > > > component_unbind_all(dev, NULL);
> > > > }
> > > >
> > > > @@ -624,6 +916,9 @@ 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);
> > > >
> > > > @@ -654,7 +949,9 @@ 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);
> > > > +
> > > > + if (priv->ida_id >= 0)
> > > > + ida_free(&lwmi_om_ida, priv->ida_id);
> > > > }
> > > >
> > > > static const struct wmi_device_id lwmi_other_id_table[] = {
> > > > @@ -679,5 +976,6 @@ MODULE_IMPORT_NS("LENOVO_WMI_CD");
> > > > MODULE_IMPORT_NS("LENOVO_WMI_HELPERS");
> > > > MODULE_DEVICE_TABLE(wmi, lwmi_other_id_table);
> > > > MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
> > > > +MODULE_AUTHOR("Rong Zhang <i@rong.moe>");
> > > > MODULE_DESCRIPTION("Lenovo Other Mode WMI Driver");
> > > > MODULE_LICENSE("GPL");
> > > > --
> > > > 2.51.0
> > > >
^ permalink raw reply [flat|nested] 20+ messages in thread
end of thread, other threads:[~2025-10-27 12:21 UTC | newest]
Thread overview: 20+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-10-19 21:04 [PATCH 0/6] platform/x86: lenovo-wmi-{capdata,other}: Add HWMON for fan speed Rong Zhang
2025-10-19 21:04 ` [PATCH 1/6] platform/x86: Rename lenovo-wmi-capdata01 to lenovo-wmi-capdata Rong Zhang
2025-10-26 4:41 ` Derek John Clark
2025-10-19 21:04 ` [PATCH 2/6] platform/x86: lenovo-wmi-{capdata,other}: Support multiple Capability Data Rong Zhang
2025-10-20 1:03 ` kernel test robot
2025-10-26 4:43 ` Derek John Clark
2025-10-19 21:04 ` [PATCH 3/6] platform/x86: lenovo-wmi-capdata: Add support for Capability Data 00 Rong Zhang
2025-10-26 4:55 ` Derek John Clark
2025-10-26 17:18 ` Rong Zhang
2025-10-19 21:04 ` [PATCH 4/6] platform/x86: lenovo-wmi-other: Add HWMON for fan speed RPM Rong Zhang
2025-10-26 5:23 ` Derek John Clark
2025-10-26 19:42 ` Rong Zhang
2025-10-26 20:19 ` Derek J. Clark
2025-10-26 23:04 ` Armin Wolf
2025-10-27 12:15 ` Rong Zhang
2025-10-19 21:04 ` [PATCH 5/6] platform/x86: lenovo-wmi-capdata: Add support for Fan Test Data Rong Zhang
2025-10-19 21:04 ` [PATCH 6/6] platform/x86: lenovo-wmi-other: Report min/max RPM and hide dummy fans Rong Zhang
2025-10-26 4:39 ` [PATCH 0/6] platform/x86: lenovo-wmi-{capdata,other}: Add HWMON for fan speed Derek John Clark
2025-10-26 17:11 ` Rong Zhang
2025-10-26 22:59 ` Armin Wolf
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).