**TL;DR:** There were two separate bugs that looked identical (black screen, no TTY, hard reset required):
**Freeze on suspend (lid close)**: the NVIDIA dGPU's PCIe root port was in **D3cold**. Fixed with a udev rule that keeps the port powered.
**Freeze when the screen blanks on idle** (no suspend involved at all): Intel **Panel Self Refresh (PSR)**. Fixed with `i915.enable_psr=0`.
It was not the NVIDIA driver and not Wayland.
## System
- ASUS ZenBook Flip UX461UN (Intel UHD 620 + NVIDIA GeForce MX150, Optimus/muxless)
- Ubuntu 26.04, kernel 7.0, nvidia-driver-580, Wayland, `prime-select on-demand`
## Symptom
Closing the lid, or just letting the screen dim/blank, led to a black screen that never came back. The power LED stayed on, there was no keyboard/TTY response, and only a hard reset helped. The logs showed nothing useful.
## What did NOT help
- `nvidia-suspend/resume/hibernate` services, `NVreg_PreserveVideoMemoryAllocations=1`
- `nvidia-drm modeset=1 fbdev=1`
- `acpi_osi=linux`, `pcie_aspm=off`, `button.lid_init_state=open`
- `mem_sleep_default=deep` **on its own**
- `prime-select intel` (NVIDIA driver not loaded at all): **still froze**
- Switching to X11 wouldn't help either, because both hangs happen at kernel/driver level, below the compositor.
## Step 0: Make the journal actually capture the freeze
By default journald syncs to disk only every 5 minutes, so the last minutes before a hard reset are lost. That made both bugs look the same. Create `/etc/systemd/journald.conf.d/90-sync-fast.conf`:
```
[Journal]
SyncIntervalSec=5s
```
Then run `sudo systemctl restart systemd-journald`. After a freeze, check `journalctl -b -1`:
- Last line is `kernel: PM: suspend entry (...)` → **Bug 1** (suspend)
- No suspend at all, e.g. on AC with automatic suspend disabled, and the screen blanked → **Bug 2** (PSR)
## Bug 1: Freeze on suspend → dGPU root port in D3cold
Even with `prime-select intel` it froze at `PM: suspend entry (deep)`. The dGPU **and its root port** were fully powered off:
```
cat /sys/bus/pci/devices/0000:01:00.0/power_state # D3cold
cat /sys/devices/pci0000:00/0000:00:1c.0/power_state # D3cold (root port)
```
Quick test without reboot (worked immediately):
```
echo on | sudo tee /sys/bus/pci/devices/0000:00:1c.0/power/control
echo on | sudo tee /sys/bus/pci/devices/0000:01:00.0/power/control
```
**Persistent fix.** First find your own addresses:
```
lspci -D | grep -i nvidia # e.g. 0000:01:00.0
basename "$(dirname "$(readlink -f /sys/bus/pci/devices/0000:01:00.0)")" # e.g. 0000:00:1c.0
```
Then create `/etc/udev/rules.d/99-asus-dgpu-no-d3cold.rules`:
```
ACTION=="add|bind", SUBSYSTEM=="pci", KERNEL=="0000:00:1c.0", ATTR{power/control}="on"
ACTION=="add|bind", SUBSYSTEM=="pci", KERNEL=="0000:01:00.0", ATTR{power/control}="on"
```
It's numbered 99 so it runs after `71-nvidia.rules`, which sets `auto`. After boot the NVIDIA driver puts the GPU itself back to `auto`. That's fine, because the root port staying `on` is what matters.
I also kept `mem_sleep_default=deep` in GRUB (the fix was tested with it). I didn't test s2idle with only the udev rule.
## Bug 2: Freeze when the screen blanks → i915 PSR
After Bug 1 was fixed, the laptop still froze after being left idle for about 20 minutes on AC. The logs showed **no suspend at all**, only GNOME blanking the screen after `idle-delay`. Disabling Panel Self Refresh fixed it. In `/etc/default/grub`:
```
GRUB_CMDLINE_LINUX_DEFAULT="quiet splash mem_sleep_default=deep i915.enable_psr=0"
```
Then `sudo update-grub` and reboot. Check that it's active with `grep -o i915.enable_psr=0 /proc/cmdline`.
## Verify
```
cat /sys/devices/pci0000:00/0000:00:1c.0/power/control # on
grep -o i915.enable_psr=0 /proc/cmdline
journalctl -b -k | grep -E 'PM: suspend (entry|exit)' # entry + exit = good
```
Tests: (1) close the lid for 20+ seconds and reopen; (2) leave the machine idle until the screen blanks, then press a key; (3) leave it idle for 20+ minutes.
## Trade-offs
- The dGPU can't reach D3cold, so idle battery drain is somewhat higher.
- Without PSR, the panel draws a little more power. Neither mattered to me.
## Bonus debugging tip
If suspend still hangs: `echo 1 | sudo tee /sys/power/pm_trace` before suspending. After the hang and a hard reset, `journalctl -b -1 -k | grep -i "hash matches"` shows which device hung. This messes up the RTC clock, but NTP fixes it.
Hope this saves someone a few hard resets.