Skip to content

J713: power the display down while it is blanked (depends on #449) - #501

Closed
erikwb wants to merge 11 commits into
aurora-silicon:integration/aurorafrom
erikwb:j713/dcp-blank-power-off
Closed

erikwb wants to merge 11 commits into
aurora-silicon:integration/aurorafrom
erikwb:j713/dcp-blank-power-off

Conversation

@erikwb

@erikwb erikwb commented Oct 26, 2026

Copy link
Copy Markdown
Contributor

Depends on #449. This branch is #449's head (4fe8d2f1acfa) plus one commit, drm/apple: m4: Power the display down while it is blanked. Only that last commit is for review here.

The problem

On the J713, DPMS off (screen lock, idle blank, hyprctl dispatch dpms) only blanks the display: iomfb_m4_blank() swaps to no surface, but the display pipeline stays powered until the next frame. #449 already powers it down for system sleep with setPowerState(0) once the panel is blanked, and brings it back with setPowerState(1).

The change

  • Blank: after a successful blank, setPowerState(0), the same call J713 (M4 MacBook Air): suspend-to-idle #449 makes for system sleep.
  • Next frame: the first present after DPMS on sends setPowerState(1) before its swap, so the compositor's first frame brings the panel and backlight back. This is the path resume already uses.
  • Suspend while powered down: suspend skips its own setPowerState(0), and the display stays down after resume until the next present.
  • Refusal: a refused or failed transition fails the session, as in J713 (M4 MacBook Air): suspend-to-idle #449's sleep path. Such a failure was possible only at system sleep before this commit; it is now possible at every blank and wake. The stress run below is the evidence that it holds up.
  • debugfs: iomfb_m4 gains display_off, off_cycles, and off_us / on_us (last/longest).

How it was tested

The tested kernel is the J713 test tree with this commit. Its iomfb_m4.c is byte-identical to this branch's, its config matches the J713 daily kernel's, and its callers (dcp_poweroff() → iomfb_m4_blank(), dcp_flush() → iomfb_m4_present(), platform suspend and resume) are the same as here. This branch alone does not boot a J713 (it lacks the other J713 series), as with #449.

Power. Idle on battery, 2 s samples over 120 s per phase, medians. Each column is one run on one boot, so compare within a column:

Phase Without this commit With it
Display on, backlight off 3038 mW 2926 mW
DPMS off 2692 mW 1728 mW
Display on, backlight off 3017 mW 2914 mW
Saved by DPMS off 346 mW 1198 mW

The draw returns to its starting level after DPMS on in both runs.

Stress. 200 blank/wake cycles through Omarchy's DPMS commands, checking debugfs after every step. All 200 passed, with no errors. Each blank powered the display down, and each wake powered it up with new frames flowing.

Last Longest
setPowerState(0) 128 ms 132 ms
setPowerState(1) 55 ms 56 ms

The power-down runs inside the commit that disables the CRTC, so that commit takes about 130 ms longer. Hyprland's DPMS dispatch still returned in 10–12 ms.

Other cases.

  • Brightness while blanked: set to 100/500 while the display was down. It took effect with the first frame after wake (the firmware logged the matching value and enabled the backlight), and the panel came back visibly dimmer.
  • Blank, then system sleep: blanked with DPMS off, then suspended through /sys/power/state. Suspend skipped its own power-down (off_cycles unchanged), resumed with the display still down, and one key press brought it back.
  • Lid-close suspend: s2idle entry and exit with suspend's own blank, display back on resume. Touch ID worked before and after.
  • Firmware messages: the only one after power-up is IOAVVideoInterface open failed, which J713 (M4 MacBook Air): suspend-to-idle #449's sleep path also logs, with no effect.

Known limits

  • A CRTC-only commit after DPMS on (no new framebuffer) leaves the display down until the next frame. Before this commit the same case left it blanked, so nothing that used to show a picture now doesn't.
  • Unbind, shutdown or kexec while the display is down leave it down. A reboot through iBoot resets it.
  • Other T8132 machines take the same path, but only the J713 device tree enables this DCP today.

Checks on this branch

  • checkpatch.pl --strict: clean.
  • Full arm64 build (defconfig + asahi.config + the J713 fragments, Image, modules, dtbs): succeeds. Its only warnings are 8 Rust fetch_update deprecation notices, in code this commit doesn't touch.
  • tools/testing/m4-dcp-errors/run.py: passes, as on J713 (M4 MacBook Air): suspend-to-idle #449's head.
  • tools/testing/apple/check-backlight.py, check-backlight-retry.py and check-lifetime.py: do not compile on J713 (M4 MacBook Air): suspend-to-idle #449's head either. Their harnesses predate the current headers (linux/math64.h not found, and apple_dcp_afkep members moved). The errors are identical on both heads, and this commit doesn't touch those files.

erikwb added 11 commits October 25, 2026 20:20
IOMFB 26.x takes setPowerState (A473) on the out-of-band channel, the
older contexts' +4, which the T8132 words carry as stream 1 (bit 10): the
AP packet goes at shared-memory offset 0x8000, the firmware answers on
the same stream, and the callbacks it makes meanwhile arrive at 0x68000
with bit 10 set. The J713 backend so far admitted stream 0 only.

Let a top-level AP call name stream 1, and while it is outstanding,
decode incoming words with bit 10 as stream-1 packets. Calls nested in
one of its callbacks stay on stream 1, at the next aligned offset after
the outstanding packet, as on stream 0. Stream-0 traffic is unchanged.
The first user is system sleep.

Signed-off-by: Erik Bourget <erik@ebourget.net>
Powering the J713 display down and up with setPowerState (A473), the
26.6.2 firmware calls back into the AP: acknowledgeSetPowerState
(D106), will_power_off_signal (D002), get_calendar_time_ms (D209),
powerUpDART (D572), swap_complete_intent (D592), set_kernel_power_assert
(D700), aot_supported_peek (D203), set_power_trace_enable (D008),
set_idle_caching_state_ap (D117) and enable_backlight_message_ap_gated
(D594). The exact-shape table refused them, which quarantines the
session.

Add their shapes and answer them for this machine: the notifications
and setters with an ACK, the status-returning calls with success, AOT
support with false, and the calendar time in milliseconds. The DART
and the power assertion are the AP's to hold: the J713 keeps the
display domains on across suspend-to-idle.

Signed-off-by: Erik Bourget <erik@ebourget.net>
The J713 DCP refused system sleep: the retained T8132 session had no
qualified handshake. setPowerState (A473) out of band is that handshake
on IOMFB 26.x: with the panel blanked, setPowerState(0) powers the
display pipeline down (the firmware logs "power on -> off" and disables
its M3 helpers) and setPowerState(1) brings it back, after which the
next present scans out again.

apple-drm is a consumer of the DCP, so its suspend handler has blanked
the panel when the DCP suspends, and its resume handler restores the
CRTC after the DCP resumed. Power the display down in between, with the
coprocessor running and its power domains kept on through the wakeup
path, as on the older DCPs; refuse sleep should the panel still scan
out. Presents while the display is down are refused.

The request is the 16-byte {u64 state; u8 flags[4]; u8 result_null;
u8 pad[3]}, the reply {u32 value; u32 status}; a nonzero status fails
the transition and the session.

Signed-off-by: Erik Bourget <erik@ebourget.net>
The J713's retained DMA owners refuse system sleep: the radio's DART
with managed streams, and the display DARTs, which borrow the running
DCP's live page-table root. Neither has a suspend boundary of its own,
and their state cannot be restored from a copy.

Suspend-to-idle does not need one. These DARTs have no power domain of
their own in the tree, and the DCP keeps the display domains on across
s2idle, so their state stays where it is. Let them through for
suspend-to-idle only, saving their registers solely for resume to check
that nothing moved. Every other sleep state is still refused.

A DART whose retained state changed cannot be trusted with its live DMA,
and a resume error does not stop the PM core from resuming consumers.
Quarantine the managed streams and the borrowed display consumers
through the same paths as a translation fault before failing resume, so
that their later maps are refused. A host test exercises the extracted
check, quarantine and map functions.

Signed-off-by: Erik Bourget <erik@ebourget.net>
The J713's radio port, whose streams the OS adopts from the boot
loader's handoff, refuses system sleep in its noirq phase. It has no
suspend boundary of its own, but suspend-to-idle does not need one:
the port has no power domain in the tree, so it stays up as any other
internal port does, whose resume does nothing. Let it through for
suspend-to-idle only; every other sleep state is still refused.

Signed-off-by: Erik Bourget <erik@ebourget.net>
A coprocessor can keep running in its power domain across system sleep
without system-sleep callbacks of its own, as the J713's GPU firmware
does. Describe such a domain, which the OS keeps on during system
sleep; runtime power management is unaffected.

Signed-off-by: Erik Bourget <erik@ebourget.net>
…tem sleep

The J713's G16 GPU has no system-sleep callbacks, so in the noirq
phase genpd powers its GFX domain off, and the TVM gate above it, under
the running GPU firmware; the power-on at resume then times out. Let a
domain marked apple,sleep-retained decline its power-off while the
system sleeps: genpd then keeps it and its parents on. Runtime power
management is unchanged.

Signed-off-by: Erik Bourget <erik@ebourget.net>
Some coprocessors message the AP periodically while idle: the J713's
GPU firmware asks for ring service about twice a second, and its AOP
sends a report every second. Describe a mailbox whose messages need not
wake the system, so the OS can leave them in the FIFO while it sleeps
and handle them at resume.

Signed-off-by: Erik Bourget <erik@ebourget.net>
…uest

The receive IRQ of the ASC mailboxes is IRQF_NO_SUSPEND, so every
coprocessor message wakes a CPU out of suspend-to-idle. On the J713
the GPU firmware's idle ring-service kicks and an AOP report every
second wake it about three times a second: in 938 s of s2idle, 1472
wakeups and only 512 s in deep idle.

For a mailbox marked apple,hold-rx-in-sleep, mask the receive IRQ from
noirq suspend to noirq resume, so its messages wait in the FIFO and the
consumer handles them on resume. The mailbox is a supplier of its
consumer, so this runs after the consumer suspended and before it
resumes. The firmware throttles once its outbox fills: after 148 s of
s2idle, 11 GPU and 3 AOP messages were waiting, with 3 wakeups and
146.9 s in deep idle.

Signed-off-by: Erik Bourget <erik@ebourget.net>
The G16 firmware keeps running across suspend-to-idle: keep its GFX
domain on through system sleep, and hold its idle ring-service kicks in
the mailbox instead of waking the system about twice a second.

Signed-off-by: Erik Bourget <erik@ebourget.net>
With DPMS off, the J713 display was only blanked: the swap to no
surface stops presenting, but the display pipeline stays powered. A
blanked panel is the state in which system sleep powers the display
down with setPowerState(0), and the same works here.

After a successful blank, power the display down, and power it back up
with setPowerState(1) before the next present, so the first frame after
DPMS on brings the panel back. System sleep with the display already
down leaves it down and skips its own setPowerState(0); after resume the
next present powers it up. A refused transition fails the session, as
in system sleep. debugfs reports whether the display is down, how often
it went down, and how long the last and longest power-down and power-up
took.

On the J713, idle on battery with the backlight off, DPMS off saved
346 mW before this change (3.04 -> 2.69 W) and saves 1.20 W with it
(2.93 -> 1.73 W), each measured within one run. Over 200 blank/wake
cycles, power-down took 128-132 ms and power-up 55-56 ms.

Signed-off-by: Erik Bourget <erik@ebourget.net>
@erikwb

erikwb commented Nov 2, 2026

Copy link
Copy Markdown
Contributor Author

Superseded by #830, where this is commit 4, on top of #449's DCP sleep handshake (commits 1–3 there). On the current base, the host test m4-dpms-retirement compiles the blank and present paths, so this commit now also gives its harness stubs for the new calls, plus assertions for the power-down and power-up.

@erikwb erikwb closed this Nov 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant