Skip to contentSkip to conversation

lenovo-wmi: Add fixes and enhancement

PATCH Superseded Derek J. Clark 4 participants 24 messages May 10 – May 12

Subsystems (5)

v12 of 14 Superseded Latest is v14 →

lore
Share & tools
lore

Derek J. Clark

author maintainer
#0 lore raw

v12 cover lenovo-wmi: Add fixes and enhancement

This series adds many much needed fixes and features to the lenovo-wmi* drivers. This version has been re-ordered to ensure the least amount of churn in each patch. This has resulted in two formatting patches being added to the stable Cc list as prerequisites for some of the fixes.

Patches 1-9 fix various bugs and formatting, and are Cc'd to stable. for the formatting only ones, patch 7 moves all gamezone enums from the gamezone header into the helpers header and patch 8 adds two functions to make assigning attribute ID's for capdata0X and tunable_attr_01 cleaner.

Patch 10 adds the remaining CPU attributes that weren't previously exposed.

Patch 11 adds GPU attributes.

Patch 12 renames a name constant in preparation for patch 6.

Patch 13 adds battery charge-type 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 14 adds a module parameter, force_load_psy_ext, to bypass ACPI and BIOS compatibility checks for the power supply extension.

Patch 15 adds a debugfs directory.

Patch 16 adds a debugfs file for dumping capdata.

---
v12:
- 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/[email protected]/
- 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/[email protected]/
- Fix misnamed attribute in wmi-other.
v9: https://lore.kernel.org/platform-driver-x86/[email protected]/
- 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/[email protected]/
- 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/[email protected]/
- 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/[email protected]/
- 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/[email protected]/
- 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/[email protected]/
- 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/[email protected]/
- 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/[email protected]/
- 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/[email protected]/

Derek J. Clark (10):
platform/x86: lenovo-wmi-other: Zero initialize WMI arguments platform/x86: lenovo-wmi-other: Fix tunable_attr_01 struct members platform/x86: lenovo-wmi-helpers: Move gamezone enums to wmi-helpers platform/x86: lenovo-wmi-other: Add Attribute ID helper functions 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 platform/x86: lenovo-wmi-other: Add force_load_psy_ext module
parameter

Rong Zhang (6):
platform/x86: lenovo-wmi-helpers: Fix memory leak in
lwmi_dev_evaluate_int()
platform/x86: lenovo-wmi-other: Balance IDA id allocation and free platform/x86: lenovo-wmi-other: Balance component bind and unbind platform/x86: lenovo: Decouple lenovo-wmi-gamezone and
lenovo-wmi-other
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           |   3 +-
 drivers/platform/x86/lenovo/wmi-capdata.c     | 128 ++-
 drivers/platform/x86/lenovo/wmi-capdata.h     |  34 +-
 drivers/platform/x86/lenovo/wmi-events.c      |   2 +-
 drivers/platform/x86/lenovo/wmi-gamezone.c    |   7 +-
 drivers/platform/x86/lenovo/wmi-gamezone.h    |  20 -
 drivers/platform/x86/lenovo/wmi-helpers.c     | 139 ++-
 drivers/platform/x86/lenovo/wmi-helpers.h     |  23 +
 drivers/platform/x86/lenovo/wmi-other.c       | 867 +++++++++++++++---
 drivers/platform/x86/lenovo/wmi-other.h       |  16 -
 11 files changed, 1061 insertions(+), 197 deletions(-)
 delete mode 100644 drivers/platform/x86/lenovo/wmi-gamezone.h
 delete mode 100644 drivers/platform/x86/lenovo/wmi-other.h
Signature · 1 line
2.53.0
author maintainer
#1 lore raw

PATCH v12 1/16 platform/x86: lenovo-wmi-helpers: Fix memory leak in lwmi_dev_evaluate_int()

From: Rong Zhang <[email protected]>

lwmi_dev_evaluate_int() leaks output.pointer when retval == NULL (found by sashiko.dev [1]).

Fix it by moving ret_obj = output.pointer' outside of the if (retval)' block so that it is always freed by the __free cleanup callback.

No functional change intended.

---
 drivers/platform/x86/lenovo/wmi-helpers.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)
drivers/platform/x86/lenovo/wmi-helpers.c +2 -2
index 7379defac500..018d7642e2bd 100644
@@ -46,7 +46,6 @@ int lwmi_dev_evaluate_int(struct wmi_device *wdev, u8 instance, u32 method_id,
 			  unsigned char *buf, size_t size, u32 *retval)
 {
 	struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
-	union acpi_object *ret_obj __free(kfree) = NULL;
 	struct acpi_buffer input = { size, buf };
 	acpi_status status;
 
@@ -55,8 +54,9 @@ int lwmi_dev_evaluate_int(struct wmi_device *wdev, u8 instance, u32 method_id,
 	if (ACPI_FAILURE(status))
 		return -EIO;
 
+	union acpi_object *ret_obj __free(kfree) = output.pointer;
+
 	if (retval) {
-		ret_obj = output.pointer;
 		if (!ret_obj)
 			return -ENODATA;
 
Signature · 1 line
2.53.0
author maintainer
#2 lore raw

PATCH v12 2/16 platform/x86: lenovo-wmi-other: Balance IDA id allocation and free

In reply to Derek J. Clark

From: Rong Zhang <[email protected]>

Currently, the IDA id is only freed on wmi-other device removal or failure to create firmware-attributes device, kset, or attributes. It leaks IDA ids if the wmi-other device is bound multiple times, as the unbind callback never frees the previously allocated IDA id. Additionally, if the wmi-other device has failed to create a firmware-attributes device before it gets removed, the wmi-device removal callback double frees the same IDA id.

These bugs were found by sashiko.dev [1].

Fix them by moving ida_free() into lwmi_om_fw_attr_remove() so it is balanced with ida_alloc() in lwmi_om_fw_attr_add(). With them fixed, properly set and utilize the validity of priv->ida_id to balance firmware-attributes registration and removal, without relying on propagating the registration error to the component framework, which is more reliable and aligns with the hwmon device registration and removal sequences.

No functional change intended.

---
v9:
- Invert err logic for when allocating IDA fails.
- Rename ida_alloc err goto from 'err' to 'err_no_ida' to disambiguate from 'int err'.

---
 drivers/platform/x86/lenovo/wmi-other.c | 36 ++++++++++++++-----------
 1 file changed, 21 insertions(+), 15 deletions(-)
drivers/platform/x86/lenovo/wmi-other.c +21 -15
index 6040f45aa2b0..be3309d74e03 100644
@@ -957,17 +957,17 @@ static struct capdata01_attr_group cd01_attr_groups[] = {
 /**
  * lwmi_om_fw_attr_add() - Register all firmware_attributes_class members
  * @priv: The Other Mode driver data.
- *
- * Return: Either 0, or an error code.
  */
-static int lwmi_om_fw_attr_add(struct lwmi_om_priv *priv)
+static void lwmi_om_fw_attr_add(struct lwmi_om_priv *priv)
 {
 	unsigned int i;
 	int err;
 
-	priv->ida_id = ida_alloc(&lwmi_om_ida, GFP_KERNEL);
-	if (priv->ida_id < 0)
-		return priv->ida_id;
+	err = ida_alloc(&lwmi_om_ida, GFP_KERNEL);
+	if (err < 0)
+		goto err_no_ida;
+
+	priv->ida_id = err;
 
 	priv->fw_attr_dev = device_create(&firmware_attributes_class, NULL,
 					  MKDEV(0, 0), NULL, "%s-%u",
@@ -993,7 +993,7 @@ static int lwmi_om_fw_attr_add(struct lwmi_om_priv *priv)
 
 		cd01_attr_groups[i].tunable_attr->dev = &priv->wdev->dev;
 	}
-	return 0;
+	return;
 
 err_remove_groups:
 	while (i--)
@@ -1007,7 +1007,12 @@ static int lwmi_om_fw_attr_add(struct lwmi_om_priv *priv)
 
 err_free_ida:
 	ida_free(&lwmi_om_ida, priv->ida_id);
-	return err;
+
+err_no_ida:
+	priv->ida_id = -EIDRM;
+
+	dev_warn(&priv->wdev->dev,
+		 "failed to register firmware-attributes device: %d\n", err);
 }
 
 /**
@@ -1016,12 +1021,17 @@ static int lwmi_om_fw_attr_add(struct lwmi_om_priv *priv)
  */
 static void lwmi_om_fw_attr_remove(struct lwmi_om_priv *priv)
 {
+	if (priv->ida_id < 0)
+		return;
+
 	for (unsigned int i = 0; i < ARRAY_SIZE(cd01_attr_groups) - 1; i++)
 		sysfs_remove_group(&priv->fw_attr_kset->kobj,
 				   cd01_attr_groups[i].attr_group);
 
 	kset_unregister(priv->fw_attr_kset);
 	device_unregister(priv->fw_attr_dev);
+	ida_free(&lwmi_om_ida, priv->ida_id);
+	priv->ida_id = -EIDRM;
 }
 
 /* ======== Self (master: lenovo-wmi-other) ======== */
@@ -1063,7 +1073,9 @@ static int lwmi_om_master_bind(struct device *dev)
 
 	lwmi_om_fan_info_collect_cd00(priv);
 
-	return lwmi_om_fw_attr_add(priv);
+	lwmi_om_fw_attr_add(priv);
+
+	return 0;
 }
 
 /**
@@ -1115,13 +1127,7 @@ static int lwmi_other_probe(struct wmi_device *wdev, const void *context)
 
 static void lwmi_other_remove(struct wmi_device *wdev)
 {
-	struct lwmi_om_priv *priv = dev_get_drvdata(&wdev->dev);
-
 	component_master_del(&wdev->dev, &lwmi_om_master_ops);
-
-	/* No IDA to free if the driver is never bound to its components. */
-	if (priv->ida_id >= 0)
-		ida_free(&lwmi_om_ida, priv->ida_id);
 }
 
 static const struct wmi_device_id lwmi_other_id_table[] = {
Signature · 1 line
2.53.0
author maintainer
#3 lore raw

PATCH v12 3/16 platform/x86: lenovo-wmi-other: Balance component bind and unbind

In reply to Derek J. Clark

From: Rong Zhang <[email protected]>

When lwmi_om_master_bind() fails, the master device's components are left bound, with the aggregate device destroyed due to the failure (found by sashiko.dev [1]).

Balance calls to component_bind_all() and component_unbind_all() when an error is propagated to the component framework.

No functional change intended.

---
 drivers/platform/x86/lenovo/wmi-other.c | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)
drivers/platform/x86/lenovo/wmi-other.c +4 -1
index be3309d74e03..a6be3463341c 100644
@@ -1068,8 +1068,11 @@ static int lwmi_om_master_bind(struct device *dev)
 
 	priv->cd00_list = binder.cd00_list;
 	priv->cd01_list = binder.cd01_list;
-	if (!priv->cd00_list || !priv->cd01_list)
+	if (!priv->cd00_list || !priv->cd01_list) {
+		component_unbind_all(dev, NULL);
+
 		return -ENODEV;
+	}
 
 	lwmi_om_fan_info_collect_cd00(priv);
 
Signature · 1 line
2.53.0
author maintainer
#4 lore raw

PATCH v12 4/16 platform/x86: lenovo-wmi-other: Zero initialize WMI arguments

In reply to Derek J. Clark

Adds explicit initialization of wmi_method_args_32 declarations with zero values to prevent uninitialized data from being sent to the device BIOS when passed.

No functional change intended.

Fixes 22024ac5366f ("platform/x86: Add Lenovo Gamezone WMI Driver")
Fixes edc4b183b794 ("platform/x86: Add Lenovo Other Mode WMI Driver")

---
v12:
Fix formatting of commit message.
v7:
- Include lwmi_gz_profile_set() fix as well.

---
 drivers/platform/x86/lenovo/wmi-gamezone.c | 2 +-
 drivers/platform/x86/lenovo/wmi-other.c    | 6 +++---
 2 files changed, 4 insertions(+), 4 deletions(-)
drivers/platform/x86/lenovo/wmi-gamezone.c +1 -1
index 381836d29a96..ca559e6c031d 100644
@@ -203,7 +203,7 @@ static int lwmi_gz_profile_set(struct device *dev,
 			       enum platform_profile_option profile)
 {
 	struct lwmi_gz_priv *priv = dev_get_drvdata(dev);
-	struct wmi_method_args_32 args;
+	struct wmi_method_args_32 args = {};
 	enum thermal_mode mode;
 	int ret;
 
drivers/platform/x86/lenovo/wmi-other.c +3 -3
index a6be3463341c..1e06b894cfcc 100644
@@ -166,7 +166,7 @@ MODULE_PARM_DESC(relax_fan_constraint,
  */
 static int lwmi_om_fan_get_set(struct lwmi_om_priv *priv, int channel, u32 *val, bool set)
 {
-	struct wmi_method_args_32 args;
+	struct wmi_method_args_32 args = {};
 	u32 method_id, retval;
 	int err;
 
@@ -773,7 +773,7 @@ static ssize_t attr_current_value_store(struct kobject *kobj,
 					struct tunable_attr_01 *tunable_attr)
 {
 	struct lwmi_om_priv *priv = dev_get_drvdata(tunable_attr->dev);
-	struct wmi_method_args_32 args;
+	struct wmi_method_args_32 args = {};
 	struct capdata01 capdata;
 	enum thermal_mode mode;
 	u32 attribute_id;
@@ -836,7 +836,7 @@ static ssize_t attr_current_value_show(struct kobject *kobj,
 				       struct tunable_attr_01 *tunable_attr)
 {
 	struct lwmi_om_priv *priv = dev_get_drvdata(tunable_attr->dev);
-	struct wmi_method_args_32 args;
+	struct wmi_method_args_32 args = {};
 	enum thermal_mode mode;
 	u32 attribute_id;
 	int retval;
Signature · 1 line
2.53.0
author maintainer
#5 lore raw

PATCH v12 5/16 platform/x86: lenovo-wmi-other: Fix tunable_attr_01 struct members

In reply to Derek J. Clark

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.

No functional change intended.

---
v12:
- Drop fixes tag in formatting only patch.

---
 drivers/platform/x86/lenovo/wmi-other.c | 7 +++----
 1 file changed, 3 insertions(+), 4 deletions(-)
drivers/platform/x86/lenovo/wmi-other.c +3 -4
index 1e06b894cfcc..50a03f5fd6ab 100644
@@ -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 = {
Signature · 1 line
2.53.0
author maintainer
#6 lore raw

PATCH v12 6/16 platform/x86: lenovo: Decouple lenovo-wmi-gamezone and lenovo-wmi-other

In reply to Derek J. Clark

From: Rong Zhang <[email protected]>

Currently, lenovo-wmi-gamezone depends on lenovo-wmi-other as the former imports symbols from the latter. The imported symbols are just used to register a notifier block. However, there is no runtime dependency between both drivers, and either of them can run without the other, which is the major purpose of using the notifier framework.

Such a link-time dependency is non-optimal. A previous attempt to "fix" it made LENOVO_WMI_GAMEZONE select LENOVO_WMI_TUNING, which was fundamentally broken and resulted in undefined Kconfig behavior, as `select' cannot be used on a symbol with potentially unmet dependencies.

Decouple both drivers by moving the thermal mode notifier chain to lenovo-wmi-helpers. Methods for notifier block (un)registration are exported for lenovo-wmi-gamezone, while a method for querying the current thermal mode are exported for lenovo-wmi-other.

This turns the dependency graph from

            +------------ lenovo-wmi-gamezone
            |                     |
            v                     |
    lenovo-wmi-helpers            |
            ^                     |
            |                     V
            +------------ lenovo-wmi-other

into

            +------------ lenovo-wmi-gamezone
            |
            v
    lenovo-wmi-helpers
            ^
            |
            +------------ lenovo-wmi-other

To make it clear, the name of the notifier chain is also renamed from om_chain_head' to tm_chain_head', indicating that it's used to query the current thermal mode.

No functional change intended.

---
 drivers/platform/x86/lenovo/Kconfig        |   1 -
 drivers/platform/x86/lenovo/wmi-gamezone.c |   4 +-
 drivers/platform/x86/lenovo/wmi-helpers.c  | 101 ++++++++++++++++++++
 drivers/platform/x86/lenovo/wmi-helpers.h  |   8 ++
 drivers/platform/x86/lenovo/wmi-other.c    | 104 +--------------------
 drivers/platform/x86/lenovo/wmi-other.h    |  16 ----
 6 files changed, 112 insertions(+), 122 deletions(-)
 delete mode 100644 drivers/platform/x86/lenovo/wmi-other.h
drivers/platform/x86/lenovo/Kconfig +0 -1
index f885127b007f..09b1b055d2e0 100644
@@ -252,7 +252,6 @@ config LENOVO_WMI_GAMEZONE
 	select ACPI_PLATFORM_PROFILE
 	select LENOVO_WMI_EVENTS
 	select LENOVO_WMI_HELPERS
-	select LENOVO_WMI_TUNING
 	help
 	  Say Y here if you have a WMI aware Lenovo Legion device and would like to use the
 	  platform-profile firmware interface to manage power usage.
drivers/platform/x86/lenovo/wmi-gamezone.c +1 -3
index ca559e6c031d..a91089694727 100644
@@ -23,7 +23,6 @@
 #include "wmi-events.h"
 #include "wmi-gamezone.h"
 #include "wmi-helpers.h"
-#include "wmi-other.h"
 
 #define LENOVO_GAMEZONE_GUID "887B54E3-DDDC-4B2C-8B88-68A26A8835D0"
 
@@ -385,7 +384,7 @@ static int lwmi_gz_probe(struct wmi_device *wdev, const void *context)
 		return ret;
 
 	priv->mode_nb.notifier_call = lwmi_gz_mode_call;
-	return devm_lwmi_om_register_notifier(&wdev->dev, &priv->mode_nb);
+	return devm_lwmi_tm_register_notifier(&wdev->dev, &priv->mode_nb);
 }
 
 static const struct wmi_device_id lwmi_gz_id_table[] = {
@@ -407,7 +406,6 @@ module_wmi_driver(lwmi_gz_driver);
 
 MODULE_IMPORT_NS("LENOVO_WMI_EVENTS");
 MODULE_IMPORT_NS("LENOVO_WMI_HELPERS");
-MODULE_IMPORT_NS("LENOVO_WMI_OTHER");
 MODULE_DEVICE_TABLE(wmi, lwmi_gz_id_table);
 MODULE_AUTHOR("Derek J. Clark <[email protected]>");
 MODULE_DESCRIPTION("Lenovo GameZone WMI Driver");
drivers/platform/x86/lenovo/wmi-helpers.c +101 -0

118 lines not on this page · raw message

drivers/platform/x86/lenovo/wmi-helpers.h +8 -0
index 20fd21749803..651a039228ed 100644
@@ -7,6 +7,8 @@
 
 #include <linux/types.h>
 
+struct device;
+struct notifier_block;
 struct wmi_device;
 
 struct wmi_method_args_32 {
@@ -17,4 +19,10 @@ struct wmi_method_args_32 {
 int lwmi_dev_evaluate_int(struct wmi_device *wdev, u8 instance, u32 method_id,
 			  unsigned char *buf, size_t size, u32 *retval);
 
+int lwmi_tm_register_notifier(struct notifier_block *nb);
+int lwmi_tm_unregister_notifier(struct notifier_block *nb);
+int devm_lwmi_tm_register_notifier(struct device *dev,
+				   struct notifier_block *nb);
+int lwmi_tm_notifier_call(enum thermal_mode *mode);
+
 #endif /* !_LENOVO_WMI_HELPERS_H_ */
drivers/platform/x86/lenovo/wmi-other.c +2 -102

146 lines not on this page · raw message

drivers/platform/x86/lenovo/wmi-other.h deleted +0 -16
deleted file mode 100644
index 8ebf5602bb99..000000000000
@@ -1,16 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-
-/* Copyright (C) 2025 Derek J. Clark <[email protected]> */
-
-#ifndef _LENOVO_WMI_OTHER_H_
-#define _LENOVO_WMI_OTHER_H_
-
-struct device;
-struct notifier_block;
-
-int lwmi_om_register_notifier(struct notifier_block *nb);
-int lwmi_om_unregister_notifier(struct notifier_block *nb);
-int devm_lwmi_om_register_notifier(struct device *dev,
-				   struct notifier_block *nb);
-
-#endif /* !_LENOVO_WMI_OTHER_H_ */
Signature · 1 line
2.53.0
author maintainer
#7 lore raw

PATCH v12 7/16 platform/x86: lenovo-wmi-helpers: Move gamezone enums to wmi-helpers

In reply to Derek J. Clark

In a later patch in the series the thermal mode enum will be accessed across three separate drivers (wmi-capdata, wmi-gamezonem and wmi-other). An additional patch in the series will also add a function prototype that needs to reference this enum in wmi-helpers.h. To avoid having all these drivers begin to import each others headers, and to avoid declaring an opaque enum to hande the second case, move the thermal mode enum to helpers where it can be safely accessed by everything that needs it from a single import.

While at it, since the gamezone_events_type enum is the only remaining item in the header, move that as well and remove the gamezone header entirely.

---
v12:
- Drop fixes tag in formatting only patch.
- Fix typo in commit message.
v11:
- Move to earlier in the series as later patches depend on it.

---
 drivers/platform/x86/lenovo/wmi-events.c   |  2 +-
 drivers/platform/x86/lenovo/wmi-gamezone.c |  1 -
 drivers/platform/x86/lenovo/wmi-gamezone.h | 20 --------------------
 drivers/platform/x86/lenovo/wmi-helpers.h  | 13 +++++++++++++
 drivers/platform/x86/lenovo/wmi-other.c    |  1 -
 5 files changed, 14 insertions(+), 23 deletions(-)
 delete mode 100644 drivers/platform/x86/lenovo/wmi-gamezone.h
drivers/platform/x86/lenovo/wmi-events.c +1 -1
index 0994cd7dd504..9e9f2e82e04d 100644
@@ -17,7 +17,7 @@
 #include <linux/wmi.h>
 
 #include "wmi-events.h"
-#include "wmi-gamezone.h"
+#include "wmi-helpers.h"
 
 #define THERMAL_MODE_EVENT_GUID "D320289E-8FEA-41E0-86F9-911D83151B5F"
 
drivers/platform/x86/lenovo/wmi-gamezone.c +0 -1
index a91089694727..5a8f4aee02cf 100644
@@ -21,7 +21,6 @@
 #include <linux/wmi.h>
 
 #include "wmi-events.h"
-#include "wmi-gamezone.h"
 #include "wmi-helpers.h"
 
 #define LENOVO_GAMEZONE_GUID "887B54E3-DDDC-4B2C-8B88-68A26A8835D0"
drivers/platform/x86/lenovo/wmi-gamezone.h deleted +0 -20
deleted file mode 100644
index 6b163a5eeb95..000000000000
@@ -1,20 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-
-/* Copyright (C) 2025 Derek J. Clark <[email protected]> */
-
-#ifndef _LENOVO_WMI_GAMEZONE_H_
-#define _LENOVO_WMI_GAMEZONE_H_
-
-enum gamezone_events_type {
-	LWMI_GZ_GET_THERMAL_MODE = 1,
-};
-
-enum thermal_mode {
-	LWMI_GZ_THERMAL_MODE_QUIET =	   0x01,
-	LWMI_GZ_THERMAL_MODE_BALANCED =	   0x02,
-	LWMI_GZ_THERMAL_MODE_PERFORMANCE = 0x03,
-	LWMI_GZ_THERMAL_MODE_EXTREME =	   0xE0, /* Ver 6+ */
-	LWMI_GZ_THERMAL_MODE_CUSTOM =	   0xFF,
-};
-
-#endif /* !_LENOVO_WMI_GAMEZONE_H_ */
drivers/platform/x86/lenovo/wmi-helpers.h +13 -0
index 651a039228ed..ed7db3ebba6c 100644
@@ -16,6 +16,19 @@ struct wmi_method_args_32 {
 	u32 arg1;
 };
 
+enum lwmi_event_type {
+	LWMI_GZ_GET_THERMAL_MODE = 0x01,
+};
+
+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,
+	LWMI_GZ_THERMAL_MODE_EXTREME =	   0xE0, /* Ver 6+ */
+	LWMI_GZ_THERMAL_MODE_CUSTOM =	   0xFF,
+};
+
 int lwmi_dev_evaluate_int(struct wmi_device *wdev, u8 instance, u32 method_id,
 			  unsigned char *buf, size_t size, u32 *retval);
 
drivers/platform/x86/lenovo/wmi-other.c +0 -1
index f63e568a4e12..b4ed7af50a24 100644
@@ -46,7 +46,6 @@
 
 #include "wmi-capdata.h"
 #include "wmi-events.h"
-#include "wmi-gamezone.h"
 #include "wmi-helpers.h"
 #include "../firmware_attributes_class.h"
 
Signature · 1 line
2.53.0
author maintainer
#8 lore raw

PATCH v12 8/16 platform/x86: lenovo-wmi-other: Add Attribute ID helper functions

In reply to Derek J. Clark

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.

Adds tunable_attr_01_id() function that breaks out the members of a tunable_attr_01 struct and passes them to lwmi_attr_id().

No functional change intended.

---
v12:
- Drop fixes tag in patch with no functional changes.
- Clarify kerneldocs comments.
v11:
- Move to earlier in the series to clean up later patches that can use it.
- Add helper function that takes a tunable_attr_01 instead of breaking out all struct members manually each time the macro is used.
v9:
- Fix dropped use of mode variable in current_value_show.
v7:
- Incorporate additional replacements in lwmi_attr_01_is_supported after moving the patch that adds it to earlier in the series.
v6:
- Move lwmi_attr_id to wmi-capdata.h as static inline.
v5:
- Move references to cv/cd_mode_id to patch 4/8.
- Move lwmi_attr_id to wmi-capdata.c and export with namespace.
v4:
- Switch from macro to static inline to preserve types.

---
 drivers/platform/x86/lenovo/wmi-capdata.c |  8 ++--
 drivers/platform/x86/lenovo/wmi-capdata.h | 20 +++++++++
 drivers/platform/x86/lenovo/wmi-other.c   | 49 +++++++++--------------
 3 files changed, 44 insertions(+), 33 deletions(-)
drivers/platform/x86/lenovo/wmi-capdata.c +4 -4
index ee1fb02d8e31..169665be4dcf 100644
@@ -27,7 +27,6 @@
 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
 
 #include <linux/acpi.h>
-#include <linux/bitfield.h>
 #include <linux/bug.h>
 #include <linux/cleanup.h>
 #include <linux/component.h>
@@ -48,6 +47,7 @@
 #include <linux/wmi.h>
 
 #include "wmi-capdata.h"
+#include "wmi-helpers.h"
 
 #define LENOVO_CAPABILITY_DATA_00_GUID "362A3AFE-3D96-4665-8530-96DAD5BB300E"
 #define LENOVO_CAPABILITY_DATA_01_GUID "7A8F5407-CB67-4D6E-B547-39B3BE018154"
@@ -58,9 +58,9 @@
 
 #define LWMI_FEATURE_ID_FAN_TEST 0x05
 
-#define LWMI_ATTR_ID_FAN_TEST							\
-	(FIELD_PREP(LWMI_ATTR_DEV_ID_MASK, LWMI_DEVICE_ID_FAN) |		\
-	 FIELD_PREP(LWMI_ATTR_FEAT_ID_MASK, LWMI_FEATURE_ID_FAN_TEST))
+#define LWMI_ATTR_ID_FAN_TEST                                      \
+	lwmi_attr_id(LWMI_DEVICE_ID_FAN, LWMI_FEATURE_ID_FAN_TEST, \
+		     LWMI_GZ_THERMAL_MODE_NONE, LWMI_TYPE_ID_NONE)
 
 enum lwmi_cd_type {
 	LENOVO_CAPABILITY_DATA_00,
drivers/platform/x86/lenovo/wmi-capdata.h +20 -0
index 8c1df3efcc55..c3e760b8c3c3 100644
@@ -6,6 +6,7 @@
 #define _LENOVO_WMI_CAPDATA_H_
 
 #include <linux/bits.h>
+#include <linux/bitfield.h>
 #include <linux/types.h>
 
 #define LWMI_SUPP_VALID		BIT(0)
@@ -19,6 +20,8 @@
 
 #define LWMI_DEVICE_ID_FAN	0x04
 
+#define LWMI_TYPE_ID_NONE 0x00
+
 struct component_match;
 struct device;
 struct cd_list;
@@ -57,6 +60,23 @@ struct lwmi_cd_binder {
 	cd_list_cb_t cd_fan_list_cb;
 };
 
+/**
+ * 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: encoded capability data attribute ID.
+ */
+static inline 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));
+}
+
 void lwmi_cd_match_add_all(struct device *master, struct component_match **matchptr);
 int lwmi_cd00_get_data(struct cd_list *list, u32 attribute_id, struct capdata00 *output);
 int lwmi_cd01_get_data(struct cd_list *list, u32 attribute_id, struct capdata01 *output);
drivers/platform/x86/lenovo/wmi-other.c +20 -29

106 lines not on this page · raw message

Signature · 1 line
2.53.0
author maintainer
#9 lore raw

PATCH v12 9/16 platform/x86: lenovo-wmi-other: Limit adding attributes to supported devices

In reply to Derek J. Clark

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.

---
v12:
- Reword lwmi_is_attr_01_supported kerneldoc text block.
- Fix formatting in lwmi_is_attr_01_supported.
- Use correct type cast when passing args in new funciton.
v11:
- Also use cd_mode_id in attr_capdata_show.
v7:
- Move earlier in the series. This required dropping the use of lwmi_attr_id as it will be added later.
- Add missing switch between cd_mode_id and cv_mode_id in current_value_store.
v6:
- Zero initialize args in lwmi_is_attr_01_supported.
- Fix formatting.
v5:
- Move cv/cd_mode_id refrences from path 3/4.
- Add missing import for ARRAY_SIZE.
- Make lwmi_is_attr_01_supported return bool instead of u32.
- Various formatting fixes.
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 | 92 +++++++++++++++++++++++--
 1 file changed, 88 insertions(+), 4 deletions(-)
drivers/platform/x86/lenovo/wmi-other.c +88 -4
index 4da0da8459c7..e6834dec2381 100644
@@ -542,6 +542,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 */
 };
 
 /**
@@ -623,7 +625,7 @@ static ssize_t attr_capdata01_show(struct kobject *kobj,
 	u32 attribute_id;
 	int value, ret;
 
-	attribute_id = tunable_attr_01_id(tunable_attr, LWMI_GZ_THERMAL_MODE_CUSTOM);
+	attribute_id = tunable_attr_01_id(tunable_attr, tunable_attr->cd_mode_id);
 
 	ret = lwmi_cd01_get_data(priv->cd01_list, attribute_id, &capdata);
 	if (ret)
@@ -688,7 +690,7 @@ static ssize_t attr_current_value_store(struct kobject *kobj,
 	if (mode != LWMI_GZ_THERMAL_MODE_CUSTOM)
 		return -EBUSY;
 
-	args.arg0 = tunable_attr_01_id(tunable_attr, mode);
+	args.arg0 = tunable_attr_01_id(tunable_attr, tunable_attr->cd_mode_id);
 
 	ret = lwmi_cd01_get_data(priv->cd01_list, args.arg0, &capdata);
 	if (ret)
@@ -701,6 +703,7 @@ static ssize_t attr_current_value_store(struct kobject *kobj,
 	if (value < capdata.min_value || value > capdata.max_value)
 		return -EINVAL;
 
+	args.arg0 = tunable_attr_01_id(tunable_attr, tunable_attr->cv_mode_id);
 	args.arg1 = value;
 
 	ret = lwmi_dev_evaluate_int(priv->wdev, 0x0, LWMI_FEATURE_VALUE_SET,
@@ -741,6 +744,10 @@ static ssize_t attr_current_value_show(struct kobject *kobj,
 	if (ret)
 		return ret;
 
+	/* If "no-mode" is the supported mode, ensure we never send current mode */
+	if (tunable_attr->cv_mode_id == LWMI_GZ_THERMAL_MODE_NONE)
+		mode = tunable_attr->cv_mode_id;
+
 	args.arg0 = tunable_attr_01_id(tunable_attr, mode);
 
 	ret = lwmi_dev_evaluate_int(priv->wdev, 0x0, LWMI_FEATURE_VALUE_GET,
@@ -752,6 +759,81 @@ 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.
+ *
+ * For an attribute to be supported it must have a functional get/set method,
+ * as well as associated capability data stored in the capdata01 table.
+ *
+ * First check if the attribute has a corresponding data table under custom mode
+ * (0xff), then under no mode (0x00). If either of those passes, check if the
+ * supported field of the capdata struct is > 0. If it is supported, store the
+ * successful mode in the cd_mode_id field of tunable_attr.
+ *
+ * If the attribute capdata shows it is supported, attempt to determine the mode
+ * for the current value property get/set methods using a similar pattern to the
+ * capdata table check. If the value returned by either mode is 0 or an error,
+ * assume that mode is not supported. Otherwise, store the successful mode in the
+ * cv_mode_id field of tunable_attr.
+ *
+ * If any of the above checks fail then the attribute is not fully supported.
+ *
+ * Return: true if capdata and set/get modes are found, otherwise false.
+ */
+static bool 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 = tunable_attr_01_id(tunable_attr, modes[i]);
+
+		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 cd_mode_found;
+
+	dev_dbg(tunable_attr->dev,
+		"cd_mode_id: %#010x\n", args.arg0);
+
+	/* Determine tunable_attr->cv_mode_id, returns 1 if supported */
+	for (i = 0; i < ARRAY_SIZE(modes); i++) {
+		args.arg0 = tunable_attr_01_id(tunable_attr, modes[i]);
+
+		ret = lwmi_dev_evaluate_int(priv->wdev, 0x0, LWMI_FEATURE_VALUE_GET,
+					    (u8 *)&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 cv_mode_found;
+
+	dev_dbg(tunable_attr->dev, "cv_mode_id: %#010x, attribute support level: %#010x\n",
+		args.arg0, capdata.supported);
+
+	return capdata.supported > 0;
+}
+
 /* Lenovo WMI Other Mode Attribute macros */
 #define __LWMI_ATTR_RO(_func, _name)                                  \
 	{                                                             \
@@ -875,12 +957,14 @@ static void 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))
+			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;
 
Signature · 1 line
2.53.0
author maintainer
#10 lore raw

PATCH v12 10/16 platform/x86: lenovo-wmi-other: Add missing CPU tunable attributes

In reply to Derek J. Clark

Use an enum for all device ID's and CPU attribute feature ID's, add missing CPU attributes.

---
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(-)
Documentation/wmi/devices/lenovo-wmi-other.rst +9 -0
index 01d471156738..189dd4d31926 100644
@@ -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
 -------------------------
drivers/platform/x86/lenovo/wmi-capdata.h +4 -1
index c3e760b8c3c3..c74a0e5294e7 100644
@@ -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
 
drivers/platform/x86/lenovo/wmi-other.c +94 -5

143 lines not on this page · raw message

Signature · 1 line
2.53.0
author maintainer
#11 lore raw

PATCH v12 11/16 platform/x86: lenovo-wmi-other: Add GPU tunable attributes

In reply to Derek J. Clark

Use an enum for all GPU attribute feature ID's and add GPU attributes.

---
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(+)
Documentation/wmi/devices/lenovo-wmi-other.rst +10 -0
index 189dd4d31926..011054d64eac 100644
@@ -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
drivers/platform/x86/lenovo/wmi-capdata.h +1 -0
index c74a0e5294e7..a7cfdeaa58f7 100644
@@ -20,6 +20,7 @@
 
 enum lwmi_device_id {
 	LWMI_DEVICE_ID_CPU = 0x01,
+	LWMI_DEVICE_ID_GPU = 0x02,
 	LWMI_DEVICE_ID_FAN = 0x04,
 };
 
drivers/platform/x86/lenovo/wmi-other.c +105 -0

132 lines not on this page · raw message

Signature · 1 line
2.53.0
author maintainer
#12 lore raw

PATCH v12 12/16 platform/x86: lenovo-wmi-other: Rename LWMI_OM_FW_ATTR_BASE_PATH

In reply to Derek J. Clark

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.

---
 drivers/platform/x86/lenovo/wmi-other.c | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)
drivers/platform/x86/lenovo/wmi-other.c +2 -3
index d31af69433aa..1cd2bb5244d2 100644
@@ -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);
@@ -1136,8 +1136,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;
Signature · 1 line
2.53.0
author maintainer
#13 lore raw

PATCH v12 13/16 platform/x86: lenovo-wmi-other: Add WMI battery charge limiting

In reply to Derek J. Clark

Add charge_behaviour and charge_control_end_threshold 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_control_end_threshold attribute for BATX devices to expose this capability. 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.

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.

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_type 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. A new module parameter, force_load_psy_ext, is provided to bypass this ACPI check, as well as feature supported checks, if desired.

---
v12:
- Move force_load_psy_ext into its own patch after this.
- Use correct type casting for args in new funtions.
- 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   | 388 ++++++++++++++++++++++
 3 files changed, 390 insertions(+)
drivers/platform/x86/lenovo/Kconfig +1 -0
index 09b1b055d2e0..b9a5d18caa1e 100644
@@ -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
drivers/platform/x86/lenovo/wmi-capdata.h +1 -0
index a7cfdeaa58f7..a49229cec245 100644
@@ -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,
 };
 
drivers/platform/x86/lenovo/wmi-other.c +388 -0

440 lines not on this page · raw message

Signature · 1 line
2.53.0
#14 lore raw

Re: [PATCH v12 13/16] platform/x86: lenovo-wmi-other: Add WMI battery charge limiting

Hi Derek,

I am a little busy these days, sorry for not reviewing your series in time.

Quoted 4 lines · Derek J. Clark

Add charge_behaviour and charge_control_end_threshold attributes through a power supply extension for devices that support WMI based charge enable & disable.

Quoted 9 lines

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_control_end_threshold attribute for BATX devices to expose this capability. 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.

You said "the GET method for charge_control_end_threshold is bugged" here, but your code applied the workaround to charge_behaviour. Which is the bugged one?

Quoted 241 lines

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.

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_type 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. A new module parameter, force_load_psy_ext, is provided to bypass this ACPI check, as well as feature supported checks, if desired.

---
v12:
- Move force_load_psy_ext into its own patch after this.
- Use correct type casting for args in new funtions.
- 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   | 388 ++++++++++++++++++++++
 3 files changed, 390 insertions(+)
drivers/platform/x86/lenovo/Kconfig +1 -0
index 09b1b055d2e0..b9a5d18caa1e 100644
@@ -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
drivers/platform/x86/lenovo/wmi-capdata.h +1 -0
index a7cfdeaa58f7..a49229cec245 100644
@@ -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,
 };
 
drivers/platform/x86/lenovo/wmi-other.c +112 -0
index 1cd2bb5244d2..4d95a9109f5e 100644
@@ -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_END_THRESHOLD =	0x01,
+	LWMI_FEATURE_ID_PSU_CHARGE_BEHAVIOR =		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_END_100		0x00
+#define LWMI_CHARGE_END_80		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;
 };
 
 /*
@@ -555,6 +578,368 @@ 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
+ *
+ * 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;
+
+	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_CONTROL_END_THRESHOLD:
+		args.arg0 = LWMI_ATTR_ID_PSU(LWMI_FEATURE_ID_PSU_CHARGE_END_THRESHOLD,
+					     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_END_80:
+		val->intval = 80;
+		break;
+	case LWMI_CHARGE_END_100:
+		val->intval = 100;
+		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_BEHAVIOR,
+					     LWMI_TYPE_ID_NONE);
+		switch (val->intval) {
+		case POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO:
+			args.arg1 = LWMI_CHARGE_BEHAVIOR_AUTO;

drivers/platform/x86/lenovo/wmi-other.c static void lwmi_om_fan_info_collect_cd_fan(struct device *dev, struct cd_list * · line 679

Quoted 10 lines
drivers/platform/x86/lenovo/wmi-other.c +10 -0
+			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;

The cache shouldn't be updated if lwmi_dev_evaluate_int() returns an error, right?

Quoted 93 lines
drivers/platform/x86/lenovo/wmi-other.c +93 -0
+		break;
+	case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD:
+		args.arg0 = LWMI_ATTR_ID_PSU(LWMI_FEATURE_ID_PSU_CHARGE_END_THRESHOLD,
+					     LWMI_TYPE_ID_PSU_AC);
+		switch (val->intval) {
+		case 0 ... 80:
+			args.arg1 = LWMI_CHARGE_END_80;
+			break;
+		case 81 ... 100:
+			args.arg1 = LWMI_CHARGE_END_100;
+			break;
+		default:
+			dev_err(&priv->wdev->dev, "Got invalid charge limit value: %#x\n",
+				val->intval);
+			return -EINVAL;
+		}
+		break;
+	default:
+		return false;
+	}
+
+	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_is_suppported() - 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)
+{
+	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_BEHAVIOR,
+						LWMI_TYPE_ID_NONE);
+		break;
+	case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD:
+		attribute_id = LWMI_ATTR_ID_PSU(LWMI_FEATURE_ID_PSU_CHARGE_END_THRESHOLD,
+						LWMI_TYPE_ID_PSU_AC);
+		break;
+	default:
+		return false;
+	}
+
+	ret = lwmi_cd00_get_data(priv->cd00_list, attribute_id, &capdata);
+	if (ret)
+		return false;
+
+	dev_dbg(&priv->wdev->dev, "Battery charge feature (%#010x) support level: %#x\n",
+		attribute_id, capdata.supported);
+
+	return (capdata.supported & LWMI_SUPP_VALID) && (capdata.supported & 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;
+	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_BEHAVIOR,
+						LWMI_TYPE_ID_NONE);
+		break;
+	case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD:

drivers/platform/x86/lenovo/wmi-other.c static void lwmi_om_fan_info_collect_cd_fan(struct device *dev, struct cd_list * · line 782

Quoted 10 lines
drivers/platform/x86/lenovo/wmi-other.c +10 -0
+		attribute_id = LWMI_ATTR_ID_PSU(LWMI_FEATURE_ID_PSU_CHARGE_END_THRESHOLD,
+						LWMI_TYPE_ID_PSU_AC);
+		break;
+	default:
+		return false;
+	}
+
+	ret = lwmi_cd00_get_data(priv->cd00_list, attribute_id, &capdata);
+	if (ret)
+		return false;

This shares a lot of lines of code with lwmi_psy_prop_is_supported(). I'd factor the common code into a helper function.

I don't have a strong preference though. It's fine to keep it as is.

Quoted 20 lines
drivers/platform/x86/lenovo/wmi-other.c +20 -0
+
+	return !!(capdata.supported & LWMI_SUPP_SET);
+}
+
+static const enum power_supply_property lwmi_psy_ext_props_all[] = {
+	POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR,
+	POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD,
+};
+
+static const enum power_supply_property lwmi_psy_ext_props_threshold[] = {
+	POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD,
+};
+
+static const enum power_supply_property lwmi_psy_ext_props_behaviour[] = {
+	POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR,
+};
+
+#define DEFINE_LWMI_POWER_SUPPLY_EXTENSION(_name, _props, _behaviours)	\
+	static const struct power_supply_ext _name = {			\
+		.name			= LWMI_OM_SYSFS_NAME,		\

drivers/platform/x86/lenovo/wmi-other.c static void lwmi_om_fan_info_collect_cd_fan(struct device *dev, struct cd_list * · line 812

Quoted 10 lines
drivers/platform/x86/lenovo/wmi-other.c +10 -0
+		.properties		= _props,			\
+		.num_properties		= ARRAY_SIZE(_props),		\
+		.charge_behaviours	= _behaviours,			\
+		.get_property		= lwmi_psy_ext_get_prop,	\
+		.set_property		= lwmi_psy_ext_set_prop,	\
+		.property_is_writeable	= lwmi_psy_prop_is_writeable,	\
+	}
+
+#define LWMI_CHARGE_BEHAVIOURS (BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO) | \
+				BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE))

lwmi_psy_ext_get_prop() and lwmi_psy_ext_set_prop() use POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE. Mismatch.

Quoted 77 lines
drivers/platform/x86/lenovo/wmi-other.c +77 -0
+
+DEFINE_LWMI_POWER_SUPPLY_EXTENSION(lwmi_psy_ext_all, lwmi_psy_ext_props_all,
+				   LWMI_CHARGE_BEHAVIOURS);
+DEFINE_LWMI_POWER_SUPPLY_EXTENSION(lwmi_psy_ext_threshold,
+				   lwmi_psy_ext_props_threshold, 0);
+DEFINE_LWMI_POWER_SUPPLY_EXTENSION(lwmi_psy_ext_behaviour,
+				   lwmi_psy_ext_props_behaviour,
+				   LWMI_CHARGE_BEHAVIOURS);
+
+#define LWMI_PSY_PROP_BEHAVIOUR BIT(0)
+#define LWMI_PSY_PROP_THRESHOLD BIT(1)
+
+static const struct power_supply_ext *lwmi_psy_exts[] = {
+	[LWMI_PSY_PROP_BEHAVIOUR] =				&lwmi_psy_ext_behaviour,
+	[LWMI_PSY_PROP_THRESHOLD] =				&lwmi_psy_ext_threshold,
+	[LWMI_PSY_PROP_BEHAVIOUR | LWMI_PSY_PROP_THRESHOLD] =	&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,

drivers/platform/x86/lenovo/wmi-other.c static void lwmi_om_fan_info_collect_cd_fan(struct device *dev, struct cd_list * · line 899

Quoted 10 lines
drivers/platform/x86/lenovo/wmi-other.c +10 -0
+ * 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;
+
+	priv->bh_registered = false;

Why do we need this? priv was devm_kzalloc()ed.

Quoted 14 lines
drivers/platform/x86/lenovo/wmi-other.c +14 -0
+
+	/* 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;
+	}
+
+	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_CONTROL_END_THRESHOLD))

drivers/platform/x86/lenovo/wmi-other.c static void lwmi_om_fan_info_collect_cd_fan(struct device *dev, struct cd_list * · line 923

Quoted 10 lines
drivers/platform/x86/lenovo/wmi-other.c +10 -0
+		props |= LWMI_PSY_PROP_THRESHOLD;
+	if (!props)
+		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;

I guess the default charge behavior is auto. If that's the case, let's initialize priv->charge_behaviour to reflect this, so that it won't confuse users in most cases.

Thanks,
Rong

Quoted 39 lines
drivers/platform/x86/lenovo/wmi-other.c +22 -0
+
+	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 {
@@ -1241,6 +1626,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);
 
@@ -1263,6 +1649,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);
 }
 
maintainer
#15 lore raw

Re: [PATCH v12 13/16] platform/x86: lenovo-wmi-other: Add WMI battery charge limiting

Hi,

Quoted 20 lines · Rong Zhang

Hi Derek,

I am a little busy these days, sorry for not reviewing your series in time.

Quoted 4 lines · Derek J. Clark

Add charge_behaviour and charge_control_end_threshold attributes through a power supply extension for devices that support WMI based charge enable & disable.

Quoted 9 lines

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_control_end_threshold attribute for BATX devices to expose this capability. 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.

Quoted 39 lines

You said "the GET method for charge_control_end_threshold is bugged" here, but your code applied the workaround to charge_behaviour. Which is the bugged one?

Quoted 35 lines

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.

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_type 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. A new module parameter, force_load_psy_ext, is provided to bypass this ACPI check, as well as feature supported checks, if desired.

---
v12:
- Move force_load_psy_ext into its own patch after this.
- Use correct type casting for args in new funtions.
- 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.

I'm only seeing this now, but if there only is a "long life" (aka limit charging to a fixed max percentage e.g . 80%) then the correct userspace API to use is charge_types, sorry for the confusion about this.

charge_control_end_threshold is only for devices on which the threshold is actually configurable.

Fixed threshold devices should use charge_types, see:

https://lore.kernel.org/linux-pm/[email protected]/
https://lore.kernel.org/platform-driver-x86/[email protected]/

and also here:

https://vdwaa.nl/charge-types-api-upower.html

Regards,

Hans

Quoted 524 lines
Quoted 206 lines

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   | 388 ++++++++++++++++++++++
 3 files changed, 390 insertions(+)
drivers/platform/x86/lenovo/Kconfig +1 -0
index 09b1b055d2e0..b9a5d18caa1e 100644
@@ -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
drivers/platform/x86/lenovo/wmi-capdata.h +1 -0
index a7cfdeaa58f7..a49229cec245 100644
@@ -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,
 };
 
drivers/platform/x86/lenovo/wmi-other.c +112 -0
index 1cd2bb5244d2..4d95a9109f5e 100644
@@ -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_END_THRESHOLD =	0x01,
+	LWMI_FEATURE_ID_PSU_CHARGE_BEHAVIOR =		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_END_100		0x00
+#define LWMI_CHARGE_END_80		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;
 };
 
 /*
@@ -555,6 +578,368 @@ 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
+ *
+ * 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;
+
+	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_CONTROL_END_THRESHOLD:
+		args.arg0 = LWMI_ATTR_ID_PSU(LWMI_FEATURE_ID_PSU_CHARGE_END_THRESHOLD,
+					     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_END_80:
+		val->intval = 80;
+		break;
+	case LWMI_CHARGE_END_100:
+		val->intval = 100;
+		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_BEHAVIOR,
+					     LWMI_TYPE_ID_NONE);
+		switch (val->intval) {
+		case POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO:
+			args.arg1 = LWMI_CHARGE_BEHAVIOR_AUTO;

drivers/platform/x86/lenovo/wmi-other.c static void lwmi_om_fan_info_collect_cd_fan(struct device *dev, struct cd_list * · line 679

Quoted 10 lines
drivers/platform/x86/lenovo/wmi-other.c +10 -0
+			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;

The cache shouldn't be updated if lwmi_dev_evaluate_int() returns an error, right?

Quoted 93 lines
drivers/platform/x86/lenovo/wmi-other.c +93 -0
+		break;
+	case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD:
+		args.arg0 = LWMI_ATTR_ID_PSU(LWMI_FEATURE_ID_PSU_CHARGE_END_THRESHOLD,
+					     LWMI_TYPE_ID_PSU_AC);
+		switch (val->intval) {
+		case 0 ... 80:
+			args.arg1 = LWMI_CHARGE_END_80;
+			break;
+		case 81 ... 100:
+			args.arg1 = LWMI_CHARGE_END_100;
+			break;
+		default:
+			dev_err(&priv->wdev->dev, "Got invalid charge limit value: %#x\n",
+				val->intval);
+			return -EINVAL;
+		}
+		break;
+	default:
+		return false;
+	}
+
+	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_is_suppported() - 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)
+{
+	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_BEHAVIOR,
+						LWMI_TYPE_ID_NONE);
+		break;
+	case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD:
+		attribute_id = LWMI_ATTR_ID_PSU(LWMI_FEATURE_ID_PSU_CHARGE_END_THRESHOLD,
+						LWMI_TYPE_ID_PSU_AC);
+		break;
+	default:
+		return false;
+	}
+
+	ret = lwmi_cd00_get_data(priv->cd00_list, attribute_id, &capdata);
+	if (ret)
+		return false;
+
+	dev_dbg(&priv->wdev->dev, "Battery charge feature (%#010x) support level: %#x\n",
+		attribute_id, capdata.supported);
+
+	return (capdata.supported & LWMI_SUPP_VALID) && (capdata.supported & 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;
+	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_BEHAVIOR,
+						LWMI_TYPE_ID_NONE);
+		break;
+	case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD:

drivers/platform/x86/lenovo/wmi-other.c static void lwmi_om_fan_info_collect_cd_fan(struct device *dev, struct cd_list * · line 782

Quoted 10 lines
drivers/platform/x86/lenovo/wmi-other.c +10 -0
+		attribute_id = LWMI_ATTR_ID_PSU(LWMI_FEATURE_ID_PSU_CHARGE_END_THRESHOLD,
+						LWMI_TYPE_ID_PSU_AC);
+		break;
+	default:
+		return false;
+	}
+
+	ret = lwmi_cd00_get_data(priv->cd00_list, attribute_id, &capdata);
+	if (ret)
+		return false;

This shares a lot of lines of code with lwmi_psy_prop_is_supported(). I'd factor the common code into a helper function.

I don't have a strong preference though. It's fine to keep it as is.

Quoted 20 lines
drivers/platform/x86/lenovo/wmi-other.c +20 -0
+
+	return !!(capdata.supported & LWMI_SUPP_SET);
+}
+
+static const enum power_supply_property lwmi_psy_ext_props_all[] = {
+	POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR,
+	POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD,
+};
+
+static const enum power_supply_property lwmi_psy_ext_props_threshold[] = {
+	POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD,
+};
+
+static const enum power_supply_property lwmi_psy_ext_props_behaviour[] = {
+	POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR,
+};
+
+#define DEFINE_LWMI_POWER_SUPPLY_EXTENSION(_name, _props, _behaviours)	\
+	static const struct power_supply_ext _name = {			\
+		.name			= LWMI_OM_SYSFS_NAME,		\

drivers/platform/x86/lenovo/wmi-other.c static void lwmi_om_fan_info_collect_cd_fan(struct device *dev, struct cd_list * · line 812

Quoted 10 lines
drivers/platform/x86/lenovo/wmi-other.c +10 -0
+		.properties		= _props,			\
+		.num_properties		= ARRAY_SIZE(_props),		\
+		.charge_behaviours	= _behaviours,			\
+		.get_property		= lwmi_psy_ext_get_prop,	\
+		.set_property		= lwmi_psy_ext_set_prop,	\
+		.property_is_writeable	= lwmi_psy_prop_is_writeable,	\
+	}
+
+#define LWMI_CHARGE_BEHAVIOURS (BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO) | \
+				BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE))

lwmi_psy_ext_get_prop() and lwmi_psy_ext_set_prop() use POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE. Mismatch.

Quoted 77 lines
drivers/platform/x86/lenovo/wmi-other.c +77 -0
+
+DEFINE_LWMI_POWER_SUPPLY_EXTENSION(lwmi_psy_ext_all, lwmi_psy_ext_props_all,
+				   LWMI_CHARGE_BEHAVIOURS);
+DEFINE_LWMI_POWER_SUPPLY_EXTENSION(lwmi_psy_ext_threshold,
+				   lwmi_psy_ext_props_threshold, 0);
+DEFINE_LWMI_POWER_SUPPLY_EXTENSION(lwmi_psy_ext_behaviour,
+				   lwmi_psy_ext_props_behaviour,
+				   LWMI_CHARGE_BEHAVIOURS);
+
+#define LWMI_PSY_PROP_BEHAVIOUR BIT(0)
+#define LWMI_PSY_PROP_THRESHOLD BIT(1)
+
+static const struct power_supply_ext *lwmi_psy_exts[] = {
+	[LWMI_PSY_PROP_BEHAVIOUR] =				&lwmi_psy_ext_behaviour,
+	[LWMI_PSY_PROP_THRESHOLD] =				&lwmi_psy_ext_threshold,
+	[LWMI_PSY_PROP_BEHAVIOUR | LWMI_PSY_PROP_THRESHOLD] =	&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,

drivers/platform/x86/lenovo/wmi-other.c static void lwmi_om_fan_info_collect_cd_fan(struct device *dev, struct cd_list * · line 899

Quoted 10 lines
drivers/platform/x86/lenovo/wmi-other.c +10 -0
+ * 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;
+
+	priv->bh_registered = false;

Why do we need this? priv was devm_kzalloc()ed.

Quoted 14 lines
drivers/platform/x86/lenovo/wmi-other.c +14 -0
+
+	/* 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;
+	}
+
+	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_CONTROL_END_THRESHOLD))

drivers/platform/x86/lenovo/wmi-other.c static void lwmi_om_fan_info_collect_cd_fan(struct device *dev, struct cd_list * · line 923

Quoted 10 lines
drivers/platform/x86/lenovo/wmi-other.c +10 -0
+		props |= LWMI_PSY_PROP_THRESHOLD;
+	if (!props)
+		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;

I guess the default charge behavior is auto. If that's the case, let's initialize priv->charge_behaviour to reflect this, so that it won't confuse users in most cases.

Thanks,
Rong

Quoted 39 lines
drivers/platform/x86/lenovo/wmi-other.c +22 -0
+
+	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 {
@@ -1241,6 +1626,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);
 
@@ -1263,6 +1649,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);
 }
 
author maintainer
#16 lore raw

Re: [PATCH v12 13/16] platform/x86: lenovo-wmi-other: Add WMI battery charge limiting

In reply to Rong Zhang
Quoted 21 lines · Rong Zhang

Hi Derek,

I am a little busy these days, sorry for not reviewing your series in time.

Quoted 4 lines · Derek J. Clark

Add charge_behaviour and charge_control_end_threshold attributes through a power supply extension for devices that support WMI based charge enable & disable.

Quoted 9 lines

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_control_end_threshold attribute for BATX devices to expose this capability. 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.

Quoted 4 lines

You said "the GET method for charge_control_end_threshold is bugged" here, but your code applied the workaround to charge_behaviour. Which is the bugged one?

Hi Rong,

You're right, the commit message should read differently. The WMI method tied to harge_behaviour always returns 0, which is why we need to track it. I just put the bug explanation on the wrong section.

Quoted 362 lines
Quoted 241 lines

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.

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_type 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. A new module parameter, force_load_psy_ext, is provided to bypass this ACPI check, as well as feature supported checks, if desired.

---
v12:
- Move force_load_psy_ext into its own patch after this.
- Use correct type casting for args in new funtions.
- 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   | 388 ++++++++++++++++++++++
 3 files changed, 390 insertions(+)
drivers/platform/x86/lenovo/Kconfig +1 -0
index 09b1b055d2e0..b9a5d18caa1e 100644
@@ -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
drivers/platform/x86/lenovo/wmi-capdata.h +1 -0
index a7cfdeaa58f7..a49229cec245 100644
@@ -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,
 };
 
drivers/platform/x86/lenovo/wmi-other.c +112 -0
index 1cd2bb5244d2..4d95a9109f5e 100644
@@ -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_END_THRESHOLD =      0x01,
+     LWMI_FEATURE_ID_PSU_CHARGE_BEHAVIOR =           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_END_100          0x00
+#define LWMI_CHARGE_END_80           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;
 };
 
 /*
@@ -555,6 +578,368 @@ 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
+ *
+ * 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;
+
+     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_CONTROL_END_THRESHOLD:
+             args.arg0 = LWMI_ATTR_ID_PSU(LWMI_FEATURE_ID_PSU_CHARGE_END_THRESHOLD,
+                                          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_END_80:
+             val->intval = 80;
+             break;
+     case LWMI_CHARGE_END_100:
+             val->intval = 100;
+             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_BEHAVIOR,
+                                          LWMI_TYPE_ID_NONE);
+             switch (val->intval) {
+             case POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO:
+                     args.arg1 = LWMI_CHARGE_BEHAVIOR_AUTO;

drivers/platform/x86/lenovo/wmi-other.c static void lwmi_om_fan_info_collect_cd_fan(struct device *dev, struct cd_list * · line 679

Quoted 10 lines
drivers/platform/x86/lenovo/wmi-other.c +10 -0
+                     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;

The cache shouldn't be updated if lwmi_dev_evaluate_int() returns an error, right?

Quoted 93 lines
drivers/platform/x86/lenovo/wmi-other.c +93 -0
+             break;
+     case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD:
+             args.arg0 = LWMI_ATTR_ID_PSU(LWMI_FEATURE_ID_PSU_CHARGE_END_THRESHOLD,
+                                          LWMI_TYPE_ID_PSU_AC);
+             switch (val->intval) {
+             case 0 ... 80:
+                     args.arg1 = LWMI_CHARGE_END_80;
+                     break;
+             case 81 ... 100:
+                     args.arg1 = LWMI_CHARGE_END_100;
+                     break;
+             default:
+                     dev_err(&priv->wdev->dev, "Got invalid charge limit value: %#x\n",
+                             val->intval);
+                     return -EINVAL;
+             }
+             break;
+     default:
+             return false;
+     }
+
+     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_is_suppported() - 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)
+{
+     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_BEHAVIOR,
+                                             LWMI_TYPE_ID_NONE);
+             break;
+     case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD:
+             attribute_id = LWMI_ATTR_ID_PSU(LWMI_FEATURE_ID_PSU_CHARGE_END_THRESHOLD,
+                                             LWMI_TYPE_ID_PSU_AC);
+             break;
+     default:
+             return false;
+     }
+
+     ret = lwmi_cd00_get_data(priv->cd00_list, attribute_id, &capdata);
+     if (ret)
+             return false;
+
+     dev_dbg(&priv->wdev->dev, "Battery charge feature (%#010x) support level: %#x\n",
+             attribute_id, capdata.supported);
+
+     return (capdata.supported & LWMI_SUPP_VALID) && (capdata.supported & 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;
+     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_BEHAVIOR,
+                                             LWMI_TYPE_ID_NONE);
+             break;
+     case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD:

drivers/platform/x86/lenovo/wmi-other.c static void lwmi_om_fan_info_collect_cd_fan(struct device *dev, struct cd_list * · line 782

Quoted 10 lines
drivers/platform/x86/lenovo/wmi-other.c +10 -0
+             attribute_id = LWMI_ATTR_ID_PSU(LWMI_FEATURE_ID_PSU_CHARGE_END_THRESHOLD,
+                                             LWMI_TYPE_ID_PSU_AC);
+             break;
+     default:
+             return false;
+     }
+
+     ret = lwmi_cd00_get_data(priv->cd00_list, attribute_id, &capdata);
+     if (ret)
+             return false;

This shares a lot of lines of code with lwmi_psy_prop_is_supported(). I'd factor the common code into a helper function.

Quoted 1 line

I don't have a strong preference though. It's fine to keep it as is.

This is simple enough. Since I'm changin the file anyway I'll add it.

Quoted 116 lines
Quoted 20 lines
drivers/platform/x86/lenovo/wmi-other.c +20 -0
+
+     return !!(capdata.supported & LWMI_SUPP_SET);
+}
+
+static const enum power_supply_property lwmi_psy_ext_props_all[] = {
+     POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR,
+     POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD,
+};
+
+static const enum power_supply_property lwmi_psy_ext_props_threshold[] = {
+     POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD,
+};
+
+static const enum power_supply_property lwmi_psy_ext_props_behaviour[] = {
+     POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR,
+};
+
+#define DEFINE_LWMI_POWER_SUPPLY_EXTENSION(_name, _props, _behaviours)       \
+     static const struct power_supply_ext _name = {                  \
+             .name                   = LWMI_OM_SYSFS_NAME,           \

drivers/platform/x86/lenovo/wmi-other.c static void lwmi_om_fan_info_collect_cd_fan(struct device *dev, struct cd_list * · line 812

Quoted 10 lines
drivers/platform/x86/lenovo/wmi-other.c +10 -0
+             .properties             = _props,                       \
+             .num_properties         = ARRAY_SIZE(_props),           \
+             .charge_behaviours      = _behaviours,                  \
+             .get_property           = lwmi_psy_ext_get_prop,        \
+             .set_property           = lwmi_psy_ext_set_prop,        \
+             .property_is_writeable  = lwmi_psy_prop_is_writeable,   \
+     }
+
+#define LWMI_CHARGE_BEHAVIOURS (BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO) | \
+                             BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE))

lwmi_psy_ext_get_prop() and lwmi_psy_ext_set_prop() use POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE. Mismatch.

Quoted 81 lines
drivers/platform/x86/lenovo/wmi-other.c +81 -0
+
+DEFINE_LWMI_POWER_SUPPLY_EXTENSION(lwmi_psy_ext_all, lwmi_psy_ext_props_all,
+                                LWMI_CHARGE_BEHAVIOURS);
+DEFINE_LWMI_POWER_SUPPLY_EXTENSION(lwmi_psy_ext_threshold,
+                                lwmi_psy_ext_props_threshold, 0);
+DEFINE_LWMI_POWER_SUPPLY_EXTENSION(lwmi_psy_ext_behaviour,
+                                lwmi_psy_ext_props_behaviour,
+                                LWMI_CHARGE_BEHAVIOURS);
+
+#define LWMI_PSY_PROP_BEHAVIOUR BIT(0)
+#define LWMI_PSY_PROP_THRESHOLD BIT(1)
+
+static const struct power_supply_ext *lwmi_psy_exts[] = {
+     [LWMI_PSY_PROP_BEHAVIOUR] =                             &lwmi_psy_ext_behaviour,
+     [LWMI_PSY_PROP_THRESHOLD] =                             &lwmi_psy_ext_threshold,
+     [LWMI_PSY_PROP_BEHAVIOUR | LWMI_PSY_PROP_THRESHOLD] =   &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)
+{
Quoted 9 lines

drivers/platform/x86/lenovo/wmi-other.c static void lwmi_om_fan_info_collect_cd_fan(struct device *dev, struct cd_list * · line 899

Quoted 6 lines
drivers/platform/x86/lenovo/wmi-other.c +6 -0
+     static const char * const ideapad_hid = "VPC2004";
+     acpi_handle handle = NULL;
+     unsigned int props = 0;
+     int ret;
+
+     priv->bh_registered = false;

Why do we need this? priv was devm_kzalloc()ed.

We need to unregister the battery hook manually so the power_supply list is updated. I can't check against battery_hook or battery_ext as they will be valid always, and AFAIK we only want to run the unregister if it was registered.

Thanks,
Derek

Quoted 71 lines
Quoted 14 lines
drivers/platform/x86/lenovo/wmi-other.c +14 -0
+
+     /* 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;
+     }
+
+     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_CONTROL_END_THRESHOLD))

drivers/platform/x86/lenovo/wmi-other.c static void lwmi_om_fan_info_collect_cd_fan(struct device *dev, struct cd_list * · line 923

Quoted 10 lines
drivers/platform/x86/lenovo/wmi-other.c +10 -0
+             props |= LWMI_PSY_PROP_THRESHOLD;
+     if (!props)
+             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;

I guess the default charge behavior is auto. If that's the case, let's initialize priv->charge_behaviour to reflect this, so that it won't confuse users in most cases.

Thanks,
Rong

Quoted 39 lines
drivers/platform/x86/lenovo/wmi-other.c +22 -0
+
+     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 {
@@ -1241,6 +1626,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);
 
@@ -1263,6 +1649,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);
 }
 
#17 lore raw

Re: [PATCH v12 13/16] platform/x86: lenovo-wmi-other: Add WMI battery charge limiting

Hi Derek,

Quoted 525 lines · Derek John Clark
Quoted 21 lines · Rong Zhang

Hi Derek,

I am a little busy these days, sorry for not reviewing your series in time.

Quoted 4 lines · Derek J. Clark

Add charge_behaviour and charge_control_end_threshold attributes through a power supply extension for devices that support WMI based charge enable & disable.

Quoted 9 lines

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_control_end_threshold attribute for BATX devices to expose this capability. 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.

Quoted 4 lines

You said "the GET method for charge_control_end_threshold is bugged" here, but your code applied the workaround to charge_behaviour. Which is the bugged one?

Hi Rong,

You're right, the commit message should read differently. The WMI method tied to harge_behaviour always returns 0, which is why we need to track it. I just put the bug explanation on the wrong section.

Quoted 362 lines
Quoted 241 lines

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.

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_type 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. A new module parameter, force_load_psy_ext, is provided to bypass this ACPI check, as well as feature supported checks, if desired.

---
v12:
- Move force_load_psy_ext into its own patch after this.
- Use correct type casting for args in new funtions.
- 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   | 388 ++++++++++++++++++++++
 3 files changed, 390 insertions(+)
drivers/platform/x86/lenovo/Kconfig +1 -0
index 09b1b055d2e0..b9a5d18caa1e 100644
@@ -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
drivers/platform/x86/lenovo/wmi-capdata.h +1 -0
index a7cfdeaa58f7..a49229cec245 100644
@@ -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,
 };
 
drivers/platform/x86/lenovo/wmi-other.c +112 -0
index 1cd2bb5244d2..4d95a9109f5e 100644
@@ -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_END_THRESHOLD =      0x01,
+     LWMI_FEATURE_ID_PSU_CHARGE_BEHAVIOR =           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_END_100          0x00
+#define LWMI_CHARGE_END_80           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;
 };
 
 /*
@@ -555,6 +578,368 @@ 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
+ *
+ * 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;
+
+     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_CONTROL_END_THRESHOLD:
+             args.arg0 = LWMI_ATTR_ID_PSU(LWMI_FEATURE_ID_PSU_CHARGE_END_THRESHOLD,
+                                          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_END_80:
+             val->intval = 80;
+             break;
+     case LWMI_CHARGE_END_100:
+             val->intval = 100;
+             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_BEHAVIOR,
+                                          LWMI_TYPE_ID_NONE);
+             switch (val->intval) {
+             case POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO:
+                     args.arg1 = LWMI_CHARGE_BEHAVIOR_AUTO;

drivers/platform/x86/lenovo/wmi-other.c static void lwmi_om_fan_info_collect_cd_fan(struct device *dev, struct cd_list * · line 679

Quoted 10 lines
drivers/platform/x86/lenovo/wmi-other.c +10 -0
+                     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;

The cache shouldn't be updated if lwmi_dev_evaluate_int() returns an error, right?

Quoted 93 lines
drivers/platform/x86/lenovo/wmi-other.c +93 -0
+             break;
+     case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD:
+             args.arg0 = LWMI_ATTR_ID_PSU(LWMI_FEATURE_ID_PSU_CHARGE_END_THRESHOLD,
+                                          LWMI_TYPE_ID_PSU_AC);
+             switch (val->intval) {
+             case 0 ... 80:
+                     args.arg1 = LWMI_CHARGE_END_80;
+                     break;
+             case 81 ... 100:
+                     args.arg1 = LWMI_CHARGE_END_100;
+                     break;
+             default:
+                     dev_err(&priv->wdev->dev, "Got invalid charge limit value: %#x\n",
+                             val->intval);
+                     return -EINVAL;
+             }
+             break;
+     default:
+             return false;
+     }
+
+     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_is_suppported() - 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)
+{
+     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_BEHAVIOR,
+                                             LWMI_TYPE_ID_NONE);
+             break;
+     case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD:
+             attribute_id = LWMI_ATTR_ID_PSU(LWMI_FEATURE_ID_PSU_CHARGE_END_THRESHOLD,
+                                             LWMI_TYPE_ID_PSU_AC);
+             break;
+     default:
+             return false;
+     }
+
+     ret = lwmi_cd00_get_data(priv->cd00_list, attribute_id, &capdata);
+     if (ret)
+             return false;
+
+     dev_dbg(&priv->wdev->dev, "Battery charge feature (%#010x) support level: %#x\n",
+             attribute_id, capdata.supported);
+
+     return (capdata.supported & LWMI_SUPP_VALID) && (capdata.supported & 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;
+     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_BEHAVIOR,
+                                             LWMI_TYPE_ID_NONE);
+             break;
+     case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD:

drivers/platform/x86/lenovo/wmi-other.c static void lwmi_om_fan_info_collect_cd_fan(struct device *dev, struct cd_list * · line 782

Quoted 10 lines
drivers/platform/x86/lenovo/wmi-other.c +10 -0
+             attribute_id = LWMI_ATTR_ID_PSU(LWMI_FEATURE_ID_PSU_CHARGE_END_THRESHOLD,
+                                             LWMI_TYPE_ID_PSU_AC);
+             break;
+     default:
+             return false;
+     }
+
+     ret = lwmi_cd00_get_data(priv->cd00_list, attribute_id, &capdata);
+     if (ret)
+             return false;

This shares a lot of lines of code with lwmi_psy_prop_is_supported(). I'd factor the common code into a helper function.

Quoted 1 line

I don't have a strong preference though. It's fine to keep it as is.

This is simple enough. Since I'm changin the file anyway I'll add it.

Quoted 116 lines
Quoted 20 lines
drivers/platform/x86/lenovo/wmi-other.c +20 -0
+
+     return !!(capdata.supported & LWMI_SUPP_SET);
+}
+
+static const enum power_supply_property lwmi_psy_ext_props_all[] = {
+     POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR,
+     POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD,
+};
+
+static const enum power_supply_property lwmi_psy_ext_props_threshold[] = {
+     POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD,
+};
+
+static const enum power_supply_property lwmi_psy_ext_props_behaviour[] = {
+     POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR,
+};
+
+#define DEFINE_LWMI_POWER_SUPPLY_EXTENSION(_name, _props, _behaviours)       \
+     static const struct power_supply_ext _name = {                  \
+             .name                   = LWMI_OM_SYSFS_NAME,           \

drivers/platform/x86/lenovo/wmi-other.c static void lwmi_om_fan_info_collect_cd_fan(struct device *dev, struct cd_list * · line 812

Quoted 10 lines
drivers/platform/x86/lenovo/wmi-other.c +10 -0
+             .properties             = _props,                       \
+             .num_properties         = ARRAY_SIZE(_props),           \
+             .charge_behaviours      = _behaviours,                  \
+             .get_property           = lwmi_psy_ext_get_prop,        \
+             .set_property           = lwmi_psy_ext_set_prop,        \
+             .property_is_writeable  = lwmi_psy_prop_is_writeable,   \
+     }
+
+#define LWMI_CHARGE_BEHAVIOURS (BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO) | \
+                             BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE))

lwmi_psy_ext_get_prop() and lwmi_psy_ext_set_prop() use POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE. Mismatch.

Quoted 81 lines
drivers/platform/x86/lenovo/wmi-other.c +81 -0
+
+DEFINE_LWMI_POWER_SUPPLY_EXTENSION(lwmi_psy_ext_all, lwmi_psy_ext_props_all,
+                                LWMI_CHARGE_BEHAVIOURS);
+DEFINE_LWMI_POWER_SUPPLY_EXTENSION(lwmi_psy_ext_threshold,
+                                lwmi_psy_ext_props_threshold, 0);
+DEFINE_LWMI_POWER_SUPPLY_EXTENSION(lwmi_psy_ext_behaviour,
+                                lwmi_psy_ext_props_behaviour,
+                                LWMI_CHARGE_BEHAVIOURS);
+
+#define LWMI_PSY_PROP_BEHAVIOUR BIT(0)
+#define LWMI_PSY_PROP_THRESHOLD BIT(1)
+
+static const struct power_supply_ext *lwmi_psy_exts[] = {
+     [LWMI_PSY_PROP_BEHAVIOUR] =                             &lwmi_psy_ext_behaviour,
+     [LWMI_PSY_PROP_THRESHOLD] =                             &lwmi_psy_ext_threshold,
+     [LWMI_PSY_PROP_BEHAVIOUR | LWMI_PSY_PROP_THRESHOLD] =   &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)
+{
Quoted 9 lines

drivers/platform/x86/lenovo/wmi-other.c static void lwmi_om_fan_info_collect_cd_fan(struct device *dev, struct cd_list * · line 899

Quoted 6 lines
drivers/platform/x86/lenovo/wmi-other.c +6 -0
+     static const char * const ideapad_hid = "VPC2004";
+     acpi_handle handle = NULL;
+     unsigned int props = 0;
+     int ret;
+
+     priv->bh_registered = false;

Why do we need this? priv was devm_kzalloc()ed.

Quoted 4 lines

We need to unregister the battery hook manually so the power_supply list is updated. I can't check against battery_hook or battery_ext as they will be valid always, and AFAIK we only want to run the unregister if it was registered.

While I understood why priv->bh_registered exists, I meant we don't need to initialize it to false explicitly, as priv was devm_kzalloc()ed and hence zero-initialized. Other code paths are well paired so in any case when it reaches here priv->bh_registered must be false already. Just like we don't need to explicitly initialize priv->hwmon_dev to NULL before collecting capdata. Tl;dr, this line is a no-op.

Thanks,
Rong

Quoted 75 lines

Thanks,
Derek

Quoted 71 lines
Quoted 14 lines
drivers/platform/x86/lenovo/wmi-other.c +14 -0
+
+     /* 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;
+     }
+
+     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_CONTROL_END_THRESHOLD))

drivers/platform/x86/lenovo/wmi-other.c static void lwmi_om_fan_info_collect_cd_fan(struct device *dev, struct cd_list * · line 923

Quoted 10 lines
drivers/platform/x86/lenovo/wmi-other.c +10 -0
+             props |= LWMI_PSY_PROP_THRESHOLD;
+     if (!props)
+             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;

I guess the default charge behavior is auto. If that's the case, let's initialize priv->charge_behaviour to reflect this, so that it won't confuse users in most cases.

Thanks,
Rong

Quoted 39 lines
drivers/platform/x86/lenovo/wmi-other.c +22 -0
+
+     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 {
@@ -1241,6 +1626,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);
 
@@ -1263,6 +1649,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);
 }
 
author maintainer
#18 lore raw

PATCH v12 14/16 platform/x86: lenovo-wmi-other: Add force_load_psy_ext module parameter

In reply to Derek J. Clark

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.

---
v12:
- Move the force_load_psy_ext into its own distinct patch.

---
 drivers/platform/x86/lenovo/wmi-other.c | 16 ++++++++++++++++
 1 file changed, 16 insertions(+)
drivers/platform/x86/lenovo/wmi-other.c +16 -0
index 4d95a9109f5e..007fef658bfe 100644
@@ -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) ======== */
 
 /**
@@ -898,6 +908,11 @@ static void lwmi_om_psy_ext_init(struct lwmi_om_priv *priv)
 
 	priv->bh_registered = false;
 
+	if (force_load_psy_ext) {
+		props = LWMI_PSY_PROP_BEHAVIOUR | LWMI_PSY_PROP_THRESHOLD;
+		goto load_psy_ext;
+	}
+
 	/* Deconflict ideapad_laptop driver */
 	ret = acpi_get_devices(ideapad_hid, lwmi_acpi_match, &handle, NULL);
 	if (ret)
@@ -915,6 +930,7 @@ static void lwmi_om_psy_ext_init(struct lwmi_om_priv *priv)
 	if (!props)
 		return;
 
+load_psy_ext:
 	/* Add battery hooks */
 	priv->battery_ext = lwmi_psy_exts[props];
 	priv->battery_hook.add_battery = lwmi_add_battery;
Signature · 1 line
2.53.0
#19 lore raw

Re: [PATCH v12 14/16] platform/x86: lenovo-wmi-other: Add force_load_psy_ext module parameter

Hi Derek,

Quoted 52 lines · Derek J. Clark

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.

---
v12:
- Move the force_load_psy_ext into its own distinct patch.

---
 drivers/platform/x86/lenovo/wmi-other.c | 16 ++++++++++++++++
 1 file changed, 16 insertions(+)
drivers/platform/x86/lenovo/wmi-other.c +15 -0
index 4d95a9109f5e..007fef658bfe 100644
@@ -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) ======== */
 
 /**
@@ -898,6 +908,11 @@ static void lwmi_om_psy_ext_init(struct lwmi_om_priv *priv)
 
 	priv->bh_registered = false;
 
+	if (force_load_psy_ext) {
+		props = LWMI_PSY_PROP_BEHAVIOUR | LWMI_PSY_PROP_THRESHOLD;
+		goto load_psy_ext;
+	}
+
 	/* Deconflict ideapad_laptop driver */
 	ret = acpi_get_devices(ideapad_hid, lwmi_acpi_match, &handle, NULL);
 	if (ret)

drivers/platform/x86/lenovo/wmi-other.c static void lwmi_om_psy_ext_init(struct lwmi_om_priv *priv) · line 936

Quoted 7 lines
drivers/platform/x86/lenovo/wmi-other.c +1 -0
@@ -915,6 +930,7 @@ static void lwmi_om_psy_ext_init(struct lwmi_om_priv *priv)
 	if (!props)
 		return;
 
+load_psy_ext:
 	/* Add battery hooks */
 	priv->battery_ext = lwmi_psy_exts[props];
 	priv->battery_hook.add_battery = lwmi_add_battery;

You said "it should probably also cause is_writable to return true always when set" in your reply to Ilpo's opinion on v11.

Hmm... I agree with that, but the code here didn't seem to do so. Am I missing something?

Thanks,
Rong

author maintainer
#20 lore raw

Re: [PATCH v12 14/16] platform/x86: lenovo-wmi-other: Add force_load_psy_ext module parameter

Quoted 67 lines · Rong Zhang

Hi Derek,

Quoted 52 lines · Derek J. Clark

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.

---
v12:
- Move the force_load_psy_ext into its own distinct patch.

---
 drivers/platform/x86/lenovo/wmi-other.c | 16 ++++++++++++++++
 1 file changed, 16 insertions(+)
drivers/platform/x86/lenovo/wmi-other.c +15 -0
index 4d95a9109f5e..007fef658bfe 100644
@@ -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) ======== */
 
 /**
@@ -898,6 +908,11 @@ static void lwmi_om_psy_ext_init(struct lwmi_om_priv *priv)
 
      priv->bh_registered = false;
 
+     if (force_load_psy_ext) {
+             props = LWMI_PSY_PROP_BEHAVIOUR | LWMI_PSY_PROP_THRESHOLD;
+             goto load_psy_ext;
+     }
+
      /* Deconflict ideapad_laptop driver */
      ret = acpi_get_devices(ideapad_hid, lwmi_acpi_match, &handle, NULL);
      if (ret)

drivers/platform/x86/lenovo/wmi-other.c static void lwmi_om_psy_ext_init(struct lwmi_om_priv *priv) · line 936

Quoted 7 lines
drivers/platform/x86/lenovo/wmi-other.c +1 -0
@@ -915,6 +930,7 @@ static void lwmi_om_psy_ext_init(struct lwmi_om_priv *priv)
      if (!props)
              return;
 
+load_psy_ext:
      /* Add battery hooks */
      priv->battery_ext = lwmi_psy_exts[props];
      priv->battery_hook.add_battery = lwmi_add_battery;

You said "it should probably also cause is_writable to return true always when set" in your reply to Ilpo's opinion on v11.

Quoted 3 lines

Hmm... I agree with that, but the code here didn't seem to do so. Am I missing something?

Good catch. I indeed forgot to go back and fix that. I have this change staged in my working tree now. I'm just waiting on Ilpo's response now on how best to deal with the API confusion Hans brought up. If we have a v13 its an easy change.

Thanks,
Derek

Quoted 2 lines

Thanks,
Rong

author maintainer
#21 lore raw

PATCH v12 15/16 platform/x86: lenovo-wmi-helpers: Add helper for creating per-device debugfs dir

In reply to Derek J. Clark

From: Rong Zhang <[email protected]>

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.

---
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(+)
drivers/platform/x86/lenovo/wmi-helpers.c +34 -0
index 7a198259e393..82dfd26c9c2b 100644
@@ -17,6 +17,8 @@
  */
 
 #include <linux/acpi.h>
+#include <linux/debugfs.h>
+#include <linux/device.h>
 #include <linux/cleanup.h>
 #include <linux/errno.h>
 #include <linux/export.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 <[email protected]>");
 MODULE_DESCRIPTION("Lenovo WMI Helpers Driver");
 MODULE_LICENSE("GPL");
drivers/platform/x86/lenovo/wmi-helpers.h +2 -0
index ed7db3ebba6c..039fe61003ce 100644
@@ -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,
 };
Signature · 1 line
2.53.0
author maintainer
#22 lore raw

PATCH v12 16/16 platform/x86: lenovo-wmi-capdata: Add debugfs file for dumping capdata

In reply to Derek J. Clark

From: Rong Zhang <[email protected]>

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.

---
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(-)
drivers/platform/x86/lenovo/Kconfig +1 -0
index b9a5d18caa1e..4443f40ef8aa 100644
@@ -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
drivers/platform/x86/lenovo/wmi-capdata.c +120 -0

168 lines not on this page · raw message

drivers/platform/x86/lenovo/wmi-capdata.h +4 -3
index a49229cec245..92098aeeee84 100644
@@ -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;
Signature · 1 line
2.53.0
maintainer
#23 lore raw
In reply to Derek J. Clark
Quoted 6 lines · Derek J. Clark

This series adds many much needed fixes and features to the lenovo-wmi* drivers. This version has been re-ordered to ensure the least amount of churn in each patch. This has resulted in two formatting patches being added to the stable Cc list as prerequisites for some of the fixes.

Patches 1-9 fix various bugs and formatting, and are Cc'd to stable.

Thanks for the update.

I've taken 1-9 into review-ilpo-fixes branch and will do fixes -> for-next merge later to be able to pick up rest of the patches there.

Signature · 1 line
 i.
Quoted 151 lines

for the formatting only ones, patch 7 moves all gamezone enums from the gamezone header into the helpers header and patch 8 adds two functions to make assigning attribute ID's for capdata0X and tunable_attr_01 cleaner.

Patch 10 adds the remaining CPU attributes that weren't previously exposed.

Patch 11 adds GPU attributes.

Patch 12 renames a name constant in preparation for patch 6.

Patch 13 adds battery charge-type 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 14 adds a module parameter, force_load_psy_ext, to bypass ACPI and BIOS compatibility checks for the power supply extension.

Patch 15 adds a debugfs directory.

Patch 16 adds a debugfs file for dumping capdata.

---
v12:
- 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/[email protected]/
- 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/[email protected]/
- Fix misnamed attribute in wmi-other.
v9: https://lore.kernel.org/platform-driver-x86/[email protected]/
- 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/[email protected]/
- 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/[email protected]/
- 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/[email protected]/
- 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/[email protected]/
- 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/[email protected]/
- 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/[email protected]/
- 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/[email protected]/
- 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/[email protected]/

Derek J. Clark (10):
platform/x86: lenovo-wmi-other: Zero initialize WMI arguments platform/x86: lenovo-wmi-other: Fix tunable_attr_01 struct members platform/x86: lenovo-wmi-helpers: Move gamezone enums to wmi-helpers platform/x86: lenovo-wmi-other: Add Attribute ID helper functions 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 platform/x86: lenovo-wmi-other: Add force_load_psy_ext module
parameter

Rong Zhang (6):
platform/x86: lenovo-wmi-helpers: Fix memory leak in
lwmi_dev_evaluate_int()
platform/x86: lenovo-wmi-other: Balance IDA id allocation and free platform/x86: lenovo-wmi-other: Balance component bind and unbind platform/x86: lenovo: Decouple lenovo-wmi-gamezone and
lenovo-wmi-other
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           |   3 +-
 drivers/platform/x86/lenovo/wmi-capdata.c     | 128 ++-
 drivers/platform/x86/lenovo/wmi-capdata.h     |  34 +-
 drivers/platform/x86/lenovo/wmi-events.c      |   2 +-
 drivers/platform/x86/lenovo/wmi-gamezone.c    |   7 +-
 drivers/platform/x86/lenovo/wmi-gamezone.h    |  20 -
 drivers/platform/x86/lenovo/wmi-helpers.c     | 139 ++-
 drivers/platform/x86/lenovo/wmi-helpers.h     |  23 +
 drivers/platform/x86/lenovo/wmi-other.c       | 867 +++++++++++++++---
 drivers/platform/x86/lenovo/wmi-other.h       |  16 -
 11 files changed, 1061 insertions(+), 197 deletions(-)
 delete mode 100644 drivers/platform/x86/lenovo/wmi-gamezone.h
 delete mode 100644 drivers/platform/x86/lenovo/wmi-other.h