mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Rong Zhang <i@rong.moe>
To: "Derek J. Clark" <derekjohn.clark@gmail.com>,
	"Ilpo Järvinen" <ilpo.jarvinen@linux.intel.com>,
	"Hans de Goede" <hansg@kernel.org>
Cc: Mark Pearson <mpearson-lenovo@squebb.ca>,
	Armin Wolf <W_Armin@gmx.de>,  Jonathan Corbet <corbet@lwn.net>,
	Kurt Borja <kuurtb@gmail.com>,
	 platform-driver-x86@vger.kernel.org,
	linux-kernel@vger.kernel.org
Subject: Re: [PATCH v6 10/13] platform/x86: lenovo-wmi-other: Add WMI battery charge limiting
Date: Thu, 02 Apr 2026 02:32:41 +0800	[thread overview]
Message-ID: <fd411d1b11ed476a5db492dbb47cebcf5a382b4e.camel@rong.moe> (raw)
In-Reply-To: <20260331181208.421552-11-derekjohn.clark@gmail.com>

Hi Derek,

On Tue, 2026-03-31 at 18:12 +0000, Derek J. Clark wrote:
> Add charge-type power supply extension for devices that support WMI based
> charge enable/disable.
> 
> Lenovo Legion devices that implement WMI function and capdata ID
> 0x03010001 in their BIOS are able to enable or disable charging at 80%
> through the lenovo-wmi-other interface. Add a charge_type power supply
> extension to expose this capability to the sysfs.
> 
> The ideapad_laptop driver can also provide the charge_type attribute. To
> avoid conflicts between the drivers, get the acpi_handle and do the same
> check that ideapad_laptop does when it enables the feature. If the
> feature is supported in ideapad_laptop, abort adding the extension from
> lenovo-wmi-other. The ACPI method is more reliable when both are
> present, from my testing, so we can prefer that implementation and do
> not need to worry about de-conflicting from inside that driver. A new
> module parameter, force_load_psy_ext, is provided to bypass this ACPI
> check, if desired.
> 
> Reviewed-by: Rong Zhang <i@rong.moe>
> Reviewed-by: Mark Pearson <mpearson-lenovo@squebb.ca>
> Signed-off-by: Derek J. Clark <derekjohn.clark@gmail.com>
> ---
> 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   | 274 +++++++++++++++++++++-
>  3 files changed, 275 insertions(+), 1 deletion(-)
> 
> diff --git a/drivers/platform/x86/lenovo/Kconfig b/drivers/platform/x86/lenovo/Kconfig
> index f885127b007f..75a8b144b0da 100644
> --- a/drivers/platform/x86/lenovo/Kconfig
> +++ b/drivers/platform/x86/lenovo/Kconfig
> @@ -263,6 +263,7 @@ config LENOVO_WMI_GAMEZONE
>  config LENOVO_WMI_TUNING
>  	tristate "Lenovo Other Mode WMI Driver"
>  	depends on ACPI_WMI
> +	depends on ACPI_BATTERY
>  	select HWMON
>  	select FW_ATTR_CLASS
>  	select LENOVO_WMI_CAPDATA
> diff --git a/drivers/platform/x86/lenovo/wmi-capdata.h b/drivers/platform/x86/lenovo/wmi-capdata.h
> index 891b12ca1db6..e0a30f2c0c87 100644
> --- a/drivers/platform/x86/lenovo/wmi-capdata.h
> +++ b/drivers/platform/x86/lenovo/wmi-capdata.h
> @@ -21,6 +21,7 @@
>  enum lwmi_device_id {
>  	LWMI_DEVICE_ID_CPU = 0x01,
>  	LWMI_DEVICE_ID_GPU = 0x02,
> +	LWMI_DEVICE_ID_PSU = 0x03,
>  	LWMI_DEVICE_ID_FAN = 0x04,
>  };
>  
> diff --git a/drivers/platform/x86/lenovo/wmi-other.c b/drivers/platform/x86/lenovo/wmi-other.c
> index 0366acf04ae5..e0633c42972c 100644
> --- a/drivers/platform/x86/lenovo/wmi-other.c
> +++ b/drivers/platform/x86/lenovo/wmi-other.c
> @@ -43,9 +43,12 @@
>  #include <linux/module.h>
>  #include <linux/notifier.h>
>  #include <linux/platform_profile.h>
> +#include <linux/power_supply.h>
>  #include <linux/types.h>
>  #include <linux/wmi.h>
>  
> +#include <acpi/battery.h>
> +
>  #include "wmi-capdata.h"
>  #include "wmi-events.h"
>  #include "wmi-helpers.h"
> @@ -78,9 +81,11 @@ enum lwmi_feature_id_gpu {
>  	LWMI_FEATURE_ID_GPU_NV_CPU_BOOST =	0x0b,
>  };
>  
> -#define LWMI_FEATURE_ID_FAN_RPM 0x03
> +#define LWMI_FEATURE_ID_FAN_RPM		0x03
> +#define LWMI_FEATURE_ID_PSU_CHARGE_TYPE	0x01
>  
>  #define LWMI_TYPE_ID_CROSSLOAD	0x01
> +#define LWMI_TYPE_ID_PSU_AC	0x01
>  
>  #define LWMI_FEATURE_VALUE_GET 17
>  #define LWMI_FEATURE_VALUE_SET 18
> @@ -91,10 +96,17 @@ enum lwmi_feature_id_gpu {
>  
>  #define LWMI_FAN_DIV 100
>  
> +#define LWMI_CHARGE_TYPE_STANDARD	0x00
> +#define LWMI_CHARGE_TYPE_LONGLIFE	0x01
> +
>  #define LWMI_ATTR_ID_FAN_RPM(x)                                   \
>  	lwmi_attr_id(LWMI_DEVICE_ID_FAN, LWMI_FEATURE_ID_FAN_RPM, \
>  		     LWMI_GZ_THERMAL_MODE_NONE, LWMI_FAN_ID(x))
>  
> +#define LWMI_ATTR_ID_PSU(feat, type)			\
> +	lwmi_attr_id(LWMI_DEVICE_ID_PSU, feat,		\
> +		     LWMI_GZ_THERMAL_MODE_NONE, type)
> +
>  #define LWMI_OM_SYSFS_NAME "lenovo-wmi-other"
>  #define LWMI_OM_HWMON_NAME "lenovo_wmi_other"
>  
> @@ -136,6 +148,8 @@ struct lwmi_om_priv {
>  		bool capdata00_collected : 1;
>  		bool capdata_fan_collected : 1;
>  	} fan_flags;
> +
> +	struct acpi_battery_hook battery_hook;
>  };
>  
>  /*
> @@ -560,6 +574,263 @@ static void lwmi_om_fan_info_collect_cd_fan(struct device *dev, struct cd_list *
>  	lwmi_om_hwmon_add(priv);
>  }
>  
> +/* ======== Power Supply Extension (component: lenovo-wmi-capdata 00) ======== */
> +
> +/**
> + * lwmi_psy_ext_get_prop() - Get a power_supply_ext property
> + * @ps: The battery that was extended
> + * @ext: The extension
> + * @ext_data: Pointer the lwmi_om_priv drvdata
> + * @prop: The property to read
> + * @val: The value to return
> + *
> + * Writes the given value to the power_supply_ext property
> + *
> + * Return: 0 on success, or an error
> + */
> +static int lwmi_psy_ext_get_prop(struct power_supply *ps,
> +				 const struct power_supply_ext *ext,
> +				 void *ext_data,
> +				 enum power_supply_property prop,
> +				 union power_supply_propval *val)
> +{
> +	struct wmi_method_args_32 args = { 0x0, 0x0 };
> +	struct lwmi_om_priv *priv = ext_data;
> +	u32 retval;
> +	int ret;
> +
> +	args.arg0 = LWMI_ATTR_ID_PSU(LWMI_FEATURE_ID_PSU_CHARGE_TYPE, LWMI_TYPE_ID_PSU_AC);
> +
> +	ret = lwmi_dev_evaluate_int(priv->wdev, 0x0, LWMI_FEATURE_VALUE_GET,
> +				    (unsigned char *)&args, sizeof(args),
> +				    &retval);
> +	if (ret)
> +		return ret;
> +
> +	dev_dbg(&priv->wdev->dev, "Got return value %#x for property %#x\n", retval, prop);
> +
> +	switch (retval) {
> +	case LWMI_CHARGE_TYPE_LONGLIFE:
> +		val->intval = POWER_SUPPLY_CHARGE_TYPE_LONGLIFE;
> +		break;
> +	case LWMI_CHARGE_TYPE_STANDARD:
> +		val->intval = POWER_SUPPLY_CHARGE_TYPE_STANDARD;
> +		break;
> +	default:
> +		dev_err(&priv->wdev->dev, "Got invalid charge 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 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 wmi_method_args_32 args = { 0x0, 0x0 };
> +	struct lwmi_om_priv *priv = ext_data;
> +
> +	args.arg0 = LWMI_ATTR_ID_PSU(LWMI_FEATURE_ID_PSU_CHARGE_TYPE, LWMI_TYPE_ID_PSU_AC);
> +	switch (val->intval) {
> +	case POWER_SUPPLY_CHARGE_TYPE_LONGLIFE:
> +		args.arg1 = LWMI_CHARGE_TYPE_LONGLIFE;
> +		break;
> +	case POWER_SUPPLY_CHARGE_TYPE_STANDARD:
> +		args.arg1 = LWMI_CHARGE_TYPE_STANDARD;
> +		break;
> +	default:
> +		dev_err(&priv->wdev->dev, "Got invalid charge value: %#x\n", val->intval);
> +		return -EINVAL;
> +	}
> +
> +	dev_dbg(&priv->wdev->dev, "Attempting to set %#010x for property %#x to %#x\n",
> +		args.arg0, prop, args.arg1);
> +
> +	return lwmi_dev_evaluate_int(priv->wdev, 0x0, LWMI_FEATURE_VALUE_SET,
> +				     (unsigned char *)&args, sizeof(args), NULL);
> +}
> +
> +/**
> + * lwmi_psy_prop_is_supported() - Determine if the property is supported
> + * @priv: Pointer the lwmi_om_priv drvdata
> + *
> + * 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)
> +{
> +	u32 attribute_id = LWMI_ATTR_ID_PSU(LWMI_FEATURE_ID_PSU_CHARGE_TYPE, LWMI_TYPE_ID_PSU_AC);
> +	struct capdata00 capdata;
> +	int ret;
> +
> +	ret = lwmi_cd00_get_data(priv->cd00_list, attribute_id, &capdata);
> +	if (ret)
> +		return false;
> +
> +	dev_dbg(&priv->wdev->dev, "Battery charge mode (%#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)
> +{
> +	u32 attribute_id = LWMI_ATTR_ID_PSU(LWMI_FEATURE_ID_PSU_CHARGE_TYPE, LWMI_TYPE_ID_PSU_AC);
> +	struct lwmi_om_priv *priv = ext_data;
> +	struct capdata00 capdata;
> +	int ret;
> +
> +	ret = lwmi_cd00_get_data(priv->cd00_list, attribute_id, &capdata);
> +	if (ret)
> +		return false;
> +
> +	return !!(capdata.supported & LWMI_SUPP_SET);
> +}
> +
> +static const enum power_supply_property lwmi_psy_ext_props[] = {
> +	POWER_SUPPLY_PROP_CHARGE_TYPES,
> +};
> +
> +static const struct power_supply_ext lwmi_psy_ext = {
> +	.name			= LWMI_OM_SYSFS_NAME,
> +	.properties		= lwmi_psy_ext_props,
> +	.num_properties		= ARRAY_SIZE(lwmi_psy_ext_props),
> +	.charge_types		= (BIT(POWER_SUPPLY_CHARGE_TYPE_STANDARD) |
> +				   BIT(POWER_SUPPLY_CHARGE_TYPE_LONGLIFE)),
> +	.get_property		= lwmi_psy_ext_get_prop,
> +	.set_property		= lwmi_psy_ext_set_prop,
> +	.property_is_writeable	= lwmi_psy_prop_is_writeable,
> +};
> +
> +/**
> + * lwmi_add_battery() - Connect the power_supply_ext
> + * @battery: The battery to extend
> + * @hook: The driver hook used to extend the battery
> + *
> + * Return: 0 on success, or an error.
> + */
> +static int lwmi_add_battery(struct power_supply *battery, struct acpi_battery_hook *hook)
> +{
> +	struct lwmi_om_priv *priv = container_of(hook, struct lwmi_om_priv, battery_hook);
> +
> +	return power_supply_register_extension(battery, &lwmi_psy_ext, &priv->wdev->dev, priv);
> +}
> +
> +/**
> + * lwmi_remove_battery() - Disconnect the power_supply_ext
> + * @battery: The battery that was extended
> + * @hook: The driver hook used to extend the battery
> + *
> + * Return: 0 on success, or an error.
> + */
> +static int lwmi_remove_battery(struct power_supply *battery, struct acpi_battery_hook *hook)
> +{
> +	power_supply_unregister_extension(battery, &lwmi_psy_ext);
> +	return 0;
> +}
> +
> +/**
> + * lwmi_acpi_match() - Attempts to return the ideapad acpi handle
> + * @handle: The ACPI handle that manages battery charging
> + * @lvl: Unused
> + * @context: Void pointer to the acpi_handle object to return
> + * @retval: Unused
> + *
> + * Checks if the ideapad_laptop driver is going to manage charge_type first,
> + * then if not, hooks the battery to our WMI methods.
> + *
> + * Return: AE_CTRL_TERMINATE if found, AE_OK if not found.
> + */
> +static acpi_status lwmi_acpi_match(acpi_handle handle, u32 lvl,
> +				   void *context, void **retval)
> +{
> +	acpi_handle *ahand = context;
> +
> +	if (!handle)
> +		return AE_OK;
> +
> +	*ahand = handle;
> +
> +	return AE_CTRL_TERMINATE;
> +}
> +
> +static bool force_load_psy_ext;
> +module_param(force_load_psy_ext, bool, 0444);
> +MODULE_PARM_DESC(force_load_psy_ext,
> +	"This option will skip checking if the ideapad_laptop driver will conflict "
> +	"with adding an extension to set the battery charge type. It is recommended "
> +	"to blacklist the ideapad driver before using this option.");
> +
> +/**
> + * lwmi_om_psy_ext_init() - Hooks power supply extension to device battery
> + * @priv: Driver private data
> + *
> + * Checks if the ideapad_laptop driver is going to manage charge_type first,
> + * then if not, hooks the battery to our WMI methods.
> + */
> +static void lwmi_om_psy_ext_init(struct lwmi_om_priv *priv)
> +{
> +	static const char * const ideapad_hid = "VPC2004";
> +	acpi_handle handle = NULL;
> +	int ret;
> +
> +	/* Deconflict ideapad_laptop driver */
> +	if (force_load_psy_ext)
> +		goto load_psy_ext;
> +
> +	if (!lwmi_psy_prop_is_supported(priv))
> +		return;
> +
> +	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;
> +	}
> +
> +load_psy_ext:
> +	/* Add battery hooks */
> +	priv->battery_hook.add_battery = lwmi_add_battery;
> +	priv->battery_hook.remove_battery = lwmi_remove_battery;
> +	priv->battery_hook.name = "Lenovo WMI Other Battery Extension";
> +
> +	ret = devm_battery_hook_register(&priv->wdev->dev, &priv->battery_hook);

Using devm here ties power_supply_ext's lifecycle to the wmi-other
device instead of the aggregate device, resulting in power_supply_ext
being registered multiple times if components are rebound.

Use plain battery_hook_register() here and emit a call to
battery_hook_unregister() in the unbind callback like the hwmon device.
The registration status should also be tracked by adding a flag to
lwmi_om_priv (or NULLifying some contents of priv->battery_hook if the
registration is skipped or if it's unregistered).

Thanks,
Rong

> +	if (ret)
> +		dev_err(&priv->wdev->dev, "Error during battery hook: %i\n", ret);
> +}
> +
>  /* ======== fw_attributes (component: lenovo-wmi-capdata 01) ======== */
>  
>  struct tunable_attr_01 {
> @@ -1317,6 +1588,7 @@ static int lwmi_om_master_bind(struct device *dev)
>  		return -ENODEV;
>  
>  	lwmi_om_fan_info_collect_cd00(priv);
> +	lwmi_om_psy_ext_init(priv);
>  
>  	return lwmi_om_fw_attr_add(priv);
>  }

  reply	other threads:[~2026-04-01 18:37 UTC|newest]

Thread overview: 36+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-03-31 18:11 [PATCH v6 00/13] platform-x86: lenovo-wmi: Add fixes and enhancement Derek J. Clark
2026-03-31 18:11 ` [PATCH v6 01/13] platform/x86: lenovo-wmi-other: Move LWMI_FAN_DIV Derek J. Clark
2026-03-31 18:11 ` [PATCH v6 02/13] platform/x86: lenovo-wmi-other: Fix tunable_attr_01 struct members Derek J. Clark
2026-03-31 18:11 ` [PATCH v6 03/13] platform/x86: lenovo-wmi-other: Zero initialize WMI arguments Derek J. Clark
2026-04-01 18:26   ` Rong Zhang
2026-03-31 18:11 ` [PATCH v6 04/13] platform/x86: lenovo-wmi-helpers: Move gamezone enums to wmi-helpers Derek J. Clark
2026-04-01 18:26   ` Rong Zhang
2026-03-31 18:12 ` [PATCH v6 05/13] platform/x86: lenovo-wmi-other: Add lwmi_attr_id() function Derek J. Clark
2026-04-01 18:29   ` Rong Zhang
2026-04-01 19:24     ` Derek J. Clark
2026-03-31 18:12 ` [PATCH v6 06/13] platform/x86: lenovo-wmi-other: Limit adding attributes to supported devices Derek J. Clark
2026-03-31 22:28   ` Kurt Borja
2026-03-31 18:12 ` [PATCH v6 07/13] platform/x86: lenovo-wmi-other: Add missing CPU tunable attributes Derek J. Clark
2026-03-31 22:29   ` Kurt Borja
2026-04-01 18:30   ` Rong Zhang
2026-03-31 18:12 ` [PATCH v6 08/13] platform/x86: lenovo-wmi-other: Add GPU " Derek J. Clark
2026-03-31 22:29   ` Kurt Borja
2026-04-01 18:30   ` Rong Zhang
2026-03-31 18:12 ` [PATCH v6 09/13] platform/x86: lenovo-wmi-other: Rename LWMI_OM_FW_ATTR_BASE_PATH Derek J. Clark
2026-03-31 18:12 ` [PATCH v6 10/13] platform/x86: lenovo-wmi-other: Add WMI battery charge limiting Derek J. Clark
2026-04-01 18:32   ` Rong Zhang [this message]
2026-03-31 18:12 ` [PATCH v6 11/13] platform/x86: lenovo: Decouple lenovo-wmi-gamezone and lenovo-wmi-other Derek J. Clark
2026-04-01 18:34   ` Rong Zhang
2026-03-31 18:12 ` [PATCH v6 12/13] platform/x86: lenovo-wmi-helpers: Add helper for creating per-device debugfs dir Derek J. Clark
2026-04-01 18:35   ` Rong Zhang
2026-03-31 18:12 ` [PATCH v6 13/13] platform/x86: lenovo-wmi-capdata: Add debugfs file for dumping capdata Derek J. Clark
2026-04-01 18:44 ` [PATCH v6 00/13] platform-x86: lenovo-wmi: Add fixes and enhancement Rong Zhang
2026-04-01 19:00   ` [PATCH 1/3] platform/x86: lenovo-wmi-helpers: Fix memory leak in lwmi_dev_evaluate_int() Rong Zhang
2026-04-02 10:06     ` Ilpo Järvinen
2026-04-01 19:00   ` [PATCH 2/3] platform/x86: lenovo-wmi-other: Balance IDA id allocation and free Rong Zhang
2026-04-09 12:29     ` Ilpo Järvinen
2026-04-11 15:54       ` Derek John Clark
2026-04-01 19:00   ` [PATCH 3/3] platform/x86: lenovo-wmi-other: Balance component bind and unbind Rong Zhang
2026-04-09 12:32     ` Ilpo Järvinen
2026-04-01 19:37   ` [PATCH v6 00/13] platform-x86: lenovo-wmi: Add fixes and enhancement Derek J. Clark
2026-04-01 20:12     ` Rong Zhang

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=fd411d1b11ed476a5db492dbb47cebcf5a382b4e.camel@rong.moe \
    --to=i@rong.moe \
    --cc=W_Armin@gmx.de \
    --cc=corbet@lwn.net \
    --cc=derekjohn.clark@gmail.com \
    --cc=hansg@kernel.org \
    --cc=ilpo.jarvinen@linux.intel.com \
    --cc=kuurtb@gmail.com \
    --cc=linux-kernel@vger.kernel.org \
    --cc=mpearson-lenovo@squebb.ca \
    --cc=platform-driver-x86@vger.kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®