Repository navigation
M3 Pro: panel off on DPMS, sleep with a dark external display - #283
erykwieliczko wants to merge 7 commits into
Conversation
The controller sits in an always-on block and its parent interrupt is a chained handler, so it keeps running through suspend. A wake-armed peripheral interrupt therefore needs nothing more from the chip, but without irq_set_wake or IRQCHIP_SKIP_SET_WAKE enable_irq_wake() fails and no SPMI device can wake the system. Set IRQCHIP_SKIP_SET_WAKE. Signed-off-by: Eryk Wieliczko <eryk@wieliczko.ninja>
The PMU that keeps the RTC counter also has a one-shot alarm, reached through three more PMU registers and an SPMI peripheral interrupt that can wake the system. Allow the alarm control, compare and mask cells after the offset cell, all three or none, and the alarm interrupt and wakeup-source. Assisted-by: Claude:claude-opus-5-5 Signed-off-by: Eryk Wieliczko <eryk@wieliczko.ninja>
The PMU that keeps the RTC counter also has a one-shot alarm: a 32-bit compare value in whole seconds of the free-running counter (CLKM >> 15, without the wall-clock offset), a mask, and a control byte with an enable, a monitor bit and a latched event that is cleared by writing it back. It raises an SPMI peripheral interrupt. When the device tree describes the alarm registers as nvmem cells and the interrupt, implement read_alarm, set_alarm and alarm_irq_enable, report the alarm from a threaded handler, and make it a wake source if the node says so. Without them the RTC stays alarm-less as before. Updates of the control byte keep its other bits. Firmware can leave the alarm armed or its event latched, so the probe disarms it before it requests the interrupt. Signed-off-by: Eryk Wieliczko <eryk@wieliczko.ninja> Assisted-by: Claude:claude-opus-5-5
The Chapel PMIC's RTC alarm uses registers 0x2000 (control and event), 0x2008 (compare value) and 0x200e (interrupt mask) and raises peripheral interrupt 2 of nub-spmi0. Make the controller an interrupt controller, add the alarm cells to the PMIC layout and give them, the interrupt and wakeup-source to the SMC RTC, so a timer can wake the system. Signed-off-by: Eryk Wieliczko <eryk@wieliczko.ninja>
An acknowledged DISPEXT request keeps the PMP's external display state up while no display is shown. The requests are asserted once at probe (apple,always-on) and never withdrawn, so they also hold through suspend-to-idle. Give report entries marked apple,release-for-sleep noirq sleep callbacks: by then every display processor has suspended, with its displays off and its links released or idle. Withdraw such a request if it is acknowledged, with one replacement write of the request entry as macOS releases its display requests, and assert it again in resume_noirq, before the processors resume. Without the request the external display stays dark until reboot, so a re-assert the PMP does not acknowledge in time is written again, three times at most. The T6030 display gate marks the DISPEXT requests of the processors it enables. The PMP graph host test runs the new callbacks against its fake PMP: an acknowledged request is withdrawn and asserted again, an unacknowledged or unmarked one is left alone, and a re-assert acknowledged late is written again. Signed-off-by: Eryk Wieliczko <eryk@wieliczko.ninja> Assisted-by: Claude:claude-opus-5-5
DPMS off only blanked the 14.7 internal panel, leaving its display controller scanning out. Once the blank has been presented, send A472 with the hardware-power selector (+8) and a null output word (+11), which stops scanout and the panel; power on sends the same with state 1 before the backlight is restored. The IOMFB client, the RTKit session and every mapping are kept. On J514S this takes screen-off power from 2.86 W to 1.83 W. System sleep still only blanks the panel: suspend-to-idle cuts that power itself (1.354 W asleep with the controller stopped, 1.313 W without), so an A472 off and on for every suspend would buy nothing. A refused power on is tried once more. If the controller stays off, the backlight stays off too rather than light a dark panel, and the next power on tries again. Answer the callbacks of those transitions on the panel: will_power_off (D2) and the intent swap (D591) as notifications, powerUpDART (D574) and set_kernel_power_assert (D700) with status 0, as the host keeps the display powered, and enable_backlight_message_ap_gated (D593), which the backlight restore after power on covers. Only the J514S/J516S panel sends A472 so; other sessions handle these callbacks as before. appledrm.panel_hw_off=0 restores the blank-only behaviour. Signed-off-by: Eryk Wieliczko <eryk@wieliczko.ninja> Assisted-by: Claude:claude-opus-5-5
A native external display processor (M3 Pro HDMI and USB-C) refused system sleep while a display was attached at all, lit or not, and while its link was up, on the grounds that resume would not bring the display back. Unplugging the display was the only way to suspend. Resume does bring it back. User space turns the displays off before sleep. Suspend then releases the HDMI link (as an unplug would) and keeps a Type-C tunnel. Resume samples HPD again on HDMI and powers the Type-C CRTC back up as after DPMS off. On a J514S, with a 4K120 TV attached over HDMI (4 cycles) and over USB-C (3 cycles), every resume brought back the picture, the mode and the DP audio sink. Refuse sleep only while the firmware is starting or a display is lit. A lit display at prepare time means user space did not turn it off. Signed-off-by: Eryk Wieliczko <eryk@wieliczko.ninja> Assisted-by: Claude:claude-opus-5-5
scottjones
left a comment
There was a problem hiding this comment.
Thanks for splitting these out, it made the two patches much easier to read on their own. We went through 4e87561 and 71019b8 against our #274 notes and checked what reaches our other Macs (J613, J700, J414c).
Our #274 points:
- Refused power on: answered. iomfb_v14_7.c:3414-3423 tries A472(1) once more, keeps the backlight off if it stays refused, and the next power on tries again, with v14_bl_power_on_refused covering it. What's left is that DRM thinks the CRTC is on while the panel is dark until something does another DPMS cycle. That's fine for now since it's logged.
- Callback gating: answered. The D2/D591/D593/D574/D700 answers at iomfb_v14_7.c:1160-1178 are behind dcp_v14_board_nits(), which only J514S/J516S get (:329-332, :1714-1718). The external handler still runs first (:1106-1110), so DCPEXT doesn't change. A correction to our own note: the D574 -EINVAL on DCPEXT (:981) came in with 2210268 and is already in integration. It isn't from this PR.
- A472 on every suspend: answered. :3456 skips it when pm_suspend_in_progress(), and v14_bl_sleep_blank_only covers it. pm_suspend_target_state is set before dpm_suspend_start (kernel/power/suspend.c:512), so the DRM helper's disable sees it.
- Dark-display veto: partly. dcp.c:4846-4851 and iomfb_v14_7.c:2122 now refuse only a lit display, and prepare still refuses a lit one (dcp.c:4858-4876). The unplug/replug, two-display and J516S runs are still open, as your "Not covered yet" says.
- t6030-display-gate.c:1464: answered.
A few new things:
- On Omarchy, patch 1 runs at every idle screen-off: stock hypridle runs
hyprctl dispatch dpms offat 330 s anddpms onin after_sleep_cmd. So the dark-panel case above is reachable there many times a day. - Patch 2 changes little on stock Omarchy today, but not quite for the reason in the description. With a display attached, logind's default HandleLidSwitchDocked=ignore means a lid close doesn't ask for sleep at all, and a Suspend from the menu happens with the display lit, which is still refused. We'd like Omarchy to behave like macOS here: on battery, closing the lid with a display attached sleeps; on the charger, it keeps driving the display. Omarchy would turn the displays off and then suspend, which is exactly the path patch 2 opens. So we're keen on it, and the cycle we'd most like covered is dpms off, suspend, resume, dpms on with a display attached, plus unplugging during sleep.
- Patch 1 is on for J516S too, but :3452-3455 says "the one this was measured on" and only J514S was measured. Could you either run a DPMS cycle on a J516S or gate it to j514s until someone does?
- In v14_bl_power_on_refused the retry is two dcp_poweron() calls in a row. Real DRM does off then on. A dcp_poweroff() in between, checking it only blanks and sends no A472 off since panel_off is already set (:3459), would test what actually happens.
- Three DPMS cycles is thin for something Omarchy will hit many times a day. A longer
hyprctl dispatch dpms off/onloop on J514S, plus a few dpms off, suspend, resume cycles on Hyprland with a display attached, would cover it. - Optional: the panel's D2/D591/D574 answers copy the external ones at :977-981, and gating them on the backlight-nits flag ties two unrelated features together. A small shared helper or a named predicate would read better.
Nothing changes on our other Macs. Patch 1 is behind dcp_v14_board_nits(), so J613/J615/J514c/J516c take the old blank-only path. Patch 2 only touches the native branch (external_native is T6030 only, dcp.c:4241), and J700 and J414c don't use the 14.7 boards.
For the RC: patch 2 should wait until after it. Patch 1 by itself looks RC-safe to us. It's narrow, can be turned off with appledrm.panel_hw_off=0, and has KUnit coverage, so it could go in once the longer DPMS run and the J516S check are done. If those don't happen in time, we'd rather take it with panel_hw_off defaulting to 0.
chriskearney
left a comment
There was a problem hiding this comment.
Reviewed exact head 71019b8, treating 4e87561 and 71019b8 separately from the already-merged #274 commits. Both new patches apply with zero fuzz to current integration ba46b62; all four resulting changed files equal the PR head.
Panel DPMS: the sequencing is sensible. Blank first; require the old framebuffer retired and no recorded failure/crash before hardware off. Only acknowledged A472 updates panel_off. Power-on retries once, keeps the backlight off after two refusals, and a subsequent off/on cycle can retry. System suspend bypasses hardware off, and changing panel_hw_off to 0 after a successful off still permits the necessary power-on recovery. The added callback responses are limited to the J514S/J516S panel; the external callback handler runs first.
Independent bounded CPU checks: 22 controls passed using six exact extracted production functions with mocked RPC/blanking/kernel boundaries, including the refusal recovery through an intervening off/on cycle, opt-out, unsupported board, unretired framebuffer, crash/failure gates, and starting/lit/dark external sleep decisions. This checks control flow, not real firmware or DRM/PM concurrency. I did not boot a Mac or repeat KUnit locally.
I also checked the raw CI logs for run 40974853043: KUnit reports 1718 pass / 3 expected skips / 0 fail, including all 10 backlight-lifetime cases; compile checks both new commits. That run used the older synthetic merge 413af74 onto 2f72554, so it is not current-integration CI qualification.
No additional source blocker found. I agree with Scott's remaining qualification requests: the default-on panel path also reaches J516S without a reported J516S DPMS run; the repeated-DPMS and actual off/on refusal recovery checks still matter. Restricting enablement or defaulting off remains an alternative if those checks are unavailable. Keep the dark-external sleep change separate and pending the declared unplug/replug, two-display and J516S coverage and Scott's RC sequencing request. Existing J514S measurements are not J516S qualification, and the CPU controls do not close these hardware gaps. I am leaving this as a comment rather than approving the combined PR.
|
Tested on a J516S (16" M3 Pro, Build: packaged DPMS cycle ( Power, session locked, backlight 120/500, wluma and Syncthing stopped, 3 × 60 s per state, SMC
Runs per state were within 0.05 W of each other with the screen off. Not tested yet: system sleep with this kernel, and the dark-external-display path (no external display here). |
Split out of #274, as suggested in the review there: the panel's display-controller power-off and sleep with a dark external display. Stacked on #274 (its five commits come first here); only the last two are new.
Patches
appledrm.panel_hw_off=0restores blank-only.Power (J514S):
Testing (J514S, a kernel with #265 v3, #274 and this PR, on
63a0580a)aurora.kunitconfig): 1591 run, 1588 pass, 3 expected skips. New cases:check-commits.pyand checkpatch are clean, and every commit builds alone withW=1.Known, outside the kernel. KWin 6.8.1 can hold sleep for logind's whole
InhibitDelayMaxSecif the pointer moves while it fades the screens out. It then suspends with the external display still lit, which patch 2 refuses. Fixed upstream in KWin6a7b78b.Not covered yet (wanted before patch 2 lands, per the #274 review):
🤖 Generated with Claude Code