* [PATCH v4 0/8] platform-x86: lenovo-wmi: Add fixes and enhancement
@ 2026-03-12 3:10 Derek J. Clark
2026-03-12 3:10 ` [PATCH v4 1/8] platform-x86: lenovo-wmi-other: Move LWMI_FAN_DIV Derek J. Clark
` (7 more replies)
0 siblings, 8 replies; 21+ messages in thread
From: Derek J. Clark @ 2026-03-12 3:10 UTC (permalink / raw)
To: Ilpo Järvinen, Hans de Goede
Cc: Mark Pearson, Armin Wolf, Jonathan Corbet, Rong Zhang, Kurt Borja,
Derek J . Clark, platform-driver-x86, linux-kernel
This series adds many much needed features and fixes to the lenovo-wmi
drivers.
Patch 1 moves LWMI_FAN_DIV to be next to the rest of the fan attribute
defines in preparation for adding additional attrbiute macros. This is
so the attribute macros can all be in the same place in the file.
Patch 2 cleans up tunable_attr_01 by removing an unused pointer and
correctly assigning the members as u8 isntead of u32.
Patch 3 adds a function to make assigning attribute ID's for capdata
cleaner and easier.
Patch 4 addresses bugs where devices that don't support exposed
attributes would still create the attribute, and also attempts to
identify the correct capdata and set/get methods as some legacy
interfaces don't use the custom mode in the method or capdata ID.
Patch 5 adds the remaining CPU attributes that weren't previously
exposed.
Patch 6 adds GPU attributes.
Patch 7 renames a name constant in preparation for patch 6.
Patch 8 adds battery charge-type limiting when supported only by WMI, or
when a module parameter to skip this check is set. The MODULE_PARM_DESC
macro creates one check and two warnings in checkpatch. I reviewed other
examples from the kernel and I am following the same convention, so I
left it as is.
Due to the large number of changes since v3 I decided to not carry over
any new Reviewed-by tags.
Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
---
v4:
- Use loop instead of back gotos for identifying the working attribute
ID.
- Use function instead of macro to assign attribute_id, preserving
types.
- Removed unused defines and enum values.
- Rename charging defines to clarify thier purpose.
- Fixed various formatting issues from v3.
- Added module param to skip ACPI check when loading the driver for
the power supply extension.
- Don't abort adding power supply extension if the ACPI handle from
ideapad is not present.
- Don't worry about symmetric cleanup when cleaning up attributes in
an error state.
- Reword Patch 8 commit message to be more concise.
- Fix wording in Patch 7 to match the changes.
v3: https://lore.kernel.org/platform-driver-x86/20260224043200.2680384-1-derekjohn.clark@gmail.com/
- Re-add HWMON name const and just rename LWMI_OM_FW_ATTR_BASE_PATH
- Fix linker warnings by moving acpi/battery include to the end of the
list.
- Remove CPU/GPU OC features. These attributes are BOOL type and will
need a new constructor that I'll add later.
v2: https://lore.kernel.org/platform-driver-x86/20260215061339.2842486-1-derekjohn.clark@gmail.com/
- Fix gpu_mode misisng from attributes list.
- Fix prototypes for power suppy patch.
- Reorganize CPU and GPU attributes alphabetically.
- Break out the patch consolidating the driver name cost.
- Move some of the refactoring of attribute_id back to into patch 1
where it belongs.
- Fix some additional typos in function prototypes.
v1: https://lore.kernel.org/platform-driver-x86/20260213081243.794288-1-derekjohn.clark@gmail.com/
Derek J. Clark (8):
platform-x86: lenovo-wmi-other: Move LWMI_FAN_DIV
platform-x86: lenovo-wmi-other: Fix tunable_attr_01 struct members
platform/x86: lenovo-wmi-other: Add lwmi_attr_id() function
platform/x86: lenovo-wmi-other: Limit adding attributes to supported
devices
platform/x86: lenovo-wmi-other: Add missing CPU tunable attributes
platform/x86: lenovo-wmi-other: Add GPU tunable attributes
platform-x86: lenovo-wmi-other: Rename LWMI_OM_FW_ATTR_BASE_PATH
platform/x86: lenovo-wmi-other: Add WMI battery charge limiting
.../wmi/devices/lenovo-wmi-other.rst | 19 +
drivers/platform/x86/lenovo/wmi-capdata.h | 7 +-
drivers/platform/x86/lenovo/wmi-gamezone.h | 1 +
drivers/platform/x86/lenovo/wmi-other.c | 586 ++++++++++++++++--
4 files changed, 571 insertions(+), 42 deletions(-)
--
2.53.0
^ permalink raw reply [flat|nested] 21+ messages in thread
* [PATCH v4 1/8] platform-x86: lenovo-wmi-other: Move LWMI_FAN_DIV
2026-03-12 3:10 [PATCH v4 0/8] platform-x86: lenovo-wmi: Add fixes and enhancement Derek J. Clark
@ 2026-03-12 3:10 ` Derek J. Clark
2026-03-12 3:10 ` [PATCH v4 2/8] platform-x86: lenovo-wmi-other: Fix tunable_attr_01 struct members Derek J. Clark
` (6 subsequent siblings)
7 siblings, 0 replies; 21+ messages in thread
From: Derek J. Clark @ 2026-03-12 3:10 UTC (permalink / raw)
To: Ilpo Järvinen, Hans de Goede
Cc: Mark Pearson, Armin Wolf, Jonathan Corbet, Rong Zhang, Kurt Borja,
Derek J . Clark, platform-driver-x86, linux-kernel
Later patches in this series will add additional attribute ID macros.
Keep the fan defines together and permit attribute ID macros to stay
together as well by moving LWMI_FAN_DIV.
Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
---
drivers/platform/x86/lenovo/wmi-other.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/drivers/platform/x86/lenovo/wmi-other.c b/drivers/platform/x86/lenovo/wmi-other.c
index 6040f45aa2b0..caf360b76fc5 100644
--- a/drivers/platform/x86/lenovo/wmi-other.c
+++ b/drivers/platform/x86/lenovo/wmi-other.c
@@ -71,13 +71,13 @@
#define LWMI_FAN_NR 4
#define LWMI_FAN_ID(x) ((x) + LWMI_FAN_ID_BASE)
+#define LWMI_FAN_DIV 100
+
#define LWMI_ATTR_ID_FAN_RPM(x) \
(FIELD_PREP(LWMI_ATTR_DEV_ID_MASK, LWMI_DEVICE_ID_FAN) | \
FIELD_PREP(LWMI_ATTR_FEAT_ID_MASK, LWMI_FEATURE_ID_FAN_RPM) | \
FIELD_PREP(LWMI_ATTR_TYPE_ID_MASK, LWMI_FAN_ID(x)))
-#define LWMI_FAN_DIV 100
-
#define LWMI_OM_FW_ATTR_BASE_PATH "lenovo-wmi-other"
#define LWMI_OM_HWMON_NAME "lenovo_wmi_other"
--
2.53.0
^ permalink raw reply related [flat|nested] 21+ messages in thread
* [PATCH v4 2/8] platform-x86: lenovo-wmi-other: Fix tunable_attr_01 struct members
2026-03-12 3:10 [PATCH v4 0/8] platform-x86: lenovo-wmi: Add fixes and enhancement Derek J. Clark
2026-03-12 3:10 ` [PATCH v4 1/8] platform-x86: lenovo-wmi-other: Move LWMI_FAN_DIV Derek J. Clark
@ 2026-03-12 3:10 ` Derek J. Clark
2026-03-12 3:10 ` [PATCH v4 3/8] platform/x86: lenovo-wmi-other: Add lwmi_attr_id() function Derek J. Clark
` (5 subsequent siblings)
7 siblings, 0 replies; 21+ messages in thread
From: Derek J. Clark @ 2026-03-12 3:10 UTC (permalink / raw)
To: Ilpo Järvinen, Hans de Goede
Cc: Mark Pearson, Armin Wolf, Jonathan Corbet, Rong Zhang, Kurt Borja,
Derek J . Clark, platform-driver-x86, linux-kernel
In struct tunable_attr_01 the capdata pointer is unused and the size of
the id members is u32 when it should be u8. Fix these prior to adding
additional members.
Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
---
drivers/platform/x86/lenovo/wmi-other.c | 7 +++----
1 file changed, 3 insertions(+), 4 deletions(-)
diff --git a/drivers/platform/x86/lenovo/wmi-other.c b/drivers/platform/x86/lenovo/wmi-other.c
index caf360b76fc5..c1728c7c2957 100644
--- a/drivers/platform/x86/lenovo/wmi-other.c
+++ b/drivers/platform/x86/lenovo/wmi-other.c
@@ -546,11 +546,10 @@ static void lwmi_om_fan_info_collect_cd_fan(struct device *dev, struct cd_list *
/* ======== fw_attributes (component: lenovo-wmi-capdata 01) ======== */
struct tunable_attr_01 {
- struct capdata01 *capdata;
struct device *dev;
- u32 feature_id;
- u32 device_id;
- u32 type_id;
+ u8 feature_id;
+ u8 device_id;
+ u8 type_id;
};
static struct tunable_attr_01 ppt_pl1_spl = {
--
2.53.0
^ permalink raw reply related [flat|nested] 21+ messages in thread
* [PATCH v4 3/8] platform/x86: lenovo-wmi-other: Add lwmi_attr_id() function
2026-03-12 3:10 [PATCH v4 0/8] platform-x86: lenovo-wmi: Add fixes and enhancement Derek J. Clark
2026-03-12 3:10 ` [PATCH v4 1/8] platform-x86: lenovo-wmi-other: Move LWMI_FAN_DIV Derek J. Clark
2026-03-12 3:10 ` [PATCH v4 2/8] platform-x86: lenovo-wmi-other: Fix tunable_attr_01 struct members Derek J. Clark
@ 2026-03-12 3:10 ` Derek J. Clark
2026-03-15 1:08 ` Rong Zhang
2026-03-15 1:32 ` Rong Zhang
2026-03-12 3:10 ` [PATCH v4 4/8] platform/x86: lenovo-wmi-other: Limit adding attributes to supported devices Derek J. Clark
` (4 subsequent siblings)
7 siblings, 2 replies; 21+ messages in thread
From: Derek J. Clark @ 2026-03-12 3:10 UTC (permalink / raw)
To: Ilpo Järvinen, Hans de Goede
Cc: Mark Pearson, Armin Wolf, Jonathan Corbet, Rong Zhang, Kurt Borja,
Derek J . Clark, platform-driver-x86, linux-kernel
Adds lwmi_attr_id() function. In the same vein as LWMI_ATTR_ID_FAN_RPM(),
but as a generic, to de-duplicate attribute_id assignment biolerplate.
Reviewed-by: Mark Pearson <mpearson-lenovo@squebb.ca>
Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
---
v4:
- Switch from macro to static inline to preserve types.
---
drivers/platform/x86/lenovo/wmi-gamezone.h | 1 +
drivers/platform/x86/lenovo/wmi-other.c | 59 +++++++++++++---------
2 files changed, 35 insertions(+), 25 deletions(-)
diff --git a/drivers/platform/x86/lenovo/wmi-gamezone.h b/drivers/platform/x86/lenovo/wmi-gamezone.h
index 6b163a5eeb95..ddb919cf6c36 100644
--- a/drivers/platform/x86/lenovo/wmi-gamezone.h
+++ b/drivers/platform/x86/lenovo/wmi-gamezone.h
@@ -10,6 +10,7 @@ enum gamezone_events_type {
};
enum thermal_mode {
+ LWMI_GZ_THERMAL_MODE_NONE = 0x00,
LWMI_GZ_THERMAL_MODE_QUIET = 0x01,
LWMI_GZ_THERMAL_MODE_BALANCED = 0x02,
LWMI_GZ_THERMAL_MODE_PERFORMANCE = 0x03,
diff --git a/drivers/platform/x86/lenovo/wmi-other.c b/drivers/platform/x86/lenovo/wmi-other.c
index c1728c7c2957..9fff9c1f768c 100644
--- a/drivers/platform/x86/lenovo/wmi-other.c
+++ b/drivers/platform/x86/lenovo/wmi-other.c
@@ -73,10 +73,26 @@
#define LWMI_FAN_DIV 100
-#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)))
+/**
+ * lwmi_attr_id() - Formats a capability data attribute ID
+ * @dev_id: The u8 corresponding to the device ID.
+ * @feat_id: The u8 corresponding to the feature ID on the device.
+ * @mode_id: The u8 corresponding to the wmi-gamezone mode for set/get.
+ * @type_id: The u8 corresponding to the sub-device.
+ *
+ * Return: u32.
+ */
+static u32 lwmi_attr_id(u8 dev_id, u8 feat_id, u8 mode_id, u8 type_id)
+{
+ return (FIELD_PREP(LWMI_ATTR_DEV_ID_MASK, dev_id) |
+ FIELD_PREP(LWMI_ATTR_FEAT_ID_MASK, feat_id) |
+ FIELD_PREP(LWMI_ATTR_MODE_ID_MASK, mode_id) |
+ FIELD_PREP(LWMI_ATTR_TYPE_ID_MASK, type_id));
+}
+
+#define LWMI_ATTR_ID_FAN_RPM(x) \
+ lwmi_attr_id(LWMI_DEVICE_ID_FAN, LWMI_FEATURE_ID_FAN_RPM, \
+ LWMI_GZ_THERMAL_MODE_NONE, LWMI_FAN_ID(x))
#define LWMI_OM_FW_ATTR_BASE_PATH "lenovo-wmi-other"
#define LWMI_OM_HWMON_NAME "lenovo_wmi_other"
@@ -550,6 +566,8 @@ struct tunable_attr_01 {
u8 feature_id;
u8 device_id;
u8 type_id;
+ u8 cd_mode_id; /* mode arg for searching capdata */
+ u8 cv_mode_id; /* mode arg for set/get current_value */
};
static struct tunable_attr_01 ppt_pl1_spl = {
@@ -715,12 +733,8 @@ static ssize_t attr_capdata01_show(struct kobject *kobj,
u32 attribute_id;
int value, ret;
- attribute_id =
- FIELD_PREP(LWMI_ATTR_DEV_ID_MASK, tunable_attr->device_id) |
- FIELD_PREP(LWMI_ATTR_FEAT_ID_MASK, tunable_attr->feature_id) |
- FIELD_PREP(LWMI_ATTR_MODE_ID_MASK,
- LWMI_GZ_THERMAL_MODE_CUSTOM) |
- FIELD_PREP(LWMI_ATTR_TYPE_ID_MASK, tunable_attr->type_id);
+ attribute_id = lwmi_attr_id(tunable_attr->device_id, tunable_attr->feature_id,
+ LWMI_GZ_THERMAL_MODE_CUSTOM, tunable_attr->type_id);
ret = lwmi_cd01_get_data(priv->cd01_list, attribute_id, &capdata);
if (ret)
@@ -775,7 +789,6 @@ static ssize_t attr_current_value_store(struct kobject *kobj,
struct wmi_method_args_32 args;
struct capdata01 capdata;
enum thermal_mode mode;
- u32 attribute_id;
u32 value;
int ret;
@@ -786,13 +799,10 @@ static ssize_t attr_current_value_store(struct kobject *kobj,
if (mode != LWMI_GZ_THERMAL_MODE_CUSTOM)
return -EBUSY;
- attribute_id =
- FIELD_PREP(LWMI_ATTR_DEV_ID_MASK, tunable_attr->device_id) |
- FIELD_PREP(LWMI_ATTR_FEAT_ID_MASK, tunable_attr->feature_id) |
- FIELD_PREP(LWMI_ATTR_MODE_ID_MASK, mode) |
- FIELD_PREP(LWMI_ATTR_TYPE_ID_MASK, tunable_attr->type_id);
+ args.arg0 = lwmi_attr_id(tunable_attr->device_id, tunable_attr->feature_id,
+ mode, tunable_attr->type_id);
- ret = lwmi_cd01_get_data(priv->cd01_list, attribute_id, &capdata);
+ ret = lwmi_cd01_get_data(priv->cd01_list, args.arg0, &capdata);
if (ret)
return ret;
@@ -803,7 +813,8 @@ static ssize_t attr_current_value_store(struct kobject *kobj,
if (value < capdata.min_value || value > capdata.max_value)
return -EINVAL;
- args.arg0 = attribute_id;
+ args.arg0 = lwmi_attr_id(tunable_attr->device_id, tunable_attr->feature_id,
+ tunable_attr->cv_mode_id, tunable_attr->type_id);
args.arg1 = value;
ret = lwmi_dev_evaluate_int(priv->wdev, 0x0, LWMI_FEATURE_VALUE_SET,
@@ -837,7 +848,6 @@ static ssize_t attr_current_value_show(struct kobject *kobj,
struct lwmi_om_priv *priv = dev_get_drvdata(tunable_attr->dev);
struct wmi_method_args_32 args;
enum thermal_mode mode;
- u32 attribute_id;
int retval;
int ret;
@@ -845,13 +855,12 @@ static ssize_t attr_current_value_show(struct kobject *kobj,
if (ret)
return ret;
- attribute_id =
- FIELD_PREP(LWMI_ATTR_DEV_ID_MASK, tunable_attr->device_id) |
- FIELD_PREP(LWMI_ATTR_FEAT_ID_MASK, tunable_attr->feature_id) |
- FIELD_PREP(LWMI_ATTR_MODE_ID_MASK, mode) |
- FIELD_PREP(LWMI_ATTR_TYPE_ID_MASK, tunable_attr->type_id);
+ /* If "no-mode" is the supported mode, ensure we never send current mode */
+ if (tunable_attr->cv_mode_id == LWMI_GZ_THERMAL_MODE_NONE)
+ mode = tunable_attr->cv_mode_id;
- args.arg0 = attribute_id;
+ args.arg0 = lwmi_attr_id(tunable_attr->device_id, tunable_attr->feature_id,
+ mode, tunable_attr->type_id);
ret = lwmi_dev_evaluate_int(priv->wdev, 0x0, LWMI_FEATURE_VALUE_GET,
(unsigned char *)&args, sizeof(args),
--
2.53.0
^ permalink raw reply related [flat|nested] 21+ messages in thread
* [PATCH v4 4/8] platform/x86: lenovo-wmi-other: Limit adding attributes to supported devices
2026-03-12 3:10 [PATCH v4 0/8] platform-x86: lenovo-wmi: Add fixes and enhancement Derek J. Clark
` (2 preceding siblings ...)
2026-03-12 3:10 ` [PATCH v4 3/8] platform/x86: lenovo-wmi-other: Add lwmi_attr_id() function Derek J. Clark
@ 2026-03-12 3:10 ` Derek J. Clark
2026-03-15 1:26 ` Rong Zhang
2026-03-12 3:10 ` [PATCH v4 5/8] platform/x86: lenovo-wmi-other: Add missing CPU tunable attributes Derek J. Clark
` (3 subsequent siblings)
7 siblings, 1 reply; 21+ messages in thread
From: Derek J. Clark @ 2026-03-12 3:10 UTC (permalink / raw)
To: Ilpo Järvinen, Hans de Goede
Cc: Mark Pearson, Armin Wolf, Jonathan Corbet, Rong Zhang, Kurt Borja,
Derek J . Clark, platform-driver-x86, linux-kernel
Adds lwmi_is_attr_01_supported, and only creates the attribute subfolder
if the attribute is supported by the hardware. Due to some poorly
implemented BIOS this is a multi-step sequence of events. This is
because:
- Some BIOS support getting the capability data from custom mode (0xff),
while others only support it in no-mode (0x00).
- Some BIOS support get/set for the current value from custom mode (0xff),
while others only support it in no-mode (0x00).
- Some BIOS report capability data for a method that is not fully
implemented.
- Some BIOS have methods fully implemented, but no complimentary
capability data.
To ensure we only expose fully implemented methods with corresponding
capability data, we check each outcome before reporting that an
attribute can be supported.
Checking for lwmi_is_attr_01_supported during remove is not done to
ensure that we don't attempt to call cd01 or send WMI events if one of
the interfaces being removed was the cause of the driver unloading.
Reviewed-by: Mark Pearson <mpearson-lenovo@squebb.ca>
Reported-by: Kurt Borja <kuurtb@gmail.com>
Closes: https://lore.kernel.org/platform-driver-x86/DG60P3SHXR8H.3NSEHMZ6J7XRC@gmail.com/
Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
---
v4:
- Use for loop instead of backtrace gotos for checking if an attribute
is supported.
- Add include for dev_printk.
- Wrap dev_dbg in lwmi_is_attr_01_supported earlier.
- Don't use symmetric cleanup of attributes in error states.
---
drivers/platform/x86/lenovo/wmi-other.c | 76 ++++++++++++++++++++++++-
1 file changed, 74 insertions(+), 2 deletions(-)
diff --git a/drivers/platform/x86/lenovo/wmi-other.c b/drivers/platform/x86/lenovo/wmi-other.c
index 9fff9c1f768c..55a26e5617d4 100644
--- a/drivers/platform/x86/lenovo/wmi-other.c
+++ b/drivers/platform/x86/lenovo/wmi-other.c
@@ -32,6 +32,7 @@
#include <linux/component.h>
#include <linux/container_of.h>
#include <linux/device.h>
+#include <linux/dev_printk.h>
#include <linux/export.h>
#include <linux/gfp_types.h>
#include <linux/hwmon.h>
@@ -871,6 +872,76 @@ static ssize_t attr_current_value_show(struct kobject *kobj,
return sysfs_emit(buf, "%d\n", retval);
}
+/**
+ * lwmi_attr_01_is_supported() - Determine if the given attribute is supported.
+ * @tunable_attr: The attribute to verify.
+ *
+ * First check if the attribute has a corresponding capdata01 table in the cd01
+ * module under the "custom" mode (0xff). If that is not present then check if
+ * there is a corresponding "no-mode" (0x00) entry. If either of those passes,
+ * check capdata->supported for values > 0. If capdata is available, attempt to
+ * determine the set/get mode for the current value property using a similar
+ * pattern. If the value returned by either custom or no-mode is 0, or we get
+ * an error, we assume that mode is not supported. If any of the above checks
+ * fail then the attribute is not fully supported.
+ *
+ * The probed cd_mode_id/cv_mode_id are stored on the tunable_attr for later
+ * reference.
+ *
+ * Return: Support level, or an error code.
+ */
+static int lwmi_attr_01_is_supported(struct tunable_attr_01 *tunable_attr)
+{
+ u8 modes[2] = { LWMI_GZ_THERMAL_MODE_CUSTOM, LWMI_GZ_THERMAL_MODE_NONE };
+ struct lwmi_om_priv *priv = dev_get_drvdata(tunable_attr->dev);
+ struct wmi_method_args_32 args;
+ bool cd_mode_found = false;
+ bool cv_mode_found = false;
+ struct capdata01 capdata;
+ int retval, ret, i;
+
+ /* Determine tunable_attr->cd_mode_id*/
+ for (i = 0; i < ARRAY_SIZE(modes); i++) {
+ args.arg0 = lwmi_attr_id(tunable_attr->device_id, tunable_attr->feature_id,
+ modes[i], tunable_attr->type_id);
+
+ ret = lwmi_cd01_get_data(priv->cd01_list, args.arg0, &capdata);
+ if (ret || !capdata.supported)
+ continue;
+ tunable_attr->cd_mode_id = modes[i];
+ cd_mode_found = true;
+ break;
+ }
+
+ if (!cd_mode_found)
+ return -EOPNOTSUPP;
+
+ /* Determine tunable_attr->cv_mode_id, returns 1 if supported*/
+ for (i = 0; i < ARRAY_SIZE(modes); i++) {
+ args.arg0 = lwmi_attr_id(tunable_attr->device_id, tunable_attr->feature_id,
+ modes[i], tunable_attr->type_id);
+
+ ret = lwmi_dev_evaluate_int(priv->wdev, 0x0, LWMI_FEATURE_VALUE_GET,
+ (unsigned char *)&args, sizeof(args),
+ &retval);
+ if (ret || !retval)
+ continue;
+ tunable_attr->cv_mode_id = modes[i];
+ cv_mode_found = true;
+ break;
+ }
+
+ if (!cv_mode_found)
+ return -EOPNOTSUPP;
+
+ dev_dbg(tunable_attr->dev,
+ "cd_mode_id: %02x%02x%02x%02x, cv_mode_id: %#08x attribute support level: %x\n",
+ tunable_attr->device_id, tunable_attr->feature_id, tunable_attr->cd_mode_id,
+ tunable_attr->type_id, args.arg0, capdata.supported);
+
+ return capdata.supported;
+}
+
/* Lenovo WMI Other Mode Attribute macros */
#define __LWMI_ATTR_RO(_func, _name) \
{ \
@@ -994,12 +1065,13 @@ static int lwmi_om_fw_attr_add(struct lwmi_om_priv *priv)
}
for (i = 0; i < ARRAY_SIZE(cd01_attr_groups) - 1; i++) {
+ cd01_attr_groups[i].tunable_attr->dev = &priv->wdev->dev;
+ if (lwmi_attr_01_is_supported(cd01_attr_groups[i].tunable_attr) <= 0)
+ continue;
err = sysfs_create_group(&priv->fw_attr_kset->kobj,
cd01_attr_groups[i].attr_group);
if (err)
goto err_remove_groups;
-
- cd01_attr_groups[i].tunable_attr->dev = &priv->wdev->dev;
}
return 0;
--
2.53.0
^ permalink raw reply related [flat|nested] 21+ messages in thread
* [PATCH v4 5/8] platform/x86: lenovo-wmi-other: Add missing CPU tunable attributes
2026-03-12 3:10 [PATCH v4 0/8] platform-x86: lenovo-wmi: Add fixes and enhancement Derek J. Clark
` (3 preceding siblings ...)
2026-03-12 3:10 ` [PATCH v4 4/8] platform/x86: lenovo-wmi-other: Limit adding attributes to supported devices Derek J. Clark
@ 2026-03-12 3:10 ` Derek J. Clark
2026-03-12 3:10 ` [PATCH v4 6/8] platform/x86: lenovo-wmi-other: Add GPU " Derek J. Clark
` (2 subsequent siblings)
7 siblings, 0 replies; 21+ messages in thread
From: Derek J. Clark @ 2026-03-12 3:10 UTC (permalink / raw)
To: Ilpo Järvinen, Hans de Goede
Cc: Mark Pearson, Armin Wolf, Jonathan Corbet, Rong Zhang, Kurt Borja,
Derek J . Clark, platform-driver-x86, linux-kernel
Use an enum for all device ID's and CPU attribute feature ID's,
add missing CPU attributes.
Reviewed-by: Mark Pearson <mpearson-lenovo@squebb.ca>
Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
---
v4:
- Align type ID defines.
- Align CPU feature enum values.
- remove cpu_oc_stat from Documentation.
v3:
- Remove cpu_oc_stat.
---
.../wmi/devices/lenovo-wmi-other.rst | 9 ++
drivers/platform/x86/lenovo/wmi-capdata.h | 5 +-
drivers/platform/x86/lenovo/wmi-other.c | 100 ++++++++++++++++--
3 files changed, 107 insertions(+), 7 deletions(-)
diff --git a/Documentation/wmi/devices/lenovo-wmi-other.rst b/Documentation/wmi/devices/lenovo-wmi-other.rst
index 01d471156738..82c17361e749 100644
--- a/Documentation/wmi/devices/lenovo-wmi-other.rst
+++ b/Documentation/wmi/devices/lenovo-wmi-other.rst
@@ -68,9 +68,18 @@ Each attribute has the following properties:
- type
The following firmware-attributes are implemented:
+ - cpu_temp: CPU Thermal Load Limit
+ - ppt_cpu_cl: CPU Cross Loading Power Limit
+ - ppt_pl1_apu_spl: Platform Profile Tracking APU Sustained Power Limit
- ppt_pl1_spl: Platform Profile Tracking Sustained Power Limit
+ - ppt_pl1_spl_cl: Platform Profile Tracking Cross Loading Sustained Power Limit
+ - ppt_pl1_tau: Exceed Duration for Platform Profile Tracking Sustained Power Limit
- ppt_pl2_sppt: Platform Profile Tracking Slow Package Power Tracking
+ - ppt_pl2_sppt_cl: Platform Profile Tracking Cross Loading Slow Package Tracking
- ppt_pl3_fppt: Platform Profile Tracking Fast Package Power Tracking
+ - ppt_pl3_fppt_cl: Platform Profile Tracking Cross Loading Fast Package Power Tracking
+ - ppt_pl4_ipl: Platform Profile Trakcing Instantaneous Power Limit
+ - ppt_pl4_ipl_cl: Platform Profile Tracking Cross Loading Instantaneous Power Limit
LENOVO_FAN_TEST_DATA
-------------------------
diff --git a/drivers/platform/x86/lenovo/wmi-capdata.h b/drivers/platform/x86/lenovo/wmi-capdata.h
index 8c1df3efcc55..0e61a873d5ff 100644
--- a/drivers/platform/x86/lenovo/wmi-capdata.h
+++ b/drivers/platform/x86/lenovo/wmi-capdata.h
@@ -17,7 +17,10 @@
#define LWMI_ATTR_MODE_ID_MASK GENMASK(15, 8)
#define LWMI_ATTR_TYPE_ID_MASK GENMASK(7, 0)
-#define LWMI_DEVICE_ID_FAN 0x04
+enum lwmi_device_id {
+ LWMI_DEVICE_ID_CPU = 0x01,
+ LWMI_DEVICE_ID_FAN = 0x04,
+};
struct component_match;
struct device;
diff --git a/drivers/platform/x86/lenovo/wmi-other.c b/drivers/platform/x86/lenovo/wmi-other.c
index 55a26e5617d4..11b5f71092fe 100644
--- a/drivers/platform/x86/lenovo/wmi-other.c
+++ b/drivers/platform/x86/lenovo/wmi-other.c
@@ -55,15 +55,21 @@
#define LENOVO_OTHER_MODE_GUID "DC2A8805-3A8C-41BA-A6F7-092E0089CD3B"
-#define LWMI_DEVICE_ID_CPU 0x01
-
-#define LWMI_FEATURE_ID_CPU_SPPT 0x01
-#define LWMI_FEATURE_ID_CPU_SPL 0x02
-#define LWMI_FEATURE_ID_CPU_FPPT 0x03
+enum lwmi_feature_id_cpu {
+ LWMI_FEATURE_ID_CPU_SPPT = 0x01,
+ LWMI_FEATURE_ID_CPU_SPL = 0x02,
+ LWMI_FEATURE_ID_CPU_FPPT = 0x03,
+ LWMI_FEATURE_ID_CPU_TEMP = 0x04,
+ LWMI_FEATURE_ID_CPU_APU = 0x05,
+ LWMI_FEATURE_ID_CPU_CL = 0x06,
+ LWMI_FEATURE_ID_CPU_TAU = 0x07,
+ LWMI_FEATURE_ID_CPU_IPL = 0x09,
+};
#define LWMI_FEATURE_ID_FAN_RPM 0x03
-#define LWMI_TYPE_ID_NONE 0x00
+#define LWMI_TYPE_ID_NONE 0x00
+#define LWMI_TYPE_ID_CROSSLOAD 0x01
#define LWMI_FEATURE_VALUE_GET 17
#define LWMI_FEATURE_VALUE_SET 18
@@ -577,18 +583,72 @@ static struct tunable_attr_01 ppt_pl1_spl = {
.type_id = LWMI_TYPE_ID_NONE,
};
+static struct tunable_attr_01 ppt_pl1_spl_cl = {
+ .device_id = LWMI_DEVICE_ID_CPU,
+ .feature_id = LWMI_FEATURE_ID_CPU_SPL,
+ .type_id = LWMI_TYPE_ID_CROSSLOAD,
+};
+
static struct tunable_attr_01 ppt_pl2_sppt = {
.device_id = LWMI_DEVICE_ID_CPU,
.feature_id = LWMI_FEATURE_ID_CPU_SPPT,
.type_id = LWMI_TYPE_ID_NONE,
};
+static struct tunable_attr_01 ppt_pl2_sppt_cl = {
+ .device_id = LWMI_DEVICE_ID_CPU,
+ .feature_id = LWMI_FEATURE_ID_CPU_SPPT,
+ .type_id = LWMI_TYPE_ID_CROSSLOAD,
+};
+
static struct tunable_attr_01 ppt_pl3_fppt = {
.device_id = LWMI_DEVICE_ID_CPU,
.feature_id = LWMI_FEATURE_ID_CPU_FPPT,
.type_id = LWMI_TYPE_ID_NONE,
};
+static struct tunable_attr_01 ppt_pl3_fppt_cl = {
+ .device_id = LWMI_DEVICE_ID_CPU,
+ .feature_id = LWMI_FEATURE_ID_CPU_FPPT,
+ .type_id = LWMI_TYPE_ID_CROSSLOAD,
+};
+
+static struct tunable_attr_01 cpu_temp = {
+ .device_id = LWMI_DEVICE_ID_CPU,
+ .feature_id = LWMI_FEATURE_ID_CPU_TEMP,
+ .type_id = LWMI_TYPE_ID_NONE,
+};
+
+static struct tunable_attr_01 ppt_pl1_apu_spl = {
+ .device_id = LWMI_DEVICE_ID_CPU,
+ .feature_id = LWMI_FEATURE_ID_CPU_APU,
+ .type_id = LWMI_TYPE_ID_NONE,
+};
+
+static struct tunable_attr_01 ppt_cpu_cl = {
+ .device_id = LWMI_DEVICE_ID_CPU,
+ .feature_id = LWMI_FEATURE_ID_CPU_CL,
+ .type_id = LWMI_TYPE_ID_NONE,
+};
+
+static struct tunable_attr_01 ppt_pl1_tau = {
+ .device_id = LWMI_DEVICE_ID_CPU,
+ .feature_id = LWMI_FEATURE_ID_CPU_TAU,
+ .type_id = LWMI_TYPE_ID_NONE,
+};
+
+static struct tunable_attr_01 ppt_pl4_ipl = {
+ .device_id = LWMI_DEVICE_ID_CPU,
+ .feature_id = LWMI_FEATURE_ID_CPU_IPL,
+ .type_id = LWMI_TYPE_ID_NONE,
+};
+
+static struct tunable_attr_01 ppt_pl4_ipl_cl = {
+ .device_id = LWMI_DEVICE_ID_CPU,
+ .feature_id = LWMI_FEATURE_ID_CPU_IPL,
+ .type_id = LWMI_TYPE_ID_CROSSLOAD,
+};
+
struct capdata01_attr_group {
const struct attribute_group *attr_group;
struct tunable_attr_01 *tunable_attr;
@@ -1019,17 +1079,45 @@ static int lwmi_attr_01_is_supported(struct tunable_attr_01 *tunable_attr)
.name = _fsname, .attrs = _attrname##_attrs \
}
+LWMI_ATTR_GROUP_TUNABLE_CAP01(cpu_temp, "cpu_temp",
+ "Set the CPU thermal load limit");
+LWMI_ATTR_GROUP_TUNABLE_CAP01(ppt_cpu_cl, "ppt_cpu_cl",
+ "Set the CPU cross loading power limit");
+LWMI_ATTR_GROUP_TUNABLE_CAP01(ppt_pl1_apu_spl, "ppt_pl1_apu_spl",
+ "Set the APU sustained power limit");
LWMI_ATTR_GROUP_TUNABLE_CAP01(ppt_pl1_spl, "ppt_pl1_spl",
"Set the CPU sustained power limit");
+LWMI_ATTR_GROUP_TUNABLE_CAP01(ppt_pl1_spl_cl, "ppt_pl1_spl_cl",
+ "Set the CPU cross loading sustained power limit");
LWMI_ATTR_GROUP_TUNABLE_CAP01(ppt_pl2_sppt, "ppt_pl2_sppt",
"Set the CPU slow package power tracking limit");
+LWMI_ATTR_GROUP_TUNABLE_CAP01(ppt_pl2_sppt_cl, "ppt_pl2_sppt_cl",
+ "Set the CPU cross loading slow package power tracking limit");
LWMI_ATTR_GROUP_TUNABLE_CAP01(ppt_pl3_fppt, "ppt_pl3_fppt",
"Set the CPU fast package power tracking limit");
+LWMI_ATTR_GROUP_TUNABLE_CAP01(ppt_pl3_fppt_cl, "ppt_pl3_fppt_cl",
+ "Set the CPU cross loading fast package power tracking limit");
+LWMI_ATTR_GROUP_TUNABLE_CAP01(ppt_pl1_tau, "ppt_pl1_tau",
+ "Set the CPU sustained power limit exceed duration");
+LWMI_ATTR_GROUP_TUNABLE_CAP01(ppt_pl4_ipl, "ppt_pl4_ipl",
+ "Set the CPU instantaneous power limit");
+LWMI_ATTR_GROUP_TUNABLE_CAP01(ppt_pl4_ipl_cl, "ppt_pl4_ipl_cl",
+ "Set the CPU cross loading instantaneous power limit");
+
static struct capdata01_attr_group cd01_attr_groups[] = {
+ { &cpu_temp_attr_group, &cpu_temp },
+ { &ppt_cpu_cl_attr_group, &ppt_cpu_cl },
+ { &ppt_pl1_apu_spl_attr_group, &ppt_pl1_apu_spl },
{ &ppt_pl1_spl_attr_group, &ppt_pl1_spl },
+ { &ppt_pl1_spl_cl_attr_group, &ppt_pl1_spl_cl },
+ { &ppt_pl1_tau_attr_group, &ppt_pl1_tau },
{ &ppt_pl2_sppt_attr_group, &ppt_pl2_sppt },
+ { &ppt_pl2_sppt_cl_attr_group, &ppt_pl2_sppt_cl },
{ &ppt_pl3_fppt_attr_group, &ppt_pl3_fppt },
+ { &ppt_pl3_fppt_cl_attr_group, &ppt_pl3_fppt_cl },
+ { &ppt_pl4_ipl_attr_group, &ppt_pl4_ipl },
+ { &ppt_pl4_ipl_cl_attr_group, &ppt_pl4_ipl_cl },
{},
};
--
2.53.0
^ permalink raw reply related [flat|nested] 21+ messages in thread
* [PATCH v4 6/8] platform/x86: lenovo-wmi-other: Add GPU tunable attributes
2026-03-12 3:10 [PATCH v4 0/8] platform-x86: lenovo-wmi: Add fixes and enhancement Derek J. Clark
` (4 preceding siblings ...)
2026-03-12 3:10 ` [PATCH v4 5/8] platform/x86: lenovo-wmi-other: Add missing CPU tunable attributes Derek J. Clark
@ 2026-03-12 3:10 ` Derek J. Clark
2026-03-12 3:10 ` [PATCH v4 7/8] platform-x86: lenovo-wmi-other: Rename LWMI_OM_FW_ATTR_BASE_PATH Derek J. Clark
2026-03-12 3:10 ` [PATCH v4 8/8] platform/x86: lenovo-wmi-other: Add WMI battery charge limiting Derek J. Clark
7 siblings, 0 replies; 21+ messages in thread
From: Derek J. Clark @ 2026-03-12 3:10 UTC (permalink / raw)
To: Ilpo Järvinen, Hans de Goede
Cc: Mark Pearson, Armin Wolf, Jonathan Corbet, Rong Zhang, Kurt Borja,
Derek J . Clark, platform-driver-x86, linux-kernel
Use an enum for all GPU attribute feature ID's and add GPU attributes.
Reviewed-by: Mark Pearson <mpearson-lenovo@squebb.ca>
Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
---
v4:
- Align CPU feature enum values.
- Remove gpu_oc_stat from Documentation.
v3:
- Remove gpu_oc_stat.
---
.../wmi/devices/lenovo-wmi-other.rst | 10 ++
drivers/platform/x86/lenovo/wmi-capdata.h | 1 +
drivers/platform/x86/lenovo/wmi-other.c | 105 ++++++++++++++++++
3 files changed, 116 insertions(+)
diff --git a/Documentation/wmi/devices/lenovo-wmi-other.rst b/Documentation/wmi/devices/lenovo-wmi-other.rst
index 82c17361e749..bfbea59637bd 100644
--- a/Documentation/wmi/devices/lenovo-wmi-other.rst
+++ b/Documentation/wmi/devices/lenovo-wmi-other.rst
@@ -69,6 +69,16 @@ Each attribute has the following properties:
The following firmware-attributes are implemented:
- cpu_temp: CPU Thermal Load Limit
+ - dgpu_boost_clk: Dedicated GPU Boost Clock
+ - dgpu_enable: Dedicated GPU Enabled Status
+ - gpu_didvid: GPU Device Identifier and Vendor Identifier
+ - gpu_mode: GPU Mode by Power Limit
+ - gpu_nv_ac_offset: Nvidia GPU AC Total Processing Power Baseline Offset
+ - gpu_nv_bpl: Nvidia GPU Base Power Limit
+ - gpu_nv_cpu_boost: Nvidia GPU to CPU Dynamic Boost Limit
+ - gpu_nv_ctgp: Nvidia GPU Configurable Total Graphics Power
+ - gpu_nv_ppab: Nvidia GPU Power Performance Aware Boost Limit
+ - gpu_temp: GPU Thermal Load Limit
- ppt_cpu_cl: CPU Cross Loading Power Limit
- ppt_pl1_apu_spl: Platform Profile Tracking APU Sustained Power Limit
- ppt_pl1_spl: Platform Profile Tracking Sustained Power Limit
diff --git a/drivers/platform/x86/lenovo/wmi-capdata.h b/drivers/platform/x86/lenovo/wmi-capdata.h
index 0e61a873d5ff..196481b1ce17 100644
--- a/drivers/platform/x86/lenovo/wmi-capdata.h
+++ b/drivers/platform/x86/lenovo/wmi-capdata.h
@@ -19,6 +19,7 @@
enum lwmi_device_id {
LWMI_DEVICE_ID_CPU = 0x01,
+ LWMI_DEVICE_ID_GPU = 0x02,
LWMI_DEVICE_ID_FAN = 0x04,
};
diff --git a/drivers/platform/x86/lenovo/wmi-other.c b/drivers/platform/x86/lenovo/wmi-other.c
index 11b5f71092fe..7b98e9271eec 100644
--- a/drivers/platform/x86/lenovo/wmi-other.c
+++ b/drivers/platform/x86/lenovo/wmi-other.c
@@ -66,6 +66,19 @@ enum lwmi_feature_id_cpu {
LWMI_FEATURE_ID_CPU_IPL = 0x09,
};
+enum lwmi_feature_id_gpu {
+ LWMI_FEATURE_ID_GPU_NV_PPAB = 0x01,
+ LWMI_FEATURE_ID_GPU_NV_CTGP = 0x02,
+ LWMI_FEATURE_ID_GPU_TEMP = 0x03,
+ LWMI_FEATURE_ID_GPU_AC_OFFSET = 0x04,
+ LWMI_FEATURE_ID_DGPU_BOOST_CLK = 0x06,
+ LWMI_FEATURE_ID_DGPU_EN = 0x07,
+ LWMI_FEATURE_ID_GPU_MODE = 0x08,
+ LWMI_FEATURE_ID_DGPU_DIDVID = 0x09,
+ LWMI_FEATURE_ID_GPU_NV_BPL = 0x0a,
+ LWMI_FEATURE_ID_GPU_NV_CPU_BOOST = 0x0b,
+};
+
#define LWMI_FEATURE_ID_FAN_RPM 0x03
#define LWMI_TYPE_ID_NONE 0x00
@@ -649,6 +662,66 @@ static struct tunable_attr_01 ppt_pl4_ipl_cl = {
.type_id = LWMI_TYPE_ID_CROSSLOAD,
};
+static struct tunable_attr_01 gpu_nv_ppab = {
+ .device_id = LWMI_DEVICE_ID_GPU,
+ .feature_id = LWMI_FEATURE_ID_GPU_NV_PPAB,
+ .type_id = LWMI_TYPE_ID_NONE,
+};
+
+static struct tunable_attr_01 gpu_nv_ctgp = {
+ .device_id = LWMI_DEVICE_ID_GPU,
+ .feature_id = LWMI_FEATURE_ID_GPU_NV_CTGP,
+ .type_id = LWMI_TYPE_ID_NONE,
+};
+
+static struct tunable_attr_01 gpu_temp = {
+ .device_id = LWMI_DEVICE_ID_GPU,
+ .feature_id = LWMI_FEATURE_ID_GPU_TEMP,
+ .type_id = LWMI_TYPE_ID_NONE,
+};
+
+static struct tunable_attr_01 gpu_nv_ac_offset = {
+ .device_id = LWMI_DEVICE_ID_GPU,
+ .feature_id = LWMI_FEATURE_ID_GPU_AC_OFFSET,
+ .type_id = LWMI_TYPE_ID_NONE,
+};
+
+static struct tunable_attr_01 dgpu_boost_clk = {
+ .device_id = LWMI_DEVICE_ID_GPU,
+ .feature_id = LWMI_FEATURE_ID_DGPU_BOOST_CLK,
+ .type_id = LWMI_TYPE_ID_NONE,
+};
+
+static struct tunable_attr_01 dgpu_enable = {
+ .device_id = LWMI_DEVICE_ID_GPU,
+ .feature_id = LWMI_FEATURE_ID_DGPU_EN,
+ .type_id = LWMI_TYPE_ID_NONE,
+};
+
+static struct tunable_attr_01 gpu_mode = {
+ .device_id = LWMI_DEVICE_ID_GPU,
+ .feature_id = LWMI_FEATURE_ID_GPU_MODE,
+ .type_id = LWMI_TYPE_ID_NONE,
+};
+
+static struct tunable_attr_01 dgpu_didvid = {
+ .device_id = LWMI_DEVICE_ID_GPU,
+ .feature_id = LWMI_FEATURE_ID_DGPU_DIDVID,
+ .type_id = LWMI_TYPE_ID_NONE,
+};
+
+static struct tunable_attr_01 gpu_nv_bpl = {
+ .device_id = LWMI_DEVICE_ID_GPU,
+ .feature_id = LWMI_FEATURE_ID_GPU_NV_BPL,
+ .type_id = LWMI_TYPE_ID_NONE,
+};
+
+static struct tunable_attr_01 gpu_nv_cpu_boost = {
+ .device_id = LWMI_DEVICE_ID_GPU,
+ .feature_id = LWMI_FEATURE_ID_GPU_NV_CPU_BOOST,
+ .type_id = LWMI_TYPE_ID_NONE,
+};
+
struct capdata01_attr_group {
const struct attribute_group *attr_group;
struct tunable_attr_01 *tunable_attr;
@@ -1079,6 +1152,7 @@ static int lwmi_attr_01_is_supported(struct tunable_attr_01 *tunable_attr)
.name = _fsname, .attrs = _attrname##_attrs \
}
+/* CPU tunable attributes */
LWMI_ATTR_GROUP_TUNABLE_CAP01(cpu_temp, "cpu_temp",
"Set the CPU thermal load limit");
LWMI_ATTR_GROUP_TUNABLE_CAP01(ppt_cpu_cl, "ppt_cpu_cl",
@@ -1104,9 +1178,40 @@ LWMI_ATTR_GROUP_TUNABLE_CAP01(ppt_pl4_ipl, "ppt_pl4_ipl",
LWMI_ATTR_GROUP_TUNABLE_CAP01(ppt_pl4_ipl_cl, "ppt_pl4_ipl_cl",
"Set the CPU cross loading instantaneous power limit");
+/* GPU tunable attributes */
+LWMI_ATTR_GROUP_TUNABLE_CAP01(dgpu_boost_clk, "gpu_boost_clk",
+ "Set the dedicated GPU boost clock");
+LWMI_ATTR_GROUP_TUNABLE_CAP01(dgpu_didvid, "gpu_didvid",
+ "Get the GPU device identifier and vendor identifier");
+LWMI_ATTR_GROUP_TUNABLE_CAP01(dgpu_enable, "dgpu_enable",
+ "Set the dedicated Nvidia GPU enabled status");
+LWMI_ATTR_GROUP_TUNABLE_CAP01(gpu_mode, "gpu_mode",
+ "Set the GPU mode by power limit");
+LWMI_ATTR_GROUP_TUNABLE_CAP01(gpu_nv_ac_offset, "gpu_nv_ac_offset",
+ "Set the Nvidia GPU AC total processing power baseline offset");
+LWMI_ATTR_GROUP_TUNABLE_CAP01(gpu_nv_bpl, "gpu_nv_bpl",
+ "Set the Nvidia GPU base power limit");
+LWMI_ATTR_GROUP_TUNABLE_CAP01(gpu_nv_cpu_boost, "gpu_nv_cpu_boost",
+ "Set the Nvidia GPU to CPU dynamic boost limit");
+LWMI_ATTR_GROUP_TUNABLE_CAP01(gpu_nv_ctgp, "gpu_nv_ctgp",
+ "Set the GPU configurable total graphics power");
+LWMI_ATTR_GROUP_TUNABLE_CAP01(gpu_nv_ppab, "gpu_nv_ppab",
+ "Set the Nvidia GPU power performance aware boost limit");
+LWMI_ATTR_GROUP_TUNABLE_CAP01(gpu_temp, "gpu_temp",
+ "Set the GPU thermal load limit");
static struct capdata01_attr_group cd01_attr_groups[] = {
{ &cpu_temp_attr_group, &cpu_temp },
+ { &dgpu_boost_clk_attr_group, &dgpu_boost_clk },
+ { &dgpu_didvid_attr_group, &dgpu_didvid },
+ { &dgpu_enable_attr_group, &dgpu_enable },
+ { &gpu_mode_attr_group, &gpu_mode },
+ { &gpu_nv_ac_offset_attr_group, &gpu_nv_ac_offset },
+ { &gpu_nv_bpl_attr_group, &gpu_nv_bpl },
+ { &gpu_nv_cpu_boost_attr_group, &gpu_nv_cpu_boost },
+ { &gpu_nv_ctgp_attr_group, &gpu_nv_ctgp },
+ { &gpu_nv_ppab_attr_group, &gpu_nv_ppab },
+ { &gpu_temp_attr_group, &gpu_temp },
{ &ppt_cpu_cl_attr_group, &ppt_cpu_cl },
{ &ppt_pl1_apu_spl_attr_group, &ppt_pl1_apu_spl },
{ &ppt_pl1_spl_attr_group, &ppt_pl1_spl },
--
2.53.0
^ permalink raw reply related [flat|nested] 21+ messages in thread
* [PATCH v4 7/8] platform-x86: lenovo-wmi-other: Rename LWMI_OM_FW_ATTR_BASE_PATH
2026-03-12 3:10 [PATCH v4 0/8] platform-x86: lenovo-wmi: Add fixes and enhancement Derek J. Clark
` (5 preceding siblings ...)
2026-03-12 3:10 ` [PATCH v4 6/8] platform/x86: lenovo-wmi-other: Add GPU " Derek J. Clark
@ 2026-03-12 3:10 ` Derek J. Clark
2026-03-12 3:10 ` [PATCH v4 8/8] platform/x86: lenovo-wmi-other: Add WMI battery charge limiting Derek J. Clark
7 siblings, 0 replies; 21+ messages in thread
From: Derek J. Clark @ 2026-03-12 3:10 UTC (permalink / raw)
To: Ilpo Järvinen, Hans de Goede
Cc: Mark Pearson, Armin Wolf, Jonathan Corbet, Rong Zhang, Kurt Borja,
Derek J . Clark, platform-driver-x86, linux-kernel
In the next patch a power supply extension is added which requires
a name attribute. Instead of creating another const macro with the
same information, rename LWMI_OM_FW_ATTR_BASE_PATH to
LWMI_OM_SYSFS_NAME.
Reviewed-by: Mark Pearson <mpearson-lenovo@squebb.ca>
Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
---
drivers/platform/x86/lenovo/wmi-other.c | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/drivers/platform/x86/lenovo/wmi-other.c b/drivers/platform/x86/lenovo/wmi-other.c
index 7b98e9271eec..61ac9bee352f 100644
--- a/drivers/platform/x86/lenovo/wmi-other.c
+++ b/drivers/platform/x86/lenovo/wmi-other.c
@@ -114,7 +114,7 @@ static u32 lwmi_attr_id(u8 dev_id, u8 feat_id, u8 mode_id, u8 type_id)
lwmi_attr_id(LWMI_DEVICE_ID_FAN, LWMI_FEATURE_ID_FAN_RPM, \
LWMI_GZ_THERMAL_MODE_NONE, LWMI_FAN_ID(x))
-#define LWMI_OM_FW_ATTR_BASE_PATH "lenovo-wmi-other"
+#define LWMI_OM_SYSFS_NAME "lenovo-wmi-other"
#define LWMI_OM_HWMON_NAME "lenovo_wmi_other"
static BLOCKING_NOTIFIER_HEAD(om_chain_head);
@@ -1243,8 +1243,7 @@ static int lwmi_om_fw_attr_add(struct lwmi_om_priv *priv)
priv->fw_attr_dev = device_create(&firmware_attributes_class, NULL,
MKDEV(0, 0), NULL, "%s-%u",
- LWMI_OM_FW_ATTR_BASE_PATH,
- priv->ida_id);
+ LWMI_OM_SYSFS_NAME, priv->ida_id);
if (IS_ERR(priv->fw_attr_dev)) {
err = PTR_ERR(priv->fw_attr_dev);
goto err_free_ida;
--
2.53.0
^ permalink raw reply related [flat|nested] 21+ messages in thread
* [PATCH v4 8/8] platform/x86: lenovo-wmi-other: Add WMI battery charge limiting
2026-03-12 3:10 [PATCH v4 0/8] platform-x86: lenovo-wmi: Add fixes and enhancement Derek J. Clark
` (6 preceding siblings ...)
2026-03-12 3:10 ` [PATCH v4 7/8] platform-x86: lenovo-wmi-other: Rename LWMI_OM_FW_ATTR_BASE_PATH Derek J. Clark
@ 2026-03-12 3:10 ` Derek J. Clark
2026-03-15 2:00 ` Rong Zhang
2026-03-17 23:46 ` kernel test robot
7 siblings, 2 replies; 21+ messages in thread
From: Derek J. Clark @ 2026-03-12 3:10 UTC (permalink / raw)
To: Ilpo Järvinen, Hans de Goede
Cc: Mark Pearson, Armin Wolf, Jonathan Corbet, Rong Zhang, Kurt Borja,
Derek J . Clark, platform-driver-x86, linux-kernel
Add charge-type power supply extension for devices that support WMI based
charge enable/disable.
Lenovo Legion devices that implement WMI function and capdata ID
0x03010001 in their BIOS are able to enable or disable charging at 80%
through the lenovo-wmi-other interface. Add a charge_type power supply
extension to expose this capability to the sysfs.
The ideapad_laptop driver can also provide the charge_type attribute. To
avoid conflicts between the drivers, get the acpi_handle and do the same
check that ideapad_laptop does when it enables the feature. If the
feature is supported in ideapad_laptop, abort adding the extension from
lenovo-wmi-other. The ACPI method is more reliable when both are
present, from my testing, so we can prefer that implementation and do
not need to worry about de-conflicting from inside that driver. A new
module parameter, force_load_psy_ext, is provided to bypass this ACPI
check, if desired.
Reviewed-by: Mark Pearson <mpearson-lenovo@squebb.ca>
Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
---
v4:
- Remove unused defines.
- Disambiguate charging defines by renaming them to be more consistent
with the kernel modes they represent.
- Add module parameter to ignore ACPI checks.
- Don't fail if the ACPI handle isn't found, skip the ACPI check
instead.
---
drivers/platform/x86/lenovo/wmi-capdata.h | 1 +
drivers/platform/x86/lenovo/wmi-other.c | 234 +++++++++++++++++++++-
2 files changed, 234 insertions(+), 1 deletion(-)
diff --git a/drivers/platform/x86/lenovo/wmi-capdata.h b/drivers/platform/x86/lenovo/wmi-capdata.h
index 196481b1ce17..cc0d1c176c2f 100644
--- a/drivers/platform/x86/lenovo/wmi-capdata.h
+++ b/drivers/platform/x86/lenovo/wmi-capdata.h
@@ -20,6 +20,7 @@
enum lwmi_device_id {
LWMI_DEVICE_ID_CPU = 0x01,
LWMI_DEVICE_ID_GPU = 0x02,
+ LWMI_DEVICE_ID_PSU = 0x03,
LWMI_DEVICE_ID_FAN = 0x04,
};
diff --git a/drivers/platform/x86/lenovo/wmi-other.c b/drivers/platform/x86/lenovo/wmi-other.c
index 61ac9bee352f..cab9ae6bd811 100644
--- a/drivers/platform/x86/lenovo/wmi-other.c
+++ b/drivers/platform/x86/lenovo/wmi-other.c
@@ -43,9 +43,12 @@
#include <linux/module.h>
#include <linux/notifier.h>
#include <linux/platform_profile.h>
+#include <linux/power_supply.h>
#include <linux/types.h>
#include <linux/wmi.h>
+#include <acpi/battery.h>
+
#include "wmi-capdata.h"
#include "wmi-events.h"
#include "wmi-gamezone.h"
@@ -79,10 +82,12 @@ enum lwmi_feature_id_gpu {
LWMI_FEATURE_ID_GPU_NV_CPU_BOOST = 0x0b,
};
-#define LWMI_FEATURE_ID_FAN_RPM 0x03
+#define LWMI_FEATURE_ID_FAN_RPM 0x03
+#define LWMI_FEATURE_ID_PSU_CHARGE_TYPE 0x01
#define LWMI_TYPE_ID_NONE 0x00
#define LWMI_TYPE_ID_CROSSLOAD 0x01
+#define LWMI_TYPE_ID_PSU_AC 0x01
#define LWMI_FEATURE_VALUE_GET 17
#define LWMI_FEATURE_VALUE_SET 18
@@ -93,6 +98,9 @@ enum lwmi_feature_id_gpu {
#define LWMI_FAN_DIV 100
+#define LWMI_CHARGE_TYPE_STANDARD 0x00
+#define LWMI_CHARGE_TYPE_LONGLIFE 0x01
+
/**
* lwmi_attr_id() - Formats a capability data attribute ID
* @dev_id: The u8 corresponding to the device ID.
@@ -114,6 +122,10 @@ static u32 lwmi_attr_id(u8 dev_id, u8 feat_id, u8 mode_id, u8 type_id)
lwmi_attr_id(LWMI_DEVICE_ID_FAN, LWMI_FEATURE_ID_FAN_RPM, \
LWMI_GZ_THERMAL_MODE_NONE, LWMI_FAN_ID(x))
+#define LWMI_ATTR_ID_PSU(feat, type) \
+ lwmi_attr_id(LWMI_DEVICE_ID_PSU, feat, \
+ LWMI_GZ_THERMAL_MODE_NONE, type)
+
#define LWMI_OM_SYSFS_NAME "lenovo-wmi-other"
#define LWMI_OM_HWMON_NAME "lenovo_wmi_other"
@@ -155,6 +167,8 @@ struct lwmi_om_priv {
bool capdata00_collected : 1;
bool capdata_fan_collected : 1;
} fan_flags;
+
+ struct acpi_battery_hook battery_hook;
};
/*
@@ -579,6 +593,223 @@ static void lwmi_om_fan_info_collect_cd_fan(struct device *dev, struct cd_list *
lwmi_om_hwmon_add(priv);
}
+/* ======== Power Supply Extension (component: lenovo-wmi-capdata 00) ======== */
+
+/**
+ * lwmi_psy_ext_get_prop() - Get a power_supply_ext property
+ * @ps: The battery that was extended
+ * @ext: The extension
+ * @ext_data: Pointer the lwmi_om_priv drvdata
+ * @prop: The property to read
+ * @val: The value to return
+ *
+ * Writes the given value to the power_supply_ext property
+ *
+ * Return: 0 on success, or an error
+ */
+static int lwmi_psy_ext_get_prop(struct power_supply *ps,
+ const struct power_supply_ext *ext,
+ void *ext_data,
+ enum power_supply_property prop,
+ union power_supply_propval *val)
+{
+ struct lwmi_om_priv *priv = ext_data;
+ struct wmi_method_args_32 args;
+ u32 retval;
+ int ret;
+
+ args.arg0 = LWMI_ATTR_ID_PSU(LWMI_FEATURE_ID_PSU_CHARGE_TYPE, LWMI_TYPE_ID_PSU_AC);
+
+ ret = lwmi_dev_evaluate_int(priv->wdev, 0x0, LWMI_FEATURE_VALUE_GET,
+ (unsigned char *)&args, sizeof(args),
+ &retval);
+ if (ret)
+ return ret;
+
+ dev_dbg(&priv->wdev->dev, "Got return value %x for property %x\n", retval, prop);
+
+ if (retval == LWMI_CHARGE_TYPE_LONGLIFE)
+ val->intval = POWER_SUPPLY_CHARGE_TYPE_LONGLIFE;
+ else
+ val->intval = POWER_SUPPLY_CHARGE_TYPE_STANDARD;
+
+ return 0;
+}
+
+/**
+ * lwmi_psy_ext_set_prop() - Set a power_supply_ext property
+ * @ps: The battery that was extended
+ * @ext: The extension
+ * @ext_data: Pointer the lwmi_om_priv drvdata
+ * @prop: The property to write
+ * @val: The value to write
+ *
+ * Writes the given value to the power_supply_ext property
+ *
+ * Return: 0 on success, or an error
+ */
+static int lwmi_psy_ext_set_prop(struct power_supply *ps,
+ const struct power_supply_ext *ext,
+ void *ext_data,
+ enum power_supply_property prop,
+ const union power_supply_propval *val)
+{
+ struct lwmi_om_priv *priv = ext_data;
+ struct wmi_method_args_32 args;
+
+ args.arg0 = LWMI_ATTR_ID_PSU(LWMI_FEATURE_ID_PSU_CHARGE_TYPE, LWMI_TYPE_ID_PSU_AC);
+ if (val->intval == POWER_SUPPLY_CHARGE_TYPE_LONGLIFE)
+ args.arg1 = LWMI_CHARGE_TYPE_LONGLIFE;
+ else
+ args.arg1 = LWMI_CHARGE_TYPE_STANDARD;
+
+ dev_dbg(&priv->wdev->dev, "Attempting to set %#08x for property %x to %x\n",
+ args.arg0, prop, args.arg1);
+
+ return lwmi_dev_evaluate_int(priv->wdev, 0x0, LWMI_FEATURE_VALUE_SET,
+ (unsigned char *)&args, sizeof(args), NULL);
+}
+
+/**
+ * lwmi_psy_prop_is_writeable() - Determine if the property is supported
+ * @ps: The battery that was extended
+ * @ext: The extension
+ * @ext_data: Pointer the lwmi_om_priv drvdata
+ * @prop: The property to check
+ *
+ * Checks capdata 00 to determine if the property is supported.
+ *
+ * Return: Support level, or false
+ */
+static int lwmi_psy_prop_is_writeable(struct power_supply *ps,
+ const struct power_supply_ext *ext,
+ void *ext_data,
+ enum power_supply_property prop)
+{
+ struct lwmi_om_priv *priv = ext_data;
+ struct capdata00 capdata;
+ u32 attribute_id = LWMI_ATTR_ID_PSU(LWMI_FEATURE_ID_PSU_CHARGE_TYPE, LWMI_TYPE_ID_PSU_AC);
+ int ret;
+
+ ret = lwmi_cd00_get_data(priv->cd00_list, attribute_id, &capdata);
+ if (ret)
+ return false;
+
+ dev_dbg(&priv->wdev->dev, "Battery charge mode (%#08x) support level: %x\n",
+ attribute_id, capdata.supported);
+
+ return capdata.supported;
+}
+
+static const enum power_supply_property lwmi_psy_ext_props[] = {
+ POWER_SUPPLY_PROP_CHARGE_TYPES,
+};
+
+static const struct power_supply_ext lwmi_psy_ext = {
+ .name = LWMI_OM_SYSFS_NAME,
+ .properties = lwmi_psy_ext_props,
+ .num_properties = ARRAY_SIZE(lwmi_psy_ext_props),
+ .charge_types = (BIT(POWER_SUPPLY_CHARGE_TYPE_STANDARD) |
+ BIT(POWER_SUPPLY_CHARGE_TYPE_LONGLIFE)),
+ .get_property = lwmi_psy_ext_get_prop,
+ .set_property = lwmi_psy_ext_set_prop,
+ .property_is_writeable = lwmi_psy_prop_is_writeable,
+};
+
+/**
+ * lwmi_add_battery() - Connect the power_supply_ext
+ * @battery: The battery to extend
+ * @hook: The driver hook used to extend the battery
+ *
+ * Return: 0 on success, or an error.
+ */
+static int lwmi_add_battery(struct power_supply *battery, struct acpi_battery_hook *hook)
+{
+ struct lwmi_om_priv *priv = container_of(hook, struct lwmi_om_priv, battery_hook);
+
+ return power_supply_register_extension(battery, &lwmi_psy_ext, &priv->wdev->dev, priv);
+}
+
+/**
+ * lwmi_remove_battery() - Disconnect the power_supply_ext
+ * @battery: The battery that was extended
+ * @hook: The driver hook used to extend the battery
+ *
+ * Return: 0 on success, or an error.
+ */
+static int lwmi_remove_battery(struct power_supply *battery, struct acpi_battery_hook *hook)
+{
+ power_supply_unregister_extension(battery, &lwmi_psy_ext);
+ return 0;
+}
+
+/**
+ * lwmi_acpi_match() - Attempts to return the ideapad acpi handle
+ * @handle: The ACPI handle that manages battery charging
+ * @lvl: Unused
+ * @context: Void pointer to the acpi_handle object to return
+ * @retval: Unused
+ *
+ * Checks if the ideapad_laptop driver is going to manage charge_type first,
+ * then if not, hooks the battery to our WMI methods.
+ *
+ * Return: AE_CTRL_TERMINATE if found, AE_OK if not found.
+ */
+static acpi_status lwmi_acpi_match(acpi_handle handle, u32 lvl,
+ void *context, void **retval)
+{
+ acpi_handle *ahand = context;
+
+ if (!handle)
+ return AE_OK;
+
+ *ahand = handle;
+
+ return AE_CTRL_TERMINATE;
+}
+
+static bool force_load_psy_ext;
+module_param(force_load_psy_ext, bool, 0444);
+MODULE_PARM_DESC(force_load_psy_ext,
+ "This option will skip checking if the ideapad_laptop driver will conflict "
+ "with adding an extension to set the battery charge type. It is recommended "
+ "to blacklist the ideapad driver before using this option.");
+
+/**
+ * lwmi_om_ps_ext_init() - Hooks power supply extension to device battery
+ * @priv: Driver private data
+ *
+ * Checks if the ideapad_laptop driver is going to manage charge_type first,
+ * then if not, hooks the battery to our WMI methods.
+ */
+static void lwmi_om_ps_ext_init(struct lwmi_om_priv *priv)
+{
+ static const char * const ideapad_hid = "VPC2004";
+ acpi_handle handle = NULL;
+ int ret;
+
+ /* Deconflict ideapad_laptop driver */
+ ret = acpi_get_devices(ideapad_hid, lwmi_acpi_match, &handle, NULL);
+ if (ret)
+ return;
+
+ if (handle && !force_load_psy_ext) {
+ if (acpi_has_method(handle, "GBMD") && acpi_has_method(handle, "SBMC")) {
+ dev_dbg(&priv->wdev->dev, "ideapad_laptop driver manages battery for device.\n");
+ return;
+ }
+ }
+
+ /* Add battery hooks */
+ priv->battery_hook.add_battery = lwmi_add_battery;
+ priv->battery_hook.remove_battery = lwmi_remove_battery;
+ priv->battery_hook.name = "Lenovo WMI Other Battery Extension";
+
+ ret = devm_battery_hook_register(&priv->wdev->dev, &priv->battery_hook);
+ if (ret)
+ dev_err(&priv->wdev->dev, "Error during battery hook: %i\n", ret);
+}
+
/* ======== fw_attributes (component: lenovo-wmi-capdata 01) ======== */
struct tunable_attr_01 {
@@ -1334,6 +1565,7 @@ static int lwmi_om_master_bind(struct device *dev)
return -ENODEV;
lwmi_om_fan_info_collect_cd00(priv);
+ lwmi_om_ps_ext_init(priv);
return lwmi_om_fw_attr_add(priv);
}
--
2.53.0
^ permalink raw reply related [flat|nested] 21+ messages in thread
* Re: [PATCH v4 3/8] platform/x86: lenovo-wmi-other: Add lwmi_attr_id() function
2026-03-12 3:10 ` [PATCH v4 3/8] platform/x86: lenovo-wmi-other: Add lwmi_attr_id() function Derek J. Clark
@ 2026-03-15 1:08 ` Rong Zhang
2026-03-15 3:38 ` Derek J. Clark
2026-03-15 1:32 ` Rong Zhang
1 sibling, 1 reply; 21+ messages in thread
From: Rong Zhang @ 2026-03-15 1:08 UTC (permalink / raw)
To: Derek J. Clark, Ilpo Järvinen, Hans de Goede
Cc: Mark Pearson, Armin Wolf, Jonathan Corbet, Kurt Borja,
platform-driver-x86, linux-kernel
Hi Derek,
On Thu, 2026-03-12 at 03:10 +0000, Derek J. Clark wrote:
> Adds lwmi_attr_id() function. In the same vein as LWMI_ATTR_ID_FAN_RPM(),
> but as a generic, to de-duplicate attribute_id assignment biolerplate.
>
> Reviewed-by: Mark Pearson <mpearson-lenovo@squebb.ca>
> Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
> ---
> v4:
> - Switch from macro to static inline to preserve types.
> ---
> drivers/platform/x86/lenovo/wmi-gamezone.h | 1 +
> drivers/platform/x86/lenovo/wmi-other.c | 59 +++++++++++++---------
> 2 files changed, 35 insertions(+), 25 deletions(-)
>
> diff --git a/drivers/platform/x86/lenovo/wmi-gamezone.h b/drivers/platform/x86/lenovo/wmi-gamezone.h
> index 6b163a5eeb95..ddb919cf6c36 100644
> --- a/drivers/platform/x86/lenovo/wmi-gamezone.h
> +++ b/drivers/platform/x86/lenovo/wmi-gamezone.h
> @@ -10,6 +10,7 @@ enum gamezone_events_type {
> };
>
> enum thermal_mode {
> + LWMI_GZ_THERMAL_MODE_NONE = 0x00,
> LWMI_GZ_THERMAL_MODE_QUIET = 0x01,
> LWMI_GZ_THERMAL_MODE_BALANCED = 0x02,
> LWMI_GZ_THERMAL_MODE_PERFORMANCE = 0x03,
> diff --git a/drivers/platform/x86/lenovo/wmi-other.c b/drivers/platform/x86/lenovo/wmi-other.c
> index c1728c7c2957..9fff9c1f768c 100644
> --- a/drivers/platform/x86/lenovo/wmi-other.c
> +++ b/drivers/platform/x86/lenovo/wmi-other.c
> @@ -73,10 +73,26 @@
>
> #define LWMI_FAN_DIV 100
>
> -#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)))
> +/**
> + * lwmi_attr_id() - Formats a capability data attribute ID
> + * @dev_id: The u8 corresponding to the device ID.
> + * @feat_id: The u8 corresponding to the feature ID on the device.
> + * @mode_id: The u8 corresponding to the wmi-gamezone mode for set/get.
> + * @type_id: The u8 corresponding to the sub-device.
> + *
> + * Return: u32.
> + */
> +static u32 lwmi_attr_id(u8 dev_id, u8 feat_id, u8 mode_id, u8 type_id)
> +{
> + return (FIELD_PREP(LWMI_ATTR_DEV_ID_MASK, dev_id) |
> + FIELD_PREP(LWMI_ATTR_FEAT_ID_MASK, feat_id) |
> + FIELD_PREP(LWMI_ATTR_MODE_ID_MASK, mode_id) |
> + FIELD_PREP(LWMI_ATTR_TYPE_ID_MASK, type_id));
> +}
> +
> +#define LWMI_ATTR_ID_FAN_RPM(x) \
> + lwmi_attr_id(LWMI_DEVICE_ID_FAN, LWMI_FEATURE_ID_FAN_RPM, \
> + LWMI_GZ_THERMAL_MODE_NONE, LWMI_FAN_ID(x))
>
> #define LWMI_OM_FW_ATTR_BASE_PATH "lenovo-wmi-other"
> #define LWMI_OM_HWMON_NAME "lenovo_wmi_other"
> @@ -550,6 +566,8 @@ struct tunable_attr_01 {
> u8 feature_id;
> u8 device_id;
> u8 type_id;
> + u8 cd_mode_id; /* mode arg for searching capdata */
> + u8 cv_mode_id; /* mode arg for set/get current_value */
Adding them actually depends on [PATCH v4 4/8], otherwise you always
get 0 when accessing them in this patch. There are potential
regressions...
> };
>
> static struct tunable_attr_01 ppt_pl1_spl = {
> @@ -715,12 +733,8 @@ static ssize_t attr_capdata01_show(struct kobject *kobj,
> u32 attribute_id;
> int value, ret;
>
> - attribute_id =
> - FIELD_PREP(LWMI_ATTR_DEV_ID_MASK, tunable_attr->device_id) |
> - FIELD_PREP(LWMI_ATTR_FEAT_ID_MASK, tunable_attr->feature_id) |
> - FIELD_PREP(LWMI_ATTR_MODE_ID_MASK,
> - LWMI_GZ_THERMAL_MODE_CUSTOM) |
> - FIELD_PREP(LWMI_ATTR_TYPE_ID_MASK, tunable_attr->type_id);
> + attribute_id = lwmi_attr_id(tunable_attr->device_id, tunable_attr->feature_id,
> + LWMI_GZ_THERMAL_MODE_CUSTOM, tunable_attr->type_id);
>
> ret = lwmi_cd01_get_data(priv->cd01_list, attribute_id, &capdata);
> if (ret)
> @@ -775,7 +789,6 @@ static ssize_t attr_current_value_store(struct kobject *kobj,
> struct wmi_method_args_32 args;
> struct capdata01 capdata;
> enum thermal_mode mode;
> - u32 attribute_id;
> u32 value;
> int ret;
>
> @@ -786,13 +799,10 @@ static ssize_t attr_current_value_store(struct kobject *kobj,
> if (mode != LWMI_GZ_THERMAL_MODE_CUSTOM)
> return -EBUSY;
>
> - attribute_id =
> - FIELD_PREP(LWMI_ATTR_DEV_ID_MASK, tunable_attr->device_id) |
> - FIELD_PREP(LWMI_ATTR_FEAT_ID_MASK, tunable_attr->feature_id) |
> - FIELD_PREP(LWMI_ATTR_MODE_ID_MASK, mode) |
> - FIELD_PREP(LWMI_ATTR_TYPE_ID_MASK, tunable_attr->type_id);
> + args.arg0 = lwmi_attr_id(tunable_attr->device_id, tunable_attr->feature_id,
> + mode, tunable_attr->type_id);
>
> - ret = lwmi_cd01_get_data(priv->cd01_list, attribute_id, &capdata);
> + ret = lwmi_cd01_get_data(priv->cd01_list, args.arg0, &capdata);
> if (ret)
> return ret;
>
> @@ -803,7 +813,8 @@ static ssize_t attr_current_value_store(struct kobject *kobj,
> if (value < capdata.min_value || value > capdata.max_value)
> return -EINVAL;
>
> - args.arg0 = attribute_id;
> + args.arg0 = lwmi_attr_id(tunable_attr->device_id, tunable_attr->feature_id,
> + tunable_attr->cv_mode_id, tunable_attr->type_id);
...here...
> args.arg1 = value;
>
> ret = lwmi_dev_evaluate_int(priv->wdev, 0x0, LWMI_FEATURE_VALUE_SET,
> @@ -837,7 +848,6 @@ static ssize_t attr_current_value_show(struct kobject *kobj,
> struct lwmi_om_priv *priv = dev_get_drvdata(tunable_attr->dev);
> struct wmi_method_args_32 args;
> enum thermal_mode mode;
> - u32 attribute_id;
> int retval;
> int ret;
>
> @@ -845,13 +855,12 @@ static ssize_t attr_current_value_show(struct kobject *kobj,
> if (ret)
> return ret;
>
> - attribute_id =
> - FIELD_PREP(LWMI_ATTR_DEV_ID_MASK, tunable_attr->device_id) |
> - FIELD_PREP(LWMI_ATTR_FEAT_ID_MASK, tunable_attr->feature_id) |
> - FIELD_PREP(LWMI_ATTR_MODE_ID_MASK, mode) |
> - FIELD_PREP(LWMI_ATTR_TYPE_ID_MASK, tunable_attr->type_id);
> + /* If "no-mode" is the supported mode, ensure we never send current mode */
> + if (tunable_attr->cv_mode_id == LWMI_GZ_THERMAL_MODE_NONE)
> + mode = tunable_attr->cv_mode_id;
...and here.
Thanks,
Rong
>
> - args.arg0 = attribute_id;
> + args.arg0 = lwmi_attr_id(tunable_attr->device_id, tunable_attr->feature_id,
> + mode, tunable_attr->type_id);
>
> ret = lwmi_dev_evaluate_int(priv->wdev, 0x0, LWMI_FEATURE_VALUE_GET,
> (unsigned char *)&args, sizeof(args),
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH v4 4/8] platform/x86: lenovo-wmi-other: Limit adding attributes to supported devices
2026-03-12 3:10 ` [PATCH v4 4/8] platform/x86: lenovo-wmi-other: Limit adding attributes to supported devices Derek J. Clark
@ 2026-03-15 1:26 ` Rong Zhang
2026-03-15 1:39 ` Rong Zhang
2026-03-15 3:47 ` Derek J. Clark
0 siblings, 2 replies; 21+ messages in thread
From: Rong Zhang @ 2026-03-15 1:26 UTC (permalink / raw)
To: Derek J. Clark, Ilpo Järvinen, Hans de Goede
Cc: Mark Pearson, Armin Wolf, Jonathan Corbet, Kurt Borja,
platform-driver-x86, linux-kernel
Hi Derek,
On Thu, 2026-03-12 at 03:10 +0000, Derek J. Clark wrote:
> Adds lwmi_is_attr_01_supported, and only creates the attribute subfolder
> if the attribute is supported by the hardware. Due to some poorly
> implemented BIOS this is a multi-step sequence of events. This is
> because:
> - Some BIOS support getting the capability data from custom mode (0xff),
> while others only support it in no-mode (0x00).
> - Some BIOS support get/set for the current value from custom mode (0xff),
> while others only support it in no-mode (0x00).
> - Some BIOS report capability data for a method that is not fully
> implemented.
> - Some BIOS have methods fully implemented, but no complimentary
> capability data.
>
> To ensure we only expose fully implemented methods with corresponding
> capability data, we check each outcome before reporting that an
> attribute can be supported.
>
> Checking for lwmi_is_attr_01_supported during remove is not done to
> ensure that we don't attempt to call cd01 or send WMI events if one of
> the interfaces being removed was the cause of the driver unloading.
>
> Reviewed-by: Mark Pearson <mpearson-lenovo@squebb.ca>
> Reported-by: Kurt Borja <kuurtb@gmail.com>
> Closes: https://lore.kernel.org/platform-driver-x86/DG60P3SHXR8H.3NSEHMZ6J7XRC@gmail.com/
> Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
> ---
> v4:
> - Use for loop instead of backtrace gotos for checking if an attribute
> is supported.
> - Add include for dev_printk.
> - Wrap dev_dbg in lwmi_is_attr_01_supported earlier.
> - Don't use symmetric cleanup of attributes in error states.
> ---
> drivers/platform/x86/lenovo/wmi-other.c | 76 ++++++++++++++++++++++++-
> 1 file changed, 74 insertions(+), 2 deletions(-)
>
> diff --git a/drivers/platform/x86/lenovo/wmi-other.c b/drivers/platform/x86/lenovo/wmi-other.c
> index 9fff9c1f768c..55a26e5617d4 100644
> --- a/drivers/platform/x86/lenovo/wmi-other.c
> +++ b/drivers/platform/x86/lenovo/wmi-other.c
> @@ -32,6 +32,7 @@
> #include <linux/component.h>
> #include <linux/container_of.h>
> #include <linux/device.h>
> +#include <linux/dev_printk.h>
> #include <linux/export.h>
> #include <linux/gfp_types.h>
> #include <linux/hwmon.h>
> @@ -871,6 +872,76 @@ static ssize_t attr_current_value_show(struct kobject *kobj,
> return sysfs_emit(buf, "%d\n", retval);
> }
>
> +/**
> + * lwmi_attr_01_is_supported() - Determine if the given attribute is supported.
> + * @tunable_attr: The attribute to verify.
> + *
> + * First check if the attribute has a corresponding capdata01 table in the cd01
> + * module under the "custom" mode (0xff). If that is not present then check if
> + * there is a corresponding "no-mode" (0x00) entry. If either of those passes,
> + * check capdata->supported for values > 0. If capdata is available, attempt to
> + * determine the set/get mode for the current value property using a similar
> + * pattern. If the value returned by either custom or no-mode is 0, or we get
> + * an error, we assume that mode is not supported. If any of the above checks
> + * fail then the attribute is not fully supported.
> + *
> + * The probed cd_mode_id/cv_mode_id are stored on the tunable_attr for later
> + * reference.
> + *
> + * Return: Support level, or an error code.
> + */
> +static int lwmi_attr_01_is_supported(struct tunable_attr_01 *tunable_attr)
> +{
> + u8 modes[2] = { LWMI_GZ_THERMAL_MODE_CUSTOM, LWMI_GZ_THERMAL_MODE_NONE };
> + struct lwmi_om_priv *priv = dev_get_drvdata(tunable_attr->dev);
> + struct wmi_method_args_32 args;
> + bool cd_mode_found = false;
> + bool cv_mode_found = false;
> + struct capdata01 capdata;
> + int retval, ret, i;
> +
> + /* Determine tunable_attr->cd_mode_id*/
> + for (i = 0; i < ARRAY_SIZE(modes); i++) {
#include <linux/array_size.h>
> + args.arg0 = lwmi_attr_id(tunable_attr->device_id, tunable_attr->feature_id,
> + modes[i], tunable_attr->type_id);
> +
> + ret = lwmi_cd01_get_data(priv->cd01_list, args.arg0, &capdata);
> + if (ret || !capdata.supported)
> + continue;
> + tunable_attr->cd_mode_id = modes[i];
> + cd_mode_found = true;
> + break;
> + }
> +
> + if (!cd_mode_found)
> + return -EOPNOTSUPP;
> +
> + /* Determine tunable_attr->cv_mode_id, returns 1 if supported*/
> + for (i = 0; i < ARRAY_SIZE(modes); i++) {
> + args.arg0 = lwmi_attr_id(tunable_attr->device_id, tunable_attr->feature_id,
> + modes[i], tunable_attr->type_id);
> +
> + ret = lwmi_dev_evaluate_int(priv->wdev, 0x0, LWMI_FEATURE_VALUE_GET,
> + (unsigned char *)&args, sizeof(args),
> + &retval);
> + if (ret || !retval)
> + continue;
> + tunable_attr->cv_mode_id = modes[i];
> + cv_mode_found = true;
> + break;
> + }
> +
> + if (!cv_mode_found)
> + return -EOPNOTSUPP;
> +
> + dev_dbg(tunable_attr->dev,
> + "cd_mode_id: %02x%02x%02x%02x, cv_mode_id: %#08x attribute support level: %x\n",
^ ^ ^ ^
> + tunable_attr->device_id, tunable_attr->feature_id, tunable_attr->cd_mode_id,
> + tunable_attr->type_id, args.arg0, capdata.supported);
^
dev_dbg(tunable_attr->dev,
"cd_mode_id: %#10x, cv_mode_id: %#10x, attribute support level: %#10x\n",
lwmi_attr_id(...), args.arg0, capdata.supported);
> +
> + return capdata.supported;
You are casting u32 to int. Return it in a pointer argument if you need
it.
> +}
> +
> /* Lenovo WMI Other Mode Attribute macros */
> #define __LWMI_ATTR_RO(_func, _name) \
> { \
> @@ -994,12 +1065,13 @@ static int lwmi_om_fw_attr_add(struct lwmi_om_priv *priv)
> }
>
> for (i = 0; i < ARRAY_SIZE(cd01_attr_groups) - 1; i++) {
> + cd01_attr_groups[i].tunable_attr->dev = &priv->wdev->dev;
> + if (lwmi_attr_01_is_supported(cd01_attr_groups[i].tunable_attr) <= 0)
Extra whitespace.
> + continue;
Add an empty line in between.
Thanks,
Rong
> err = sysfs_create_group(&priv->fw_attr_kset->kobj,
> cd01_attr_groups[i].attr_group);
> if (err)
> goto err_remove_groups;
> -
> - cd01_attr_groups[i].tunable_attr->dev = &priv->wdev->dev;
> }
> return 0;
>
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH v4 3/8] platform/x86: lenovo-wmi-other: Add lwmi_attr_id() function
2026-03-12 3:10 ` [PATCH v4 3/8] platform/x86: lenovo-wmi-other: Add lwmi_attr_id() function Derek J. Clark
2026-03-15 1:08 ` Rong Zhang
@ 2026-03-15 1:32 ` Rong Zhang
2026-03-15 3:49 ` Derek J. Clark
1 sibling, 1 reply; 21+ messages in thread
From: Rong Zhang @ 2026-03-15 1:32 UTC (permalink / raw)
To: Derek J. Clark, Ilpo Järvinen, Hans de Goede
Cc: Mark Pearson, Armin Wolf, Jonathan Corbet, Kurt Borja,
platform-driver-x86, linux-kernel
Hi Derek,
On Thu, 2026-03-12 at 03:10 +0000, Derek J. Clark wrote:
> Adds lwmi_attr_id() function. In the same vein as LWMI_ATTR_ID_FAN_RPM(),
> but as a generic, to de-duplicate attribute_id assignment biolerplate.
>
> Reviewed-by: Mark Pearson <mpearson-lenovo@squebb.ca>
> Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
> ---
> v4:
> - Switch from macro to static inline to preserve types.
> ---
> drivers/platform/x86/lenovo/wmi-gamezone.h | 1 +
> drivers/platform/x86/lenovo/wmi-other.c | 59 +++++++++++++---------
> 2 files changed, 35 insertions(+), 25 deletions(-)
>
> diff --git a/drivers/platform/x86/lenovo/wmi-gamezone.h b/drivers/platform/x86/lenovo/wmi-gamezone.h
> index 6b163a5eeb95..ddb919cf6c36 100644
> --- a/drivers/platform/x86/lenovo/wmi-gamezone.h
> +++ b/drivers/platform/x86/lenovo/wmi-gamezone.h
> @@ -10,6 +10,7 @@ enum gamezone_events_type {
> };
>
> enum thermal_mode {
> + LWMI_GZ_THERMAL_MODE_NONE = 0x00,
> LWMI_GZ_THERMAL_MODE_QUIET = 0x01,
> LWMI_GZ_THERMAL_MODE_BALANCED = 0x02,
> LWMI_GZ_THERMAL_MODE_PERFORMANCE = 0x03,
> diff --git a/drivers/platform/x86/lenovo/wmi-other.c b/drivers/platform/x86/lenovo/wmi-other.c
> index c1728c7c2957..9fff9c1f768c 100644
> --- a/drivers/platform/x86/lenovo/wmi-other.c
> +++ b/drivers/platform/x86/lenovo/wmi-other.c
> @@ -73,10 +73,26 @@
>
> #define LWMI_FAN_DIV 100
>
> -#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)))
> +/**
> + * lwmi_attr_id() - Formats a capability data attribute ID
> + * @dev_id: The u8 corresponding to the device ID.
> + * @feat_id: The u8 corresponding to the feature ID on the device.
> + * @mode_id: The u8 corresponding to the wmi-gamezone mode for set/get.
> + * @type_id: The u8 corresponding to the sub-device.
> + *
> + * Return: u32.
> + */
> +static u32 lwmi_attr_id(u8 dev_id, u8 feat_id, u8 mode_id, u8 type_id)
> +{
> + return (FIELD_PREP(LWMI_ATTR_DEV_ID_MASK, dev_id) |
> + FIELD_PREP(LWMI_ATTR_FEAT_ID_MASK, feat_id) |
> + FIELD_PREP(LWMI_ATTR_MODE_ID_MASK, mode_id) |
> + FIELD_PREP(LWMI_ATTR_TYPE_ID_MASK, type_id));
> +}
Hmm, I'd suggest adding them to wmi-capdata.h so that you can refactor
LWMI_ATTR_ID_FAN_TEST in wmi-capdata.c too.
Thanks,
Rong
> +
> +#define LWMI_ATTR_ID_FAN_RPM(x) \
> + lwmi_attr_id(LWMI_DEVICE_ID_FAN, LWMI_FEATURE_ID_FAN_RPM, \
> + LWMI_GZ_THERMAL_MODE_NONE, LWMI_FAN_ID(x))
>
> #define LWMI_OM_FW_ATTR_BASE_PATH "lenovo-wmi-other"
> #define LWMI_OM_HWMON_NAME "lenovo_wmi_other"
> @@ -550,6 +566,8 @@ struct tunable_attr_01 {
> u8 feature_id;
> u8 device_id;
> u8 type_id;
> + u8 cd_mode_id; /* mode arg for searching capdata */
> + u8 cv_mode_id; /* mode arg for set/get current_value */
> };
>
> static struct tunable_attr_01 ppt_pl1_spl = {
> @@ -715,12 +733,8 @@ static ssize_t attr_capdata01_show(struct kobject *kobj,
> u32 attribute_id;
> int value, ret;
>
> - attribute_id =
> - FIELD_PREP(LWMI_ATTR_DEV_ID_MASK, tunable_attr->device_id) |
> - FIELD_PREP(LWMI_ATTR_FEAT_ID_MASK, tunable_attr->feature_id) |
> - FIELD_PREP(LWMI_ATTR_MODE_ID_MASK,
> - LWMI_GZ_THERMAL_MODE_CUSTOM) |
> - FIELD_PREP(LWMI_ATTR_TYPE_ID_MASK, tunable_attr->type_id);
> + attribute_id = lwmi_attr_id(tunable_attr->device_id, tunable_attr->feature_id,
> + LWMI_GZ_THERMAL_MODE_CUSTOM, tunable_attr->type_id);
>
> ret = lwmi_cd01_get_data(priv->cd01_list, attribute_id, &capdata);
> if (ret)
> @@ -775,7 +789,6 @@ static ssize_t attr_current_value_store(struct kobject *kobj,
> struct wmi_method_args_32 args;
> struct capdata01 capdata;
> enum thermal_mode mode;
> - u32 attribute_id;
> u32 value;
> int ret;
>
> @@ -786,13 +799,10 @@ static ssize_t attr_current_value_store(struct kobject *kobj,
> if (mode != LWMI_GZ_THERMAL_MODE_CUSTOM)
> return -EBUSY;
>
> - attribute_id =
> - FIELD_PREP(LWMI_ATTR_DEV_ID_MASK, tunable_attr->device_id) |
> - FIELD_PREP(LWMI_ATTR_FEAT_ID_MASK, tunable_attr->feature_id) |
> - FIELD_PREP(LWMI_ATTR_MODE_ID_MASK, mode) |
> - FIELD_PREP(LWMI_ATTR_TYPE_ID_MASK, tunable_attr->type_id);
> + args.arg0 = lwmi_attr_id(tunable_attr->device_id, tunable_attr->feature_id,
> + mode, tunable_attr->type_id);
>
> - ret = lwmi_cd01_get_data(priv->cd01_list, attribute_id, &capdata);
> + ret = lwmi_cd01_get_data(priv->cd01_list, args.arg0, &capdata);
> if (ret)
> return ret;
>
> @@ -803,7 +813,8 @@ static ssize_t attr_current_value_store(struct kobject *kobj,
> if (value < capdata.min_value || value > capdata.max_value)
> return -EINVAL;
>
> - args.arg0 = attribute_id;
> + args.arg0 = lwmi_attr_id(tunable_attr->device_id, tunable_attr->feature_id,
> + tunable_attr->cv_mode_id, tunable_attr->type_id);
> args.arg1 = value;
>
> ret = lwmi_dev_evaluate_int(priv->wdev, 0x0, LWMI_FEATURE_VALUE_SET,
> @@ -837,7 +848,6 @@ static ssize_t attr_current_value_show(struct kobject *kobj,
> struct lwmi_om_priv *priv = dev_get_drvdata(tunable_attr->dev);
> struct wmi_method_args_32 args;
> enum thermal_mode mode;
> - u32 attribute_id;
> int retval;
> int ret;
>
> @@ -845,13 +855,12 @@ static ssize_t attr_current_value_show(struct kobject *kobj,
> if (ret)
> return ret;
>
> - attribute_id =
> - FIELD_PREP(LWMI_ATTR_DEV_ID_MASK, tunable_attr->device_id) |
> - FIELD_PREP(LWMI_ATTR_FEAT_ID_MASK, tunable_attr->feature_id) |
> - FIELD_PREP(LWMI_ATTR_MODE_ID_MASK, mode) |
> - FIELD_PREP(LWMI_ATTR_TYPE_ID_MASK, tunable_attr->type_id);
> + /* If "no-mode" is the supported mode, ensure we never send current mode */
> + if (tunable_attr->cv_mode_id == LWMI_GZ_THERMAL_MODE_NONE)
> + mode = tunable_attr->cv_mode_id;
>
> - args.arg0 = attribute_id;
> + args.arg0 = lwmi_attr_id(tunable_attr->device_id, tunable_attr->feature_id,
> + mode, tunable_attr->type_id);
>
> ret = lwmi_dev_evaluate_int(priv->wdev, 0x0, LWMI_FEATURE_VALUE_GET,
> (unsigned char *)&args, sizeof(args),
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH v4 4/8] platform/x86: lenovo-wmi-other: Limit adding attributes to supported devices
2026-03-15 1:26 ` Rong Zhang
@ 2026-03-15 1:39 ` Rong Zhang
2026-03-15 3:47 ` Derek J. Clark
1 sibling, 0 replies; 21+ messages in thread
From: Rong Zhang @ 2026-03-15 1:39 UTC (permalink / raw)
To: Derek J. Clark, Ilpo Järvinen, Hans de Goede
Cc: Mark Pearson, Armin Wolf, Jonathan Corbet, Kurt Borja,
platform-driver-x86, linux-kernel
Hi Derek,
On Sun, 2026-03-15 at 09:26 +0800, Rong Zhang wrote:
> Hi Derek,
>
> On Thu, 2026-03-12 at 03:10 +0000, Derek J. Clark wrote:
> > Adds lwmi_is_attr_01_supported, and only creates the attribute subfolder
> > if the attribute is supported by the hardware. Due to some poorly
> > implemented BIOS this is a multi-step sequence of events. This is
> > because:
> > - Some BIOS support getting the capability data from custom mode (0xff),
> > while others only support it in no-mode (0x00).
> > - Some BIOS support get/set for the current value from custom mode (0xff),
> > while others only support it in no-mode (0x00).
> > - Some BIOS report capability data for a method that is not fully
> > implemented.
> > - Some BIOS have methods fully implemented, but no complimentary
> > capability data.
> >
> > To ensure we only expose fully implemented methods with corresponding
> > capability data, we check each outcome before reporting that an
> > attribute can be supported.
> >
> > Checking for lwmi_is_attr_01_supported during remove is not done to
> > ensure that we don't attempt to call cd01 or send WMI events if one of
> > the interfaces being removed was the cause of the driver unloading.
> >
> > Reviewed-by: Mark Pearson <mpearson-lenovo@squebb.ca>
> > Reported-by: Kurt Borja <kuurtb@gmail.com>
> > Closes: https://lore.kernel.org/platform-driver-x86/DG60P3SHXR8H.3NSEHMZ6J7XRC@gmail.com/
> > Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
> > ---
> > v4:
> > - Use for loop instead of backtrace gotos for checking if an attribute
> > is supported.
> > - Add include for dev_printk.
> > - Wrap dev_dbg in lwmi_is_attr_01_supported earlier.
> > - Don't use symmetric cleanup of attributes in error states.
> > ---
> > drivers/platform/x86/lenovo/wmi-other.c | 76 ++++++++++++++++++++++++-
> > 1 file changed, 74 insertions(+), 2 deletions(-)
> >
> > diff --git a/drivers/platform/x86/lenovo/wmi-other.c b/drivers/platform/x86/lenovo/wmi-other.c
> > index 9fff9c1f768c..55a26e5617d4 100644
> > --- a/drivers/platform/x86/lenovo/wmi-other.c
> > +++ b/drivers/platform/x86/lenovo/wmi-other.c
> > @@ -32,6 +32,7 @@
> > #include <linux/component.h>
> > #include <linux/container_of.h>
> > #include <linux/device.h>
> > +#include <linux/dev_printk.h>
> > #include <linux/export.h>
> > #include <linux/gfp_types.h>
> > #include <linux/hwmon.h>
> > @@ -871,6 +872,76 @@ static ssize_t attr_current_value_show(struct kobject *kobj,
> > return sysfs_emit(buf, "%d\n", retval);
> > }
> >
> > +/**
> > + * lwmi_attr_01_is_supported() - Determine if the given attribute is supported.
> > + * @tunable_attr: The attribute to verify.
> > + *
> > + * First check if the attribute has a corresponding capdata01 table in the cd01
> > + * module under the "custom" mode (0xff). If that is not present then check if
> > + * there is a corresponding "no-mode" (0x00) entry. If either of those passes,
> > + * check capdata->supported for values > 0. If capdata is available, attempt to
> > + * determine the set/get mode for the current value property using a similar
> > + * pattern. If the value returned by either custom or no-mode is 0, or we get
> > + * an error, we assume that mode is not supported. If any of the above checks
> > + * fail then the attribute is not fully supported.
> > + *
> > + * The probed cd_mode_id/cv_mode_id are stored on the tunable_attr for later
> > + * reference.
> > + *
> > + * Return: Support level, or an error code.
> > + */
> > +static int lwmi_attr_01_is_supported(struct tunable_attr_01 *tunable_attr)
> > +{
> > + u8 modes[2] = { LWMI_GZ_THERMAL_MODE_CUSTOM, LWMI_GZ_THERMAL_MODE_NONE };
> > + struct lwmi_om_priv *priv = dev_get_drvdata(tunable_attr->dev);
> > + struct wmi_method_args_32 args;
> > + bool cd_mode_found = false;
> > + bool cv_mode_found = false;
> > + struct capdata01 capdata;
> > + int retval, ret, i;
> > +
> > + /* Determine tunable_attr->cd_mode_id*/
> > + for (i = 0; i < ARRAY_SIZE(modes); i++) {
>
> #include <linux/array_size.h>
>
> > + args.arg0 = lwmi_attr_id(tunable_attr->device_id, tunable_attr->feature_id,
> > + modes[i], tunable_attr->type_id);
> > +
> > + ret = lwmi_cd01_get_data(priv->cd01_list, args.arg0, &capdata);
> > + if (ret || !capdata.supported)
> > + continue;
> > + tunable_attr->cd_mode_id = modes[i];
> > + cd_mode_found = true;
> > + break;
> > + }
> > +
> > + if (!cd_mode_found)
> > + return -EOPNOTSUPP;
> > +
> > + /* Determine tunable_attr->cv_mode_id, returns 1 if supported*/
> > + for (i = 0; i < ARRAY_SIZE(modes); i++) {
> > + args.arg0 = lwmi_attr_id(tunable_attr->device_id, tunable_attr->feature_id,
> > + modes[i], tunable_attr->type_id);
> > +
> > + ret = lwmi_dev_evaluate_int(priv->wdev, 0x0, LWMI_FEATURE_VALUE_GET,
> > + (unsigned char *)&args, sizeof(args),
> > + &retval);
> > + if (ret || !retval)
> > + continue;
> > + tunable_attr->cv_mode_id = modes[i];
> > + cv_mode_found = true;
> > + break;
> > + }
> > +
> > + if (!cv_mode_found)
> > + return -EOPNOTSUPP;
> > +
> > + dev_dbg(tunable_attr->dev,
> > + "cd_mode_id: %02x%02x%02x%02x, cv_mode_id: %#08x attribute support level: %x\n",
> ^ ^ ^ ^
> > + tunable_attr->device_id, tunable_attr->feature_id, tunable_attr->cd_mode_id,
> > + tunable_attr->type_id, args.arg0, capdata.supported);
> ^
>
> dev_dbg(tunable_attr->dev,
> "cd_mode_id: %#10x, cv_mode_id: %#10x, attribute support level: %#10x\n",
Sorry, they should be %#010x.
Thanks,
Rong
> lwmi_attr_id(...), args.arg0, capdata.supported);
>
> > +
> > + return capdata.supported;
>
> You are casting u32 to int. Return it in a pointer argument if you need
> it.
>
> > +}
> > +
> > /* Lenovo WMI Other Mode Attribute macros */
> > #define __LWMI_ATTR_RO(_func, _name) \
> > { \
> > @@ -994,12 +1065,13 @@ static int lwmi_om_fw_attr_add(struct lwmi_om_priv *priv)
> > }
> >
> > for (i = 0; i < ARRAY_SIZE(cd01_attr_groups) - 1; i++) {
> > + cd01_attr_groups[i].tunable_attr->dev = &priv->wdev->dev;
> > + if (lwmi_attr_01_is_supported(cd01_attr_groups[i].tunable_attr) <= 0)
>
> Extra whitespace.
>
> > + continue;
>
> Add an empty line in between.
>
> Thanks,
> Rong
>
> > err = sysfs_create_group(&priv->fw_attr_kset->kobj,
> > cd01_attr_groups[i].attr_group);
> > if (err)
> > goto err_remove_groups;
> > -
> > - cd01_attr_groups[i].tunable_attr->dev = &priv->wdev->dev;
> > }
> > return 0;
> >
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH v4 8/8] platform/x86: lenovo-wmi-other: Add WMI battery charge limiting
2026-03-12 3:10 ` [PATCH v4 8/8] platform/x86: lenovo-wmi-other: Add WMI battery charge limiting Derek J. Clark
@ 2026-03-15 2:00 ` Rong Zhang
2026-03-15 3:52 ` Derek J. Clark
2026-03-17 23:46 ` kernel test robot
1 sibling, 1 reply; 21+ messages in thread
From: Rong Zhang @ 2026-03-15 2:00 UTC (permalink / raw)
To: Derek J. Clark, Ilpo Järvinen, Hans de Goede
Cc: Mark Pearson, Armin Wolf, Jonathan Corbet, Kurt Borja,
platform-driver-x86, linux-kernel
Hi Derek,
On Thu, 2026-03-12 at 03:10 +0000, Derek J. Clark wrote:
> Add charge-type power supply extension for devices that support WMI based
> charge enable/disable.
>
> Lenovo Legion devices that implement WMI function and capdata ID
> 0x03010001 in their BIOS are able to enable or disable charging at 80%
> through the lenovo-wmi-other interface. Add a charge_type power supply
> extension to expose this capability to the sysfs.
>
> The ideapad_laptop driver can also provide the charge_type attribute. To
> avoid conflicts between the drivers, get the acpi_handle and do the same
> check that ideapad_laptop does when it enables the feature. If the
> feature is supported in ideapad_laptop, abort adding the extension from
> lenovo-wmi-other. The ACPI method is more reliable when both are
> present, from my testing, so we can prefer that implementation and do
> not need to worry about de-conflicting from inside that driver. A new
> module parameter, force_load_psy_ext, is provided to bypass this ACPI
> check, if desired.
>
> Reviewed-by: Mark Pearson <mpearson-lenovo@squebb.ca>
> Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
> ---
> v4:
> - Remove unused defines.
> - Disambiguate charging defines by renaming them to be more consistent
> with the kernel modes they represent.
> - Add module parameter to ignore ACPI checks.
> - Don't fail if the ACPI handle isn't found, skip the ACPI check
> instead.
> ---
> drivers/platform/x86/lenovo/wmi-capdata.h | 1 +
> drivers/platform/x86/lenovo/wmi-other.c | 234 +++++++++++++++++++++-
> 2 files changed, 234 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/platform/x86/lenovo/wmi-capdata.h b/drivers/platform/x86/lenovo/wmi-capdata.h
> index 196481b1ce17..cc0d1c176c2f 100644
> --- a/drivers/platform/x86/lenovo/wmi-capdata.h
> +++ b/drivers/platform/x86/lenovo/wmi-capdata.h
> @@ -20,6 +20,7 @@
> enum lwmi_device_id {
> LWMI_DEVICE_ID_CPU = 0x01,
> LWMI_DEVICE_ID_GPU = 0x02,
> + LWMI_DEVICE_ID_PSU = 0x03,
> LWMI_DEVICE_ID_FAN = 0x04,
> };
>
> diff --git a/drivers/platform/x86/lenovo/wmi-other.c b/drivers/platform/x86/lenovo/wmi-other.c
> index 61ac9bee352f..cab9ae6bd811 100644
> --- a/drivers/platform/x86/lenovo/wmi-other.c
> +++ b/drivers/platform/x86/lenovo/wmi-other.c
> @@ -43,9 +43,12 @@
> #include <linux/module.h>
> #include <linux/notifier.h>
> #include <linux/platform_profile.h>
> +#include <linux/power_supply.h>
> #include <linux/types.h>
> #include <linux/wmi.h>
>
> +#include <acpi/battery.h>
> +
> #include "wmi-capdata.h"
> #include "wmi-events.h"
> #include "wmi-gamezone.h"
> @@ -79,10 +82,12 @@ enum lwmi_feature_id_gpu {
> LWMI_FEATURE_ID_GPU_NV_CPU_BOOST = 0x0b,
> };
>
> -#define LWMI_FEATURE_ID_FAN_RPM 0x03
> +#define LWMI_FEATURE_ID_FAN_RPM 0x03
> +#define LWMI_FEATURE_ID_PSU_CHARGE_TYPE 0x01
>
> #define LWMI_TYPE_ID_NONE 0x00
> #define LWMI_TYPE_ID_CROSSLOAD 0x01
> +#define LWMI_TYPE_ID_PSU_AC 0x01
>
> #define LWMI_FEATURE_VALUE_GET 17
> #define LWMI_FEATURE_VALUE_SET 18
> @@ -93,6 +98,9 @@ enum lwmi_feature_id_gpu {
>
> #define LWMI_FAN_DIV 100
>
> +#define LWMI_CHARGE_TYPE_STANDARD 0x00
> +#define LWMI_CHARGE_TYPE_LONGLIFE 0x01
> +
> /**
> * lwmi_attr_id() - Formats a capability data attribute ID
> * @dev_id: The u8 corresponding to the device ID.
> @@ -114,6 +122,10 @@ static u32 lwmi_attr_id(u8 dev_id, u8 feat_id, u8 mode_id, u8 type_id)
> lwmi_attr_id(LWMI_DEVICE_ID_FAN, LWMI_FEATURE_ID_FAN_RPM, \
> LWMI_GZ_THERMAL_MODE_NONE, LWMI_FAN_ID(x))
>
> +#define LWMI_ATTR_ID_PSU(feat, type) \
> + lwmi_attr_id(LWMI_DEVICE_ID_PSU, feat, \
> + LWMI_GZ_THERMAL_MODE_NONE, type)
> +
> #define LWMI_OM_SYSFS_NAME "lenovo-wmi-other"
> #define LWMI_OM_HWMON_NAME "lenovo_wmi_other"
>
> @@ -155,6 +167,8 @@ struct lwmi_om_priv {
> bool capdata00_collected : 1;
> bool capdata_fan_collected : 1;
> } fan_flags;
> +
> + struct acpi_battery_hook battery_hook;
> };
>
> /*
> @@ -579,6 +593,223 @@ static void lwmi_om_fan_info_collect_cd_fan(struct device *dev, struct cd_list *
> lwmi_om_hwmon_add(priv);
> }
>
> +/* ======== Power Supply Extension (component: lenovo-wmi-capdata 00) ======== */
> +
> +/**
> + * lwmi_psy_ext_get_prop() - Get a power_supply_ext property
> + * @ps: The battery that was extended
> + * @ext: The extension
> + * @ext_data: Pointer the lwmi_om_priv drvdata
> + * @prop: The property to read
> + * @val: The value to return
> + *
> + * Writes the given value to the power_supply_ext property
> + *
> + * Return: 0 on success, or an error
> + */
> +static int lwmi_psy_ext_get_prop(struct power_supply *ps,
> + const struct power_supply_ext *ext,
> + void *ext_data,
> + enum power_supply_property prop,
> + union power_supply_propval *val)
> +{
> + struct lwmi_om_priv *priv = ext_data;
> + struct wmi_method_args_32 args;
> + u32 retval;
> + int ret;
> +
> + args.arg0 = LWMI_ATTR_ID_PSU(LWMI_FEATURE_ID_PSU_CHARGE_TYPE, LWMI_TYPE_ID_PSU_AC);
> +
> + ret = lwmi_dev_evaluate_int(priv->wdev, 0x0, LWMI_FEATURE_VALUE_GET,
> + (unsigned char *)&args, sizeof(args),
> + &retval);
> + if (ret)
> + return ret;
> +
> + dev_dbg(&priv->wdev->dev, "Got return value %x for property %x\n", retval, prop);
%#x
> +
> + if (retval == LWMI_CHARGE_TYPE_LONGLIFE)
> + val->intval = POWER_SUPPLY_CHARGE_TYPE_LONGLIFE;
> + else
> + val->intval = POWER_SUPPLY_CHARGE_TYPE_STANDARD;
I'd suggest using a switch statement here with a default path to
dev_err() unexpected retval.
> +
> + return 0;
> +}
> +
> +/**
> + * lwmi_psy_ext_set_prop() - Set a power_supply_ext property
> + * @ps: The battery that was extended
> + * @ext: The extension
> + * @ext_data: Pointer the lwmi_om_priv drvdata
> + * @prop: The property to write
> + * @val: The value to write
> + *
> + * Writes the given value to the power_supply_ext property
> + *
> + * Return: 0 on success, or an error
> + */
> +static int lwmi_psy_ext_set_prop(struct power_supply *ps,
> + const struct power_supply_ext *ext,
> + void *ext_data,
> + enum power_supply_property prop,
> + const union power_supply_propval *val)
> +{
> + struct lwmi_om_priv *priv = ext_data;
> + struct wmi_method_args_32 args;
> +
> + args.arg0 = LWMI_ATTR_ID_PSU(LWMI_FEATURE_ID_PSU_CHARGE_TYPE, LWMI_TYPE_ID_PSU_AC);
> + if (val->intval == POWER_SUPPLY_CHARGE_TYPE_LONGLIFE)
> + args.arg1 = LWMI_CHARGE_TYPE_LONGLIFE;
> + else
> + args.arg1 = LWMI_CHARGE_TYPE_STANDARD;
Ditto.
> +
> + dev_dbg(&priv->wdev->dev, "Attempting to set %#08x for property %x to %x\n",
%#010x and %#x
> + args.arg0, prop, args.arg1);
> +
> + return lwmi_dev_evaluate_int(priv->wdev, 0x0, LWMI_FEATURE_VALUE_SET,
> + (unsigned char *)&args, sizeof(args), NULL);
> +}
> +
> +/**
> + * lwmi_psy_prop_is_writeable() - Determine if the property is supported
> + * @ps: The battery that was extended
> + * @ext: The extension
> + * @ext_data: Pointer the lwmi_om_priv drvdata
> + * @prop: The property to check
> + *
> + * Checks capdata 00 to determine if the property is supported.
> + *
> + * Return: Support level, or false
> + */
> +static int lwmi_psy_prop_is_writeable(struct power_supply *ps,
> + const struct power_supply_ext *ext,
> + void *ext_data,
> + enum power_supply_property prop)
> +{
> + struct lwmi_om_priv *priv = ext_data;
> + struct capdata00 capdata;
> + u32 attribute_id = LWMI_ATTR_ID_PSU(LWMI_FEATURE_ID_PSU_CHARGE_TYPE, LWMI_TYPE_ID_PSU_AC);
> + int ret;
> +
> + ret = lwmi_cd00_get_data(priv->cd00_list, attribute_id, &capdata);
> + if (ret)
> + return false;
> +
> + dev_dbg(&priv->wdev->dev, "Battery charge mode (%#08x) support level: %x\n",
Ditto.
> + attribute_id, capdata.supported);
> +
> + return capdata.supported;
> +}
> +
> +static const enum power_supply_property lwmi_psy_ext_props[] = {
> + POWER_SUPPLY_PROP_CHARGE_TYPES,
> +};
> +
> +static const struct power_supply_ext lwmi_psy_ext = {
> + .name = LWMI_OM_SYSFS_NAME,
> + .properties = lwmi_psy_ext_props,
> + .num_properties = ARRAY_SIZE(lwmi_psy_ext_props),
> + .charge_types = (BIT(POWER_SUPPLY_CHARGE_TYPE_STANDARD) |
> + BIT(POWER_SUPPLY_CHARGE_TYPE_LONGLIFE)),
> + .get_property = lwmi_psy_ext_get_prop,
> + .set_property = lwmi_psy_ext_set_prop,
> + .property_is_writeable = lwmi_psy_prop_is_writeable,
> +};
Update Kconfig with `depends on ACPI_BATTERY' like other drivers did.
> +
> +/**
> + * lwmi_add_battery() - Connect the power_supply_ext
> + * @battery: The battery to extend
> + * @hook: The driver hook used to extend the battery
> + *
> + * Return: 0 on success, or an error.
> + */
> +static int lwmi_add_battery(struct power_supply *battery, struct acpi_battery_hook *hook)
> +{
> + struct lwmi_om_priv *priv = container_of(hook, struct lwmi_om_priv, battery_hook);
> +
> + return power_supply_register_extension(battery, &lwmi_psy_ext, &priv->wdev->dev, priv);
> +}
> +
> +/**
> + * lwmi_remove_battery() - Disconnect the power_supply_ext
> + * @battery: The battery that was extended
> + * @hook: The driver hook used to extend the battery
> + *
> + * Return: 0 on success, or an error.
> + */
> +static int lwmi_remove_battery(struct power_supply *battery, struct acpi_battery_hook *hook)
> +{
> + power_supply_unregister_extension(battery, &lwmi_psy_ext);
> + return 0;
> +}
> +
> +/**
> + * lwmi_acpi_match() - Attempts to return the ideapad acpi handle
> + * @handle: The ACPI handle that manages battery charging
> + * @lvl: Unused
> + * @context: Void pointer to the acpi_handle object to return
> + * @retval: Unused
> + *
> + * Checks if the ideapad_laptop driver is going to manage charge_type first,
> + * then if not, hooks the battery to our WMI methods.
> + *
> + * Return: AE_CTRL_TERMINATE if found, AE_OK if not found.
> + */
> +static acpi_status lwmi_acpi_match(acpi_handle handle, u32 lvl,
> + void *context, void **retval)
> +{
> + acpi_handle *ahand = context;
> +
> + if (!handle)
> + return AE_OK;
> +
> + *ahand = handle;
> +
> + return AE_CTRL_TERMINATE;
> +}
> +
> +static bool force_load_psy_ext;
> +module_param(force_load_psy_ext, bool, 0444);
> +MODULE_PARM_DESC(force_load_psy_ext,
> + "This option will skip checking if the ideapad_laptop driver will conflict "
> + "with adding an extension to set the battery charge type. It is recommended "
> + "to blacklist the ideapad driver before using this option.");
> +
> +/**
> + * lwmi_om_ps_ext_init() - Hooks power supply extension to device battery
> + * @priv: Driver private data
> + *
> + * Checks if the ideapad_laptop driver is going to manage charge_type first,
> + * then if not, hooks the battery to our WMI methods.
> + */
> +static void lwmi_om_ps_ext_init(struct lwmi_om_priv *priv)
> +{
> + static const char * const ideapad_hid = "VPC2004";
> + acpi_handle handle = NULL;
> + int ret;
> +
> + /* Deconflict ideapad_laptop driver */
> + ret = acpi_get_devices(ideapad_hid, lwmi_acpi_match, &handle, NULL);
Skip it in the force_load_psy_ext=Y case.
> + if (ret)
> + return;
> +
> + if (handle && !force_load_psy_ext) {
> + if (acpi_has_method(handle, "GBMD") && acpi_has_method(handle, "SBMC")) {
Connect them with `&&' to prevent unnecessary indents and...
> + dev_dbg(&priv->wdev->dev, "ideapad_laptop driver manages battery for device.\n");
...line length exceeding 100.
Thanks,
Rong
> + return;
> + }
> + }
> +
> + /* Add battery hooks */
> + priv->battery_hook.add_battery = lwmi_add_battery;
> + priv->battery_hook.remove_battery = lwmi_remove_battery;
> + priv->battery_hook.name = "Lenovo WMI Other Battery Extension";
> +
> + ret = devm_battery_hook_register(&priv->wdev->dev, &priv->battery_hook);
> + if (ret)
> + dev_err(&priv->wdev->dev, "Error during battery hook: %i\n", ret);
> +}
> +
> /* ======== fw_attributes (component: lenovo-wmi-capdata 01) ======== */
>
> struct tunable_attr_01 {
> @@ -1334,6 +1565,7 @@ static int lwmi_om_master_bind(struct device *dev)
> return -ENODEV;
>
> lwmi_om_fan_info_collect_cd00(priv);
> + lwmi_om_ps_ext_init(priv);
>
> return lwmi_om_fw_attr_add(priv);
> }
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH v4 3/8] platform/x86: lenovo-wmi-other: Add lwmi_attr_id() function
2026-03-15 1:08 ` Rong Zhang
@ 2026-03-15 3:38 ` Derek J. Clark
0 siblings, 0 replies; 21+ messages in thread
From: Derek J. Clark @ 2026-03-15 3:38 UTC (permalink / raw)
To: Rong Zhang, Ilpo Järvinen, Hans de Goede
Cc: Mark Pearson, Armin Wolf, Jonathan Corbet, Kurt Borja,
platform-driver-x86, linux-kernel
On March 14, 2026 6:08:55 PM PDT, Rong Zhang <i@rong.moe> wrote:
>Hi Derek,
>
>On Thu, 2026-03-12 at 03:10 +0000, Derek J. Clark wrote:
>> Adds lwmi_attr_id() function. In the same vein as LWMI_ATTR_ID_FAN_RPM(),
>> but as a generic, to de-duplicate attribute_id assignment biolerplate.
>>
>> Reviewed-by: Mark Pearson <mpearson-lenovo@squebb.ca>
>> Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
>> ---
>> v4:
>> - Switch from macro to static inline to preserve types.
>> ---
>> drivers/platform/x86/lenovo/wmi-gamezone.h | 1 +
>> drivers/platform/x86/lenovo/wmi-other.c | 59 +++++++++++++---------
>> 2 files changed, 35 insertions(+), 25 deletions(-)
>>
>> diff --git a/drivers/platform/x86/lenovo/wmi-gamezone.h b/drivers/platform/x86/lenovo/wmi-gamezone.h
>> index 6b163a5eeb95..ddb919cf6c36 100644
>> --- a/drivers/platform/x86/lenovo/wmi-gamezone.h
>> +++ b/drivers/platform/x86/lenovo/wmi-gamezone.h
>> @@ -10,6 +10,7 @@ enum gamezone_events_type {
>> };
>>
>> enum thermal_mode {
>> + LWMI_GZ_THERMAL_MODE_NONE = 0x00,
>> LWMI_GZ_THERMAL_MODE_QUIET = 0x01,
>> LWMI_GZ_THERMAL_MODE_BALANCED = 0x02,
>> LWMI_GZ_THERMAL_MODE_PERFORMANCE = 0x03,
>> diff --git a/drivers/platform/x86/lenovo/wmi-other.c b/drivers/platform/x86/lenovo/wmi-other.c
>> index c1728c7c2957..9fff9c1f768c 100644
>> --- a/drivers/platform/x86/lenovo/wmi-other.c
>> +++ b/drivers/platform/x86/lenovo/wmi-other.c
>> @@ -73,10 +73,26 @@
>>
>> #define LWMI_FAN_DIV 100
>>
>> -#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)))
>> +/**
>> + * lwmi_attr_id() - Formats a capability data attribute ID
>> + * @dev_id: The u8 corresponding to the device ID.
>> + * @feat_id: The u8 corresponding to the feature ID on the device.
>> + * @mode_id: The u8 corresponding to the wmi-gamezone mode for set/get.
>> + * @type_id: The u8 corresponding to the sub-device.
>> + *
>> + * Return: u32.
>> + */
>> +static u32 lwmi_attr_id(u8 dev_id, u8 feat_id, u8 mode_id, u8 type_id)
>> +{
>> + return (FIELD_PREP(LWMI_ATTR_DEV_ID_MASK, dev_id) |
>> + FIELD_PREP(LWMI_ATTR_FEAT_ID_MASK, feat_id) |
>> + FIELD_PREP(LWMI_ATTR_MODE_ID_MASK, mode_id) |
>> + FIELD_PREP(LWMI_ATTR_TYPE_ID_MASK, type_id));
>> +}
>> +
>> +#define LWMI_ATTR_ID_FAN_RPM(x) \
>> + lwmi_attr_id(LWMI_DEVICE_ID_FAN, LWMI_FEATURE_ID_FAN_RPM, \
>> + LWMI_GZ_THERMAL_MODE_NONE, LWMI_FAN_ID(x))
>>
>> #define LWMI_OM_FW_ATTR_BASE_PATH "lenovo-wmi-other"
>> #define LWMI_OM_HWMON_NAME "lenovo_wmi_other"
>> @@ -550,6 +566,8 @@ struct tunable_attr_01 {
>> u8 feature_id;
>> u8 device_id;
>> u8 type_id;
>> + u8 cd_mode_id; /* mode arg for searching capdata */
>> + u8 cv_mode_id; /* mode arg for set/get current_value */
>
>Adding them actually depends on [PATCH v4 4/8], otherwise you always
>get 0 when accessing them in this patch. There are potential
>regressions...
>
>
Hi Rong,
Good catch. I think they got moved during a rebase edit by mistake. Thanks.
- Derek
>> };
>>
>> static struct tunable_attr_01 ppt_pl1_spl = {
>> @@ -715,12 +733,8 @@ static ssize_t attr_capdata01_show(struct kobject *kobj,
>> u32 attribute_id;
>> int value, ret;
>>
>> - attribute_id =
>> - FIELD_PREP(LWMI_ATTR_DEV_ID_MASK, tunable_attr->device_id) |
>> - FIELD_PREP(LWMI_ATTR_FEAT_ID_MASK, tunable_attr->feature_id) |
>> - FIELD_PREP(LWMI_ATTR_MODE_ID_MASK,
>> - LWMI_GZ_THERMAL_MODE_CUSTOM) |
>> - FIELD_PREP(LWMI_ATTR_TYPE_ID_MASK, tunable_attr->type_id);
>> + attribute_id = lwmi_attr_id(tunable_attr->device_id, tunable_attr->feature_id,
>> + LWMI_GZ_THERMAL_MODE_CUSTOM, tunable_attr->type_id);
>>
>> ret = lwmi_cd01_get_data(priv->cd01_list, attribute_id, &capdata);
>> if (ret)
>> @@ -775,7 +789,6 @@ static ssize_t attr_current_value_store(struct kobject *kobj,
>> struct wmi_method_args_32 args;
>> struct capdata01 capdata;
>> enum thermal_mode mode;
>> - u32 attribute_id;
>> u32 value;
>> int ret;
>>
>> @@ -786,13 +799,10 @@ static ssize_t attr_current_value_store(struct kobject *kobj,
>> if (mode != LWMI_GZ_THERMAL_MODE_CUSTOM)
>> return -EBUSY;
>>
>> - attribute_id =
>> - FIELD_PREP(LWMI_ATTR_DEV_ID_MASK, tunable_attr->device_id) |
>> - FIELD_PREP(LWMI_ATTR_FEAT_ID_MASK, tunable_attr->feature_id) |
>> - FIELD_PREP(LWMI_ATTR_MODE_ID_MASK, mode) |
>> - FIELD_PREP(LWMI_ATTR_TYPE_ID_MASK, tunable_attr->type_id);
>> + args.arg0 = lwmi_attr_id(tunable_attr->device_id, tunable_attr->feature_id,
>> + mode, tunable_attr->type_id);
>>
>> - ret = lwmi_cd01_get_data(priv->cd01_list, attribute_id, &capdata);
>> + ret = lwmi_cd01_get_data(priv->cd01_list, args.arg0, &capdata);
>> if (ret)
>> return ret;
>>
>> @@ -803,7 +813,8 @@ static ssize_t attr_current_value_store(struct kobject *kobj,
>> if (value < capdata.min_value || value > capdata.max_value)
>> return -EINVAL;
>>
>> - args.arg0 = attribute_id;
>> + args.arg0 = lwmi_attr_id(tunable_attr->device_id, tunable_attr->feature_id,
>> + tunable_attr->cv_mode_id, tunable_attr->type_id);
>
>...here...
>
>> args.arg1 = value;
>>
>> ret = lwmi_dev_evaluate_int(priv->wdev, 0x0, LWMI_FEATURE_VALUE_SET,
>> @@ -837,7 +848,6 @@ static ssize_t attr_current_value_show(struct kobject *kobj,
>> struct lwmi_om_priv *priv = dev_get_drvdata(tunable_attr->dev);
>> struct wmi_method_args_32 args;
>> enum thermal_mode mode;
>> - u32 attribute_id;
>> int retval;
>> int ret;
>>
>> @@ -845,13 +855,12 @@ static ssize_t attr_current_value_show(struct kobject *kobj,
>> if (ret)
>> return ret;
>>
>> - attribute_id =
>> - FIELD_PREP(LWMI_ATTR_DEV_ID_MASK, tunable_attr->device_id) |
>> - FIELD_PREP(LWMI_ATTR_FEAT_ID_MASK, tunable_attr->feature_id) |
>> - FIELD_PREP(LWMI_ATTR_MODE_ID_MASK, mode) |
>> - FIELD_PREP(LWMI_ATTR_TYPE_ID_MASK, tunable_attr->type_id);
>> + /* If "no-mode" is the supported mode, ensure we never send current mode */
>> + if (tunable_attr->cv_mode_id == LWMI_GZ_THERMAL_MODE_NONE)
>> + mode = tunable_attr->cv_mode_id;
>
>...and here.
>
>Thanks,
>Rong
>
>>
>> - args.arg0 = attribute_id;
>> + args.arg0 = lwmi_attr_id(tunable_attr->device_id, tunable_attr->feature_id,
>> + mode, tunable_attr->type_id);
>>
>> ret = lwmi_dev_evaluate_int(priv->wdev, 0x0, LWMI_FEATURE_VALUE_GET,
>> (unsigned char *)&args, sizeof(args),
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH v4 4/8] platform/x86: lenovo-wmi-other: Limit adding attributes to supported devices
2026-03-15 1:26 ` Rong Zhang
2026-03-15 1:39 ` Rong Zhang
@ 2026-03-15 3:47 ` Derek J. Clark
2026-03-15 18:50 ` Rong Zhang
1 sibling, 1 reply; 21+ messages in thread
From: Derek J. Clark @ 2026-03-15 3:47 UTC (permalink / raw)
To: Rong Zhang, Ilpo Järvinen, Hans de Goede
Cc: Mark Pearson, Armin Wolf, Jonathan Corbet, Kurt Borja,
platform-driver-x86, linux-kernel
On March 14, 2026 6:26:08 PM PDT, Rong Zhang <i@rong.moe> wrote:
>Hi Derek,
>
>On Thu, 2026-03-12 at 03:10 +0000, Derek J. Clark wrote:
>> Adds lwmi_is_attr_01_supported, and only creates the attribute subfolder
>> if the attribute is supported by the hardware. Due to some poorly
>> implemented BIOS this is a multi-step sequence of events. This is
>> because:
>> - Some BIOS support getting the capability data from custom mode (0xff),
>> while others only support it in no-mode (0x00).
>> - Some BIOS support get/set for the current value from custom mode (0xff),
>> while others only support it in no-mode (0x00).
>> - Some BIOS report capability data for a method that is not fully
>> implemented.
>> - Some BIOS have methods fully implemented, but no complimentary
>> capability data.
>>
>> To ensure we only expose fully implemented methods with corresponding
>> capability data, we check each outcome before reporting that an
>> attribute can be supported.
>>
>> Checking for lwmi_is_attr_01_supported during remove is not done to
>> ensure that we don't attempt to call cd01 or send WMI events if one of
>> the interfaces being removed was the cause of the driver unloading.
>>
>> Reviewed-by: Mark Pearson <mpearson-lenovo@squebb.ca>
>> Reported-by: Kurt Borja <kuurtb@gmail.com>
>> Closes: https://lore.kernel.org/platform-driver-x86/DG60P3SHXR8H.3NSEHMZ6J7XRC@gmail.com/
>> Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
>> ---
>> v4:
>> - Use for loop instead of backtrace gotos for checking if an attribute
>> is supported.
>> - Add include for dev_printk.
>> - Wrap dev_dbg in lwmi_is_attr_01_supported earlier.
>> - Don't use symmetric cleanup of attributes in error states.
>> ---
>> drivers/platform/x86/lenovo/wmi-other.c | 76 ++++++++++++++++++++++++-
>> 1 file changed, 74 insertions(+), 2 deletions(-)
>>
>> diff --git a/drivers/platform/x86/lenovo/wmi-other.c b/drivers/platform/x86/lenovo/wmi-other.c
>> index 9fff9c1f768c..55a26e5617d4 100644
>> --- a/drivers/platform/x86/lenovo/wmi-other.c
>> +++ b/drivers/platform/x86/lenovo/wmi-other.c
>> @@ -32,6 +32,7 @@
>> #include <linux/component.h>
>> #include <linux/container_of.h>
>> #include <linux/device.h>
>> +#include <linux/dev_printk.h>
>> #include <linux/export.h>
>> #include <linux/gfp_types.h>
>> #include <linux/hwmon.h>
>> @@ -871,6 +872,76 @@ static ssize_t attr_current_value_show(struct kobject *kobj,
>> return sysfs_emit(buf, "%d\n", retval);
>> }
>>
>> +/**
>> + * lwmi_attr_01_is_supported() - Determine if the given attribute is supported.
>> + * @tunable_attr: The attribute to verify.
>> + *
>> + * First check if the attribute has a corresponding capdata01 table in the cd01
>> + * module under the "custom" mode (0xff). If that is not present then check if
>> + * there is a corresponding "no-mode" (0x00) entry. If either of those passes,
>> + * check capdata->supported for values > 0. If capdata is available, attempt to
>> + * determine the set/get mode for the current value property using a similar
>> + * pattern. If the value returned by either custom or no-mode is 0, or we get
>> + * an error, we assume that mode is not supported. If any of the above checks
>> + * fail then the attribute is not fully supported.
>> + *
>> + * The probed cd_mode_id/cv_mode_id are stored on the tunable_attr for later
>> + * reference.
>> + *
>> + * Return: Support level, or an error code.
>> + */
>> +static int lwmi_attr_01_is_supported(struct tunable_attr_01 *tunable_attr)
>> +{
>> + u8 modes[2] = { LWMI_GZ_THERMAL_MODE_CUSTOM, LWMI_GZ_THERMAL_MODE_NONE };
>> + struct lwmi_om_priv *priv = dev_get_drvdata(tunable_attr->dev);
>> + struct wmi_method_args_32 args;
>> + bool cd_mode_found = false;
>> + bool cv_mode_found = false;
>> + struct capdata01 capdata;
>> + int retval, ret, i;
>> +
>> + /* Determine tunable_attr->cd_mode_id*/
>> + for (i = 0; i < ARRAY_SIZE(modes); i++) {
>
>#include <linux/array_size.h>
>
>> + args.arg0 = lwmi_attr_id(tunable_attr->device_id, tunable_attr->feature_id,
>> + modes[i], tunable_attr->type_id);
>> +
>> + ret = lwmi_cd01_get_data(priv->cd01_list, args.arg0, &capdata);
>> + if (ret || !capdata.supported)
>> + continue;
>> + tunable_attr->cd_mode_id = modes[i];
>> + cd_mode_found = true;
>> + break;
>> + }
>> +
>> + if (!cd_mode_found)
>> + return -EOPNOTSUPP;
>> +
>> + /* Determine tunable_attr->cv_mode_id, returns 1 if supported*/
>> + for (i = 0; i < ARRAY_SIZE(modes); i++) {
>> + args.arg0 = lwmi_attr_id(tunable_attr->device_id, tunable_attr->feature_id,
>> + modes[i], tunable_attr->type_id);
>> +
>> + ret = lwmi_dev_evaluate_int(priv->wdev, 0x0, LWMI_FEATURE_VALUE_GET,
>> + (unsigned char *)&args, sizeof(args),
>> + &retval);
>> + if (ret || !retval)
>> + continue;
>> + tunable_attr->cv_mode_id = modes[i];
>> + cv_mode_found = true;
>> + break;
>> + }
>> +
>> + if (!cv_mode_found)
>> + return -EOPNOTSUPP;
>> +
>> + dev_dbg(tunable_attr->dev,
>> + "cd_mode_id: %02x%02x%02x%02x, cv_mode_id: %#08x attribute support level: %x\n",
> ^ ^ ^ ^
>> + tunable_attr->device_id, tunable_attr->feature_id, tunable_attr->cd_mode_id,
>> + tunable_attr->type_id, args.arg0, capdata.supported);
> ^
>
> dev_dbg(tunable_attr->dev,
> "cd_mode_id: %#10x, cv_mode_id: %#10x, attribute support level: %#10x\n",
> lwmi_attr_id(...), args.arg0, capdata.supported);
>
I hadn't thought about using that here, good idea.
>> +
>> + return capdata.supported;
>
>You are casting u32 to int. Return it in a pointer argument if you need
>it.
We don't do anything with the specific value, we just check that the level is above 0. We also don't do anything with the error codes specifically. Perhaps this should return a bool instead and we can return true if this is above 0 and false in all other paths? That would also improve the syntax a bit since the function name is a question and that would allow for !.._is_supported checks.
Thanks,
Derek
>> +}
>> +
>> /* Lenovo WMI Other Mode Attribute macros */
>> #define __LWMI_ATTR_RO(_func, _name) \
>> { \
>> @@ -994,12 +1065,13 @@ static int lwmi_om_fw_attr_add(struct lwmi_om_priv *priv)
>> }
>>
>> for (i = 0; i < ARRAY_SIZE(cd01_attr_groups) - 1; i++) {
>> + cd01_attr_groups[i].tunable_attr->dev = &priv->wdev->dev;
>> + if (lwmi_attr_01_is_supported(cd01_attr_groups[i].tunable_attr) <= 0)
>
>Extra whitespace.
>
>> + continue;
>
>Add an empty line in between.
>
>Thanks,
>Rong
>
>> err = sysfs_create_group(&priv->fw_attr_kset->kobj,
>> cd01_attr_groups[i].attr_group);
>> if (err)
>> goto err_remove_groups;
>> -
>> - cd01_attr_groups[i].tunable_attr->dev = &priv->wdev->dev;
>> }
>> return 0;
>>
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH v4 3/8] platform/x86: lenovo-wmi-other: Add lwmi_attr_id() function
2026-03-15 1:32 ` Rong Zhang
@ 2026-03-15 3:49 ` Derek J. Clark
0 siblings, 0 replies; 21+ messages in thread
From: Derek J. Clark @ 2026-03-15 3:49 UTC (permalink / raw)
To: Rong Zhang, Ilpo Järvinen, Hans de Goede
Cc: Mark Pearson, Armin Wolf, Jonathan Corbet, Kurt Borja,
platform-driver-x86, linux-kernel
On March 14, 2026 6:32:51 PM PDT, Rong Zhang <i@rong.moe> wrote:
>Hi Derek,
>
>On Thu, 2026-03-12 at 03:10 +0000, Derek J. Clark wrote:
>> Adds lwmi_attr_id() function. In the same vein as LWMI_ATTR_ID_FAN_RPM(),
>> but as a generic, to de-duplicate attribute_id assignment biolerplate.
>>
>> Reviewed-by: Mark Pearson <mpearson-lenovo@squebb.ca>
>> Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
>> ---
>> v4:
>> - Switch from macro to static inline to preserve types.
>> ---
>> drivers/platform/x86/lenovo/wmi-gamezone.h | 1 +
>> drivers/platform/x86/lenovo/wmi-other.c | 59 +++++++++++++---------
>> 2 files changed, 35 insertions(+), 25 deletions(-)
>>
>> diff --git a/drivers/platform/x86/lenovo/wmi-gamezone.h b/drivers/platform/x86/lenovo/wmi-gamezone.h
>> index 6b163a5eeb95..ddb919cf6c36 100644
>> --- a/drivers/platform/x86/lenovo/wmi-gamezone.h
>> +++ b/drivers/platform/x86/lenovo/wmi-gamezone.h
>> @@ -10,6 +10,7 @@ enum gamezone_events_type {
>> };
>>
>> enum thermal_mode {
>> + LWMI_GZ_THERMAL_MODE_NONE = 0x00,
>> LWMI_GZ_THERMAL_MODE_QUIET = 0x01,
>> LWMI_GZ_THERMAL_MODE_BALANCED = 0x02,
>> LWMI_GZ_THERMAL_MODE_PERFORMANCE = 0x03,
>> diff --git a/drivers/platform/x86/lenovo/wmi-other.c b/drivers/platform/x86/lenovo/wmi-other.c
>> index c1728c7c2957..9fff9c1f768c 100644
>> --- a/drivers/platform/x86/lenovo/wmi-other.c
>> +++ b/drivers/platform/x86/lenovo/wmi-other.c
>> @@ -73,10 +73,26 @@
>>
>> #define LWMI_FAN_DIV 100
>>
>> -#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)))
>> +/**
>> + * lwmi_attr_id() - Formats a capability data attribute ID
>> + * @dev_id: The u8 corresponding to the device ID.
>> + * @feat_id: The u8 corresponding to the feature ID on the device.
>> + * @mode_id: The u8 corresponding to the wmi-gamezone mode for set/get.
>> + * @type_id: The u8 corresponding to the sub-device.
>> + *
>> + * Return: u32.
>> + */
>> +static u32 lwmi_attr_id(u8 dev_id, u8 feat_id, u8 mode_id, u8 type_id)
>> +{
>> + return (FIELD_PREP(LWMI_ATTR_DEV_ID_MASK, dev_id) |
>> + FIELD_PREP(LWMI_ATTR_FEAT_ID_MASK, feat_id) |
>> + FIELD_PREP(LWMI_ATTR_MODE_ID_MASK, mode_id) |
>> + FIELD_PREP(LWMI_ATTR_TYPE_ID_MASK, type_id));
>> +}
>
>Hmm, I'd suggest adding them to wmi-capdata.h so that you can refactor
>LWMI_ATTR_ID_FAN_TEST in wmi-capdata.c too.
>
>Thanks,
>Rong
>
Can do. I was trying to avoid logic in the headers (despite Ilpo saying it was alright in this case) but that's a valid reason to move it.
Thanks,
- Derek
>> +
>> +#define LWMI_ATTR_ID_FAN_RPM(x) \
>> + lwmi_attr_id(LWMI_DEVICE_ID_FAN, LWMI_FEATURE_ID_FAN_RPM, \
>> + LWMI_GZ_THERMAL_MODE_NONE, LWMI_FAN_ID(x))
>>
>> #define LWMI_OM_FW_ATTR_BASE_PATH "lenovo-wmi-other"
>> #define LWMI_OM_HWMON_NAME "lenovo_wmi_other"
>> @@ -550,6 +566,8 @@ struct tunable_attr_01 {
>> u8 feature_id;
>> u8 device_id;
>> u8 type_id;
>> + u8 cd_mode_id; /* mode arg for searching capdata */
>> + u8 cv_mode_id; /* mode arg for set/get current_value */
>> };
>>
>> static struct tunable_attr_01 ppt_pl1_spl = {
>> @@ -715,12 +733,8 @@ static ssize_t attr_capdata01_show(struct kobject *kobj,
>> u32 attribute_id;
>> int value, ret;
>>
>> - attribute_id =
>> - FIELD_PREP(LWMI_ATTR_DEV_ID_MASK, tunable_attr->device_id) |
>> - FIELD_PREP(LWMI_ATTR_FEAT_ID_MASK, tunable_attr->feature_id) |
>> - FIELD_PREP(LWMI_ATTR_MODE_ID_MASK,
>> - LWMI_GZ_THERMAL_MODE_CUSTOM) |
>> - FIELD_PREP(LWMI_ATTR_TYPE_ID_MASK, tunable_attr->type_id);
>> + attribute_id = lwmi_attr_id(tunable_attr->device_id, tunable_attr->feature_id,
>> + LWMI_GZ_THERMAL_MODE_CUSTOM, tunable_attr->type_id);
>>
>> ret = lwmi_cd01_get_data(priv->cd01_list, attribute_id, &capdata);
>> if (ret)
>> @@ -775,7 +789,6 @@ static ssize_t attr_current_value_store(struct kobject *kobj,
>> struct wmi_method_args_32 args;
>> struct capdata01 capdata;
>> enum thermal_mode mode;
>> - u32 attribute_id;
>> u32 value;
>> int ret;
>>
>> @@ -786,13 +799,10 @@ static ssize_t attr_current_value_store(struct kobject *kobj,
>> if (mode != LWMI_GZ_THERMAL_MODE_CUSTOM)
>> return -EBUSY;
>>
>> - attribute_id =
>> - FIELD_PREP(LWMI_ATTR_DEV_ID_MASK, tunable_attr->device_id) |
>> - FIELD_PREP(LWMI_ATTR_FEAT_ID_MASK, tunable_attr->feature_id) |
>> - FIELD_PREP(LWMI_ATTR_MODE_ID_MASK, mode) |
>> - FIELD_PREP(LWMI_ATTR_TYPE_ID_MASK, tunable_attr->type_id);
>> + args.arg0 = lwmi_attr_id(tunable_attr->device_id, tunable_attr->feature_id,
>> + mode, tunable_attr->type_id);
>>
>> - ret = lwmi_cd01_get_data(priv->cd01_list, attribute_id, &capdata);
>> + ret = lwmi_cd01_get_data(priv->cd01_list, args.arg0, &capdata);
>> if (ret)
>> return ret;
>>
>> @@ -803,7 +813,8 @@ static ssize_t attr_current_value_store(struct kobject *kobj,
>> if (value < capdata.min_value || value > capdata.max_value)
>> return -EINVAL;
>>
>> - args.arg0 = attribute_id;
>> + args.arg0 = lwmi_attr_id(tunable_attr->device_id, tunable_attr->feature_id,
>> + tunable_attr->cv_mode_id, tunable_attr->type_id);
>> args.arg1 = value;
>>
>> ret = lwmi_dev_evaluate_int(priv->wdev, 0x0, LWMI_FEATURE_VALUE_SET,
>> @@ -837,7 +848,6 @@ static ssize_t attr_current_value_show(struct kobject *kobj,
>> struct lwmi_om_priv *priv = dev_get_drvdata(tunable_attr->dev);
>> struct wmi_method_args_32 args;
>> enum thermal_mode mode;
>> - u32 attribute_id;
>> int retval;
>> int ret;
>>
>> @@ -845,13 +855,12 @@ static ssize_t attr_current_value_show(struct kobject *kobj,
>> if (ret)
>> return ret;
>>
>> - attribute_id =
>> - FIELD_PREP(LWMI_ATTR_DEV_ID_MASK, tunable_attr->device_id) |
>> - FIELD_PREP(LWMI_ATTR_FEAT_ID_MASK, tunable_attr->feature_id) |
>> - FIELD_PREP(LWMI_ATTR_MODE_ID_MASK, mode) |
>> - FIELD_PREP(LWMI_ATTR_TYPE_ID_MASK, tunable_attr->type_id);
>> + /* If "no-mode" is the supported mode, ensure we never send current mode */
>> + if (tunable_attr->cv_mode_id == LWMI_GZ_THERMAL_MODE_NONE)
>> + mode = tunable_attr->cv_mode_id;
>>
>> - args.arg0 = attribute_id;
>> + args.arg0 = lwmi_attr_id(tunable_attr->device_id, tunable_attr->feature_id,
>> + mode, tunable_attr->type_id);
>>
>> ret = lwmi_dev_evaluate_int(priv->wdev, 0x0, LWMI_FEATURE_VALUE_GET,
>> (unsigned char *)&args, sizeof(args),
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH v4 8/8] platform/x86: lenovo-wmi-other: Add WMI battery charge limiting
2026-03-15 2:00 ` Rong Zhang
@ 2026-03-15 3:52 ` Derek J. Clark
2026-03-15 18:59 ` Rong Zhang
0 siblings, 1 reply; 21+ messages in thread
From: Derek J. Clark @ 2026-03-15 3:52 UTC (permalink / raw)
To: Rong Zhang, Ilpo Järvinen, Hans de Goede
Cc: Mark Pearson, Armin Wolf, Jonathan Corbet, Kurt Borja,
platform-driver-x86, linux-kernel
On March 14, 2026 7:00:20 PM PDT, Rong Zhang <i@rong.moe> wrote:
>Hi Derek,
>
>On Thu, 2026-03-12 at 03:10 +0000, Derek J. Clark wrote:
>> Add charge-type power supply extension for devices that support WMI based
>> charge enable/disable.
>>
>> Lenovo Legion devices that implement WMI function and capdata ID
>> 0x03010001 in their BIOS are able to enable or disable charging at 80%
>> through the lenovo-wmi-other interface. Add a charge_type power supply
>> extension to expose this capability to the sysfs.
>>
>> The ideapad_laptop driver can also provide the charge_type attribute. To
>> avoid conflicts between the drivers, get the acpi_handle and do the same
>> check that ideapad_laptop does when it enables the feature. If the
>> feature is supported in ideapad_laptop, abort adding the extension from
>> lenovo-wmi-other. The ACPI method is more reliable when both are
>> present, from my testing, so we can prefer that implementation and do
>> not need to worry about de-conflicting from inside that driver. A new
>> module parameter, force_load_psy_ext, is provided to bypass this ACPI
>> check, if desired.
>>
>> Reviewed-by: Mark Pearson <mpearson-lenovo@squebb.ca>
>> Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
>> ---
>> v4:
>> - Remove unused defines.
>> - Disambiguate charging defines by renaming them to be more consistent
>> with the kernel modes they represent.
>> - Add module parameter to ignore ACPI checks.
>> - Don't fail if the ACPI handle isn't found, skip the ACPI check
>> instead.
>> ---
>> drivers/platform/x86/lenovo/wmi-capdata.h | 1 +
>> drivers/platform/x86/lenovo/wmi-other.c | 234 +++++++++++++++++++++-
>> 2 files changed, 234 insertions(+), 1 deletion(-)
>>
>> diff --git a/drivers/platform/x86/lenovo/wmi-capdata.h b/drivers/platform/x86/lenovo/wmi-capdata.h
>> index 196481b1ce17..cc0d1c176c2f 100644
>> --- a/drivers/platform/x86/lenovo/wmi-capdata.h
>> +++ b/drivers/platform/x86/lenovo/wmi-capdata.h
>> @@ -20,6 +20,7 @@
>> enum lwmi_device_id {
>> LWMI_DEVICE_ID_CPU = 0x01,
>> LWMI_DEVICE_ID_GPU = 0x02,
>> + LWMI_DEVICE_ID_PSU = 0x03,
>> LWMI_DEVICE_ID_FAN = 0x04,
>> };
>>
>> diff --git a/drivers/platform/x86/lenovo/wmi-other.c b/drivers/platform/x86/lenovo/wmi-other.c
>> index 61ac9bee352f..cab9ae6bd811 100644
>> --- a/drivers/platform/x86/lenovo/wmi-other.c
>> +++ b/drivers/platform/x86/lenovo/wmi-other.c
>> @@ -43,9 +43,12 @@
>> #include <linux/module.h>
>> #include <linux/notifier.h>
>> #include <linux/platform_profile.h>
>> +#include <linux/power_supply.h>
>> #include <linux/types.h>
>> #include <linux/wmi.h>
>>
>> +#include <acpi/battery.h>
>> +
>> #include "wmi-capdata.h"
>> #include "wmi-events.h"
>> #include "wmi-gamezone.h"
>> @@ -79,10 +82,12 @@ enum lwmi_feature_id_gpu {
>> LWMI_FEATURE_ID_GPU_NV_CPU_BOOST = 0x0b,
>> };
>>
>> -#define LWMI_FEATURE_ID_FAN_RPM 0x03
>> +#define LWMI_FEATURE_ID_FAN_RPM 0x03
>> +#define LWMI_FEATURE_ID_PSU_CHARGE_TYPE 0x01
>>
>> #define LWMI_TYPE_ID_NONE 0x00
>> #define LWMI_TYPE_ID_CROSSLOAD 0x01
>> +#define LWMI_TYPE_ID_PSU_AC 0x01
>>
>> #define LWMI_FEATURE_VALUE_GET 17
>> #define LWMI_FEATURE_VALUE_SET 18
>> @@ -93,6 +98,9 @@ enum lwmi_feature_id_gpu {
>>
>> #define LWMI_FAN_DIV 100
>>
>> +#define LWMI_CHARGE_TYPE_STANDARD 0x00
>> +#define LWMI_CHARGE_TYPE_LONGLIFE 0x01
>> +
>> /**
>> * lwmi_attr_id() - Formats a capability data attribute ID
>> * @dev_id: The u8 corresponding to the device ID.
>> @@ -114,6 +122,10 @@ static u32 lwmi_attr_id(u8 dev_id, u8 feat_id, u8 mode_id, u8 type_id)
>> lwmi_attr_id(LWMI_DEVICE_ID_FAN, LWMI_FEATURE_ID_FAN_RPM, \
>> LWMI_GZ_THERMAL_MODE_NONE, LWMI_FAN_ID(x))
>>
>> +#define LWMI_ATTR_ID_PSU(feat, type) \
>> + lwmi_attr_id(LWMI_DEVICE_ID_PSU, feat, \
>> + LWMI_GZ_THERMAL_MODE_NONE, type)
>> +
>> #define LWMI_OM_SYSFS_NAME "lenovo-wmi-other"
>> #define LWMI_OM_HWMON_NAME "lenovo_wmi_other"
>>
>> @@ -155,6 +167,8 @@ struct lwmi_om_priv {
>> bool capdata00_collected : 1;
>> bool capdata_fan_collected : 1;
>> } fan_flags;
>> +
>> + struct acpi_battery_hook battery_hook;
>> };
>>
>> /*
>> @@ -579,6 +593,223 @@ static void lwmi_om_fan_info_collect_cd_fan(struct device *dev, struct cd_list *
>> lwmi_om_hwmon_add(priv);
>> }
>>
>> +/* ======== Power Supply Extension (component: lenovo-wmi-capdata 00) ======== */
>> +
>> +/**
>> + * lwmi_psy_ext_get_prop() - Get a power_supply_ext property
>> + * @ps: The battery that was extended
>> + * @ext: The extension
>> + * @ext_data: Pointer the lwmi_om_priv drvdata
>> + * @prop: The property to read
>> + * @val: The value to return
>> + *
>> + * Writes the given value to the power_supply_ext property
>> + *
>> + * Return: 0 on success, or an error
>> + */
>> +static int lwmi_psy_ext_get_prop(struct power_supply *ps,
>> + const struct power_supply_ext *ext,
>> + void *ext_data,
>> + enum power_supply_property prop,
>> + union power_supply_propval *val)
>> +{
>> + struct lwmi_om_priv *priv = ext_data;
>> + struct wmi_method_args_32 args;
>> + u32 retval;
>> + int ret;
>> +
>> + args.arg0 = LWMI_ATTR_ID_PSU(LWMI_FEATURE_ID_PSU_CHARGE_TYPE, LWMI_TYPE_ID_PSU_AC);
>> +
>> + ret = lwmi_dev_evaluate_int(priv->wdev, 0x0, LWMI_FEATURE_VALUE_GET,
>> + (unsigned char *)&args, sizeof(args),
>> + &retval);
>> + if (ret)
>> + return ret;
>> +
>> + dev_dbg(&priv->wdev->dev, "Got return value %x for property %x\n", retval, prop);
>
>%#x
>
>> +
>> + if (retval == LWMI_CHARGE_TYPE_LONGLIFE)
>> + val->intval = POWER_SUPPLY_CHARGE_TYPE_LONGLIFE;
>> + else
>> + val->intval = POWER_SUPPLY_CHARGE_TYPE_STANDARD;
>
>I'd suggest using a switch statement here with a default path to
>dev_err() unexpected retval.
>
I'll give it a shot. I recall there being some unexplained issues when I tried that originally, though I can't recall the specifics other than it seemed to trigger false negative outcomes for some reason. I'll let you know if I run into it again with more details so we can agree on a way forward if it becomes a problem.
Thanks again,
- Derek
>> +
>> + return 0;
>> +}
>> +
>> +/**
>> + * lwmi_psy_ext_set_prop() - Set a power_supply_ext property
>> + * @ps: The battery that was extended
>> + * @ext: The extension
>> + * @ext_data: Pointer the lwmi_om_priv drvdata
>> + * @prop: The property to write
>> + * @val: The value to write
>> + *
>> + * Writes the given value to the power_supply_ext property
>> + *
>> + * Return: 0 on success, or an error
>> + */
>> +static int lwmi_psy_ext_set_prop(struct power_supply *ps,
>> + const struct power_supply_ext *ext,
>> + void *ext_data,
>> + enum power_supply_property prop,
>> + const union power_supply_propval *val)
>> +{
>> + struct lwmi_om_priv *priv = ext_data;
>> + struct wmi_method_args_32 args;
>> +
>> + args.arg0 = LWMI_ATTR_ID_PSU(LWMI_FEATURE_ID_PSU_CHARGE_TYPE, LWMI_TYPE_ID_PSU_AC);
>> + if (val->intval == POWER_SUPPLY_CHARGE_TYPE_LONGLIFE)
>> + args.arg1 = LWMI_CHARGE_TYPE_LONGLIFE;
>> + else
>> + args.arg1 = LWMI_CHARGE_TYPE_STANDARD;
>
>Ditto.
>
>> +
>> + dev_dbg(&priv->wdev->dev, "Attempting to set %#08x for property %x to %x\n",
>
>%#010x and %#x
>
>> + args.arg0, prop, args.arg1);
>> +
>> + return lwmi_dev_evaluate_int(priv->wdev, 0x0, LWMI_FEATURE_VALUE_SET,
>> + (unsigned char *)&args, sizeof(args), NULL);
>> +}
>> +
>> +/**
>> + * lwmi_psy_prop_is_writeable() - Determine if the property is supported
>> + * @ps: The battery that was extended
>> + * @ext: The extension
>> + * @ext_data: Pointer the lwmi_om_priv drvdata
>> + * @prop: The property to check
>> + *
>> + * Checks capdata 00 to determine if the property is supported.
>> + *
>> + * Return: Support level, or false
>> + */
>> +static int lwmi_psy_prop_is_writeable(struct power_supply *ps,
>> + const struct power_supply_ext *ext,
>> + void *ext_data,
>> + enum power_supply_property prop)
>> +{
>> + struct lwmi_om_priv *priv = ext_data;
>> + struct capdata00 capdata;
>> + u32 attribute_id = LWMI_ATTR_ID_PSU(LWMI_FEATURE_ID_PSU_CHARGE_TYPE, LWMI_TYPE_ID_PSU_AC);
>> + int ret;
>> +
>> + ret = lwmi_cd00_get_data(priv->cd00_list, attribute_id, &capdata);
>> + if (ret)
>> + return false;
>> +
>> + dev_dbg(&priv->wdev->dev, "Battery charge mode (%#08x) support level: %x\n",
>
>Ditto.
>
>> + attribute_id, capdata.supported);
>> +
>> + return capdata.supported;
>> +}
>> +
>> +static const enum power_supply_property lwmi_psy_ext_props[] = {
>> + POWER_SUPPLY_PROP_CHARGE_TYPES,
>> +};
>> +
>> +static const struct power_supply_ext lwmi_psy_ext = {
>> + .name = LWMI_OM_SYSFS_NAME,
>> + .properties = lwmi_psy_ext_props,
>> + .num_properties = ARRAY_SIZE(lwmi_psy_ext_props),
>> + .charge_types = (BIT(POWER_SUPPLY_CHARGE_TYPE_STANDARD) |
>> + BIT(POWER_SUPPLY_CHARGE_TYPE_LONGLIFE)),
>> + .get_property = lwmi_psy_ext_get_prop,
>> + .set_property = lwmi_psy_ext_set_prop,
>> + .property_is_writeable = lwmi_psy_prop_is_writeable,
>> +};
>
>Update Kconfig with `depends on ACPI_BATTERY' like other drivers did.
>
>> +
>> +/**
>> + * lwmi_add_battery() - Connect the power_supply_ext
>> + * @battery: The battery to extend
>> + * @hook: The driver hook used to extend the battery
>> + *
>> + * Return: 0 on success, or an error.
>> + */
>> +static int lwmi_add_battery(struct power_supply *battery, struct acpi_battery_hook *hook)
>> +{
>> + struct lwmi_om_priv *priv = container_of(hook, struct lwmi_om_priv, battery_hook);
>> +
>> + return power_supply_register_extension(battery, &lwmi_psy_ext, &priv->wdev->dev, priv);
>> +}
>> +
>> +/**
>> + * lwmi_remove_battery() - Disconnect the power_supply_ext
>> + * @battery: The battery that was extended
>> + * @hook: The driver hook used to extend the battery
>> + *
>> + * Return: 0 on success, or an error.
>> + */
>> +static int lwmi_remove_battery(struct power_supply *battery, struct acpi_battery_hook *hook)
>> +{
>> + power_supply_unregister_extension(battery, &lwmi_psy_ext);
>> + return 0;
>> +}
>> +
>> +/**
>> + * lwmi_acpi_match() - Attempts to return the ideapad acpi handle
>> + * @handle: The ACPI handle that manages battery charging
>> + * @lvl: Unused
>> + * @context: Void pointer to the acpi_handle object to return
>> + * @retval: Unused
>> + *
>> + * Checks if the ideapad_laptop driver is going to manage charge_type first,
>> + * then if not, hooks the battery to our WMI methods.
>> + *
>> + * Return: AE_CTRL_TERMINATE if found, AE_OK if not found.
>> + */
>> +static acpi_status lwmi_acpi_match(acpi_handle handle, u32 lvl,
>> + void *context, void **retval)
>> +{
>> + acpi_handle *ahand = context;
>> +
>> + if (!handle)
>> + return AE_OK;
>> +
>> + *ahand = handle;
>> +
>> + return AE_CTRL_TERMINATE;
>> +}
>> +
>> +static bool force_load_psy_ext;
>> +module_param(force_load_psy_ext, bool, 0444);
>> +MODULE_PARM_DESC(force_load_psy_ext,
>> + "This option will skip checking if the ideapad_laptop driver will conflict "
>> + "with adding an extension to set the battery charge type. It is recommended "
>> + "to blacklist the ideapad driver before using this option.");
>> +
>> +/**
>> + * lwmi_om_ps_ext_init() - Hooks power supply extension to device battery
>> + * @priv: Driver private data
>> + *
>> + * Checks if the ideapad_laptop driver is going to manage charge_type first,
>> + * then if not, hooks the battery to our WMI methods.
>> + */
>> +static void lwmi_om_ps_ext_init(struct lwmi_om_priv *priv)
>> +{
>> + static const char * const ideapad_hid = "VPC2004";
>> + acpi_handle handle = NULL;
>> + int ret;
>> +
>> + /* Deconflict ideapad_laptop driver */
>> + ret = acpi_get_devices(ideapad_hid, lwmi_acpi_match, &handle, NULL);
>
>Skip it in the force_load_psy_ext=Y case.
>
>> + if (ret)
>> + return;
>> +
>> + if (handle && !force_load_psy_ext) {
>> + if (acpi_has_method(handle, "GBMD") && acpi_has_method(handle, "SBMC")) {
>
>Connect them with `&&' to prevent unnecessary indents and...
>
>> + dev_dbg(&priv->wdev->dev, "ideapad_laptop driver manages battery for device.\n");
>
>...line length exceeding 100.
>
>Thanks,
>Rong
>
>> + return;
>> + }
>> + }
>> +
>> + /* Add battery hooks */
>> + priv->battery_hook.add_battery = lwmi_add_battery;
>> + priv->battery_hook.remove_battery = lwmi_remove_battery;
>> + priv->battery_hook.name = "Lenovo WMI Other Battery Extension";
>> +
>> + ret = devm_battery_hook_register(&priv->wdev->dev, &priv->battery_hook);
>> + if (ret)
>> + dev_err(&priv->wdev->dev, "Error during battery hook: %i\n", ret);
>> +}
>> +
>> /* ======== fw_attributes (component: lenovo-wmi-capdata 01) ======== */
>>
>> struct tunable_attr_01 {
>> @@ -1334,6 +1565,7 @@ static int lwmi_om_master_bind(struct device *dev)
>> return -ENODEV;
>>
>> lwmi_om_fan_info_collect_cd00(priv);
>> + lwmi_om_ps_ext_init(priv);
>>
>> return lwmi_om_fw_attr_add(priv);
>> }
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH v4 4/8] platform/x86: lenovo-wmi-other: Limit adding attributes to supported devices
2026-03-15 3:47 ` Derek J. Clark
@ 2026-03-15 18:50 ` Rong Zhang
0 siblings, 0 replies; 21+ messages in thread
From: Rong Zhang @ 2026-03-15 18:50 UTC (permalink / raw)
To: Derek J. Clark
Cc: Ilpo Järvinen, Hans de Goede, Mark Pearson, Armin Wolf,
Jonathan Corbet, Kurt Borja, platform-driver-x86, linux-kernel
Hi Derek,
On Sat, 2026-03-14 at 20:47 -0700, Derek J. Clark wrote:
> On March 14, 2026 6:26:08 PM PDT, Rong Zhang <i@rong.moe> wrote:
> > Hi Derek,
> >
> > On Thu, 2026-03-12 at 03:10 +0000, Derek J. Clark wrote:
> > > Adds lwmi_is_attr_01_supported, and only creates the attribute subfolder
> > > if the attribute is supported by the hardware. Due to some poorly
> > > implemented BIOS this is a multi-step sequence of events. This is
> > > because:
> > > - Some BIOS support getting the capability data from custom mode (0xff),
> > > while others only support it in no-mode (0x00).
> > > - Some BIOS support get/set for the current value from custom mode (0xff),
> > > while others only support it in no-mode (0x00).
> > > - Some BIOS report capability data for a method that is not fully
> > > implemented.
> > > - Some BIOS have methods fully implemented, but no complimentary
> > > capability data.
> > >
> > > To ensure we only expose fully implemented methods with corresponding
> > > capability data, we check each outcome before reporting that an
> > > attribute can be supported.
> > >
> > > Checking for lwmi_is_attr_01_supported during remove is not done to
> > > ensure that we don't attempt to call cd01 or send WMI events if one of
> > > the interfaces being removed was the cause of the driver unloading.
> > >
> > > Reviewed-by: Mark Pearson <mpearson-lenovo@squebb.ca>
> > > Reported-by: Kurt Borja <kuurtb@gmail.com>
> > > Closes: https://lore.kernel.org/platform-driver-x86/DG60P3SHXR8H.3NSEHMZ6J7XRC@gmail.com/
> > > Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
> > > ---
> > > v4:
> > > - Use for loop instead of backtrace gotos for checking if an attribute
> > > is supported.
> > > - Add include for dev_printk.
> > > - Wrap dev_dbg in lwmi_is_attr_01_supported earlier.
> > > - Don't use symmetric cleanup of attributes in error states.
> > > ---
> > > drivers/platform/x86/lenovo/wmi-other.c | 76 ++++++++++++++++++++++++-
> > > 1 file changed, 74 insertions(+), 2 deletions(-)
> > >
> > > diff --git a/drivers/platform/x86/lenovo/wmi-other.c b/drivers/platform/x86/lenovo/wmi-other.c
> > > index 9fff9c1f768c..55a26e5617d4 100644
> > > --- a/drivers/platform/x86/lenovo/wmi-other.c
> > > +++ b/drivers/platform/x86/lenovo/wmi-other.c
> > > @@ -32,6 +32,7 @@
> > > #include <linux/component.h>
> > > #include <linux/container_of.h>
> > > #include <linux/device.h>
> > > +#include <linux/dev_printk.h>
> > > #include <linux/export.h>
> > > #include <linux/gfp_types.h>
> > > #include <linux/hwmon.h>
> > > @@ -871,6 +872,76 @@ static ssize_t attr_current_value_show(struct kobject *kobj,
> > > return sysfs_emit(buf, "%d\n", retval);
> > > }
> > >
> > > +/**
> > > + * lwmi_attr_01_is_supported() - Determine if the given attribute is supported.
> > > + * @tunable_attr: The attribute to verify.
> > > + *
> > > + * First check if the attribute has a corresponding capdata01 table in the cd01
> > > + * module under the "custom" mode (0xff). If that is not present then check if
> > > + * there is a corresponding "no-mode" (0x00) entry. If either of those passes,
> > > + * check capdata->supported for values > 0. If capdata is available, attempt to
> > > + * determine the set/get mode for the current value property using a similar
> > > + * pattern. If the value returned by either custom or no-mode is 0, or we get
> > > + * an error, we assume that mode is not supported. If any of the above checks
> > > + * fail then the attribute is not fully supported.
> > > + *
> > > + * The probed cd_mode_id/cv_mode_id are stored on the tunable_attr for later
> > > + * reference.
> > > + *
> > > + * Return: Support level, or an error code.
> > > + */
> > > +static int lwmi_attr_01_is_supported(struct tunable_attr_01 *tunable_attr)
> > > +{
> > > + u8 modes[2] = { LWMI_GZ_THERMAL_MODE_CUSTOM, LWMI_GZ_THERMAL_MODE_NONE };
> > > + struct lwmi_om_priv *priv = dev_get_drvdata(tunable_attr->dev);
> > > + struct wmi_method_args_32 args;
> > > + bool cd_mode_found = false;
> > > + bool cv_mode_found = false;
> > > + struct capdata01 capdata;
> > > + int retval, ret, i;
> > > +
> > > + /* Determine tunable_attr->cd_mode_id*/
> > > + for (i = 0; i < ARRAY_SIZE(modes); i++) {
> >
> > #include <linux/array_size.h>
> >
> > > + args.arg0 = lwmi_attr_id(tunable_attr->device_id, tunable_attr->feature_id,
> > > + modes[i], tunable_attr->type_id);
> > > +
> > > + ret = lwmi_cd01_get_data(priv->cd01_list, args.arg0, &capdata);
> > > + if (ret || !capdata.supported)
> > > + continue;
> > > + tunable_attr->cd_mode_id = modes[i];
> > > + cd_mode_found = true;
> > > + break;
> > > + }
> > > +
> > > + if (!cd_mode_found)
> > > + return -EOPNOTSUPP;
> > > +
> > > + /* Determine tunable_attr->cv_mode_id, returns 1 if supported*/
> > > + for (i = 0; i < ARRAY_SIZE(modes); i++) {
> > > + args.arg0 = lwmi_attr_id(tunable_attr->device_id, tunable_attr->feature_id,
> > > + modes[i], tunable_attr->type_id);
> > > +
> > > + ret = lwmi_dev_evaluate_int(priv->wdev, 0x0, LWMI_FEATURE_VALUE_GET,
> > > + (unsigned char *)&args, sizeof(args),
> > > + &retval);
> > > + if (ret || !retval)
> > > + continue;
> > > + tunable_attr->cv_mode_id = modes[i];
> > > + cv_mode_found = true;
> > > + break;
> > > + }
> > > +
> > > + if (!cv_mode_found)
> > > + return -EOPNOTSUPP;
> > > +
> > > + dev_dbg(tunable_attr->dev,
> > > + "cd_mode_id: %02x%02x%02x%02x, cv_mode_id: %#08x attribute support level: %x\n",
> > ^ ^ ^ ^
> > > + tunable_attr->device_id, tunable_attr->feature_id, tunable_attr->cd_mode_id,
> > > + tunable_attr->type_id, args.arg0, capdata.supported);
> > ^
> >
> > dev_dbg(tunable_attr->dev,
> > "cd_mode_id: %#10x, cv_mode_id: %#10x, attribute support level: %#10x\n",
> > lwmi_attr_id(...), args.arg0, capdata.supported);
> >
>
> I hadn't thought about using that here, good idea.
>
> > > +
> > > + return capdata.supported;
> >
> > You are casting u32 to int. Return it in a pointer argument if you need
> > it.
>
> We don't do anything with the specific value, we just check that the level is above 0. We also don't do anything with the error codes specifically. Perhaps this should return a bool instead and we can return true if this is above 0 and false in all other paths? That would also improve the syntax a bit since the function name is a question and that would allow for !.._is_supported checks.
Yeah, that's a good idea.
Thanks,
Rong
>
> Thanks,
> Derek
>
> > > +}
> > > +
> > > /* Lenovo WMI Other Mode Attribute macros */
> > > #define __LWMI_ATTR_RO(_func, _name) \
> > > { \
> > > @@ -994,12 +1065,13 @@ static int lwmi_om_fw_attr_add(struct lwmi_om_priv *priv)
> > > }
> > >
> > > for (i = 0; i < ARRAY_SIZE(cd01_attr_groups) - 1; i++) {
> > > + cd01_attr_groups[i].tunable_attr->dev = &priv->wdev->dev;
> > > + if (lwmi_attr_01_is_supported(cd01_attr_groups[i].tunable_attr) <= 0)
> >
> > Extra whitespace.
> >
> > > + continue;
> >
> > Add an empty line in between.
> >
> > Thanks,
> > Rong
> >
> > > err = sysfs_create_group(&priv->fw_attr_kset->kobj,
> > > cd01_attr_groups[i].attr_group);
> > > if (err)
> > > goto err_remove_groups;
> > > -
> > > - cd01_attr_groups[i].tunable_attr->dev = &priv->wdev->dev;
> > > }
> > > return 0;
> > >
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH v4 8/8] platform/x86: lenovo-wmi-other: Add WMI battery charge limiting
2026-03-15 3:52 ` Derek J. Clark
@ 2026-03-15 18:59 ` Rong Zhang
0 siblings, 0 replies; 21+ messages in thread
From: Rong Zhang @ 2026-03-15 18:59 UTC (permalink / raw)
To: Derek J. Clark
Cc: Ilpo Järvinen, Hans de Goede, Mark Pearson, Armin Wolf,
Jonathan Corbet, Kurt Borja, platform-driver-x86, linux-kernel
Hi Derek,
On Sat, 2026-03-14 at 20:52 -0700, Derek J. Clark wrote:
> On March 14, 2026 7:00:20 PM PDT, Rong Zhang <i@rong.moe> wrote:
> > Hi Derek,
> >
> > On Thu, 2026-03-12 at 03:10 +0000, Derek J. Clark wrote:
> > > Add charge-type power supply extension for devices that support WMI based
> > > charge enable/disable.
> > >
> > > Lenovo Legion devices that implement WMI function and capdata ID
> > > 0x03010001 in their BIOS are able to enable or disable charging at 80%
> > > through the lenovo-wmi-other interface. Add a charge_type power supply
> > > extension to expose this capability to the sysfs.
> > >
> > > The ideapad_laptop driver can also provide the charge_type attribute. To
> > > avoid conflicts between the drivers, get the acpi_handle and do the same
> > > check that ideapad_laptop does when it enables the feature. If the
> > > feature is supported in ideapad_laptop, abort adding the extension from
> > > lenovo-wmi-other. The ACPI method is more reliable when both are
> > > present, from my testing, so we can prefer that implementation and do
> > > not need to worry about de-conflicting from inside that driver. A new
> > > module parameter, force_load_psy_ext, is provided to bypass this ACPI
> > > check, if desired.
> > >
> > > Reviewed-by: Mark Pearson <mpearson-lenovo@squebb.ca>
> > > Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
> > > ---
> > > v4:
> > > - Remove unused defines.
> > > - Disambiguate charging defines by renaming them to be more consistent
> > > with the kernel modes they represent.
> > > - Add module parameter to ignore ACPI checks.
> > > - Don't fail if the ACPI handle isn't found, skip the ACPI check
> > > instead.
> > > ---
> > > drivers/platform/x86/lenovo/wmi-capdata.h | 1 +
> > > drivers/platform/x86/lenovo/wmi-other.c | 234 +++++++++++++++++++++-
> > > 2 files changed, 234 insertions(+), 1 deletion(-)
> > >
> > > diff --git a/drivers/platform/x86/lenovo/wmi-capdata.h b/drivers/platform/x86/lenovo/wmi-capdata.h
> > > index 196481b1ce17..cc0d1c176c2f 100644
> > > --- a/drivers/platform/x86/lenovo/wmi-capdata.h
> > > +++ b/drivers/platform/x86/lenovo/wmi-capdata.h
> > > @@ -20,6 +20,7 @@
> > > enum lwmi_device_id {
> > > LWMI_DEVICE_ID_CPU = 0x01,
> > > LWMI_DEVICE_ID_GPU = 0x02,
> > > + LWMI_DEVICE_ID_PSU = 0x03,
> > > LWMI_DEVICE_ID_FAN = 0x04,
> > > };
> > >
> > > diff --git a/drivers/platform/x86/lenovo/wmi-other.c b/drivers/platform/x86/lenovo/wmi-other.c
> > > index 61ac9bee352f..cab9ae6bd811 100644
> > > --- a/drivers/platform/x86/lenovo/wmi-other.c
> > > +++ b/drivers/platform/x86/lenovo/wmi-other.c
> > > @@ -43,9 +43,12 @@
> > > #include <linux/module.h>
> > > #include <linux/notifier.h>
> > > #include <linux/platform_profile.h>
> > > +#include <linux/power_supply.h>
> > > #include <linux/types.h>
> > > #include <linux/wmi.h>
> > >
> > > +#include <acpi/battery.h>
> > > +
> > > #include "wmi-capdata.h"
> > > #include "wmi-events.h"
> > > #include "wmi-gamezone.h"
> > > @@ -79,10 +82,12 @@ enum lwmi_feature_id_gpu {
> > > LWMI_FEATURE_ID_GPU_NV_CPU_BOOST = 0x0b,
> > > };
> > >
> > > -#define LWMI_FEATURE_ID_FAN_RPM 0x03
> > > +#define LWMI_FEATURE_ID_FAN_RPM 0x03
> > > +#define LWMI_FEATURE_ID_PSU_CHARGE_TYPE 0x01
> > >
> > > #define LWMI_TYPE_ID_NONE 0x00
> > > #define LWMI_TYPE_ID_CROSSLOAD 0x01
> > > +#define LWMI_TYPE_ID_PSU_AC 0x01
> > >
> > > #define LWMI_FEATURE_VALUE_GET 17
> > > #define LWMI_FEATURE_VALUE_SET 18
> > > @@ -93,6 +98,9 @@ enum lwmi_feature_id_gpu {
> > >
> > > #define LWMI_FAN_DIV 100
> > >
> > > +#define LWMI_CHARGE_TYPE_STANDARD 0x00
> > > +#define LWMI_CHARGE_TYPE_LONGLIFE 0x01
> > > +
> > > /**
> > > * lwmi_attr_id() - Formats a capability data attribute ID
> > > * @dev_id: The u8 corresponding to the device ID.
> > > @@ -114,6 +122,10 @@ static u32 lwmi_attr_id(u8 dev_id, u8 feat_id, u8 mode_id, u8 type_id)
> > > lwmi_attr_id(LWMI_DEVICE_ID_FAN, LWMI_FEATURE_ID_FAN_RPM, \
> > > LWMI_GZ_THERMAL_MODE_NONE, LWMI_FAN_ID(x))
> > >
> > > +#define LWMI_ATTR_ID_PSU(feat, type) \
> > > + lwmi_attr_id(LWMI_DEVICE_ID_PSU, feat, \
> > > + LWMI_GZ_THERMAL_MODE_NONE, type)
> > > +
> > > #define LWMI_OM_SYSFS_NAME "lenovo-wmi-other"
> > > #define LWMI_OM_HWMON_NAME "lenovo_wmi_other"
> > >
> > > @@ -155,6 +167,8 @@ struct lwmi_om_priv {
> > > bool capdata00_collected : 1;
> > > bool capdata_fan_collected : 1;
> > > } fan_flags;
> > > +
> > > + struct acpi_battery_hook battery_hook;
> > > };
> > >
> > > /*
> > > @@ -579,6 +593,223 @@ static void lwmi_om_fan_info_collect_cd_fan(struct device *dev, struct cd_list *
> > > lwmi_om_hwmon_add(priv);
> > > }
> > >
> > > +/* ======== Power Supply Extension (component: lenovo-wmi-capdata 00) ======== */
> > > +
> > > +/**
> > > + * lwmi_psy_ext_get_prop() - Get a power_supply_ext property
> > > + * @ps: The battery that was extended
> > > + * @ext: The extension
> > > + * @ext_data: Pointer the lwmi_om_priv drvdata
> > > + * @prop: The property to read
> > > + * @val: The value to return
> > > + *
> > > + * Writes the given value to the power_supply_ext property
> > > + *
> > > + * Return: 0 on success, or an error
> > > + */
> > > +static int lwmi_psy_ext_get_prop(struct power_supply *ps,
> > > + const struct power_supply_ext *ext,
> > > + void *ext_data,
> > > + enum power_supply_property prop,
> > > + union power_supply_propval *val)
> > > +{
> > > + struct lwmi_om_priv *priv = ext_data;
> > > + struct wmi_method_args_32 args;
> > > + u32 retval;
> > > + int ret;
> > > +
> > > + args.arg0 = LWMI_ATTR_ID_PSU(LWMI_FEATURE_ID_PSU_CHARGE_TYPE, LWMI_TYPE_ID_PSU_AC);
> > > +
> > > + ret = lwmi_dev_evaluate_int(priv->wdev, 0x0, LWMI_FEATURE_VALUE_GET,
> > > + (unsigned char *)&args, sizeof(args),
> > > + &retval);
> > > + if (ret)
> > > + return ret;
> > > +
> > > + dev_dbg(&priv->wdev->dev, "Got return value %x for property %x\n", retval, prop);
> >
> > %#x
> >
> > > +
> > > + if (retval == LWMI_CHARGE_TYPE_LONGLIFE)
> > > + val->intval = POWER_SUPPLY_CHARGE_TYPE_LONGLIFE;
> > > + else
> > > + val->intval = POWER_SUPPLY_CHARGE_TYPE_STANDARD;
> >
> > I'd suggest using a switch statement here with a default path to
> > dev_err() unexpected retval.
> >
>
> I'll give it a shot. I recall there being some unexplained issues when I tried that originally, though I can't recall the specifics other than it seemed to trigger false negative outcomes for some reason. I'll let you know if I run into it again with more details so we can agree on a way forward if it becomes a problem.
IMO we should be cautious while handling the returned value from
firmware, as we never know how broken it could be. Treating
POWER_SUPPLY_CHARGE_TYPE_STANDARD as a wildcard is a bad idea.
If there are some false negatives, we should handle them explicitly
instead of blindly mapping them to POWER_SUPPLY_CHARGE_TYPE_STANDARD.
Thanks,
Rong
>
> Thanks again,
> - Derek
>
> > > +
> > > + return 0;
> > > +}
> > > +
> > > +/**
> > > + * lwmi_psy_ext_set_prop() - Set a power_supply_ext property
> > > + * @ps: The battery that was extended
> > > + * @ext: The extension
> > > + * @ext_data: Pointer the lwmi_om_priv drvdata
> > > + * @prop: The property to write
> > > + * @val: The value to write
> > > + *
> > > + * Writes the given value to the power_supply_ext property
> > > + *
> > > + * Return: 0 on success, or an error
> > > + */
> > > +static int lwmi_psy_ext_set_prop(struct power_supply *ps,
> > > + const struct power_supply_ext *ext,
> > > + void *ext_data,
> > > + enum power_supply_property prop,
> > > + const union power_supply_propval *val)
> > > +{
> > > + struct lwmi_om_priv *priv = ext_data;
> > > + struct wmi_method_args_32 args;
> > > +
> > > + args.arg0 = LWMI_ATTR_ID_PSU(LWMI_FEATURE_ID_PSU_CHARGE_TYPE, LWMI_TYPE_ID_PSU_AC);
> > > + if (val->intval == POWER_SUPPLY_CHARGE_TYPE_LONGLIFE)
> > > + args.arg1 = LWMI_CHARGE_TYPE_LONGLIFE;
> > > + else
> > > + args.arg1 = LWMI_CHARGE_TYPE_STANDARD;
> >
> > Ditto.
> >
> > > +
> > > + dev_dbg(&priv->wdev->dev, "Attempting to set %#08x for property %x to %x\n",
> >
> > %#010x and %#x
> >
> > > + args.arg0, prop, args.arg1);
> > > +
> > > + return lwmi_dev_evaluate_int(priv->wdev, 0x0, LWMI_FEATURE_VALUE_SET,
> > > + (unsigned char *)&args, sizeof(args), NULL);
> > > +}
> > > +
> > > +/**
> > > + * lwmi_psy_prop_is_writeable() - Determine if the property is supported
> > > + * @ps: The battery that was extended
> > > + * @ext: The extension
> > > + * @ext_data: Pointer the lwmi_om_priv drvdata
> > > + * @prop: The property to check
> > > + *
> > > + * Checks capdata 00 to determine if the property is supported.
> > > + *
> > > + * Return: Support level, or false
> > > + */
> > > +static int lwmi_psy_prop_is_writeable(struct power_supply *ps,
> > > + const struct power_supply_ext *ext,
> > > + void *ext_data,
> > > + enum power_supply_property prop)
> > > +{
> > > + struct lwmi_om_priv *priv = ext_data;
> > > + struct capdata00 capdata;
> > > + u32 attribute_id = LWMI_ATTR_ID_PSU(LWMI_FEATURE_ID_PSU_CHARGE_TYPE, LWMI_TYPE_ID_PSU_AC);
> > > + int ret;
> > > +
> > > + ret = lwmi_cd00_get_data(priv->cd00_list, attribute_id, &capdata);
> > > + if (ret)
> > > + return false;
> > > +
> > > + dev_dbg(&priv->wdev->dev, "Battery charge mode (%#08x) support level: %x\n",
> >
> > Ditto.
> >
> > > + attribute_id, capdata.supported);
> > > +
> > > + return capdata.supported;
> > > +}
> > > +
> > > +static const enum power_supply_property lwmi_psy_ext_props[] = {
> > > + POWER_SUPPLY_PROP_CHARGE_TYPES,
> > > +};
> > > +
> > > +static const struct power_supply_ext lwmi_psy_ext = {
> > > + .name = LWMI_OM_SYSFS_NAME,
> > > + .properties = lwmi_psy_ext_props,
> > > + .num_properties = ARRAY_SIZE(lwmi_psy_ext_props),
> > > + .charge_types = (BIT(POWER_SUPPLY_CHARGE_TYPE_STANDARD) |
> > > + BIT(POWER_SUPPLY_CHARGE_TYPE_LONGLIFE)),
> > > + .get_property = lwmi_psy_ext_get_prop,
> > > + .set_property = lwmi_psy_ext_set_prop,
> > > + .property_is_writeable = lwmi_psy_prop_is_writeable,
> > > +};
> >
> > Update Kconfig with `depends on ACPI_BATTERY' like other drivers did.
> >
> > > +
> > > +/**
> > > + * lwmi_add_battery() - Connect the power_supply_ext
> > > + * @battery: The battery to extend
> > > + * @hook: The driver hook used to extend the battery
> > > + *
> > > + * Return: 0 on success, or an error.
> > > + */
> > > +static int lwmi_add_battery(struct power_supply *battery, struct acpi_battery_hook *hook)
> > > +{
> > > + struct lwmi_om_priv *priv = container_of(hook, struct lwmi_om_priv, battery_hook);
> > > +
> > > + return power_supply_register_extension(battery, &lwmi_psy_ext, &priv->wdev->dev, priv);
> > > +}
> > > +
> > > +/**
> > > + * lwmi_remove_battery() - Disconnect the power_supply_ext
> > > + * @battery: The battery that was extended
> > > + * @hook: The driver hook used to extend the battery
> > > + *
> > > + * Return: 0 on success, or an error.
> > > + */
> > > +static int lwmi_remove_battery(struct power_supply *battery, struct acpi_battery_hook *hook)
> > > +{
> > > + power_supply_unregister_extension(battery, &lwmi_psy_ext);
> > > + return 0;
> > > +}
> > > +
> > > +/**
> > > + * lwmi_acpi_match() - Attempts to return the ideapad acpi handle
> > > + * @handle: The ACPI handle that manages battery charging
> > > + * @lvl: Unused
> > > + * @context: Void pointer to the acpi_handle object to return
> > > + * @retval: Unused
> > > + *
> > > + * Checks if the ideapad_laptop driver is going to manage charge_type first,
> > > + * then if not, hooks the battery to our WMI methods.
> > > + *
> > > + * Return: AE_CTRL_TERMINATE if found, AE_OK if not found.
> > > + */
> > > +static acpi_status lwmi_acpi_match(acpi_handle handle, u32 lvl,
> > > + void *context, void **retval)
> > > +{
> > > + acpi_handle *ahand = context;
> > > +
> > > + if (!handle)
> > > + return AE_OK;
> > > +
> > > + *ahand = handle;
> > > +
> > > + return AE_CTRL_TERMINATE;
> > > +}
> > > +
> > > +static bool force_load_psy_ext;
> > > +module_param(force_load_psy_ext, bool, 0444);
> > > +MODULE_PARM_DESC(force_load_psy_ext,
> > > + "This option will skip checking if the ideapad_laptop driver will conflict "
> > > + "with adding an extension to set the battery charge type. It is recommended "
> > > + "to blacklist the ideapad driver before using this option.");
> > > +
> > > +/**
> > > + * lwmi_om_ps_ext_init() - Hooks power supply extension to device battery
> > > + * @priv: Driver private data
> > > + *
> > > + * Checks if the ideapad_laptop driver is going to manage charge_type first,
> > > + * then if not, hooks the battery to our WMI methods.
> > > + */
> > > +static void lwmi_om_ps_ext_init(struct lwmi_om_priv *priv)
> > > +{
> > > + static const char * const ideapad_hid = "VPC2004";
> > > + acpi_handle handle = NULL;
> > > + int ret;
> > > +
> > > + /* Deconflict ideapad_laptop driver */
> > > + ret = acpi_get_devices(ideapad_hid, lwmi_acpi_match, &handle, NULL);
> >
> > Skip it in the force_load_psy_ext=Y case.
> >
> > > + if (ret)
> > > + return;
> > > +
> > > + if (handle && !force_load_psy_ext) {
> > > + if (acpi_has_method(handle, "GBMD") && acpi_has_method(handle, "SBMC")) {
> >
> > Connect them with `&&' to prevent unnecessary indents and...
> >
> > > + dev_dbg(&priv->wdev->dev, "ideapad_laptop driver manages battery for device.\n");
> >
> > ...line length exceeding 100.
> >
> > Thanks,
> > Rong
> >
> > > + return;
> > > + }
> > > + }
> > > +
> > > + /* Add battery hooks */
> > > + priv->battery_hook.add_battery = lwmi_add_battery;
> > > + priv->battery_hook.remove_battery = lwmi_remove_battery;
> > > + priv->battery_hook.name = "Lenovo WMI Other Battery Extension";
> > > +
> > > + ret = devm_battery_hook_register(&priv->wdev->dev, &priv->battery_hook);
> > > + if (ret)
> > > + dev_err(&priv->wdev->dev, "Error during battery hook: %i\n", ret);
> > > +}
> > > +
> > > /* ======== fw_attributes (component: lenovo-wmi-capdata 01) ======== */
> > >
> > > struct tunable_attr_01 {
> > > @@ -1334,6 +1565,7 @@ static int lwmi_om_master_bind(struct device *dev)
> > > return -ENODEV;
> > >
> > > lwmi_om_fan_info_collect_cd00(priv);
> > > + lwmi_om_ps_ext_init(priv);
> > >
> > > return lwmi_om_fw_attr_add(priv);
> > > }
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH v4 8/8] platform/x86: lenovo-wmi-other: Add WMI battery charge limiting
2026-03-12 3:10 ` [PATCH v4 8/8] platform/x86: lenovo-wmi-other: Add WMI battery charge limiting Derek J. Clark
2026-03-15 2:00 ` Rong Zhang
@ 2026-03-17 23:46 ` kernel test robot
1 sibling, 0 replies; 21+ messages in thread
From: kernel test robot @ 2026-03-17 23:46 UTC (permalink / raw)
To: Derek J. Clark, Ilpo Järvinen, Hans de Goede
Cc: llvm, oe-kbuild-all, Mark Pearson, Armin Wolf, Jonathan Corbet,
Rong Zhang, Kurt Borja, Derek J . Clark, platform-driver-x86,
linux-kernel
Hi Derek,
kernel test robot noticed the following build errors:
[auto build test ERROR on linus/master]
[also build test ERROR on v7.0-rc4 next-20260317]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]
url: https://github.com/intel-lab-lkp/linux/commits/Derek-J-Clark/platform-x86-lenovo-wmi-other-Move-LWMI_FAN_DIV/20260312-111508
base: linus/master
patch link: https://lore.kernel.org/r/20260312031032.3467565-9-derekjohn.clark%40gmail.com
patch subject: [PATCH v4 8/8] platform/x86: lenovo-wmi-other: Add WMI battery charge limiting
config: x86_64-randconfig-003-20260317 (https://download.01.org/0day-ci/archive/20260318/202603180742.oHHUR1NX-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/20260318/202603180742.oHHUR1NX-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/202603180742.oHHUR1NX-lkp@intel.com/
All errors (new ones prefixed by >>, old ones prefixed by <<):
>> ERROR: modpost: "devm_battery_hook_register" [drivers/platform/x86/lenovo/lenovo-wmi-other.ko] undefined!
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply [flat|nested] 21+ messages in thread
end of thread, other threads:[~2026-03-17 23:46 UTC | newest]
Thread overview: 21+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-03-12 3:10 [PATCH v4 0/8] platform-x86: lenovo-wmi: Add fixes and enhancement Derek J. Clark
2026-03-12 3:10 ` [PATCH v4 1/8] platform-x86: lenovo-wmi-other: Move LWMI_FAN_DIV Derek J. Clark
2026-03-12 3:10 ` [PATCH v4 2/8] platform-x86: lenovo-wmi-other: Fix tunable_attr_01 struct members Derek J. Clark
2026-03-12 3:10 ` [PATCH v4 3/8] platform/x86: lenovo-wmi-other: Add lwmi_attr_id() function Derek J. Clark
2026-03-15 1:08 ` Rong Zhang
2026-03-15 3:38 ` Derek J. Clark
2026-03-15 1:32 ` Rong Zhang
2026-03-15 3:49 ` Derek J. Clark
2026-03-12 3:10 ` [PATCH v4 4/8] platform/x86: lenovo-wmi-other: Limit adding attributes to supported devices Derek J. Clark
2026-03-15 1:26 ` Rong Zhang
2026-03-15 1:39 ` Rong Zhang
2026-03-15 3:47 ` Derek J. Clark
2026-03-15 18:50 ` Rong Zhang
2026-03-12 3:10 ` [PATCH v4 5/8] platform/x86: lenovo-wmi-other: Add missing CPU tunable attributes Derek J. Clark
2026-03-12 3:10 ` [PATCH v4 6/8] platform/x86: lenovo-wmi-other: Add GPU " Derek J. Clark
2026-03-12 3:10 ` [PATCH v4 7/8] platform-x86: lenovo-wmi-other: Rename LWMI_OM_FW_ATTR_BASE_PATH Derek J. Clark
2026-03-12 3:10 ` [PATCH v4 8/8] platform/x86: lenovo-wmi-other: Add WMI battery charge limiting Derek J. Clark
2026-03-15 2:00 ` Rong Zhang
2026-03-15 3:52 ` Derek J. Clark
2026-03-15 18:59 ` Rong Zhang
2026-03-17 23:46 ` kernel test robot
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox