diff mbox series

[v4] platform/x86: asus-wmi: Support setting a maximum charging percentage

Message ID 20190901190453.29720-1-kristian@klausen.dk (mailing list archive)
State Not Applicable, archived
Headers show
Series [v4] platform/x86: asus-wmi: Support setting a maximum charging percentage | expand

Commit Message

Kristian Klausen Sept. 1, 2019, 7:04 p.m. UTC
Most newer ASUS laptops supports settings a maximum charging percentage,
which help prolonging the battery life.

Tested on a Zenbook UX430UNR.

Signed-off-by: Kristian Klausen <kristian@klausen.dk>
---
V4:
Use asus_wmi_dev_is_present() instead of asus_wmi_get_devstate_simple()
to check if the RSOC is available.
asus_wmi_dev_is_present() use ASUS_WMI_DSTS_STATUS_BIT and
ASUS_WMI_DSTS_UNKNOWN_BIT which isn't mentioned in the spec for the RSOC.
Only unregister the battery_hook if battery_hook_register was called
previously.

V3:
Refactor to use the new battery hooking API[1] and knobs[2].

[1] https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=fa93854f7a7ed63d054405bf3779247d5300edd3
[2] https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=813cab8f3994250e136819ae48fbd1c95d980466

V2:
Add sysfs documentation.
Reorder ASUS_WMI_CHARGE_THRESHOLD and rename to ASUS_WMI_DEVID_RSOC.
Add a comment explaining the charge_threshold variable.
Rephrase the commit message (charge threshold -> maximum charging
percentage).

The sysfs knob is still called "charge_threshold", as
maximum_charging_percentage seems a bit long.
I did look on some of the other platform modules, the LG module
use battery_care_limit and the Samsung module use
battery_life_extender.

 drivers/platform/x86/asus-wmi.c            | 97 ++++++++++++++++++++++
 include/linux/platform_data/x86/asus-wmi.h |  3 +
 2 files changed, 100 insertions(+)

Comments

Andy Shevchenko March 3, 2020, 2:45 p.m. UTC | #1
On Sun, Sep 1, 2019 at 10:11 PM Kristian Klausen <kristian@klausen.dk> wrote:
>
> Most newer ASUS laptops supports settings a maximum charging percentage,
> which help prolonging the battery life.
>
> Tested on a Zenbook UX430UNR.

Hi!

I found this patch in my email box eventually.
Can you remind what is the status of it?
Is this change still needed?

>
> Signed-off-by: Kristian Klausen <kristian@klausen.dk>
> ---
> V4:
> Use asus_wmi_dev_is_present() instead of asus_wmi_get_devstate_simple()
> to check if the RSOC is available.
> asus_wmi_dev_is_present() use ASUS_WMI_DSTS_STATUS_BIT and
> ASUS_WMI_DSTS_UNKNOWN_BIT which isn't mentioned in the spec for the RSOC.
> Only unregister the battery_hook if battery_hook_register was called
> previously.
>
> V3:
> Refactor to use the new battery hooking API[1] and knobs[2].
>
> [1] https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=fa93854f7a7ed63d054405bf3779247d5300edd3
> [2] https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=813cab8f3994250e136819ae48fbd1c95d980466
>
> V2:
> Add sysfs documentation.
> Reorder ASUS_WMI_CHARGE_THRESHOLD and rename to ASUS_WMI_DEVID_RSOC.
> Add a comment explaining the charge_threshold variable.
> Rephrase the commit message (charge threshold -> maximum charging
> percentage).
>
> The sysfs knob is still called "charge_threshold", as
> maximum_charging_percentage seems a bit long.
> I did look on some of the other platform modules, the LG module
> use battery_care_limit and the Samsung module use
> battery_life_extender.
>
>  drivers/platform/x86/asus-wmi.c            | 97 ++++++++++++++++++++++
>  include/linux/platform_data/x86/asus-wmi.h |  3 +
>  2 files changed, 100 insertions(+)
>
> diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c
> index 34dfbed65332..f0085953b381 100644
> --- a/drivers/platform/x86/asus-wmi.c
> +++ b/drivers/platform/x86/asus-wmi.c
> @@ -26,6 +26,7 @@
>  #include <linux/rfkill.h>
>  #include <linux/pci.h>
>  #include <linux/pci_hotplug.h>
> +#include <linux/power_supply.h>
>  #include <linux/hwmon.h>
>  #include <linux/hwmon-sysfs.h>
>  #include <linux/debugfs.h>
> @@ -36,6 +37,7 @@
>  #include <linux/acpi.h>
>  #include <linux/dmi.h>
>  #include <acpi/video.h>
> +#include <acpi/battery.h>
>
>  #include "asus-wmi.h"
>
> @@ -195,6 +197,9 @@ struct asus_wmi {
>         u8 fan_boost_mode_mask;
>         u8 fan_boost_mode;
>
> +       // The RSOC controls the maximum charging percentage.
> +       bool battery_rsoc_available;
> +
>         struct hotplug_slot hotplug_slot;
>         struct mutex hotplug_lock;
>         struct mutex wmi_lock;
> @@ -368,6 +373,95 @@ static bool asus_wmi_dev_is_present(struct asus_wmi *asus, u32 dev_id)
>         return status == 0 && (retval & ASUS_WMI_DSTS_PRESENCE_BIT);
>  }
>
> +/* Battery ********************************************************************/
> +
> +/* The battery maximum charging percentage */
> +static int charge_end_threshold;
> +
> +static ssize_t charge_control_end_threshold_store(struct device *dev,
> +                                                 struct device_attribute *attr,
> +                                                 const char *buf, size_t count)
> +{
> +       int value, ret, rv;
> +
> +       ret = kstrtouint(buf, 10, &value);
> +
> +       if (!count || ret != 0)
> +               return -EINVAL;
> +       if (value < 0 || value > 100)
> +               return -EINVAL;
> +
> +       asus_wmi_set_devstate(ASUS_WMI_DEVID_RSOC, value, &rv);
> +
> +       if (rv != 1)
> +               return -EIO;
> +
> +       /* There isn't any method in the DSDT to read the threshold, so we
> +        * save the threshold.
> +        */
> +       charge_end_threshold = value;
> +       return count;
> +}
> +
> +static ssize_t charge_control_end_threshold_show(struct device *device,
> +                                                struct device_attribute *attr,
> +                                                char *buf)
> +{
> +       return sprintf(buf, "%d\n", charge_end_threshold);
> +}
> +
> +static DEVICE_ATTR_RW(charge_control_end_threshold);
> +
> +static int asus_wmi_battery_add(struct power_supply *battery)
> +{
> +       /* The WMI method does not provide a way to specific a battery, so we
> +        * just assume it is the first battery.
> +        */
> +       if (!strcmp(battery->desc->name, "BAT0") == 0)
> +               return -ENODEV;
> +
> +       if (device_create_file(&battery->dev,
> +           &dev_attr_charge_control_end_threshold))
> +               return -ENODEV;
> +
> +       /* The charge threshold is only reset when the system is power cycled,
> +        * and we can't get the current threshold so let set it to 100% when
> +        * a battery is added.
> +        */
> +       asus_wmi_set_devstate(ASUS_WMI_DEVID_RSOC, 100, NULL);
> +       charge_end_threshold = 100;
> +
> +       return 0;
> +}
> +
> +static int asus_wmi_battery_remove(struct power_supply *battery)
> +{
> +       device_remove_file(&battery->dev,
> +                          &dev_attr_charge_control_end_threshold);
> +       return 0;
> +}
> +
> +static struct acpi_battery_hook battery_hook = {
> +       .add_battery = asus_wmi_battery_add,
> +       .remove_battery = asus_wmi_battery_remove,
> +       .name = "ASUS Battery Extension",
> +};
> +
> +static void asus_wmi_battery_init(struct asus_wmi *asus)
> +{
> +       asus_wmi->battery_rsoc_available = false;
> +       if (asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_RSOC)) {
> +               asus_wmi->battery_rsoc_available = true;
> +               battery_hook_register(&battery_hook);
> +       }
> +}
> +
> +static void asus_wmi_battery_exit(struct asus_wmi *asus)
> +{
> +       if (asus_wmi->battery_rsoc_available)
> +               battery_hook_unregister(&battery_hook);
> +}
> +
>  /* LEDs ***********************************************************************/
>
>  /*
> @@ -2433,6 +2527,8 @@ static int asus_wmi_add(struct platform_device *pdev)
>                 goto fail_wmi_handler;
>         }
>
> +       asus_wmi_battery_init(asus);
> +
>         asus_wmi_debugfs_init(asus);
>
>         return 0;
> @@ -2468,6 +2564,7 @@ static int asus_wmi_remove(struct platform_device *device)
>         asus_wmi_debugfs_exit(asus);
>         asus_wmi_sysfs_exit(asus->platform_device);
>         asus_fan_set_auto(asus);
> +       asus_wmi_battery_exit(asus);
>
>         kfree(asus);
>         return 0;
> diff --git a/include/linux/platform_data/x86/asus-wmi.h b/include/linux/platform_data/x86/asus-wmi.h
> index 409e16064f4b..60249e22e844 100644
> --- a/include/linux/platform_data/x86/asus-wmi.h
> +++ b/include/linux/platform_data/x86/asus-wmi.h
> @@ -81,6 +81,9 @@
>  /* Deep S3 / Resume on LID open */
>  #define ASUS_WMI_DEVID_LID_RESUME      0x00120031
>
> +/* Maximum charging percentage */
> +#define ASUS_WMI_DEVID_RSOC            0x00120057
> +
>  /* DSTS masks */
>  #define ASUS_WMI_DSTS_STATUS_BIT       0x00000001
>  #define ASUS_WMI_DSTS_UNKNOWN_BIT      0x00000002
> --
> 2.23.0
>
Kristian Klausen March 3, 2020, 5:44 p.m. UTC | #2
On 03.03.2020 15.45, Andy Shevchenko wrote:
> On Sun, Sep 1, 2019 at 10:11 PM Kristian Klausen <kristian@klausen.dk> wrote:
>> Most newer ASUS laptops supports settings a maximum charging percentage,
>> which help prolonging the battery life.
>>
>> Tested on a Zenbook UX430UNR.
> Hi!
>
> I found this patch in my email box eventually.
> Can you remind what is the status of it?
> Is this change still needed?

No, it isn't needed anymore. It was superseded by another patch series, 
which was accepted a long time ago.

https://www.spinics.net/lists/platform-driver-x86/msg19540.html
https://www.spinics.net/lists/platform-driver-x86/msg19542.html
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=7c28503db19cfa28e394a394aca61c79fbf3f969

>
>> Signed-off-by: Kristian Klausen <kristian@klausen.dk>
>> ---
>> V4:
>> Use asus_wmi_dev_is_present() instead of asus_wmi_get_devstate_simple()
>> to check if the RSOC is available.
>> asus_wmi_dev_is_present() use ASUS_WMI_DSTS_STATUS_BIT and
>> ASUS_WMI_DSTS_UNKNOWN_BIT which isn't mentioned in the spec for the RSOC.
>> Only unregister the battery_hook if battery_hook_register was called
>> previously.
>>
>> V3:
>> Refactor to use the new battery hooking API[1] and knobs[2].
>>
>> [1] https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=fa93854f7a7ed63d054405bf3779247d5300edd3
>> [2] https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=813cab8f3994250e136819ae48fbd1c95d980466
>>
>> V2:
>> Add sysfs documentation.
>> Reorder ASUS_WMI_CHARGE_THRESHOLD and rename to ASUS_WMI_DEVID_RSOC.
>> Add a comment explaining the charge_threshold variable.
>> Rephrase the commit message (charge threshold -> maximum charging
>> percentage).
>>
>> The sysfs knob is still called "charge_threshold", as
>> maximum_charging_percentage seems a bit long.
>> I did look on some of the other platform modules, the LG module
>> use battery_care_limit and the Samsung module use
>> battery_life_extender.
>>
>>   drivers/platform/x86/asus-wmi.c            | 97 ++++++++++++++++++++++
>>   include/linux/platform_data/x86/asus-wmi.h |  3 +
>>   2 files changed, 100 insertions(+)
>>
>> diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c
>> index 34dfbed65332..f0085953b381 100644
>> --- a/drivers/platform/x86/asus-wmi.c
>> +++ b/drivers/platform/x86/asus-wmi.c
>> @@ -26,6 +26,7 @@
>>   #include <linux/rfkill.h>
>>   #include <linux/pci.h>
>>   #include <linux/pci_hotplug.h>
>> +#include <linux/power_supply.h>
>>   #include <linux/hwmon.h>
>>   #include <linux/hwmon-sysfs.h>
>>   #include <linux/debugfs.h>
>> @@ -36,6 +37,7 @@
>>   #include <linux/acpi.h>
>>   #include <linux/dmi.h>
>>   #include <acpi/video.h>
>> +#include <acpi/battery.h>
>>
>>   #include "asus-wmi.h"
>>
>> @@ -195,6 +197,9 @@ struct asus_wmi {
>>          u8 fan_boost_mode_mask;
>>          u8 fan_boost_mode;
>>
>> +       // The RSOC controls the maximum charging percentage.
>> +       bool battery_rsoc_available;
>> +
>>          struct hotplug_slot hotplug_slot;
>>          struct mutex hotplug_lock;
>>          struct mutex wmi_lock;
>> @@ -368,6 +373,95 @@ static bool asus_wmi_dev_is_present(struct asus_wmi *asus, u32 dev_id)
>>          return status == 0 && (retval & ASUS_WMI_DSTS_PRESENCE_BIT);
>>   }
>>
>> +/* Battery ********************************************************************/
>> +
>> +/* The battery maximum charging percentage */
>> +static int charge_end_threshold;
>> +
>> +static ssize_t charge_control_end_threshold_store(struct device *dev,
>> +                                                 struct device_attribute *attr,
>> +                                                 const char *buf, size_t count)
>> +{
>> +       int value, ret, rv;
>> +
>> +       ret = kstrtouint(buf, 10, &value);
>> +
>> +       if (!count || ret != 0)
>> +               return -EINVAL;
>> +       if (value < 0 || value > 100)
>> +               return -EINVAL;
>> +
>> +       asus_wmi_set_devstate(ASUS_WMI_DEVID_RSOC, value, &rv);
>> +
>> +       if (rv != 1)
>> +               return -EIO;
>> +
>> +       /* There isn't any method in the DSDT to read the threshold, so we
>> +        * save the threshold.
>> +        */
>> +       charge_end_threshold = value;
>> +       return count;
>> +}
>> +
>> +static ssize_t charge_control_end_threshold_show(struct device *device,
>> +                                                struct device_attribute *attr,
>> +                                                char *buf)
>> +{
>> +       return sprintf(buf, "%d\n", charge_end_threshold);
>> +}
>> +
>> +static DEVICE_ATTR_RW(charge_control_end_threshold);
>> +
>> +static int asus_wmi_battery_add(struct power_supply *battery)
>> +{
>> +       /* The WMI method does not provide a way to specific a battery, so we
>> +        * just assume it is the first battery.
>> +        */
>> +       if (!strcmp(battery->desc->name, "BAT0") == 0)
>> +               return -ENODEV;
>> +
>> +       if (device_create_file(&battery->dev,
>> +           &dev_attr_charge_control_end_threshold))
>> +               return -ENODEV;
>> +
>> +       /* The charge threshold is only reset when the system is power cycled,
>> +        * and we can't get the current threshold so let set it to 100% when
>> +        * a battery is added.
>> +        */
>> +       asus_wmi_set_devstate(ASUS_WMI_DEVID_RSOC, 100, NULL);
>> +       charge_end_threshold = 100;
>> +
>> +       return 0;
>> +}
>> +
>> +static int asus_wmi_battery_remove(struct power_supply *battery)
>> +{
>> +       device_remove_file(&battery->dev,
>> +                          &dev_attr_charge_control_end_threshold);
>> +       return 0;
>> +}
>> +
>> +static struct acpi_battery_hook battery_hook = {
>> +       .add_battery = asus_wmi_battery_add,
>> +       .remove_battery = asus_wmi_battery_remove,
>> +       .name = "ASUS Battery Extension",
>> +};
>> +
>> +static void asus_wmi_battery_init(struct asus_wmi *asus)
>> +{
>> +       asus_wmi->battery_rsoc_available = false;
>> +       if (asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_RSOC)) {
>> +               asus_wmi->battery_rsoc_available = true;
>> +               battery_hook_register(&battery_hook);
>> +       }
>> +}
>> +
>> +static void asus_wmi_battery_exit(struct asus_wmi *asus)
>> +{
>> +       if (asus_wmi->battery_rsoc_available)
>> +               battery_hook_unregister(&battery_hook);
>> +}
>> +
>>   /* LEDs ***********************************************************************/
>>
>>   /*
>> @@ -2433,6 +2527,8 @@ static int asus_wmi_add(struct platform_device *pdev)
>>                  goto fail_wmi_handler;
>>          }
>>
>> +       asus_wmi_battery_init(asus);
>> +
>>          asus_wmi_debugfs_init(asus);
>>
>>          return 0;
>> @@ -2468,6 +2564,7 @@ static int asus_wmi_remove(struct platform_device *device)
>>          asus_wmi_debugfs_exit(asus);
>>          asus_wmi_sysfs_exit(asus->platform_device);
>>          asus_fan_set_auto(asus);
>> +       asus_wmi_battery_exit(asus);
>>
>>          kfree(asus);
>>          return 0;
>> diff --git a/include/linux/platform_data/x86/asus-wmi.h b/include/linux/platform_data/x86/asus-wmi.h
>> index 409e16064f4b..60249e22e844 100644
>> --- a/include/linux/platform_data/x86/asus-wmi.h
>> +++ b/include/linux/platform_data/x86/asus-wmi.h
>> @@ -81,6 +81,9 @@
>>   /* Deep S3 / Resume on LID open */
>>   #define ASUS_WMI_DEVID_LID_RESUME      0x00120031
>>
>> +/* Maximum charging percentage */
>> +#define ASUS_WMI_DEVID_RSOC            0x00120057
>> +
>>   /* DSTS masks */
>>   #define ASUS_WMI_DSTS_STATUS_BIT       0x00000001
>>   #define ASUS_WMI_DSTS_UNKNOWN_BIT      0x00000002
>> --
>> 2.23.0
>>
>
diff mbox series

Patch

diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c
index 34dfbed65332..f0085953b381 100644
--- a/drivers/platform/x86/asus-wmi.c
+++ b/drivers/platform/x86/asus-wmi.c
@@ -26,6 +26,7 @@ 
 #include <linux/rfkill.h>
 #include <linux/pci.h>
 #include <linux/pci_hotplug.h>
+#include <linux/power_supply.h>
 #include <linux/hwmon.h>
 #include <linux/hwmon-sysfs.h>
 #include <linux/debugfs.h>
@@ -36,6 +37,7 @@ 
 #include <linux/acpi.h>
 #include <linux/dmi.h>
 #include <acpi/video.h>
+#include <acpi/battery.h>
 
 #include "asus-wmi.h"
 
@@ -195,6 +197,9 @@  struct asus_wmi {
 	u8 fan_boost_mode_mask;
 	u8 fan_boost_mode;
 
+	// The RSOC controls the maximum charging percentage.
+	bool battery_rsoc_available;
+
 	struct hotplug_slot hotplug_slot;
 	struct mutex hotplug_lock;
 	struct mutex wmi_lock;
@@ -368,6 +373,95 @@  static bool asus_wmi_dev_is_present(struct asus_wmi *asus, u32 dev_id)
 	return status == 0 && (retval & ASUS_WMI_DSTS_PRESENCE_BIT);
 }
 
+/* Battery ********************************************************************/
+
+/* The battery maximum charging percentage */
+static int charge_end_threshold;
+
+static ssize_t charge_control_end_threshold_store(struct device *dev,
+						  struct device_attribute *attr,
+						  const char *buf, size_t count)
+{
+	int value, ret, rv;
+
+	ret = kstrtouint(buf, 10, &value);
+
+	if (!count || ret != 0)
+		return -EINVAL;
+	if (value < 0 || value > 100)
+		return -EINVAL;
+
+	asus_wmi_set_devstate(ASUS_WMI_DEVID_RSOC, value, &rv);
+
+	if (rv != 1)
+		return -EIO;
+
+	/* There isn't any method in the DSDT to read the threshold, so we
+	 * save the threshold.
+	 */
+	charge_end_threshold = value;
+	return count;
+}
+
+static ssize_t charge_control_end_threshold_show(struct device *device,
+						 struct device_attribute *attr,
+						 char *buf)
+{
+	return sprintf(buf, "%d\n", charge_end_threshold);
+}
+
+static DEVICE_ATTR_RW(charge_control_end_threshold);
+
+static int asus_wmi_battery_add(struct power_supply *battery)
+{
+	/* The WMI method does not provide a way to specific a battery, so we
+	 * just assume it is the first battery.
+	 */
+	if (!strcmp(battery->desc->name, "BAT0") == 0)
+		return -ENODEV;
+
+	if (device_create_file(&battery->dev,
+	    &dev_attr_charge_control_end_threshold))
+		return -ENODEV;
+
+	/* The charge threshold is only reset when the system is power cycled,
+	 * and we can't get the current threshold so let set it to 100% when
+	 * a battery is added.
+	 */
+	asus_wmi_set_devstate(ASUS_WMI_DEVID_RSOC, 100, NULL);
+	charge_end_threshold = 100;
+
+	return 0;
+}
+
+static int asus_wmi_battery_remove(struct power_supply *battery)
+{
+	device_remove_file(&battery->dev,
+			   &dev_attr_charge_control_end_threshold);
+	return 0;
+}
+
+static struct acpi_battery_hook battery_hook = {
+	.add_battery = asus_wmi_battery_add,
+	.remove_battery = asus_wmi_battery_remove,
+	.name = "ASUS Battery Extension",
+};
+
+static void asus_wmi_battery_init(struct asus_wmi *asus)
+{
+	asus_wmi->battery_rsoc_available = false;
+	if (asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_RSOC)) {
+		asus_wmi->battery_rsoc_available = true;
+		battery_hook_register(&battery_hook);
+	}
+}
+
+static void asus_wmi_battery_exit(struct asus_wmi *asus)
+{
+	if (asus_wmi->battery_rsoc_available)
+		battery_hook_unregister(&battery_hook);
+}
+
 /* LEDs ***********************************************************************/
 
 /*
@@ -2433,6 +2527,8 @@  static int asus_wmi_add(struct platform_device *pdev)
 		goto fail_wmi_handler;
 	}
 
+	asus_wmi_battery_init(asus);
+
 	asus_wmi_debugfs_init(asus);
 
 	return 0;
@@ -2468,6 +2564,7 @@  static int asus_wmi_remove(struct platform_device *device)
 	asus_wmi_debugfs_exit(asus);
 	asus_wmi_sysfs_exit(asus->platform_device);
 	asus_fan_set_auto(asus);
+	asus_wmi_battery_exit(asus);
 
 	kfree(asus);
 	return 0;
diff --git a/include/linux/platform_data/x86/asus-wmi.h b/include/linux/platform_data/x86/asus-wmi.h
index 409e16064f4b..60249e22e844 100644
--- a/include/linux/platform_data/x86/asus-wmi.h
+++ b/include/linux/platform_data/x86/asus-wmi.h
@@ -81,6 +81,9 @@ 
 /* Deep S3 / Resume on LID open */
 #define ASUS_WMI_DEVID_LID_RESUME	0x00120031
 
+/* Maximum charging percentage */
+#define ASUS_WMI_DEVID_RSOC		0x00120057
+
 /* DSTS masks */
 #define ASUS_WMI_DSTS_STATUS_BIT	0x00000001
 #define ASUS_WMI_DSTS_UNKNOWN_BIT	0x00000002