Set Battery Charging Limit During Initramfs

2025-02-26

Linux has a cool knob named /sys/class/power_supply/BAT0/charge_control_end_threshold, which is used to control the charge threshold for laptop battery.

Setting the battery charge threshold

Echo some value to the following path:

sudo sh -c 'echo 80 > /sys/class/power_supply/BAT0/charge_control_end_threshold'

Persisting on reboot

There's currently 3 ways of doing it:

systemd-tmpfiles

Write the following to /etc/tmpfiles.d/custom-charge-limit.conf:

#    Path                  Mode UID  GID  Age Argument
w /sys/class/power_supply/BAT0/charge_control_end_threshold - - - - 50

This sets the threshold to 50, and it re-set it every time your OS is booted

According to online sources, set an udev rule in /etc/udev/rules.d/90_custom_battery.rules can help [1]:

SUBSYSTEM=="power_supply", ENV{POWER_SUPPLY_ONLINE}=="1", RUN+="/bin/sh -c 'echo 80 > /sys/class/power_supply/BAT0/charge_control_end_threshold'"

The charge control threshold gets applied when you plug in your power supply.

Patching the Linux Kernel

The Problem

For ASUS laptops, This sys setting persists until you turn off your computer and on again, that is because according asus-wmi.c in Linux source file, it resets the threshold every power cycle :

static int asus_wmi_battery_add(struct power_supply *battery, struct acpi_battery_hook *hook)
{
    /* 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;
}

Line 1454-1455 showed that the devstate for ASUS_WMI_DEVID_RSOC is hard-coded to 100.

The Solution

We can modify the source code, by changing 100 to some threshold you desire, and export a patch for further usage.

First, backup the original file, so that we will make a patch.

cd /usr/src/linux/drivers/platform/x86
sudo cp asus-wmi.c /tmp/
sudoedit asus-wmi.c

Go to line 1454 and change 100 to your desired value. Save and re-build kernel.

In the mean time, we save it to a patch that we can re-use later:

diff /tmp/asus-wmi.c asus-wmi.c > /tmp/asus-wmi.patch

When the new Linux come out, we can apply patch again with that patch.

# After running eselect linux
sudo patch /usr/src/linux/drivers/platform/x86/asus-wmi.c < /tmp/asus-wmi.patch

TODO: try to integrate it with Gentoo's gentoo-sources package [2] [3]


  1. Online website: link

  2. Creating a patch wiki page

  3. /etc/portage/patches/ link