Fixing Suspend/Resume on Meteor Lake: One Kernel Parameter, One Year Too Late
I held the power button down more than 300 times on this machine.
ThinkPad P1 Gen7, Intel Core Ultra 9 185H (Meteor Lake), kernel 6.x through 7.x. Every suspend attempt ended the same way: freeze on entry or black screen on resume, then a hard shutdown. Over a year of real work on a machine that couldn’t sleep. That is not a minor inconvenience — it means no lid-close during meetings, no quick suspend between tasks, battery draining because you forgot to plug in before walking away.
I tried the obvious things. PSR1, DC2, EnableS0ixPowerManagement, split_lock_detect. Two attempts on 6.18, both complete freezes with no logs to inspect. The fix was one parameter I was missing the whole time.
Symptoms — so you know you’re in the right place
- Freeze on suspend entry (system stops, display goes dark, no wake)
- Black screen on resume — power LED on, machine unresponsive
- Hard shutdown required every time
- No logs from the failed attempt (journalctl -b -1 is empty or truncated — the freeze happens before the logger flushes)
- Problem persists across kernel versions (6.x and 7.x both affected)
- Works fine under Windows — this is Linux-specific
Hardware that fits: any Meteor Lake laptop (Intel Core Ultra 100 or 200 series) using the i915 driver for the integrated GPU. P1 Gen7 is confirmed. Other MTL systems with an NVIDIA dGPU are likely affected.
Root cause
PSR2 (Panel Self Refresh 2) lets the display controller skip frame fetches when nothing changes on screen. The selective fetch variant (psr2_sel_fetch) is more aggressive — it only fetches changed regions of the frame.
On Meteor Lake, this selective fetch corrupts display pipeline state before the suspend path runs. The i915 driver hits an atomic commit failure, which hangs the GPU, which prevents the suspend handoff from completing cleanly. You get a freeze.
The linux-surface project isolated this in issue #2158. The fix disables selective fetch while keeping PSR2 itself active — you keep the display power savings, you lose only the more aggressive partial-region variant.
The fix
Minimal — add these to your kernel cmdline
i915.enable_psr=2 i915.enable_dc=1 i915.enable_psr2_sel_fetch=0 mem_sleep_default=s2idle
i915.enable_psr=2— PSR2 enabled (saves ~0.3 W, enables DEEP_SLEEP)i915.enable_dc=1— DC5/DC6 display power statesi915.enable_psr2_sel_fetch=0— the fix: disables selective fetch, prevents the hangmem_sleep_default=s2idle— Meteor Lake has no S3; s2idle (S0ix) is what you get
i915.enable_psr2_sel_fetch=0 is the single line that changes everything. Everything else is tuning.
With NVIDIA dGPU (RTX series)
i915.enable_psr=2 i915.enable_dc=1 i915.enable_psr2_sel_fetch=0
i915.enable_fbc=1 i915.enable_guc=3
i915.force_probe=7d55 xe.force_probe=!7d55
nvidia_drm.modeset=1 nvidia_drm.fbdev=1
nvidia.NVreg_DynamicPowerManagement=0x02
nvidia.NVreg_PreserveVideoMemoryAllocations=1
mem_sleep_default=s2idle
i915.force_probe=7d55 and xe.force_probe=!7d55 explicitly assign the Meteor Lake iGPU to i915 and block the xe driver from contending for it. On a dual-GPU system this matters. 0x7d55 is the P1 Gen7 device ID — yours may differ; check lspci -nn | grep VGA.
NVreg_DynamicPowerManagement=0x02 enables fine-grained D3 for the dGPU (RTX power-gates when idle).NVreg_PreserveVideoMemoryAllocations=1 lets the NVIDIA driver save and restore VRAM state across suspend — needed for reliable resume when the dGPU was active.
With QEMU/KVM GPU passthrough
i915.enable_psr=2 i915.enable_dc=1 i915.enable_psr2_sel_fetch=0
intel_iommu=on iommu=pt
mem_sleep_default=s2idle
iommu=pt (passthrough mode) keeps IOMMU overhead low for host devices. Keep it if you run VMs with GPU passthrough; CUDA on bare metal doesn’t need it.
How to apply (GRUB / grub-btrfs / systemd-boot)
GRUB — edit /etc/default/grub, append to GRUB_CMDLINE_LINUX_DEFAULT, then:
sudo grub-mkconfig -o /boot/grub/grub.cfg
systemd-boot — edit your loader entry in /boot/loader/entries/*.conf, append to the options line. No rebuild needed.
Testing
# Verify s2idle is active
cat /sys/power/mem_sleep
# Should show: s2idle [s2idle] or similar — s2idle in brackets = selected
# Force it if not set
echo s2idle | sudo tee /sys/power/mem_sleep
# Suspend
sudo systemctl suspend
# Wait 10 seconds. Press power button once to wake.
# If it works — capture logs immediately
dmesg > dmesg-after-suspend.log
journalctl -b > journal-after-suspend.log
# Quick sanity check
journalctl -b | grep -E 'PM: suspend|resume'
If you see PM: suspend entry and PM: suspend exit in the journal with timestamps a few seconds apart, suspend worked. If the machine is unresponsive after pressing the power button, it froze. Hard shutdown, then:
# Check previous boot
journalctl -b -1 | grep -E 'PM: suspend|i915.*fail|drm.*fail|panic'
dmesg -b -1 > dmesg-failed.log
The failure mode typically shows drm_atomic_commit errors or i915 assertion failures immediately before the freeze point.
Power tuning — what to do after suspend works
USB autosuspend — don’t use the global kill switch
I had usbcore.autosuspend=-1 in my cmdline, which disables USB autosuspend for every device globally. That works, but it’s lazy and costs battery. The right approach: enable autosuspend globally in TLP and exempt only the devices that must not sleep.
# /etc/tlp.conf
USB_AUTOSUSPEND=1
USB_DENYLIST="1050:0407 27c6:6594 04f2:b805"
The IDs above are YubiKey (1050:0407), Goodix fingerprint reader (27c6:6594), and Chicony integrated camera (04f2:b805). Find yours with lsusb. Add Bluetooth dongles, smart card readers, anything that must respond instantly.
Remove usbcore.autosuspend=-1 from your cmdline after confirming suspend works. Regenerate GRUB config, reboot, test again.
TLP — already well-tuned for Meteor Lake
If you’re running TLP, the settings that matter on MTL:
CPU_DRIVER_OPMODE_ON_AC=active
CPU_DRIVER_OPMODE_ON_BAT=active
CPU_SCALING_GOVERNOR_ON_AC=powersave
CPU_SCALING_GOVERNOR_ON_BAT=powersave
CPU_BOOST_ON_AC=1
CPU_BOOST_ON_BAT=0
PLATFORM_PROFILE_ON_AC=balanced
PLATFORM_PROFILE_ON_BAT=low-power
active mode is correct for intel_pstate on MTL — don’t switch to passive. powersave governor with intel_pstate is not what the name implies; it still boosts when needed, just with better efficiency decisions.
Power consumption — measured after the fix
Idle, screen on, on battery, light desktop:
- Idle draw: ~6.5 W whole-system (
upowerenergy-rate) - CPU package power: ~2.4 W (
turbostat --show PkgWatt) - Intel GPU RC6 residency: 98% (
intel_gpu_top) — the iGPU is parked almost the entire idle window - Battery: ~13 h at idle (89.9 Wh full ÷ 6.5 W; pack at 99.9% health) — 12 h+ realistic, mixed use less
98% RC6 means PSR2 is doing its job without the selective-fetch hang; above 90% idle is the target. Confirm on your own machine with intel_gpu_top and turbostat.
Known non-critical issues
NVIDIA “PRH failed to update thermal limit”
NVRM: GPU0 nvAssertFailedNoLog: Assertion failed: PRH failed to update thermal limit!
Appears twice on boot (at ~7 s and ~147 s on my system). This is a bug in nvidia-open — the Platform Request Handler fails to sync with GSP firmware. Non-fatal; doesn’t cause crashes; doesn’t affect suspend. If it bothers you, switch to the proprietary nvidia package. Otherwise ignore it for now.
“Correcting number of heads (0x00)”
Expected when no external display is connected. Not a bug in your config.
TLP vm.laptop_mode deprecation
tlp: vm.laptop_mode is deprecated. Ignoring setting.
Remove the line from your TLP config. Cosmetic.
What I got wrong before this
On 6.18 I was using i915.enable_psr=1 (PSR1) and i915.enable_dc=2. Two freeze attempts, no logs either time. The enable_psr2_sel_fetch parameter didn’t exist in that config. That was the gap — not the PSR level, not DC mode. One missing line.
nvidia.NVreg_EnableS0ixPowerManagement=1 was also in there. With psr2_sel_fetch=0 in place, that parameter is unnecessary — the suspend issue is display-pipeline-side, not S0ix negotiation. Removed it from the current config.
A year and 300+ hard shutdowns for a one-liner. That’s how it goes sometimes.
References
- linux-surface issue #2158 — where the PSR2 sel_fetch root cause was identified
- Arch BBS #313469 — kernel 7.x MTL regression thread
- Meteor Lake power tuning guide
- TLP documentation
- Ubuntu certified: ThinkPad P1 Gen7
Angel Sulev
Cybersecurity + Agentic AI Expert
Senior specialist in cybersecurity and Agentic AI with 30+ years turning security into competitive advantage.
About meRelated Posts
Private LLMs vs ChatGPT: why your company shouldn't use the OpenAI API for sensitive data
Every time your team pastes a contract, an internal email, or customer data into ChatGPT, that …
Zero Trust is not a product you buy: it's an architecture you build
In recent years “Zero Trust” has gone from being a rigorous security concept to a …
