Perusing the Linux Kernel source code: Setting battery charge threshold on ASUS laptops
My ASUS VivoBook X407UAR laptop supports ASUS Battery Health Charging
available in the MyASUS software. This software helps users extract maximum life out of a battery. However, MyASUS software works only on Windows. This article explains how to set a charge threshold on compatible ASUS laptops running openSUSE Tumbleweed. Any Linux distribution running Linux Kernel 5.4 and above should support this functionality.
For all this to work on Windows, ASUS supplies an ACPI BIOS with WMI objects embedded in it. ACPI is an open standard while WMI is a proprietary implementation of another open standard called CIM. So, on Windows, the ACPI BIOS with embedded WMI objects interfaces seamlessly. However, all of this makes it difficult for the Linux kernel to interface with platform devices. You know where I am going with this ;)
Setting a charge threshold using a systemd service
Check the battery name
Confirm if laptop supports ASUS Battery Health Charging
. If it does, find the battery name:
ls /sys/class/power_supply/
In my case it returns:
AC0 BAT0
To be double sure, list what is available in BAT0
:
ls /sys/class/power_supply/BAT0
If the output contains charge_control_end_threshold
, we are good to go. If it does not, then one of the following statements must be true:
- Linux Kernel version running on the laptop is older than 5.4;
- The laptop does not support
ASUS Battery Health Charging
.
Deploy a systemd
unit file
Create a systemd
unit
file with the following content:
[Unit]
Description=Set the battery charge threshold
After=multi-user.target
StartLimitBurst=0
[Service]
Type=oneshot
Restart=on-failure
ExecStart=/bin/bash -c 'echo 60 > /sys/class/power_supply/BAT0/charge_control_end_threshold'
[Install]
WantedBy=multi-user.target
I have set the threshold to 60 percent
because that is exactly what the MyASUS software would do in Maximum Lifespan Mode
on Windows.
Save it as /etc/systemd/system/battery-charge-threshold.service
. The unit
file can be named anything.
Enable and start the service:
systemctl enable battery-charge-threshold.service
systemctl start battery-charge-threshold.service
If no mistakes were made in following the above steps, the following should be the result:
cat /sys/class/power_supply/BAT0/status
Not charging
My GNOME 41 DE also prominently displays the status of the battery as Not charging
in the UI.
Will my Linux Kernel version support setting battery charge threshold?
Examine the history of charge threshold in Linux source
I had tried setting a charge threshold on the same laptop when it was running openSUSE Leap 15.3; openSUSE Leap uses 5.3.18 Linux Kernel which obviously does not support the charge threshold. It worked fine with the MX Linux Advanced Hardware Support
edition as long as I used it. I then moved to openSUSE Tumbleweed.
For those who like to dig deeper, git clone
the Linux source code. git
commands will help in obtaining various details about the history of linux/drivers/platform/x86/asus-wmi.c
. An example is given below.
git log --all drivers/platform/x86/asus-wmi.c | grep -i "charge threshold"
The output is:
The WMI method to set the charge threshold does not provide a
Fixes: 7973353e92ee ("platform/x86: asus-wmi: Refactor charge threshold to use the battery hooking API")
platform/x86: asus-wmi: Refactor charge threshold to use the battery hooking API
platform/x86: asus-wmi: Add support for charge threshold
Further, pick the oldest commit message and search:
git log --grep="asus-wmi: Add support for charge threshold"
This command gives the following output:
commit d507a54f5865d8dcbdd16c66a1a2da15640878ca
Author: Kristian Klausen kristian@klausen.dk
Date: Mon Aug 5 21:23:05 2019 +0200
platform/x86: asus-wmi: Add support for charge threshold
Most newer ASUS laptops supports limiting the battery charge level, which
help prolonging the battery life.
Tested on a Zenbook UX430UNR.
Signed-off-by: Kristian Klausen <kristian@klausen.dk>
Signed-off-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
Pick the commit hash
(d507a54f5865d8dcbdd16c66a1a2da15640878ca
, in this case) and do the following to see the changes compared to previous version:
git show d507a54f5865d8dcbdd16c66a1a2da15640878ca
Now, it is time to check what Linux Kernel versions this commit found its way into. The oldest is the most relevant in our investigation:
git tag --contains d507a54f5865d8dcbdd16c66a1a2da15640878ca
Use GitHub for better usability; terminal for speed
Once the commit hash
is determined using the terminal, it can be used in GitHub to analyze the history of ASUS ACPI WMI kernel drive/module. GitHub hosts a mirror of Linux Kernel and has a much better user experience than the terminal.
Latest Linux Kernel 5.9 and above
The latest Linux Kernel supports all available battery names for ASUS laptops that support ASUS Battery Health Charging
(BAT0
, BAT1
, BATC
and BATT
). BATC
support added via commit 1d2dd379bd99ee4356ae4552fd1b8e43c7ca02cd
. It is quite evident if we analyze linux/drivers/platform/x86/asus-wmi.c
:
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.
* Note: On some newer ASUS laptops (Zenbook UM431DA), the primary/first
* battery is named BATT.
*/
if (strcmp(battery->desc->name, "BAT0") != 0 &&
strcmp(battery->desc->name, "BAT1") != 0 &&
strcmp(battery->desc->name, "BATC") != 0 &&
strcmp(battery->desc->name, "BATT") != 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;
}
Arch Linux and its derivatives, or openSUSE Tumbleweed or Debian Sid or Fedora have full support.
Linux Kernel 5.8 and above
BAT0
, BATT
and BAT1
battery names supported. BAT1
support added via commit 9a33e375d98ece5ea40c576eabd3257acb90c509
:
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.
* Note: On some newer ASUS laptops (Zenbook UM431DA), the primary/first
* battery is named BATT.
*/
if (strcmp(battery->desc->name, "BAT0") != 0 &&
strcmp(battery->desc->name, "BAT1") != 0 &&
strcmp(battery->desc->name, "BATT") != 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;
}
Linux Kernel 5.7 and above
BAT0
and BATT
battery names supported. BATT
support added via commit 6b3586d45bba14f6912f37488090c37a3710e7b4
:
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.
* Note: On some newer ASUS laptops (Zenbook UM431DA), the primary/first
* battery is named BATT.
*/
if (strcmp(battery->desc->name, "BAT0") != 0 &&
strcmp(battery->desc->name, "BATT") != 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;
}
Linux Kernel 5.4 and above
Linux ACPI WMI driver/module for ASUS implements support for ASUS Battery Health Charging
and BAT0
via commit d507a54f5865d8dcbdd16c66a1a2da15640878ca
and commit 7973353e92ee1e7ca3b2eb361a4b7cb66c92abee
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;
}