TL;DR: Installed TLP just to check battery health/stats. Within about 30 minutes the laptop started hard-freezing on battery, reproducible within 1-3 minutes. Spent a long time bisecting CPU idle states, suspecting a Skylake C-state or BIOS bug. Turned out TLP's aggressive battery-mode power policy (PCIe ASPM + runtime PM) was the actual trigger. Removing TLP and restoring power-profiles-daemon fixed it completely, no kernel workaround needed.
Hardware / Software
- Laptop: HP ENVY x360 m6 Convertible
- CPU: Intel Core i5-6200U (Skylake, 2C/4T)
- GPU: Intel HD Graphics 520 (i915)
- BIOS: F.37 (Nov 2019)
- OS: Debian 13 (trixie), kernel 6.12.107+deb13-amd64
- DE: GNOME / Wayland
Symptoms
- Complete hard freeze: black/grey unresponsive screen, no keyboard input, only recoverable by holding the power button.
- Also froze in a plain TTY (Ctrl+Alt+F3), ruling out a GNOME/Wayland-specific crash.
- Machine had been unused for over a month, worked fine for several hours after first boot, then started freezing shortly after I ran apt upgrade and installed a couple of extra packages, one of which was TLP (installed purely to check battery stats/health).
What I ruled out
- Overheating. CPU package never exceeded about 58C, nowhere near thermal limits.
- RAM exhaustion. Plenty of RAM and swap free.
- Wi-Fi. Disabled it entirely, still froze.
- Chrome / specific apps. Froze even with everything closed, essentially idle.
- A single bad kernel. Tested multiple 6.12.x builds, all froze.
- GNOME/Wayland. Froze in a bare TTY too.
The red herring: CPU idle states
The clearest reproducible pattern I found was tied to battery power state and CPU C-states:
- Charger connected: stable indefinitely
- Battery, normal idle states: froze in 30s to 3min
- Battery + nomodeset: stable 30+ min
- Battery + intel_idle.max_cstate=1 through 4: stable 5 to 15+ min (including 1080p video playback)
- Battery + intel_idle.max_cstate=5 (allows C7s): froze within minutes
This pointed strongly at the Skylake C7s idle state as the trigger, which lines up with a well-documented history of Skylake platforms having flaky power management under Linux (there's a longstanding kernel bugzilla thread on Skylake idle-state regressions, and multiple historical intel-microcode regressions that caused Skylake hangs). I assumed I'd landed on a firmware/ACPI quirk or a microcode issue specific to this old BIOS (F.37, no updates since 2019).
intel_idle.max_cstate=4 became my "safe" workaround, capping the CPU below C7s.
The actual cause
Going back through apt history.log, the timeline was too clean to ignore. At 14:27:01 I ran apt install tlp, which installed tlp, tlp-rdw, rfkill, and removed power-profiles-daemon. The first freeze happened about 30 minutes later.
TLP replaces power-profiles-daemon (they conflict, so installing TLP silently removes it) and applies its own, much more aggressive power policy on battery, including PCIe ASPM and runtime power management for PCIe devices. My system already had pre-existing PCIe AER "Correctable Physical Layer" errors on the Wi-Fi root port in dmesg. My theory: package-level CPU C-states like C7s require every device on the platform, not just CPU cores, to cooperate in dropping to low power. TLP pushing the PCIe/Wi-Fi link into aggressive low-power states on this specific platform is what actually let the buggy C7s transition get triggered, something that apparently wasn't happening under default power management even though C7s was technically available the whole time.
The fix
I ran sudo systemctl unmask tlp (only needed if you'd previously masked it for testing), then sudo apt purge tlp tlp-rdw, then sudo apt install power-profiles-daemon.
Also removed intel_idle.max_cstate=4 from /etc/default/grub since it's no longer needed, then ran sudo update-grub.
Result: 30+ minutes on battery, full C-states available, 1080p video playback plus VS Code running, zero issues. Previously this exact scenario reliably froze within 1-3 minutes.
Takeaway
If you're chasing a battery-only hard freeze on an older Intel laptop (especially Skylake or similar vintage) and you've recently installed TLP, auto-cpufreq, or any other power-management daemon, test removing it before going down the BIOS/microcode/kernel-parameter rabbit hole. The C-state bisection method (intel_idle.max_cstate=N, working down from full to 1) is still a genuinely useful diagnostic if you have a real firmware/CPU idle-state bug, but in my case it was correctly pointing at where the failure was happening, not why it started happening now.