diff mbox series

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

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

Commit Message

Kristian Klausen Aug. 13, 2019, 12:30 a.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>
---
I can't pass the asus struct to asus_wmi_battery_{add,remove}, so I use a
global variable. Is there any better way to do it?
I think the implementation of asus_wmi_battery_{init,exit} could be
improved, any ideas?

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            | 91 ++++++++++++++++++++++
 include/linux/platform_data/x86/asus-wmi.h |  3 +
 2 files changed, 94 insertions(+)

Comments

Daniel Drake Aug. 15, 2019, 3:28 a.m. UTC | #1
On Tue, Aug 13, 2019 at 8:30 AM 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.
>
> Signed-off-by: Kristian Klausen <kristian@klausen.dk>
> ---
> I can't pass the asus struct to asus_wmi_battery_{add,remove}, so I use a
> global variable. Is there any better way to do it?
> I think the implementation of asus_wmi_battery_{init,exit} could be
> improved, any ideas?

Added Ognjen and Rafael.
Is there a better way to have the sysfs file handlers of files added
via the battery hooking API reach back to the device that hooked in,
in order to access it's private data?
I see that thinkpad_acpi also uses global variables for this :(

> 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            | 91 ++++++++++++++++++++++
>  include/linux/platform_data/x86/asus-wmi.h |  3 +
>  2 files changed, 94 insertions(+)
>
> diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c
> index 34dfbed65332..06c830c1c04f 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"
>
> @@ -368,6 +370,92 @@ 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)
> +{
> +       if (asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_RSOC) >= 0)
> +               battery_hook_register(&battery_hook);
> +}

asus_wmi_get_devstate_simple() checks ASUS_WMI_DSTS_STATUS_BIT and
ASUS_WMI_DSTS_UNKNOWN_BIT for this device. However the spec makes no
mentions of those bits being valid in this case.
Your code probably works anyway but I think it would be better to use
asus_wmi_dev_is_present() instead.

> +static void asus_wmi_battery_exit(struct asus_wmi *asus)
> +{
> +       if (asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_RSOC) >= 0)
> +               battery_hook_unregister(&battery_hook);
> +}

The unregister here should probably happen on the condition that we
previously registered the hook.
Don't rely on the devstate bits, since they might have changed
unpredictably (who knows), there might be a problem communicating with
the hardware, etc.

>  /* LEDs ***********************************************************************/
>
>  /*
> @@ -2433,6 +2521,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 +2558,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.22.0
>
Kristian Klausen Aug. 26, 2019, 6:47 p.m. UTC | #2
On 15.08.2019 05.28, Daniel Drake wrote:
> On Tue, Aug 13, 2019 at 8:30 AM 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.
>>
>> Signed-off-by: Kristian Klausen <kristian@klausen.dk>
>> ---
>> I can't pass the asus struct to asus_wmi_battery_{add,remove}, so I use a
>> global variable. Is there any better way to do it?
>> I think the implementation of asus_wmi_battery_{init,exit} could be
>> improved, any ideas?
> Added Ognjen and Rafael.
> Is there a better way to have the sysfs file handlers of files added
> via the battery hooking API reach back to the device that hooked in,
> in order to access it's private data?
> I see that thinkpad_acpi also uses global variables for this :(
For now I think it is the only option.

I did notice that V1 of this patch has been merged into the for-next 
branch by Andy[1].
Was that a mistake Andy? and how do you want me to proceed? Should I 
create a refactoring patch? V1 really isn't the proper way to do this.

[1] 
http://git.infradead.org/linux-platform-drivers-x86.git/commit/d507a54f5865d8dcbdd16c66a1a2da15640878ca
>> 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            | 91 ++++++++++++++++++++++
>>   include/linux/platform_data/x86/asus-wmi.h |  3 +
>>   2 files changed, 94 insertions(+)
>>
>> diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c
>> index 34dfbed65332..06c830c1c04f 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"
>>
>> @@ -368,6 +370,92 @@ 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)
>> +{
>> +       if (asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_RSOC) >= 0)
>> +               battery_hook_register(&battery_hook);
>> +}
> asus_wmi_get_devstate_simple() checks ASUS_WMI_DSTS_STATUS_BIT and
> ASUS_WMI_DSTS_UNKNOWN_BIT for this device. However the spec makes no
> mentions of those bits being valid in this case.
> Your code probably works anyway but I think it would be better to use
> asus_wmi_dev_is_present() instead.
Ack
>
>> +static void asus_wmi_battery_exit(struct asus_wmi *asus)
>> +{
>> +       if (asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_RSOC) >= 0)
>> +               battery_hook_unregister(&battery_hook);
>> +}
> The unregister here should probably happen on the condition that we
> previously registered the hook.
> Don't rely on the devstate bits, since they might have changed
> unpredictably (who knows), there might be a problem communicating with
> the hardware, etc.
Ack, something like a: bool charge_threshold_supported?
>
>>   /* LEDs ***********************************************************************/
>>
>>   /*
>> @@ -2433,6 +2521,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 +2558,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.22.0
>>
Andy Shevchenko Sept. 7, 2019, 5:49 p.m. UTC | #3
On Mon, Aug 26, 2019 at 10:09 PM Kristian Klausen <kristian@klausen.dk> wrote:
>
> On 15.08.2019 05.28, Daniel Drake wrote:
> > On Tue, Aug 13, 2019 at 8:30 AM Kristian Klausen <kristian@klausen.dk> wrote:
> >> Most newer ASUS laptops supports settings a maximum charging percentage,
> >> which help prolonging the battery life.

> >> I can't pass the asus struct to asus_wmi_battery_{add,remove}, so I use a
> >> global variable. Is there any better way to do it?
> >> I think the implementation of asus_wmi_battery_{init,exit} could be
> >> improved, any ideas?
> > Added Ognjen and Rafael.
> > Is there a better way to have the sysfs file handlers of files added
> > via the battery hooking API reach back to the device that hooked in,
> > in order to access it's private data?
> > I see that thinkpad_acpi also uses global variables for this :(
> For now I think it is the only option.
>
> I did notice that V1 of this patch has been merged into the for-next
> branch by Andy[1].
> Was that a mistake Andy? and how do you want me to proceed? Should I
> create a refactoring patch? V1 really isn't the proper way to do this.

Oh, I see. Can it be fixed quickly? Then refactoring patch on top of
the branch is preferred.
Otherwise I will remove the original from the tree. Just tell me which
one is more solid.

> [1]
> http://git.infradead.org/linux-platform-drivers-x86.git/commit/d507a54f5865d8dcbdd16c66a1a2da15640878ca
Andy Shevchenko Sept. 9, 2019, 11:30 a.m. UTC | #4
On Sat, Sep 07, 2019 at 08:49:27PM +0300, Andy Shevchenko wrote:
> On Mon, Aug 26, 2019 at 10:09 PM Kristian Klausen <kristian@klausen.dk> wrote:
> > On 15.08.2019 05.28, Daniel Drake wrote:

> > I did notice that V1 of this patch has been merged into the for-next
> > branch by Andy[1].
> > Was that a mistake Andy? and how do you want me to proceed? Should I
> > create a refactoring patch? V1 really isn't the proper way to do this.
> 
> Oh, I see. Can it be fixed quickly? Then refactoring patch on top of
> the branch is preferred.
> Otherwise I will remove the original from the tree. Just tell me which
> one is more solid.

Okay, there are a lot of patches on top right now. And we are at last week
before merge window. So, send me a followup fix on top of our for-next branch.

Sorry for inconvenience.
Kristian Klausen Sept. 9, 2019, 5:34 p.m. UTC | #5
On 09.09.2019 13.30, Andy Shevchenko wrote:
> On Sat, Sep 07, 2019 at 08:49:27PM +0300, Andy Shevchenko wrote:
>> On Mon, Aug 26, 2019 at 10:09 PM Kristian Klausen <kristian@klausen.dk> wrote:
>>> On 15.08.2019 05.28, Daniel Drake wrote:
>>> I did notice that V1 of this patch has been merged into the for-next
>>> branch by Andy[1].
>>> Was that a mistake Andy? and how do you want me to proceed? Should I
>>> create a refactoring patch? V1 really isn't the proper way to do this.
>> Oh, I see. Can it be fixed quickly? Then refactoring patch on top of
>> the branch is preferred.
>> Otherwise I will remove the original from the tree. Just tell me which
>> one is more solid.
> Okay, there are a lot of patches on top right now. And we are at last week
> before merge window. So, send me a followup fix on top of our for-next branch.
>
> Sorry for inconvenience.
I just sent a refactoring patch series, feel free to change whatever is 
needed.
diff mbox series

Patch

diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c
index 34dfbed65332..06c830c1c04f 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"
 
@@ -368,6 +370,92 @@  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)
+{
+	if (asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_RSOC) >= 0)
+		battery_hook_register(&battery_hook);
+}
+
+static void asus_wmi_battery_exit(struct asus_wmi *asus)
+{
+	if (asus_wmi_get_devstate_simple(asus, ASUS_WMI_DEVID_RSOC) >= 0)
+		battery_hook_unregister(&battery_hook);
+}
+
 /* LEDs ***********************************************************************/
 
 /*
@@ -2433,6 +2521,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 +2558,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