ThinkPad T480 with internal and external battery

The ThinkPad T480 ships with two batteries — an internal BAT0 and a removable external BAT1 — and by default Omarchy installs power-profiles-daemon (PPD), which does not manage charge thresholds. The result: both batteries charge to 100% every time, accelerating cell wear. On my T480 BAT1 was already down to 47% of design capacity after only 226 cycles — a textbook case of a cell stressed by sitting fully charged.

The fix for ThinkPads is tlp, which reads and writes thresholds via native thinkpad_acpi and persists them across reboots. Since version 1.6, TLP coexists with power-profiles-daemon — just install it alongside and split responsibilities.

Initial diagnosis

Before changing anything, check battery state:

for b in BAT0 BAT1; do
  echo "=== $b ==="
  cat /sys/class/power_supply/$b/model_name
  echo "design: $(cat /sys/class/power_supply/$b/energy_full_design) µWh"
  echo "now:    $(cat /sys/class/power_supply/$b/energy_full) µWh"
  echo "start:  $(cat /sys/class/power_supply/$b/charge_control_start_threshold)"
  echo "stop:   $(cat /sys/class/power_supply/$b/charge_control_end_threshold)"
done

If start is 0 and stop is 100, there is no threshold protection. The ratio energy_full / energy_full_design is the battery’s real health.

Splitting responsibilities

The goal is to run both daemons, each owning its domain:

DomainOwner
Charge thresholds (40/80)TLP
Dual-battery / discharge orderTLP (via firmware)
USB/PCIe runtime PMTLP
Per-AC/BAT Wi-Fi power saveTLP
Profile menu (performance / balanced / power-saver)power-profiles-daemon
CPU governor and EPPpower-profiles-daemon

When TLP detects PPD running, it automatically disables its own CPU controls — no conflict.

Installation

# Install TLP and the NetworkManager hook
sudo pacman -S tlp tlp-rdw

# Enable the service (power-profiles-daemon already comes enabled on Omarchy)
sudo systemctl enable --now tlp.service

# Mask services TLP replaces
sudo systemctl mask systemd-rfkill.service systemd-rfkill.socket

If you previously removed power-profiles-daemon, reinstall it:

sudo pacman -S power-profiles-daemon
sudo systemctl enable --now power-profiles-daemon.service

Custom dual-battery config

Create /etc/tlp.d/00-thinkpad-t480.conf:

# CPU and platform_profile are managed by power-profiles-daemon.
# TLP handles thresholds, Wi-Fi PM, and runtime PM.

# 40/80 thresholds on both batteries
START_CHARGE_THRESH_BAT0=40
STOP_CHARGE_THRESH_BAT0=80
START_CHARGE_THRESH_BAT1=40
STOP_CHARGE_THRESH_BAT1=80

# Use native thinkpad_acpi (T480 supports it)
NATACPI_ENABLE=1
TPACPI_ENABLE=1
TPSMAPI_ENABLE=0

# Wi-Fi power save
WIFI_PWR_ON_AC=off
WIFI_PWR_ON_BAT=on

# USB/PCIe runtime PM
RUNTIME_PM_ON_AC=on
RUNTIME_PM_ON_BAT=on

Apply immediately without rebooting:

sudo tlp start

On the first run you should see:

Warning: CPU_ENERGY_PERF_POLICY_ON_AC/BAT/SAV is not set because power-profiles-daemon is running.
TLP started using profile balanced/BAT (auto).

That warning is expected — TLP noticed PPD and is handing CPU control over to it.

Why 40/80?

Lithium-ion cells degrade faster when held at voltage extremes for long periods. Keeping them between 40% and 80% minimizes electrochemical stress without giving up too much runtime. Common combinations:

RangeProfileTrade-off
40/80BalancedGreat lifespan, good runtime
50/70AggressiveMaximum lifespan, limited runtime
75/80GentleNearly full runtime, modest protection

For a laptop that spends most of its life plugged in, 40/80 is the recommended default.

Verification

sudo tlp-stat -b   # detailed battery status
sudo tlp-stat -s   # general status
sudo tlp-stat -c   # effective config

The output should show natacpi (thinkpad_acpi) = active and the thresholds applied on both. If a battery was above the stop threshold when you applied the config, TLP won’t force-discharge — it just stops charging, and the level drops naturally with use.

Discharge order

The T480 firmware discharges the external battery (BAT1) first and charges the internal one (BAT0) first. This is ideal — it preserves the hard-to-replace cell. TLP doesn’t change this behavior, nor does it need to.

If you want to force-discharge a specific battery (useful when it’s sitting above the stop threshold):

sudo tlp fulldischarge BAT1

Do this only while unplugged; the laptop stays usable on the other battery.

Helper scripts: toggling thresholds and AC/battery profiles

Two small scripts in /usr/local/bin keep the routine smooth:

cinco-battery-thresholds toggles the config between two common profiles:

sudo cinco-battery-thresholds home    # 75/80 — laptop sits plugged in all day
sudo cinco-battery-thresholds travel  # 75/100 — travel, max runtime
sudo cinco-battery-thresholds status  # show what's in the .conf and sysfs

The script rewrites START_CHARGE_THRESH_BAT[01] and STOP_CHARGE_THRESH_BAT[01] in /etc/tlp.d/00-thinkpad-t480.conf and runs tlp start to apply.

cinco-power-mode, invoked by a udev rule at /etc/udev/rules.d/99-power-profile.rules, swaps the power profile when AC is plugged or unplugged:

SUBSYSTEM=="power_supply", ATTR{type}=="Mains", ATTR{online}=="0", \
  RUN+="/usr/bin/systemd-run --no-block --collect --unit=cinco-power-battery \
        --property=After=power-profiles-daemon.service \
        /usr/local/bin/cinco-power-mode battery"
SUBSYSTEM=="power_supply", ATTR{type}=="Mains", ATTR{online}=="1", \
  RUN+="/usr/bin/systemd-run --no-block --collect --unit=cinco-power-ac \
        --property=After=power-profiles-daemon.service \
        /usr/local/bin/cinco-power-mode ac"

The script sets performance + 80% brightness on AC, and balanced + 40% brightness on battery.

Caution: avoid power-saver directly on battery. On the T480, intel_pstate in power-saver pins the CPU to ~800-900MHz with no turbo. Fine for light browsing; bad for development. Prefer balanced.

If the Omarchy profile menu stopped working

Clicking the battery icon in Waybar opens Omarchy’s profile selector (performance / balanced / power-saver) via omarchy-menu power. That menu calls powerprofilesctl directly — if you removed power-profiles-daemon at any point, the menu breaks.

The fix is to reinstall PPD (it coexists with TLP):

sudo pacman -S power-profiles-daemon
sudo systemctl enable --now power-profiles-daemon.service
sudo tlp start   # re-apply; TLP detects PPD and yields CPU control

Confirm both are running:

systemctl is-active power-profiles-daemon.service   # active
systemctl is-active tlp.service                     # active
powerprofilesctl list                               # 3 profiles available

Update: troubleshooting after an Omarchy upgrade

In May/2026, after running omarchy-update, three things broke at the same time. Documenting because the combo can hit other people too.

Symptom 1: CPU stuck at 800-900MHz

btop showed every core dragging. Cause: cinco-power-mode battery was setting power-saver (shown above as the example to avoid), which via intel_pstate forces energy_performance_preference=power and kills turbo boost.

Fix: edit the script to fall back to balanced by default on battery, with power-saver as a fallback:

case "$mode" in
  ac)      set_profile performance balanced ;;
  battery) set_profile balanced power-saver ;;  # was: 'power-saver balanced'
esac

Symptom 2: tlp.service stuck in failed state

journalctl -u tlp.service:

Starting TLP system startup/shutdown...
tlp[XXXX]: Applying power save settings...
tlp.service: Main process exited, code=killed, status=15/TERM
tlp.service: Failed with result 'signal'.

Death loop: every tlp.service start was killed with SIGTERM. Cause: the power-profiles-daemon unit ships with Conflicts=tuned.service tlp.service auto-cpufreq.service system76-power.service in [Unit]. When TLP starts, systemd stops PPD; PPD restarts itself via Restart=on-failure; systemd kills TLP in retaliation.

The obvious attempt that doesn’t work in this version of systemd:

# /etc/systemd/system/power-profiles-daemon.service.d/override.conf
[Unit]
Conflicts=

systemctl show power-profiles-daemon -p Conflicts still lists tlp.service. For Conflicts=, drop-ins can’t reset the cumulative list.

Fix that works: copy the full unit to /etc/systemd/system/ (which takes precedence) and edit it:

sudo cp /usr/lib/systemd/system/power-profiles-daemon.service \
        /etc/systemd/system/power-profiles-daemon.service

sudo sed -i 's|Conflicts=tuned.service tlp.service auto-cpufreq.service system76-power.service|Conflicts=tuned.service auto-cpufreq.service system76-power.service|' \
        /etc/systemd/system/power-profiles-daemon.service

sudo systemctl daemon-reload
sudo systemctl restart tlp.service power-profiles-daemon.service

Verify:

systemctl show power-profiles-daemon.service -p Conflicts
# Should show the list WITHOUT tlp.service

Symptom 3: BAT1 vanished from /sys/class/power_supply/

External battery physically docked, but the kernel only lists BAT0. Diagnosis:

$ ls /sys/class/power_supply/
AC  BAT0       # BAT1 gone

$ for d in /sys/bus/acpi/devices/PNP0C0A:*; do
    echo "$d: status=$(cat $d/status)"
  done
/sys/bus/acpi/devices/PNP0C0A:00: status=31    # internal OK
/sys/bus/acpi/devices/PNP0C0A:01: status=0     # external slot: empty

Status 0 means ACPI/BIOS reports “no battery in this slot” — it’s not the kernel failing to register, it’s firmware refusing to acknowledge.

On the T480, the external is mounted via a bay/dock mechanism: its physical_node points to /sys/devices/platform/dock.0, and the slot has an eject file. The LENOVO Rmv_Batt SSDT loads normally. The problem is the EC (Embedded Controller) with stale cache after the upgrade — it “forgot” the dock event from when the battery was inserted.

There’s a known bug (fwupd/firmware-lenovo issue #574) where the BIOS 1.55 → 1.56 update permanently corrupts the EC’s auth state, and not even a BIOS downgrade fixes it — only an external SPI flash reset. My case was milder: only volatile EC cache.

Fix that worked:

  1. EC reset via Emergency Reset Hole — small hole on the bottom of the laptop, marked with a circular arrow icon. Power off, unplug AC, press with a paper clip for ~10s, power back on.

  2. Battery hot-swap — unlatch, physically remove, wait 5s, reinsert firmly until the latch clicks. Power Bridge is designed for this: the internal keeps the system running.

After the reset, both ACPI and kernel saw it again:

$ ls /sys/class/power_supply/
AC  BAT0  BAT1   # back

$ cat /sys/bus/acpi/devices/PNP0C0A:01/status
31

If neither reset nor hot-swap brings it back, you likely have the issue #574 corruption — software won’t fix that one.

Environment

  • ThinkPad T480 (BIOS N24ET81W — updated from N24ET76W in 2026/04)
  • Arch Linux kernel 6.19
  • Omarchy (Hyprland)
  • TLP 1.10.0
  • Native thinkpad_acpi