* [PATCH v13 0/7] lenovo-wmi: Add fixes and enhancement
@ 2026-05-12 23:58 Derek J. Clark
2026-05-12 23:58 ` [PATCH v13 1/7] platform/x86: lenovo-wmi-other: Add missing CPU tunable attributes Derek J. Clark
` (6 more replies)
0 siblings, 7 replies; 10+ messages in thread
From: Derek J. Clark @ 2026-05-12 23:58 UTC (permalink / raw)
To: Ilpo Järvinen, Hans de Goede
Cc: Mark Pearson, Armin Wolf, Jonathan Corbet, Rong Zhang, Kurt Borja,
Derek J . Clark, Pierre-Loup A . Griffais,
Nícolas F . R . A . Prado, marshall, hyacinth,
platform-driver-x86, linux-kernel
This series adds many much needed features to the lenovo-wmi* drivers.
This version is based off platform-driver-x86/review-ilpo-fixes branch,
so patches 1-9 from v12 have been dropped.
Patch 1 adds the remaining CPU attributes that weren't previously
exposed.
Patch 2 adds GPU attributes.
Patch 3 renames a name constant in preparation for patch 6.
Patch 4 adds battery charge-types limiting when supported only by WMI, or
when a module parameter to skip compatibility checks 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.
Patch 5 adds a module parameter, force_load_psy_ext, to bypass ACPI and
BIOS compatibility checks for the power supply extension.
Patch 6 adds a debugfs directory.
Patch 7 adds a debugfs file for dumping capdata.
Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
---
v13:
- Rebase off review-ilpo-fixes and drop merged patches 1-9.
- Switch back to charge_types based on feedback from Hans.
- Ensure force_load_psy_ext also makes is_writable and is_supported
checks for lwmi_psy_ext_prop* functions always return true.
- Add helper function for is_writable and is_supported to reduce
duplicated code.
- Move bugged attribute description to the correct paragraph in patch
4.
v12: https://lore.kernel.org/platform-driver-x86/20260411162334.25682-1-derekjohn.clark@gmail.com/
- Break out force_load_psy_ext into its own patch.
- Drop fixes tags for formatting only prerequisite patches Cc'd to
stable.
- Fix formatting in various places.
- Use correct type when passing args in new functions. I will revisit
& fix older functions in a new series.
v11: https://lore.kernel.org/platform-driver-x86/20260507180507.912966-1-derekjohn.clark@gmail.com/
- Reorganize patches in a way that cleans up the churn with
assignment of attribute IDs. This also allowed dropping the
patch that moved the LWMI_FAN_DIV patch as it is now effectively
moved when declaring the updated LWMI_ATTR_ID_FAN_DIV macro. As a
result, some formatting only patches were added to the Cc list for
stable since as they are now prerequisites to some of the fixes.
- Refactor charge control patch to use charge_behavior and
charge_control_end_threshold instead of charge_types to be in
accordance with the power supply class documentation.
- Minor additional formatting fixes.
v10: https://lore.kernel.org/platform-driver-x86/20260411162334.25682-1-derekjohn.clark@gmail.com/
- Fix misnamed attribute in wmi-other.
v9: https://lore.kernel.org/platform-driver-x86/20260411162334.25682-1-derekjohn.clark@gmail.com/
- Invert ida_alloc fail logic.
- Fix dropped mode arg in current_value_show.
- Fix misnamed attribute in documentation.
v8: https://lore.kernel.org/platform-driver-x86/20260406201400.438221-1-derekjohn.clark@gmail.com/
- Clean up rebase mixup with re-assinment of wmi_method_args_32 in
patches 6 and 14 from {} to { 0x00, 0x00 } and back.
- Combine ret_obj declaration and assignment in patch 1.
v7: https://lore.kernel.org/platform-driver-x86/20260402032424.678528-1-derekjohn.clark@gmail.com/
- Add 3 additional bug fix patches from Rong Zhang reported by
shashiko.dev
- Reorder patches so the fixes are upfront in patches 1-7.
- Fix various typos and rebase mixups from v6.
v6: https://lore.kernel.org/platform-driver-x86/20260331181208.421552-1-derekjohn.clark@gmail.com/
- Incorporate Rong Zhang's debugfs and decoupling patches into the
series.
- Add a patch to clean up too many cross-references to wmi-gamezone.h
- Make lwmi_attr_id a static inline in wmi-capdata.h
- Added a patch to fix a bug where ares.arg1 is uninitialized when it
is sent to the firmware.
- Add supported checks before adding battery extenstion, and ensure
both the new checks and the is_writable checks are not casting u32
to i32.
- Misc formating changes.
v5: https://lore.kernel.org/platform-driver-x86/20260324221032.1333636-1-derekjohn.clark@gmail.com/
- Remove cv/cd_mode_id references that occured before patch 4.
- Move lwmi_attr_id to capdata.c with a namespace export.
- Fix mixing include.
- Make lwmi_attr_is_supported return bool.
- Use switch instead of if for setting/getting charge type state.
- Various formatting fixes.
v4: https://lore.kernel.org/platform-driver-x86/20260312031032.3467565-1-derekjohn.clark@gmail.com/
- 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 (5):
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
platform/x86: lenovo-wmi-other: Add force_load_psy_ext module
parameter
Rong Zhang (2):
platform/x86: lenovo-wmi-helpers: Add helper for creating per-device
debugfs dir
platform/x86: lenovo-wmi-capdata: Add debugfs file for dumping capdata
.../wmi/devices/lenovo-wmi-other.rst | 19 +
drivers/platform/x86/lenovo/Kconfig | 2 +
drivers/platform/x86/lenovo/wmi-capdata.c | 120 ++++
drivers/platform/x86/lenovo/wmi-capdata.h | 14 +-
drivers/platform/x86/lenovo/wmi-helpers.c | 34 +
drivers/platform/x86/lenovo/wmi-helpers.h | 2 +
drivers/platform/x86/lenovo/wmi-other.c | 617 +++++++++++++++++-
7 files changed, 797 insertions(+), 11 deletions(-)
base-commit: 2997606dd17729404cef9821ce66dd037b6019eb
--
2.53.0
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH v13 1/7] platform/x86: lenovo-wmi-other: Add missing CPU tunable attributes
2026-05-12 23:58 [PATCH v13 0/7] lenovo-wmi: Add fixes and enhancement Derek J. Clark
@ 2026-05-12 23:58 ` Derek J. Clark
2026-05-12 23:58 ` [PATCH v13 2/7] platform/x86: lenovo-wmi-other: Add GPU " Derek J. Clark
` (5 subsequent siblings)
6 siblings, 0 replies; 10+ messages in thread
From: Derek J. Clark @ 2026-05-12 23:58 UTC (permalink / raw)
To: Ilpo Järvinen, Hans de Goede
Cc: Mark Pearson, Armin Wolf, Jonathan Corbet, Rong Zhang, Kurt Borja,
Derek J . Clark, Pierre-Loup A . Griffais,
Nícolas F . R . A . Prado, marshall, hyacinth,
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: Rong Zhang <i@rong.moe>
Reviewed-by: Mark Pearson <mpearson-lenovo@squebb.ca>
Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
---
v7:
- Fix typo in documentation.
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 | 99 ++++++++++++++++++-
3 files changed, 107 insertions(+), 6 deletions(-)
diff --git a/Documentation/wmi/devices/lenovo-wmi-other.rst b/Documentation/wmi/devices/lenovo-wmi-other.rst
index 01d471156738..189dd4d31926 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 Tracking 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 c3e760b8c3c3..c74a0e5294e7 100644
--- a/drivers/platform/x86/lenovo/wmi-capdata.h
+++ b/drivers/platform/x86/lenovo/wmi-capdata.h
@@ -18,7 +18,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,
+};
#define LWMI_TYPE_ID_NONE 0x00
diff --git a/drivers/platform/x86/lenovo/wmi-other.c b/drivers/platform/x86/lenovo/wmi-other.c
index d318ba432fdc..fe4cd7da017e 100644
--- a/drivers/platform/x86/lenovo/wmi-other.c
+++ b/drivers/platform/x86/lenovo/wmi-other.c
@@ -51,14 +51,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_CROSSLOAD 0x01
+
#define LWMI_FEATURE_VALUE_GET 17
#define LWMI_FEATURE_VALUE_SET 18
@@ -566,18 +573,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;
@@ -913,17 +974,45 @@ static bool 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 },
{},
};
base-commit: 2997606dd17729404cef9821ce66dd037b6019eb
--
2.53.0
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH v13 2/7] platform/x86: lenovo-wmi-other: Add GPU tunable attributes
2026-05-12 23:58 [PATCH v13 0/7] lenovo-wmi: Add fixes and enhancement Derek J. Clark
2026-05-12 23:58 ` [PATCH v13 1/7] platform/x86: lenovo-wmi-other: Add missing CPU tunable attributes Derek J. Clark
@ 2026-05-12 23:58 ` Derek J. Clark
2026-05-12 23:58 ` [PATCH v13 3/7] platform/x86: lenovo-wmi-other: Rename LWMI_OM_FW_ATTR_BASE_PATH Derek J. Clark
` (4 subsequent siblings)
6 siblings, 0 replies; 10+ messages in thread
From: Derek J. Clark @ 2026-05-12 23:58 UTC (permalink / raw)
To: Ilpo Järvinen, Hans de Goede
Cc: Mark Pearson, Armin Wolf, Jonathan Corbet, Rong Zhang, Kurt Borja,
Derek J . Clark, Pierre-Loup A . Griffais,
Nícolas F . R . A . Prado, marshall, hyacinth,
platform-driver-x86, linux-kernel
Use an enum for all GPU attribute feature ID's and add GPU attributes.
Reviewed-by: Rong Zhang <i@rong.moe>
Reviewed-by: Mark Pearson <mpearson-lenovo@squebb.ca>
Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
---
v9:
- Rename gpu_didvid -> dgpu_didvid in documentation to match actual
attribute name.
v7:
- Fix typo in dgpu_boost_clk attribute string.
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 189dd4d31926..011054d64eac 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_didvid: Dedicated GPU Device Identifier and Vendor Identifier
+ - dgpu_enable: Dedicated GPU Enabled Status
+ - 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 c74a0e5294e7..a7cfdeaa58f7 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_FAN = 0x04,
};
diff --git a/drivers/platform/x86/lenovo/wmi-other.c b/drivers/platform/x86/lenovo/wmi-other.c
index fe4cd7da017e..9a6183bbcc92 100644
--- a/drivers/platform/x86/lenovo/wmi-other.c
+++ b/drivers/platform/x86/lenovo/wmi-other.c
@@ -62,6 +62,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_CROSSLOAD 0x01
@@ -639,6 +652,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;
@@ -974,6 +1047,7 @@ static bool 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",
@@ -999,9 +1073,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, "dgpu_boost_clk",
+ "Set the dedicated GPU boost clock");
+LWMI_ATTR_GROUP_TUNABLE_CAP01(dgpu_didvid, "dgpu_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] 10+ messages in thread
* [PATCH v13 3/7] platform/x86: lenovo-wmi-other: Rename LWMI_OM_FW_ATTR_BASE_PATH
2026-05-12 23:58 [PATCH v13 0/7] lenovo-wmi: Add fixes and enhancement Derek J. Clark
2026-05-12 23:58 ` [PATCH v13 1/7] platform/x86: lenovo-wmi-other: Add missing CPU tunable attributes Derek J. Clark
2026-05-12 23:58 ` [PATCH v13 2/7] platform/x86: lenovo-wmi-other: Add GPU " Derek J. Clark
@ 2026-05-12 23:58 ` Derek J. Clark
2026-05-12 23:58 ` [PATCH v13 4/7] platform/x86: lenovo-wmi-other: Add WMI battery charge limiting Derek J. Clark
` (3 subsequent siblings)
6 siblings, 0 replies; 10+ messages in thread
From: Derek J. Clark @ 2026-05-12 23:58 UTC (permalink / raw)
To: Ilpo Järvinen, Hans de Goede
Cc: Mark Pearson, Armin Wolf, Jonathan Corbet, Rong Zhang, Kurt Borja,
Derek J . Clark, Pierre-Loup A . Griffais,
Nícolas F . R . A . Prado, marshall, hyacinth,
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: Rong Zhang <i@rong.moe>
Tested-by: Rong Zhang <i@rong.moe>
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 9a6183bbcc92..beb92c7cff25 100644
--- a/drivers/platform/x86/lenovo/wmi-other.c
+++ b/drivers/platform/x86/lenovo/wmi-other.c
@@ -92,7 +92,7 @@ enum lwmi_feature_id_gpu {
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 DEFINE_IDA(lwmi_om_ida);
@@ -1138,8 +1138,7 @@ static void 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] 10+ messages in thread
* [PATCH v13 4/7] platform/x86: lenovo-wmi-other: Add WMI battery charge limiting
2026-05-12 23:58 [PATCH v13 0/7] lenovo-wmi: Add fixes and enhancement Derek J. Clark
` (2 preceding siblings ...)
2026-05-12 23:58 ` [PATCH v13 3/7] platform/x86: lenovo-wmi-other: Rename LWMI_OM_FW_ATTR_BASE_PATH Derek J. Clark
@ 2026-05-12 23:58 ` Derek J. Clark
2026-05-13 14:43 ` Rong Zhang
2026-05-12 23:58 ` [PATCH v13 5/7] platform/x86: lenovo-wmi-other: Add force_load_psy_ext module parameter Derek J. Clark
` (2 subsequent siblings)
6 siblings, 1 reply; 10+ messages in thread
From: Derek J. Clark @ 2026-05-12 23:58 UTC (permalink / raw)
To: Ilpo Järvinen, Hans de Goede
Cc: Mark Pearson, Armin Wolf, Jonathan Corbet, Rong Zhang, Kurt Borja,
Derek J . Clark, Pierre-Loup A . Griffais,
Nícolas F . R . A . Prado, marshall, hyacinth,
platform-driver-x86, linux-kernel
Add charge_behaviour and charge_types attributes through a 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_types attribute for
BATX devices to expose this capability with Standard and Long_Life types
enabled.
Additionally, devices that support WMI function and capdata ID 0x03020000
are able to force discharge of the battery. Expose this capability with
a charge_behaviour attribute in the power supply extension, with the auto
and force-discharge behaviors enabled. The GET method for this attribute
is bugged. After analyzing the DSDT, and some testing, it appears the
method grabs bit(3) instead of bit(4) from the EC register that stores the
current status, and will only report if charging has been inhibited or
not. To work around this, store and report the last setting written to the
attribute.
As some devices only expose one attribute or the other, a bitmask is
added with a lookup table and some helper macros to select the correct
configuration for the hardware at runtime.
The ideapad_laptop driver provides the charge_types attribute to provide
similar functionality. When the WMI method is set this can corrupt the
ACPI method return and cause hardware and driver errors. To avoid
conflicts between the drivers, we 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.
Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
---
v13:
- Switch back to charge_types instead of charge_control_end_threshold.
- Add helper function to reduce duplicated code when checking support
level for a given attribute.
- Remove setting priv->bh_registered to false during psy init.
v12:
- Move force_load_psy_ext into its own patch after this.
- Use correct type casting for args in new functions.
- Remove extra parenthesis.
v11:
- Refactor to use charge_behaviour and charge_control_end_threshold,
per the class documentation. The ideapad_laptop driver will be fixed
in a separate patch series.
- Due to the extensive refactoring, I have removed the reviewed-by
tags from Rong and Mark to allow them to recertify the code in its
current state.
v7:
- Use devm_battery_hook_register, manually unregister during unbind.
v6:
- Check feature flags to determine if the extension should be loaded
and if it is writable.
- Zero initialize wmi_method_args_32.
- Fix formatting.
v5:
- Use switch statement instead of if for battery charge state set/get.
- use force_load_psy_ext to skip all ACPI interactions.
- Various formatting fixes.
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/Kconfig | 1 +
drivers/platform/x86/lenovo/wmi-capdata.h | 1 +
drivers/platform/x86/lenovo/wmi-other.c | 391 ++++++++++++++++++++++
3 files changed, 393 insertions(+)
diff --git a/drivers/platform/x86/lenovo/Kconfig b/drivers/platform/x86/lenovo/Kconfig
index 09b1b055d2e0..b9a5d18caa1e 100644
--- a/drivers/platform/x86/lenovo/Kconfig
+++ b/drivers/platform/x86/lenovo/Kconfig
@@ -262,6 +262,7 @@ config LENOVO_WMI_GAMEZONE
config LENOVO_WMI_TUNING
tristate "Lenovo Other Mode WMI Driver"
depends on ACPI_WMI
+ depends on ACPI_BATTERY
select HWMON
select FW_ATTR_CLASS
select LENOVO_WMI_CAPDATA
diff --git a/drivers/platform/x86/lenovo/wmi-capdata.h b/drivers/platform/x86/lenovo/wmi-capdata.h
index a7cfdeaa58f7..a49229cec245 100644
--- a/drivers/platform/x86/lenovo/wmi-capdata.h
+++ b/drivers/platform/x86/lenovo/wmi-capdata.h
@@ -21,6 +21,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 beb92c7cff25..a423bd563291 100644
--- a/drivers/platform/x86/lenovo/wmi-other.c
+++ b/drivers/platform/x86/lenovo/wmi-other.c
@@ -41,9 +41,12 @@
#include <linux/limits.h>
#include <linux/module.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-helpers.h"
@@ -75,9 +78,15 @@ enum lwmi_feature_id_gpu {
LWMI_FEATURE_ID_GPU_NV_CPU_BOOST = 0x0b,
};
+enum lwmi_feature_id_psu {
+ LWMI_FEATURE_ID_PSU_CHARGE_TYPES = 0x01,
+ LWMI_FEATURE_ID_PSU_CHARGE_BEHAVIOUR = 0x02,
+};
+
#define LWMI_FEATURE_ID_FAN_RPM 0x03
#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
@@ -88,10 +97,19 @@ enum lwmi_feature_id_gpu {
#define LWMI_FAN_DIV 100
+#define LWMI_CHARGE_BEHAVIOR_DISCHARGE 0x00
+#define LWMI_CHARGE_BEHAVIOR_AUTO 0x01
+#define LWMI_CHARGE_TYPE_STANDARD 0x00
+#define LWMI_CHARGE_TYPE_LONGLIFE 0x01
+
#define LWMI_ATTR_ID_FAN_RPM(x) \
lwmi_attr_id(LWMI_DEVICE_ID_FAN, LWMI_FEATURE_ID_FAN_RPM, \
LWMI_GZ_THERMAL_MODE_NONE, LWMI_FAN_ID(x))
+#define LWMI_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"
@@ -131,6 +149,11 @@ struct lwmi_om_priv {
bool capdata00_collected : 1;
bool capdata_fan_collected : 1;
} fan_flags;
+
+ enum power_supply_charge_behaviour charge_behaviour;
+ const struct power_supply_ext *battery_ext;
+ struct acpi_battery_hook battery_hook;
+ bool bh_registered;
};
/*
@@ -557,6 +580,371 @@ 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 to the lwmi_om_priv drvdata
+ * @prop: The property to read
+ * @val: The value to return
+ *
+ * Reads the given value from 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;
+
+ switch (prop) {
+ case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR:
+ /* Reading from BIOS reads the wrong bit. Use cached value */
+ val->intval = priv->charge_behaviour;
+ return 0;
+ case POWER_SUPPLY_PROP_CHARGE_TYPES:
+ args.arg0 = LWMI_ATTR_ID_PSU(LWMI_FEATURE_ID_PSU_CHARGE_TYPES,
+ LWMI_TYPE_ID_PSU_AC);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ ret = lwmi_dev_evaluate_int(priv->wdev, 0x0, LWMI_FEATURE_VALUE_GET,
+ (u8 *)&args, sizeof(args),
+ &retval);
+ if (ret)
+ return ret;
+
+ dev_dbg(&priv->wdev->dev, "Got return value %#x for property %#x\n", retval, prop);
+
+ switch (retval) {
+ case LWMI_CHARGE_TYPE_LONGLIFE:
+ val->intval = POWER_SUPPLY_CHARGE_TYPE_LONGLIFE;
+ break;
+ case LWMI_CHARGE_TYPE_STANDARD:
+ val->intval = POWER_SUPPLY_CHARGE_TYPE_STANDARD;
+ break;
+ default:
+ dev_err(&priv->wdev->dev, "Got invalid charge limit value: %#x\n", retval);
+ return -EINVAL;
+ }
+
+ 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 to 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 = {};
+
+ switch (prop) {
+ case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR:
+ args.arg0 = LWMI_ATTR_ID_PSU(LWMI_FEATURE_ID_PSU_CHARGE_BEHAVIOUR,
+ LWMI_TYPE_ID_NONE);
+ switch (val->intval) {
+ case POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO:
+ args.arg1 = LWMI_CHARGE_BEHAVIOR_AUTO;
+ break;
+ case POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE:
+ args.arg1 = LWMI_CHARGE_BEHAVIOR_DISCHARGE;
+ break;
+ default:
+ dev_err(&priv->wdev->dev, "Got invalid charge behavior value: %#x\n",
+ val->intval);
+ return -EINVAL;
+ }
+ priv->charge_behaviour = val->intval;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_TYPES:
+ args.arg0 = LWMI_ATTR_ID_PSU(LWMI_FEATURE_ID_PSU_CHARGE_TYPES,
+ LWMI_TYPE_ID_PSU_AC);
+ switch (val->intval) {
+ case POWER_SUPPLY_CHARGE_TYPE_LONGLIFE:
+ args.arg1 = LWMI_CHARGE_TYPE_LONGLIFE;
+ break;
+ case POWER_SUPPLY_CHARGE_TYPE_STANDARD:
+ args.arg1 = LWMI_CHARGE_TYPE_STANDARD;
+ break;
+ default:
+ dev_err(&priv->wdev->dev, "Got invalid charge limit value: %#x\n",
+ val->intval);
+ return -EINVAL;
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ dev_dbg(&priv->wdev->dev, "Attempting to set %#010x for property %#x to %#x\n",
+ args.arg0, prop, args.arg1);
+
+ return lwmi_dev_evaluate_int(priv->wdev, 0x0, LWMI_FEATURE_VALUE_SET,
+ (u8 *)&args, sizeof(args), NULL);
+}
+
+/** lwmi_psy_prop_get_supported() - Gets the support level from capdata for a given property
+ * @priv: Pointer to the lwmi_om_priv drvdata
+ * @prop: The power supply property to be evaluated
+ *
+ * Return: capability data support level, or an error
+ */
+static int lwmi_psy_prop_get_supported(struct lwmi_om_priv *priv, enum power_supply_property prop)
+{
+ struct capdata00 capdata;
+ u32 attribute_id;
+ int ret;
+
+ switch (prop) {
+ case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR:
+ attribute_id = LWMI_ATTR_ID_PSU(LWMI_FEATURE_ID_PSU_CHARGE_BEHAVIOUR,
+ LWMI_TYPE_ID_NONE);
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_TYPES:
+ attribute_id = LWMI_ATTR_ID_PSU(LWMI_FEATURE_ID_PSU_CHARGE_TYPES,
+ LWMI_TYPE_ID_PSU_AC);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ ret = lwmi_cd00_get_data(priv->cd00_list, attribute_id, &capdata);
+ if (ret)
+ return ret;
+
+ dev_dbg(&priv->wdev->dev, "Battery charge feature (%#010x) support level: %#x\n",
+ attribute_id, capdata.supported);
+
+ return capdata.supported;
+}
+
+/**
+ * lwmi_psy_prop_is_supported() - Determine if the property is supported
+ * @priv: Pointer to the lwmi_om_priv drvdata
+ * @prop: The power supply property to be evaluated
+ *
+ * Checks capdata 00 to determine if the property is supported.
+ *
+ * Return: true if readable, or false
+ */
+static bool lwmi_psy_prop_is_supported(struct lwmi_om_priv *priv, enum power_supply_property prop)
+{
+ int ret;
+
+ ret = lwmi_psy_prop_get_supported(priv, prop);
+ if (ret < 0)
+ return false;
+
+ return (ret & LWMI_SUPP_VALID) && (ret & LWMI_SUPP_GET);
+}
+
+/**
+ * lwmi_psy_prop_is_writeable() - Determine if the property is writeable
+ * @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 writable.
+ *
+ * Return: true if writable, 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;
+ int ret;
+
+ ret = lwmi_psy_prop_get_supported(priv, prop);
+ if (ret < 0)
+ return false;
+
+ return !!(ret & LWMI_SUPP_SET);
+}
+
+#define DEFINE_LWMI_POWER_SUPPLY_EXTENSION(_name, _props, _behaviours, _types) \
+ static const struct power_supply_ext _name = { \
+ .name = LWMI_OM_SYSFS_NAME, \
+ .properties = _props, \
+ .num_properties = ARRAY_SIZE(_props), \
+ .charge_behaviours = _behaviours, \
+ .charge_types = _types, \
+ .get_property = lwmi_psy_ext_get_prop, \
+ .set_property = lwmi_psy_ext_set_prop, \
+ .property_is_writeable = lwmi_psy_prop_is_writeable, \
+ }
+
+static const enum power_supply_property lwmi_psy_ext_props_all[] = {
+ POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR,
+ POWER_SUPPLY_PROP_CHARGE_TYPES,
+};
+
+static const enum power_supply_property lwmi_psy_ext_props_types[] = {
+ POWER_SUPPLY_PROP_CHARGE_TYPES,
+};
+
+static const enum power_supply_property lwmi_psy_ext_props_behaviour[] = {
+ POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR,
+};
+
+#define LWMI_CHARGE_BEHAVIOURS (BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO) | \
+ BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE))
+
+#define LWMI_CHARGE_TYPES (BIT(POWER_SUPPLY_CHARGE_TYPE_STANDARD) | \
+ BIT(POWER_SUPPLY_CHARGE_TYPE_LONGLIFE))
+
+DEFINE_LWMI_POWER_SUPPLY_EXTENSION(lwmi_psy_ext_all, lwmi_psy_ext_props_all,
+ LWMI_CHARGE_BEHAVIOURS, LWMI_CHARGE_TYPES);
+DEFINE_LWMI_POWER_SUPPLY_EXTENSION(lwmi_psy_ext_types, lwmi_psy_ext_props_types,
+ 0, LWMI_CHARGE_TYPES);
+DEFINE_LWMI_POWER_SUPPLY_EXTENSION(lwmi_psy_ext_behaviour, lwmi_psy_ext_props_behaviour,
+ LWMI_CHARGE_BEHAVIOURS, 0);
+
+#define LWMI_PSY_PROP_BEHAVIOUR BIT(0)
+#define LWMI_PSY_PROP_TYPES BIT(1)
+
+static const struct power_supply_ext *lwmi_psy_exts[] = {
+ [LWMI_PSY_PROP_BEHAVIOUR] = &lwmi_psy_ext_behaviour,
+ [LWMI_PSY_PROP_TYPES] = &lwmi_psy_ext_types,
+ [LWMI_PSY_PROP_BEHAVIOUR | LWMI_PSY_PROP_TYPES] = &lwmi_psy_ext_all,
+};
+
+/**
+ * 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, priv->battery_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)
+{
+ struct lwmi_om_priv *priv = container_of(hook, struct lwmi_om_priv, battery_hook);
+
+ power_supply_unregister_extension(battery, priv->battery_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;
+}
+
+/**
+ * lwmi_om_psy_ext_init() - Hooks power supply extension to device battery
+ * @priv: Pointer to the lwmi_om_priv drvdata.
+ *
+ * Checks if the ideapad_laptop driver is going to manage charge attributes first,
+ * then if not, hooks the battery to our WMI methods if they are supported.
+ */
+static void lwmi_om_psy_ext_init(struct lwmi_om_priv *priv)
+{
+ static const char * const ideapad_hid = "VPC2004";
+ acpi_handle handle = NULL;
+ unsigned int props = 0;
+ int ret;
+
+ if (lwmi_psy_prop_is_supported(priv, POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR))
+ props |= LWMI_PSY_PROP_BEHAVIOUR;
+ if (lwmi_psy_prop_is_supported(priv, POWER_SUPPLY_PROP_CHARGE_TYPES))
+ props |= LWMI_PSY_PROP_TYPES;
+ if (!props)
+ return;
+
+ /* Deconflict ideapad_laptop driver */
+ ret = acpi_get_devices(ideapad_hid, lwmi_acpi_match, &handle, NULL);
+ if (ret)
+ return;
+
+ if (handle && 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_ext = lwmi_psy_exts[props];
+ 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";
+ priv->bh_registered = true;
+
+ battery_hook_register(&priv->battery_hook);
+}
+
+/**
+ * lwmi_om_psy_remove() - Unregister battery hook
+ * @priv: Driver private data
+ *
+ * Unregisters the battery hook if applicable.
+ */
+static void lwmi_om_psy_remove(struct lwmi_om_priv *priv)
+{
+ if (!priv->bh_registered)
+ return;
+
+ battery_hook_unregister(&priv->battery_hook);
+ priv->bh_registered = false;
+}
+
/* ======== fw_attributes (component: lenovo-wmi-capdata 01) ======== */
struct tunable_attr_01 {
@@ -1243,6 +1631,7 @@ static int lwmi_om_master_bind(struct device *dev)
}
lwmi_om_fan_info_collect_cd00(priv);
+ lwmi_om_psy_ext_init(priv);
lwmi_om_fw_attr_add(priv);
@@ -1265,6 +1654,8 @@ static void lwmi_om_master_unbind(struct device *dev)
lwmi_om_hwmon_remove(priv);
+ lwmi_om_psy_remove(priv);
+
component_unbind_all(dev, NULL);
}
--
2.53.0
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH v13 5/7] platform/x86: lenovo-wmi-other: Add force_load_psy_ext module parameter
2026-05-12 23:58 [PATCH v13 0/7] lenovo-wmi: Add fixes and enhancement Derek J. Clark
` (3 preceding siblings ...)
2026-05-12 23:58 ` [PATCH v13 4/7] platform/x86: lenovo-wmi-other: Add WMI battery charge limiting Derek J. Clark
@ 2026-05-12 23:58 ` Derek J. Clark
2026-05-13 14:44 ` Rong Zhang
2026-05-12 23:58 ` [PATCH v13 6/7] platform/x86: lenovo-wmi-helpers: Add helper for creating per-device debugfs dir Derek J. Clark
2026-05-12 23:58 ` [PATCH v13 7/7] platform/x86: lenovo-wmi-capdata: Add debugfs file for dumping capdata Derek J. Clark
6 siblings, 1 reply; 10+ messages in thread
From: Derek J. Clark @ 2026-05-12 23:58 UTC (permalink / raw)
To: Ilpo Järvinen, Hans de Goede
Cc: Mark Pearson, Armin Wolf, Jonathan Corbet, Rong Zhang, Kurt Borja,
Derek J . Clark, Pierre-Loup A . Griffais,
Nícolas F . R . A . Prado, marshall, hyacinth,
platform-driver-x86, linux-kernel
Some Lenovo BIOS have been shown to have incomplete and/or broken
capability data and WMI attribute IDs. In some cases the capability data
reports that a feature is not supported when the get/set methods are
fully implemented. It is also possible that the ACPI methods from the
ideapad_laptop driver we defer to could be bugged while the WMI method
is fully working. To aid end users in submitting more complete bug
reports in these situations, add an override to skip the ACPI and
compatibility checks to force load the power supply extension as if it
is fully supported and has no conflicts.
Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
---
v13:
- Ensure is_writable and is_supported always return true when
force_load_psy_ext is true.
v12:
- Move the force_load_psy_ext into its own distinct patch.
---
drivers/platform/x86/lenovo/wmi-other.c | 19 +++++++++++++++++++
1 file changed, 19 insertions(+)
diff --git a/drivers/platform/x86/lenovo/wmi-other.c b/drivers/platform/x86/lenovo/wmi-other.c
index a423bd563291..9d5250d41a3f 100644
--- a/drivers/platform/x86/lenovo/wmi-other.c
+++ b/drivers/platform/x86/lenovo/wmi-other.c
@@ -185,6 +185,16 @@ MODULE_PARM_DESC(relax_fan_constraint,
"Enabling this may results in HWMON attributes being out-of-sync, "
"and setting a too low RPM stops the fan. Use with caution.");
+/* Visibility of power supply extensions */
+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 behavior and battery charge "
+ "control end threshold. It will also skip checking if the BIOS reports that "
+ "those features are fully supported. It is recommended to blacklist the ideapad "
+ "driver before using this option.");
+
/* ======== HWMON (component: lenovo-wmi-capdata 00 & fan) ======== */
/**
@@ -755,6 +765,9 @@ static bool lwmi_psy_prop_is_supported(struct lwmi_om_priv *priv, enum power_sup
{
int ret;
+ if (force_load_psy_ext)
+ return true;
+
ret = lwmi_psy_prop_get_supported(priv, prop);
if (ret < 0)
return false;
@@ -781,6 +794,9 @@ static int lwmi_psy_prop_is_writeable(struct power_supply *ps,
struct lwmi_om_priv *priv = ext_data;
int ret;
+ if (force_load_psy_ext)
+ return true;
+
ret = lwmi_psy_prop_get_supported(priv, prop);
if (ret < 0)
return false;
@@ -909,6 +925,8 @@ static void lwmi_om_psy_ext_init(struct lwmi_om_priv *priv)
props |= LWMI_PSY_PROP_TYPES;
if (!props)
return;
+ if (force_load_psy_ext)
+ goto load_psy_ext;
/* Deconflict ideapad_laptop driver */
ret = acpi_get_devices(ideapad_hid, lwmi_acpi_match, &handle, NULL);
@@ -920,6 +938,7 @@ static void lwmi_om_psy_ext_init(struct lwmi_om_priv *priv)
return;
}
+load_psy_ext:
/* Add battery hooks */
priv->battery_ext = lwmi_psy_exts[props];
priv->battery_hook.add_battery = lwmi_add_battery;
--
2.53.0
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH v13 6/7] platform/x86: lenovo-wmi-helpers: Add helper for creating per-device debugfs dir
2026-05-12 23:58 [PATCH v13 0/7] lenovo-wmi: Add fixes and enhancement Derek J. Clark
` (4 preceding siblings ...)
2026-05-12 23:58 ` [PATCH v13 5/7] platform/x86: lenovo-wmi-other: Add force_load_psy_ext module parameter Derek J. Clark
@ 2026-05-12 23:58 ` Derek J. Clark
2026-05-12 23:58 ` [PATCH v13 7/7] platform/x86: lenovo-wmi-capdata: Add debugfs file for dumping capdata Derek J. Clark
6 siblings, 0 replies; 10+ messages in thread
From: Derek J. Clark @ 2026-05-12 23:58 UTC (permalink / raw)
To: Ilpo Järvinen, Hans de Goede
Cc: Mark Pearson, Armin Wolf, Jonathan Corbet, Rong Zhang, Kurt Borja,
Derek J . Clark, Pierre-Loup A . Griffais,
Nícolas F . R . A . Prado, marshall, hyacinth,
platform-driver-x86, linux-kernel
From: Rong Zhang <i@rong.moe>
We are about to add debugfs support for lenovo-wmi-capdata. Let's setup
a debugfs directory called "lenovo_wmi" for tidiness, so that any
lenovo-wmi-* device can put its subdirectory under the directory.
Subdirectories will be named after the corresponding WMI devices.
Signed-off-by: Rong Zhang <i@rong.moe>
Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
---
v13:
- Fix alphabetical include order.
v7:
- Use subsys_initcall() instead of module_init().
---
drivers/platform/x86/lenovo/wmi-helpers.c | 34 +++++++++++++++++++++++
drivers/platform/x86/lenovo/wmi-helpers.h | 2 ++
2 files changed, 36 insertions(+)
diff --git a/drivers/platform/x86/lenovo/wmi-helpers.c b/drivers/platform/x86/lenovo/wmi-helpers.c
index 7a198259e393..8f5766c391eb 100644
--- a/drivers/platform/x86/lenovo/wmi-helpers.c
+++ b/drivers/platform/x86/lenovo/wmi-helpers.c
@@ -18,6 +18,8 @@
#include <linux/acpi.h>
#include <linux/cleanup.h>
+#include <linux/debugfs.h>
+#include <linux/device.h>
#include <linux/errno.h>
#include <linux/export.h>
#include <linux/module.h>
@@ -185,6 +187,38 @@ int lwmi_tm_notifier_call(enum thermal_mode *mode)
}
EXPORT_SYMBOL_NS_GPL(lwmi_tm_notifier_call, "LENOVO_WMI_HELPERS");
+static struct dentry *lwmi_debugfs_dir;
+
+/**
+ * lwmi_debugfs_create_dir() - Helper function for creating a debugfs directory
+ * for a device.
+ * @wdev: Pointer to the WMI device to be called.
+ *
+ * Caller must remove the directory with debugfs_remove_recursive() on device
+ * removal.
+ *
+ * Return: Pointer to the created directory.
+ */
+struct dentry *lwmi_debugfs_create_dir(struct wmi_device *wdev)
+{
+ return debugfs_create_dir(dev_name(&wdev->dev), lwmi_debugfs_dir);
+}
+EXPORT_SYMBOL_NS_GPL(lwmi_debugfs_create_dir, "LENOVO_WMI_HELPERS");
+
+static int __init lwmi_helpers_init(void)
+{
+ lwmi_debugfs_dir = debugfs_create_dir("lenovo_wmi", NULL);
+
+ return 0;
+}
+subsys_initcall(lwmi_helpers_init)
+
+static void __exit lwmi_helpers_exit(void)
+{
+ debugfs_remove_recursive(lwmi_debugfs_dir);
+}
+module_exit(lwmi_helpers_exit)
+
MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
MODULE_DESCRIPTION("Lenovo WMI Helpers Driver");
MODULE_LICENSE("GPL");
diff --git a/drivers/platform/x86/lenovo/wmi-helpers.h b/drivers/platform/x86/lenovo/wmi-helpers.h
index ed7db3ebba6c..039fe61003ce 100644
--- a/drivers/platform/x86/lenovo/wmi-helpers.h
+++ b/drivers/platform/x86/lenovo/wmi-helpers.h
@@ -16,6 +16,8 @@ struct wmi_method_args_32 {
u32 arg1;
};
+struct dentry *lwmi_debugfs_create_dir(struct wmi_device *wdev);
+
enum lwmi_event_type {
LWMI_GZ_GET_THERMAL_MODE = 0x01,
};
--
2.53.0
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH v13 7/7] platform/x86: lenovo-wmi-capdata: Add debugfs file for dumping capdata
2026-05-12 23:58 [PATCH v13 0/7] lenovo-wmi: Add fixes and enhancement Derek J. Clark
` (5 preceding siblings ...)
2026-05-12 23:58 ` [PATCH v13 6/7] platform/x86: lenovo-wmi-helpers: Add helper for creating per-device debugfs dir Derek J. Clark
@ 2026-05-12 23:58 ` Derek J. Clark
6 siblings, 0 replies; 10+ messages in thread
From: Derek J. Clark @ 2026-05-12 23:58 UTC (permalink / raw)
To: Ilpo Järvinen, Hans de Goede
Cc: Mark Pearson, Armin Wolf, Jonathan Corbet, Rong Zhang, Kurt Borja,
Derek J . Clark, Pierre-Loup A . Griffais,
Nícolas F . R . A . Prado, marshall, hyacinth,
platform-driver-x86, linux-kernel
From: Rong Zhang <i@rong.moe>
The Lenovo GameZone/Other interfaces have some delicate divergences
among different devices. When making a bug report or adding support for
new devices/interfaces, capdata is the most important information to
cross-check with.
Add a debugfs file (lenovo_wmi/<device_name>/capdata), so that users can
dump capdata and include it in their reports.
Since `struct capdata01' is just an extension to `struct capdata00',
also convert the former to include the latter anonymously
(-fms-extensions, since v6.19). This is declared as a union in the
capdata01 struct, with both the anonymous declaration and as a named
member to avoid type casting when passing just the capdata00 struct
pointer.
Tested-by: Kurt Borja <kuurtb@gmail.com>
Signed-off-by: Rong Zhang <i@rong.moe>
Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
---
v12:
- Make capdata00 a union of an anonymous member and a named member of
capdata01 to avoid casting when capdata00 is accessed directly.
---
drivers/platform/x86/lenovo/Kconfig | 1 +
drivers/platform/x86/lenovo/wmi-capdata.c | 120 ++++++++++++++++++++++
drivers/platform/x86/lenovo/wmi-capdata.h | 7 +-
3 files changed, 125 insertions(+), 3 deletions(-)
diff --git a/drivers/platform/x86/lenovo/Kconfig b/drivers/platform/x86/lenovo/Kconfig
index b9a5d18caa1e..4443f40ef8aa 100644
--- a/drivers/platform/x86/lenovo/Kconfig
+++ b/drivers/platform/x86/lenovo/Kconfig
@@ -236,6 +236,7 @@ config YT2_1380
config LENOVO_WMI_CAPDATA
tristate
depends on ACPI_WMI
+ depends on LENOVO_WMI_HELPERS
config LENOVO_WMI_EVENTS
tristate
diff --git a/drivers/platform/x86/lenovo/wmi-capdata.c b/drivers/platform/x86/lenovo/wmi-capdata.c
index 714aa6fd6f1f..d5e961566136 100644
--- a/drivers/platform/x86/lenovo/wmi-capdata.c
+++ b/drivers/platform/x86/lenovo/wmi-capdata.c
@@ -31,6 +31,7 @@
#include <linux/cleanup.h>
#include <linux/component.h>
#include <linux/container_of.h>
+#include <linux/debugfs.h>
#include <linux/device.h>
#include <linux/dev_printk.h>
#include <linux/err.h>
@@ -42,6 +43,7 @@
#include <linux/mutex_types.h>
#include <linux/notifier.h>
#include <linux/overflow.h>
+#include <linux/seq_file.h>
#include <linux/stddef.h>
#include <linux/types.h>
#include <linux/wmi.h>
@@ -87,6 +89,7 @@ struct lwmi_cd_priv {
struct notifier_block acpi_nb; /* ACPI events */
struct wmi_device *wdev;
struct cd_list *list;
+ struct dentry *debugfs_dir;
/*
* A capdata device may be a component master of another capdata device.
@@ -117,6 +120,8 @@ struct cd_list {
static struct wmi_driver lwmi_cd_driver;
+/* ======== Device components ======== */
+
/**
* lwmi_cd_match() - Match rule for the master driver.
* @dev: Pointer to the capability data parent device.
@@ -470,6 +475,116 @@ EXPORT_SYMBOL_NS_GPL(lwmi_cd01_get_data, "LENOVO_WMI_CAPDATA");
DEF_LWMI_CDXX_GET_DATA(cd_fan, LENOVO_FAN_TEST_DATA, struct capdata_fan);
EXPORT_SYMBOL_NS_GPL(lwmi_cd_fan_get_data, "LENOVO_WMI_CAPDATA");
+/* ======== debugfs ======== */
+
+/**
+ * lwmi_cd00_show() - Dump capdata00
+ * @s: Pointer to seq_file where the capdata00 is dumped.
+ * @cd00: Pointer to a capdata00 struct to be dumped.
+ */
+static void lwmi_cd00_show(struct seq_file *s, struct capdata00 *cd00)
+{
+ u8 dev = FIELD_GET(LWMI_ATTR_DEV_ID_MASK, cd00->id);
+ u8 feat = FIELD_GET(LWMI_ATTR_FEAT_ID_MASK, cd00->id);
+ u8 mode = FIELD_GET(LWMI_ATTR_MODE_ID_MASK, cd00->id);
+ u8 type = FIELD_GET(LWMI_ATTR_TYPE_ID_MASK, cd00->id);
+ bool extra = cd00->supported & ~(LWMI_SUPP_GET | LWMI_SUPP_SET | LWMI_SUPP_VALID);
+ bool get = cd00->supported & LWMI_SUPP_GET;
+ bool set = cd00->supported & LWMI_SUPP_SET;
+ bool valid = cd00->supported & LWMI_SUPP_VALID;
+
+ seq_printf(s, " id: 0x%08x [dev: %2u, feat: %2u, mode: %2u, type: %2u]\n",
+ cd00->id, dev, feat, mode, type);
+
+ seq_printf(s, " supported: 0x%08x [%c%c%c%c]\n", cd00->supported,
+ extra ? '+' : ' ',
+ get ? 'R' : ' ',
+ set ? 'W' : ' ',
+ valid ? 'V' : ' ');
+
+ seq_printf(s, " default_value: %u\n", cd00->default_value);
+}
+
+/**
+ * lwmi_cd01_show() - Dump capdata01
+ * @s: Pointer to seq_file where the capdata01 is dumped.
+ * @cd01: Pointer to a capdata01 struct to be dumped.
+ */
+static void lwmi_cd01_show(struct seq_file *s, struct capdata01 *cd01)
+{
+ /* capdata01 is an extension to capdata00. */
+ lwmi_cd00_show(s, &cd01->cd00);
+
+ seq_printf(s, " step: %u\n", cd01->step);
+ seq_printf(s, " min_value: %u\n", cd01->min_value);
+ seq_printf(s, " max_value: %u\n", cd01->max_value);
+}
+
+/**
+ * lwmi_cd_fan_show() - Dump capdata_fan
+ * @s: Pointer to seq_file where the capdata_fan is dumped.
+ * @cd_fan: Pointer to a capdata_fan struct to be dumped.
+ */
+static void lwmi_cd_fan_show(struct seq_file *s, struct capdata_fan *cd_fan)
+{
+ seq_printf(s, " id: %u\n", cd_fan->id);
+ seq_printf(s, " min_rpm: %u\n", cd_fan->min_rpm);
+ seq_printf(s, " max_rpm: %u\n", cd_fan->max_rpm);
+}
+
+/**
+ * lwmi_cd_debugfs_show() - Dump capability data to debugfs
+ * @s: Pointer to seq_file where the capability data is dumped.
+ * @data: unused.
+ *
+ * Return: 0
+ */
+static int lwmi_cd_debugfs_show(struct seq_file *s, void *data)
+{
+ struct lwmi_cd_priv *priv = s->private;
+ u8 idx;
+
+ guard(mutex)(&priv->list->list_mutex);
+
+ /* lwmi_cd_alloc() ensured priv->list->type must be a valid type. */
+ for (idx = 0; idx < priv->list->count; idx++) {
+ seq_printf(s, "%s[%u]:\n", lwmi_cd_table[priv->list->type].name, idx);
+
+ if (priv->list->type == LENOVO_CAPABILITY_DATA_00)
+ lwmi_cd00_show(s, &priv->list->cd00[idx]);
+ else if (priv->list->type == LENOVO_CAPABILITY_DATA_01)
+ lwmi_cd01_show(s, &priv->list->cd01[idx]);
+ else if (priv->list->type == LENOVO_FAN_TEST_DATA)
+ lwmi_cd_fan_show(s, &priv->list->cd_fan[idx]);
+ }
+
+ return 0;
+}
+DEFINE_SHOW_ATTRIBUTE(lwmi_cd_debugfs);
+
+/**
+ * lwmi_cd_debugfs_add() - Create debugfs directory and files for a device
+ * @priv: lenovo-wmi-capdata driver data.
+ */
+static void lwmi_cd_debugfs_add(struct lwmi_cd_priv *priv)
+{
+ priv->debugfs_dir = lwmi_debugfs_create_dir(priv->wdev);
+
+ debugfs_create_file("capdata", 0444, priv->debugfs_dir, priv, &lwmi_cd_debugfs_fops);
+}
+
+/**
+ * lwmi_cd_debugfs_remove() - Remove debugfs directory for a device
+ * @priv: lenovo-wmi-capdata driver data.
+ */
+static void lwmi_cd_debugfs_remove(struct lwmi_cd_priv *priv)
+{
+ debugfs_remove_recursive(priv->debugfs_dir);
+ priv->debugfs_dir = NULL;
+}
+
+/* ======== WMI interface ======== */
+
/**
* lwmi_cd_cache() - Cache all WMI data block information
* @priv: lenovo-wmi-capdata driver data.
@@ -772,6 +887,8 @@ static int lwmi_cd_probe(struct wmi_device *wdev, const void *context)
dev_err(&wdev->dev, "failed to register %s: %d\n",
info->name, ret);
} else {
+ lwmi_cd_debugfs_add(priv);
+
dev_dbg(&wdev->dev, "registered %s with %u items\n",
info->name, priv->list->count);
}
@@ -782,6 +899,8 @@ static void lwmi_cd_remove(struct wmi_device *wdev)
{
struct lwmi_cd_priv *priv = dev_get_drvdata(&wdev->dev);
+ lwmi_cd_debugfs_remove(priv);
+
switch (priv->list->type) {
case LENOVO_CAPABILITY_DATA_00:
lwmi_cd_sub_master_del(priv);
@@ -821,6 +940,7 @@ static struct wmi_driver lwmi_cd_driver = {
module_wmi_driver(lwmi_cd_driver);
+MODULE_IMPORT_NS("LENOVO_WMI_HELPERS");
MODULE_DEVICE_TABLE(wmi, lwmi_cd_id_table);
MODULE_AUTHOR("Derek J. Clark <derekjohn.clark@gmail.com>");
MODULE_AUTHOR("Rong Zhang <i@rong.moe>");
diff --git a/drivers/platform/x86/lenovo/wmi-capdata.h b/drivers/platform/x86/lenovo/wmi-capdata.h
index a49229cec245..92098aeeee84 100644
--- a/drivers/platform/x86/lenovo/wmi-capdata.h
+++ b/drivers/platform/x86/lenovo/wmi-capdata.h
@@ -38,9 +38,10 @@ struct capdata00 {
};
struct capdata01 {
- u32 id;
- u32 supported;
- u32 default_value;
+ union {
+ struct capdata00;
+ struct capdata00 cd00;
+ };
u32 step;
u32 min_value;
u32 max_value;
--
2.53.0
^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: [PATCH v13 4/7] platform/x86: lenovo-wmi-other: Add WMI battery charge limiting
2026-05-12 23:58 ` [PATCH v13 4/7] platform/x86: lenovo-wmi-other: Add WMI battery charge limiting Derek J. Clark
@ 2026-05-13 14:43 ` Rong Zhang
0 siblings, 0 replies; 10+ messages in thread
From: Rong Zhang @ 2026-05-13 14:43 UTC (permalink / raw)
To: Derek J. Clark, Ilpo Järvinen, Hans de Goede
Cc: Mark Pearson, Armin Wolf, Jonathan Corbet, Kurt Borja,
Pierre-Loup A . Griffais, Nícolas F . R . A . Prado,
marshall, hyacinth, platform-driver-x86, linux-kernel
Hi Derek,
On Tue, 2026-05-12 at 23:58 +0000, Derek J. Clark wrote:
> Add charge_behaviour and charge_types attributes through a 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_types attribute for
> BATX devices to expose this capability with Standard and Long_Life types
> enabled.
>
> Additionally, devices that support WMI function and capdata ID 0x03020000
> are able to force discharge of the battery. Expose this capability with
> a charge_behaviour attribute in the power supply extension, with the auto
> and force-discharge behaviors enabled. The GET method for this attribute
> is bugged. After analyzing the DSDT, and some testing, it appears the
> method grabs bit(3) instead of bit(4) from the EC register that stores the
> current status, and will only report if charging has been inhibited or
> not. To work around this, store and report the last setting written to the
> attribute.
>
> As some devices only expose one attribute or the other, a bitmask is
> added with a lookup table and some helper macros to select the correct
> configuration for the hardware at runtime.
>
> The ideapad_laptop driver provides the charge_types attribute to provide
> similar functionality. When the WMI method is set this can corrupt the
> ACPI method return and cause hardware and driver errors. To avoid
> conflicts between the drivers, we 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.
>
> Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
> ---
> v13:
> - Switch back to charge_types instead of charge_control_end_threshold.
> - Add helper function to reduce duplicated code when checking support
> level for a given attribute.
> - Remove setting priv->bh_registered to false during psy init.
> v12:
> - Move force_load_psy_ext into its own patch after this.
> - Use correct type casting for args in new functions.
> - Remove extra parenthesis.
> v11:
> - Refactor to use charge_behaviour and charge_control_end_threshold,
> per the class documentation. The ideapad_laptop driver will be fixed
> in a separate patch series.
> - Due to the extensive refactoring, I have removed the reviewed-by
> tags from Rong and Mark to allow them to recertify the code in its
> current state.
> v7:
> - Use devm_battery_hook_register, manually unregister during unbind.
> v6:
> - Check feature flags to determine if the extension should be loaded
> and if it is writable.
> - Zero initialize wmi_method_args_32.
> - Fix formatting.
> v5:
> - Use switch statement instead of if for battery charge state set/get.
> - use force_load_psy_ext to skip all ACPI interactions.
> - Various formatting fixes.
> 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/Kconfig | 1 +
> drivers/platform/x86/lenovo/wmi-capdata.h | 1 +
> drivers/platform/x86/lenovo/wmi-other.c | 391 ++++++++++++++++++++++
> 3 files changed, 393 insertions(+)
>
> diff --git a/drivers/platform/x86/lenovo/Kconfig b/drivers/platform/x86/lenovo/Kconfig
> index 09b1b055d2e0..b9a5d18caa1e 100644
> --- a/drivers/platform/x86/lenovo/Kconfig
> +++ b/drivers/platform/x86/lenovo/Kconfig
> @@ -262,6 +262,7 @@ config LENOVO_WMI_GAMEZONE
> config LENOVO_WMI_TUNING
> tristate "Lenovo Other Mode WMI Driver"
> depends on ACPI_WMI
> + depends on ACPI_BATTERY
> select HWMON
> select FW_ATTR_CLASS
> select LENOVO_WMI_CAPDATA
> diff --git a/drivers/platform/x86/lenovo/wmi-capdata.h b/drivers/platform/x86/lenovo/wmi-capdata.h
> index a7cfdeaa58f7..a49229cec245 100644
> --- a/drivers/platform/x86/lenovo/wmi-capdata.h
> +++ b/drivers/platform/x86/lenovo/wmi-capdata.h
> @@ -21,6 +21,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 beb92c7cff25..a423bd563291 100644
> --- a/drivers/platform/x86/lenovo/wmi-other.c
> +++ b/drivers/platform/x86/lenovo/wmi-other.c
> @@ -41,9 +41,12 @@
> #include <linux/limits.h>
> #include <linux/module.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-helpers.h"
> @@ -75,9 +78,15 @@ enum lwmi_feature_id_gpu {
> LWMI_FEATURE_ID_GPU_NV_CPU_BOOST = 0x0b,
> };
>
> +enum lwmi_feature_id_psu {
> + LWMI_FEATURE_ID_PSU_CHARGE_TYPES = 0x01,
> + LWMI_FEATURE_ID_PSU_CHARGE_BEHAVIOUR = 0x02,
> +};
> +
> #define LWMI_FEATURE_ID_FAN_RPM 0x03
>
> #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
> @@ -88,10 +97,19 @@ enum lwmi_feature_id_gpu {
>
> #define LWMI_FAN_DIV 100
>
> +#define LWMI_CHARGE_BEHAVIOR_DISCHARGE 0x00
> +#define LWMI_CHARGE_BEHAVIOR_AUTO 0x01
> +#define LWMI_CHARGE_TYPE_STANDARD 0x00
> +#define LWMI_CHARGE_TYPE_LONGLIFE 0x01
> +
> #define LWMI_ATTR_ID_FAN_RPM(x) \
> lwmi_attr_id(LWMI_DEVICE_ID_FAN, LWMI_FEATURE_ID_FAN_RPM, \
> LWMI_GZ_THERMAL_MODE_NONE, LWMI_FAN_ID(x))
>
> +#define LWMI_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"
>
> @@ -131,6 +149,11 @@ struct lwmi_om_priv {
> bool capdata00_collected : 1;
> bool capdata_fan_collected : 1;
> } fan_flags;
> +
> + enum power_supply_charge_behaviour charge_behaviour;
> + const struct power_supply_ext *battery_ext;
> + struct acpi_battery_hook battery_hook;
> + bool bh_registered;
> };
>
> /*
> @@ -557,6 +580,371 @@ 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 to the lwmi_om_priv drvdata
> + * @prop: The property to read
> + * @val: The value to return
> + *
> + * Reads the given value from 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;
> +
> + switch (prop) {
> + case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR:
> + /* Reading from BIOS reads the wrong bit. Use cached value */
> + val->intval = priv->charge_behaviour;
> + return 0;
> + case POWER_SUPPLY_PROP_CHARGE_TYPES:
> + args.arg0 = LWMI_ATTR_ID_PSU(LWMI_FEATURE_ID_PSU_CHARGE_TYPES,
> + LWMI_TYPE_ID_PSU_AC);
> + break;
> + default:
> + return -EINVAL;
> + }
> +
> + ret = lwmi_dev_evaluate_int(priv->wdev, 0x0, LWMI_FEATURE_VALUE_GET,
> + (u8 *)&args, sizeof(args),
> + &retval);
> + if (ret)
> + return ret;
> +
> + dev_dbg(&priv->wdev->dev, "Got return value %#x for property %#x\n", retval, prop);
> +
> + switch (retval) {
> + case LWMI_CHARGE_TYPE_LONGLIFE:
> + val->intval = POWER_SUPPLY_CHARGE_TYPE_LONGLIFE;
> + break;
> + case LWMI_CHARGE_TYPE_STANDARD:
> + val->intval = POWER_SUPPLY_CHARGE_TYPE_STANDARD;
> + break;
> + default:
> + dev_err(&priv->wdev->dev, "Got invalid charge limit value: %#x\n", retval);
Forgot to update the error message?
> + return -EINVAL;
> + }
> +
> + 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 to 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 = {};
> +
> + switch (prop) {
> + case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR:
> + args.arg0 = LWMI_ATTR_ID_PSU(LWMI_FEATURE_ID_PSU_CHARGE_BEHAVIOUR,
> + LWMI_TYPE_ID_NONE);
> + switch (val->intval) {
> + case POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO:
> + args.arg1 = LWMI_CHARGE_BEHAVIOR_AUTO;
> + break;
> + case POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE:
> + args.arg1 = LWMI_CHARGE_BEHAVIOR_DISCHARGE;
> + break;
> + default:
> + dev_err(&priv->wdev->dev, "Got invalid charge behavior value: %#x\n",
> + val->intval);
> + return -EINVAL;
> + }
> + priv->charge_behaviour = val->intval;
> + break;
> + case POWER_SUPPLY_PROP_CHARGE_TYPES:
> + args.arg0 = LWMI_ATTR_ID_PSU(LWMI_FEATURE_ID_PSU_CHARGE_TYPES,
> + LWMI_TYPE_ID_PSU_AC);
> + switch (val->intval) {
> + case POWER_SUPPLY_CHARGE_TYPE_LONGLIFE:
> + args.arg1 = LWMI_CHARGE_TYPE_LONGLIFE;
> + break;
> + case POWER_SUPPLY_CHARGE_TYPE_STANDARD:
> + args.arg1 = LWMI_CHARGE_TYPE_STANDARD;
> + break;
> + default:
> + dev_err(&priv->wdev->dev, "Got invalid charge limit value: %#x\n",
> + val->intval);
Ditto.
> + return -EINVAL;
> + }
> + break;
> + default:
> + return -EINVAL;
> + }
> +
> + dev_dbg(&priv->wdev->dev, "Attempting to set %#010x for property %#x to %#x\n",
> + args.arg0, prop, args.arg1);
> +
> + return lwmi_dev_evaluate_int(priv->wdev, 0x0, LWMI_FEATURE_VALUE_SET,
> + (u8 *)&args, sizeof(args), NULL);
> +}
> +
> +/** lwmi_psy_prop_get_supported() - Gets the support level from capdata for a given property
> + * @priv: Pointer to the lwmi_om_priv drvdata
> + * @prop: The power supply property to be evaluated
> + *
> + * Return: capability data support level, or an error
> + */
> +static int lwmi_psy_prop_get_supported(struct lwmi_om_priv *priv, enum power_supply_property prop)
I'd simply use u32 as the return type and return 0 on error, which
always means "unsupported", otherwise...
> +{
> + struct capdata00 capdata;
> + u32 attribute_id;
> + int ret;
> +
> + switch (prop) {
> + case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR:
> + attribute_id = LWMI_ATTR_ID_PSU(LWMI_FEATURE_ID_PSU_CHARGE_BEHAVIOUR,
> + LWMI_TYPE_ID_NONE);
> + break;
> + case POWER_SUPPLY_PROP_CHARGE_TYPES:
> + attribute_id = LWMI_ATTR_ID_PSU(LWMI_FEATURE_ID_PSU_CHARGE_TYPES,
> + LWMI_TYPE_ID_PSU_AC);
> + break;
> + default:
> + return -EINVAL;
> + }
> +
> + ret = lwmi_cd00_get_data(priv->cd00_list, attribute_id, &capdata);
> + if (ret)
> + return ret;
> +
> + dev_dbg(&priv->wdev->dev, "Battery charge feature (%#010x) support level: %#x\n",
> + attribute_id, capdata.supported);
> +
> + return capdata.supported;
...this casts u32 to int.
Additionally, I think it's meaningless to propagate errno. It can hardly
be tracked as it's anyway converted to false later. If you'd like to
track errors, an additional dev_dbg() should be enough (any error here
is non-fatal so there's no need to use dev_warn() or so on).
Except for these tiny issues, the patch is in a good shape. With them
fixed:
Reviewed-by: Rong Zhang <i@rong.moe>
Thanks,
Rong
> +}
> +
> +/**
> + * lwmi_psy_prop_is_supported() - Determine if the property is supported
> + * @priv: Pointer to the lwmi_om_priv drvdata
> + * @prop: The power supply property to be evaluated
> + *
> + * Checks capdata 00 to determine if the property is supported.
> + *
> + * Return: true if readable, or false
> + */
> +static bool lwmi_psy_prop_is_supported(struct lwmi_om_priv *priv, enum power_supply_property prop)
> +{
> + int ret;
> +
> + ret = lwmi_psy_prop_get_supported(priv, prop);
> + if (ret < 0)
> + return false;
> +
> + return (ret & LWMI_SUPP_VALID) && (ret & LWMI_SUPP_GET);
> +}
> +
> +/**
> + * lwmi_psy_prop_is_writeable() - Determine if the property is writeable
> + * @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 writable.
> + *
> + * Return: true if writable, 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;
> + int ret;
> +
> + ret = lwmi_psy_prop_get_supported(priv, prop);
> + if (ret < 0)
> + return false;
> +
> + return !!(ret & LWMI_SUPP_SET);
> +}
> +
> +#define DEFINE_LWMI_POWER_SUPPLY_EXTENSION(_name, _props, _behaviours, _types) \
> + static const struct power_supply_ext _name = { \
> + .name = LWMI_OM_SYSFS_NAME, \
> + .properties = _props, \
> + .num_properties = ARRAY_SIZE(_props), \
> + .charge_behaviours = _behaviours, \
> + .charge_types = _types, \
> + .get_property = lwmi_psy_ext_get_prop, \
> + .set_property = lwmi_psy_ext_set_prop, \
> + .property_is_writeable = lwmi_psy_prop_is_writeable, \
> + }
> +
> +static const enum power_supply_property lwmi_psy_ext_props_all[] = {
> + POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR,
> + POWER_SUPPLY_PROP_CHARGE_TYPES,
> +};
> +
> +static const enum power_supply_property lwmi_psy_ext_props_types[] = {
> + POWER_SUPPLY_PROP_CHARGE_TYPES,
> +};
> +
> +static const enum power_supply_property lwmi_psy_ext_props_behaviour[] = {
> + POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR,
> +};
> +
> +#define LWMI_CHARGE_BEHAVIOURS (BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO) | \
> + BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE))
> +
> +#define LWMI_CHARGE_TYPES (BIT(POWER_SUPPLY_CHARGE_TYPE_STANDARD) | \
> + BIT(POWER_SUPPLY_CHARGE_TYPE_LONGLIFE))
> +
> +DEFINE_LWMI_POWER_SUPPLY_EXTENSION(lwmi_psy_ext_all, lwmi_psy_ext_props_all,
> + LWMI_CHARGE_BEHAVIOURS, LWMI_CHARGE_TYPES);
> +DEFINE_LWMI_POWER_SUPPLY_EXTENSION(lwmi_psy_ext_types, lwmi_psy_ext_props_types,
> + 0, LWMI_CHARGE_TYPES);
> +DEFINE_LWMI_POWER_SUPPLY_EXTENSION(lwmi_psy_ext_behaviour, lwmi_psy_ext_props_behaviour,
> + LWMI_CHARGE_BEHAVIOURS, 0);
> +
> +#define LWMI_PSY_PROP_BEHAVIOUR BIT(0)
> +#define LWMI_PSY_PROP_TYPES BIT(1)
> +
> +static const struct power_supply_ext *lwmi_psy_exts[] = {
> + [LWMI_PSY_PROP_BEHAVIOUR] = &lwmi_psy_ext_behaviour,
> + [LWMI_PSY_PROP_TYPES] = &lwmi_psy_ext_types,
> + [LWMI_PSY_PROP_BEHAVIOUR | LWMI_PSY_PROP_TYPES] = &lwmi_psy_ext_all,
> +};
> +
> +/**
> + * 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, priv->battery_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)
> +{
> + struct lwmi_om_priv *priv = container_of(hook, struct lwmi_om_priv, battery_hook);
> +
> + power_supply_unregister_extension(battery, priv->battery_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;
> +}
> +
> +/**
> + * lwmi_om_psy_ext_init() - Hooks power supply extension to device battery
> + * @priv: Pointer to the lwmi_om_priv drvdata.
> + *
> + * Checks if the ideapad_laptop driver is going to manage charge attributes first,
> + * then if not, hooks the battery to our WMI methods if they are supported.
> + */
> +static void lwmi_om_psy_ext_init(struct lwmi_om_priv *priv)
> +{
> + static const char * const ideapad_hid = "VPC2004";
> + acpi_handle handle = NULL;
> + unsigned int props = 0;
> + int ret;
> +
> + if (lwmi_psy_prop_is_supported(priv, POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR))
> + props |= LWMI_PSY_PROP_BEHAVIOUR;
> + if (lwmi_psy_prop_is_supported(priv, POWER_SUPPLY_PROP_CHARGE_TYPES))
> + props |= LWMI_PSY_PROP_TYPES;
> + if (!props)
> + return;
> +
> + /* Deconflict ideapad_laptop driver */
> + ret = acpi_get_devices(ideapad_hid, lwmi_acpi_match, &handle, NULL);
> + if (ret)
> + return;
> +
> + if (handle && 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_ext = lwmi_psy_exts[props];
> + 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";
> + priv->bh_registered = true;
> +
> + battery_hook_register(&priv->battery_hook);
> +}
> +
> +/**
> + * lwmi_om_psy_remove() - Unregister battery hook
> + * @priv: Driver private data
> + *
> + * Unregisters the battery hook if applicable.
> + */
> +static void lwmi_om_psy_remove(struct lwmi_om_priv *priv)
> +{
> + if (!priv->bh_registered)
> + return;
> +
> + battery_hook_unregister(&priv->battery_hook);
> + priv->bh_registered = false;
> +}
> +
> /* ======== fw_attributes (component: lenovo-wmi-capdata 01) ======== */
>
> struct tunable_attr_01 {
> @@ -1243,6 +1631,7 @@ static int lwmi_om_master_bind(struct device *dev)
> }
>
> lwmi_om_fan_info_collect_cd00(priv);
> + lwmi_om_psy_ext_init(priv);
>
> lwmi_om_fw_attr_add(priv);
>
> @@ -1265,6 +1654,8 @@ static void lwmi_om_master_unbind(struct device *dev)
>
> lwmi_om_hwmon_remove(priv);
>
> + lwmi_om_psy_remove(priv);
> +
> component_unbind_all(dev, NULL);
> }
>
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v13 5/7] platform/x86: lenovo-wmi-other: Add force_load_psy_ext module parameter
2026-05-12 23:58 ` [PATCH v13 5/7] platform/x86: lenovo-wmi-other: Add force_load_psy_ext module parameter Derek J. Clark
@ 2026-05-13 14:44 ` Rong Zhang
0 siblings, 0 replies; 10+ messages in thread
From: Rong Zhang @ 2026-05-13 14:44 UTC (permalink / raw)
To: Derek J. Clark, Ilpo Järvinen, Hans de Goede
Cc: Mark Pearson, Armin Wolf, Jonathan Corbet, Kurt Borja,
Pierre-Loup A . Griffais, Nícolas F . R . A . Prado,
marshall, hyacinth, platform-driver-x86, linux-kernel
Hi Derek,
On Tue, 2026-05-12 at 23:58 +0000, Derek J. Clark wrote:
> Some Lenovo BIOS have been shown to have incomplete and/or broken
> capability data and WMI attribute IDs. In some cases the capability data
> reports that a feature is not supported when the get/set methods are
> fully implemented. It is also possible that the ACPI methods from the
> ideapad_laptop driver we defer to could be bugged while the WMI method
> is fully working. To aid end users in submitting more complete bug
> reports in these situations, add an override to skip the ACPI and
> compatibility checks to force load the power supply extension as if it
> is fully supported and has no conflicts.
>
> Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
LGTM!
Reviewed-by: Rong Zhang <i@rong.moe>
Thanks,
Rong
> ---
> v13:
> - Ensure is_writable and is_supported always return true when
> force_load_psy_ext is true.
> v12:
> - Move the force_load_psy_ext into its own distinct patch.
> ---
> drivers/platform/x86/lenovo/wmi-other.c | 19 +++++++++++++++++++
> 1 file changed, 19 insertions(+)
>
> diff --git a/drivers/platform/x86/lenovo/wmi-other.c b/drivers/platform/x86/lenovo/wmi-other.c
> index a423bd563291..9d5250d41a3f 100644
> --- a/drivers/platform/x86/lenovo/wmi-other.c
> +++ b/drivers/platform/x86/lenovo/wmi-other.c
> @@ -185,6 +185,16 @@ MODULE_PARM_DESC(relax_fan_constraint,
> "Enabling this may results in HWMON attributes being out-of-sync, "
> "and setting a too low RPM stops the fan. Use with caution.");
>
> +/* Visibility of power supply extensions */
> +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 behavior and battery charge "
> + "control end threshold. It will also skip checking if the BIOS reports that "
> + "those features are fully supported. It is recommended to blacklist the ideapad "
> + "driver before using this option.");
> +
> /* ======== HWMON (component: lenovo-wmi-capdata 00 & fan) ======== */
>
> /**
> @@ -755,6 +765,9 @@ static bool lwmi_psy_prop_is_supported(struct lwmi_om_priv *priv, enum power_sup
> {
> int ret;
>
> + if (force_load_psy_ext)
> + return true;
> +
> ret = lwmi_psy_prop_get_supported(priv, prop);
> if (ret < 0)
> return false;
> @@ -781,6 +794,9 @@ static int lwmi_psy_prop_is_writeable(struct power_supply *ps,
> struct lwmi_om_priv *priv = ext_data;
> int ret;
>
> + if (force_load_psy_ext)
> + return true;
> +
> ret = lwmi_psy_prop_get_supported(priv, prop);
> if (ret < 0)
> return false;
> @@ -909,6 +925,8 @@ static void lwmi_om_psy_ext_init(struct lwmi_om_priv *priv)
> props |= LWMI_PSY_PROP_TYPES;
> if (!props)
> return;
> + if (force_load_psy_ext)
> + goto load_psy_ext;
>
> /* Deconflict ideapad_laptop driver */
> ret = acpi_get_devices(ideapad_hid, lwmi_acpi_match, &handle, NULL);
> @@ -920,6 +938,7 @@ static void lwmi_om_psy_ext_init(struct lwmi_om_priv *priv)
> return;
> }
>
> +load_psy_ext:
> /* Add battery hooks */
> priv->battery_ext = lwmi_psy_exts[props];
> priv->battery_hook.add_battery = lwmi_add_battery;
^ permalink raw reply [flat|nested] 10+ messages in thread
end of thread, other threads:[~2026-05-13 14:50 UTC | newest]
Thread overview: 10+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-05-12 23:58 [PATCH v13 0/7] lenovo-wmi: Add fixes and enhancement Derek J. Clark
2026-05-12 23:58 ` [PATCH v13 1/7] platform/x86: lenovo-wmi-other: Add missing CPU tunable attributes Derek J. Clark
2026-05-12 23:58 ` [PATCH v13 2/7] platform/x86: lenovo-wmi-other: Add GPU " Derek J. Clark
2026-05-12 23:58 ` [PATCH v13 3/7] platform/x86: lenovo-wmi-other: Rename LWMI_OM_FW_ATTR_BASE_PATH Derek J. Clark
2026-05-12 23:58 ` [PATCH v13 4/7] platform/x86: lenovo-wmi-other: Add WMI battery charge limiting Derek J. Clark
2026-05-13 14:43 ` Rong Zhang
2026-05-12 23:58 ` [PATCH v13 5/7] platform/x86: lenovo-wmi-other: Add force_load_psy_ext module parameter Derek J. Clark
2026-05-13 14:44 ` Rong Zhang
2026-05-12 23:58 ` [PATCH v13 6/7] platform/x86: lenovo-wmi-helpers: Add helper for creating per-device debugfs dir Derek J. Clark
2026-05-12 23:58 ` [PATCH v13 7/7] platform/x86: lenovo-wmi-capdata: Add debugfs file for dumping capdata Derek J. Clark
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox