Skip to content

Make M3 Pro sleep use 1.4 W instead of 2.6 W, and add the RTC wake alarm - #274

Merged
chriskearney merged 5 commits into
integration/aurorafrom
feat/m3-pro-sleep-power
Oct 30, 2026
Merged

chriskearney merged 5 commits into
integration/aurorafrom
feat/m3-pro-sleep-power

Conversation

@erykwieliczko

@erykwieliczko erykwieliczko commented Oct 29, 2026 •

Copy link
Copy Markdown
Collaborator

Problem. On the M3 Pro (J514S), suspend-to-idle drew about 2.6 W. That empties the 72 Wh battery in about 28 hours asleep, against about 53 hours at 1.35 W. There was no RTC wake alarm either, so rtcwake and timed wake-ups didn't work.

Patches

  1. spmi: apple: let peripheral interrupts wake the system (IRQCHIP_SKIP_SET_WAKE; the controller is always on).
  2. dt-bindings: rtc: apple,smc-rtc: describe the PMU alarm: the three alarm cells (all or none), the interrupt and wakeup-source.
  3. rtc: macsmc: add the PMU alarm. Read, set and enable go through nvmem cells and an SPMI interrupt, and are used only when the DT describes them. Control-byte updates keep its other bits, and the probe disarms an alarm that firmware left armed before it requests the interrupt.
  4. arm64: dts: apple: t6030: describe the RTC alarm (cells at 0x2000/0x2008/0x200e, nub-spmi0 interrupt 2, wakeup-source).
  5. pmdomain: apple: pmp-report: withdraw external display requests in sleep. The DISPEXT requests asserted at probe are released in suspend_noirq and asserted again in resume_noirq, as macOS releases its display requests. A re-assert the PMP doesn't acknowledge in time is written again, up to three times. The T6030 display gate marks them apple,release-for-sleep.

Patches 1, 3 and 4 come from aurora-silicon/linux#380, with review fixes in patch 3. Patch 2 is new.

The panel display-controller power-off and sleep with a dark external display attached (patches 5 and 7 of v3) moved to #283, as suggested in the review. Without them, sleep is still refused while an external display is attached, as on integration today.

Not carried over from #380:

  • its CPU-client DCS4/FAB2 floor and its never-started-DCPEXT patch, which have no counterpart here;
  • a PMGR min-state floor drop for sleep, which measured within noise (1.354 → 1.323 W).

Where the power goes (300 s s2idle, RTC wake, SMC coulomb counter charge_counter, on battery, no display attached):

Kernel Sleep power
integration (3 runs) 2.53 / 2.59 / 2.58 W
+ patches 1–5 and the panel power-off 1.354 W
+ the same, panel power-off disabled (panel_hw_off=0) 1.313 W

The DISPEXT withdrawal (5) is the ~1.25 W win. The panel power-off saves nothing in sleep, which is why the follow-up keeps it to DPMS. energy_now/charge_now are useless for this: the gauge's remaining-capacity estimate jumps ±55 mAh after a rest.

The alarm's register layout. It was worked out on this machine for #380; I have no written source for it. Dumps of 0x2000–0x200f on J514S show it:

State 0x2000 0x200c 0x200e
disarmed at probe 01 00 01
armed 41 00 00
fired, driver unbound 03 00 00
rebound (probe disarms) 01 00 01

The hardware clears the enable bit (6) when the alarm fires and latches the event in bit 1, and writing the byte back clears it. 0x200c doesn't change.

Testing (J514S)

  • Measured on integration 30a65186 with M3 Pro: keep HDMI audio working across pause, rate changes and output switches; add USB-C DisplayPort audio #265 underneath. The series doesn't depend on M3 Pro: keep HDMI audio working across pause, rate changes and output switches; add USB-C DisplayPort audio #265, and it rebases onto 63a0580a without conflicts.
  • RTC, v4:
    • tools/testing/selftests/rtc/rtctest: 8/8 pass;
    • awake echo +10 > wakealarm: the interrupt count went 0 → 1, wakealarm cleared, and the control byte went back to 01;
    • an alarm left to fire with the driver unbound was latched (03), and the rebind's probe cleared and disarmed it;
    • 3/3 RTC wakes from s2idle (25 s), on time. The DISPEXT request was withdrawn and read back 0x10390/0x390 (target/actual) after each resume. This was with a USB-C TV attached, on a kernel with M3 Pro: panel off on DPMS, sleep with a dark external display #283 stacked so that the attached display doesn't refuse sleep. The display came back each time, with no kernel warnings.
  • RTC, earlier: rtcwake -s N + systemctl suspend woke on time in every scripted run (more than 25, 20–300 s). Unbind/rebind twice, without -EEXIST.
  • Manual: sleep and wake by lid, power button and keyboard; quick wake→sleep cycles; keyboard backlight and brightness keys after wake.
  • The PMP graph host test (t6030-pmp-graph-tests.py) runs the sleep callbacks against its fake PMP:
    • an acknowledged DISPEXT request is withdrawn and asserted again;
    • an unacknowledged or unmarked one is left alone;
    • a re-assert acknowledged late is written again, three times at most;
    • 21/21 cases pass, and all 16 mutants are killed, 4 of them new.
  • v4, on 63a0580a:

Not covered:

  • J516S: it shares the DT, but wasn't run.
  • Other machines:
    • patch 1 applies to every Apple SPMI controller, but only matters once a device arms a wake interrupt;
    • patch 3 is inert without the DT cells;
    • patches 4–5 are T6030-only.

🤖 Generated with Claude Code

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>

@maralcbr maralcbr left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@erykwieliczko thanks. The write-up makes this easy to follow, and measuring with the coulomb counter rather than energy_now was the right call. I read all six patches with the shipped chips in mind. The scope is right. There's one small blocker in the RTC probe; the rest are notes and edge checks.

Scope, as I read it

  • Patch 1 (spmi-apple-controller.c:316) changes the irqchip on every Apple SPMI controller, M1/M2 included. It only takes effect once a consumer arms a wake interrupt, and in this tree that's only the new t6030 RTC node. So M1/M2 behaviour doesn't change. For s2idle, the chained-parent reasoning holds: suspend_device_irqs() leaves the chained AIC line enabled.
  • Patch 2 is inert without the three new cells and an interrupt (rtc-macsmc.c:348-386). A missing cell gives -ENOENT, which becomes -ENODEV, then RTC_FEATURE_ALARM is cleared quietly. The M1/M2 SMC RTC nodes only list rtc_offset.
  • Patches 3, 5 and 6 only reach T6030:
    • the DT is t6030.dtsi;
    • apple,release-for-sleep is added only by gate_ext_apply() (t6030-display-gate.c:1466), and only gate_t6030 sets .dcpext;
    • the veto change is on the external_native path.
  • Patch 4's hardware off only reaches J514S/J516S, through dcp_v14_board_nits() and dcp_v14_panel_nits_admit(). The new D(574)/D(700) answers (iomfb_v14_7.c:1166-1173) apply to every v14 panel session. That's harmless, since they answer "on" or "nothing", but please confirm it's intended.

Blocker: the alarm interrupt is live before the driver data is set (patch 2)

macsmc_rtc_init_alarm() requests the threaded IRQ (rtc-macsmc.c:373). The RTC ops are already installed (:421), but platform_set_drvdata() only runs at :438.

If an alarm event is pending when the driver probes, here's what happens:

  1. The handler runs. It gets rtc through its cookie, so the handler itself is fine.
  2. It calls rtc_update_irq() (:323), which queues rtc_timer_do_work().
  3. That work calls ops->read_time on the parent device.
  4. macsmc_rtc_get_time() then reads dev_get_drvdata(), still NULL, and dereferences it (:84-91).

A pending event is plausible after an rtcwake alarm crosses a reboot, for example. The fix is to set drvdata (and init alarm_lock) before requesting the IRQ.

In the same function, device_init_wakeup() and dev_pm_set_wake_irq() (:378-382) are never undone, on unbind or on a later probe failure. A rebind then gets -EEXIST from the wake IRQ. devm_pm_set_wake_irq(), plus a devm action for device_init_wakeup(dev, false), covers both.

What I checked

  • PMP withdrawal (pmp-report.c:1382-1430).

    • apple_pmp_report_set_state() uses a spinlock and readq_poll_timeout_atomic(), so calling it under rcu_read_lock() in the noirq phase is fine.
    • Only a bit that was acknowledged is withdrawn. A withdrawal that fails is still marked and asserted again at resume, which is the right default.
    • Ordering comes from the PM phases: the DCPs suspend in the normal phase, then the PMP withdraws in noirq, re-asserts in resume_noirq, and the DCPs resume afterwards. I found no genpd race in this topology.
  • Panel off/on (iomfb_v14_7.c:3138-3160, 3393-3440).

    • Off runs under v14->lock, after the blank has retired the frame (!v14->active_fb), with a live session.
    • On runs before the backlight restore.
    • If the firmware rejects the on call, panel_off stays set and the next poweron tries again. A transport failure latches failed (:493-501), and then the panel stays dark until reboot, like any other dead session.
  • Patch 6. A lit display still vetoes sleep at prepare. The reason is in the comment at dcp.c:4854-4856: veto before Thunderbolt tears the tunnel down. Otherwise suspend takes the existing path (dcp.c:4891-4960):

    • an H17P link is disconnected;
    • an HDMI link is released when no Type-C route is active;
    • a Type-C tunnel is kept.

    Your KASAN runs stacked on #265 v2 cover the held audio link across that path.

Notes (non-blocking)

  1. The smc-rtc binding needs updating. apple,smc-rtc.yaml:21-30 allows one cell and one name, and it has additionalProperties: false. The t6030 node now adds three cell names, interrupts-extended and wakeup-source, so dtbs_check will reject it. Please add them as optional, with the alarm cells all-or-nothing.
  2. For the release notes. An explicit systemctl suspend with a lit external display is still refused. On Omarchy, hypridle's before_sleep_cmd only locks the session, and DPMS off happens only on the idle timer. The win here is sleep with a display that is attached but dark.
  3. Alarm precision. The alarm uses floor(off / 2^15), but get_time uses floor((ctr + off) / 2^15), so the alarm can fire up to a second late. A comment is enough.

Policy: maintainer opt-in?

The default changes are all M3 Pro only, and all lower power:

  • sleep goes from 2.6 W to 1.4 W, through the PMP withdrawal;
  • DPMS off while awake goes from 2.86 W to 1.83 W, through the panel hardware-off;
  • sleep is allowed with a dark display attached.

appledrm.panel_hw_off=0 only undoes the panel part. @chriskearney, please confirm you're happy with these as defaults.

Edge checks

The PR has no hardware run for these. They're for edge, not a gate:

  • J516S: sleep power, RTC wake, and DPMS off/on with the panel.
  • USB-C display attached through sleep, on this exact series. The USB-C 3/3 runs predate the rebase.
  • One M1 or M2 suspend/resume, as a sanity check for patch 1.

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>
@erykwieliczko

Copy link
Copy Markdown
Collaborator Author

@maralcbr thanks. Pushed, still on 63a0580a. There is a new patch 2 for the binding, so the series is 7 patches now.

Blocker: the alarm interrupt is live before drvdata (patch 3)

  • The probe now sets drvdata and initializes alarm_lock first, before it gets any cell or requests the IRQ. A pending alarm reported at probe reads the time through a set drvdata.
  • The wakeup setup uses devm_device_init_wakeup() and devm_pm_set_wake_irq(), so unbind and a later probe failure undo it.
  • On J514S (KASAN kernel), the driver was unbound and rebound twice: no -EEXIST, and wakeup came back enabled. Then your scenario: an alarm set 3 s ahead, the driver unbound, the alarm left to fire, the driver bound again. The latched event was delivered right at the IRQ request (count 1), with no oops and no warning. Two RTC wakes from s2idle through the rebound driver followed, on time.

Notes

  1. Binding. New patch 2, dt-bindings: rtc: apple,smc-rtc: Describe the PMU alarm:

    • the cell names are either rtc_offset alone or all four, in order (oneOf), which makes the alarm cells all-or-nothing;
    • interrupts (max 1) and wakeup-source are allowed.

    With the old binding, dtbs_check on t6030-j514s.dtb gives exactly the three errors you predicted. With the new one, dt_binding_check passes, and dtbs_check with this schema passes on all 115 Apple DTBs, M1/M2 single-cell nodes included.

  2. Release notes. Added to the description: an explicit systemctl suspend with a lit external display is still refused, and the win is sleep with a display that is attached but dark.

  3. Alarm precision. Commented in macsmc_rtc_set_alarm(). With a tick offset (as on T6030), the alarm fires at the set second or up to one second after it.

D(574)/D(700) in every v14 session

Intended. On an external session, an unhandled callback was already answered with zeros, so the reply there is the same as before, minus the ratelimited warning. On an internal session they only come after an A472 with the hardware selector, and only J514S/J516S send one. The one difference: D(574) with an argument above 1 is refused rather than zero-answered. I haven't seen one.

Testing

@CoreyH

CoreyH commented Oct 29, 2026

Copy link
Copy Markdown

T600x (M1 Pro, J314s) data point: the display-request withdrawal doesn't help here

I tried patch 6's mechanism on a J314s (integration-based 7.2.11.aurora9, PMP on via #91, nothing attached). In t600x-die0.dtsi, pmp-disp0 (report@c) and pmp-dispext0 (report@d) are apple,always-on, so their requests stay asserted through sleep. Reading the report awake gives target = actual = 0x7000.

To test without a kernel build, I used a small out-of-tree module that does what patch 6 does for chosen ids. It withdraws them in suspend_noirq if they're acknowledged and asserts them again in resume_noirq, using the driver's register sequence under rep->lock; it reaches the built-in report through drvdata. The PMP acknowledged every withdrawal and every re-assert, and resume was clean each time, including the internal panel.

Each run was a lid-closed s2idle on battery, about 11–14 minutes, averaged from charge_counter. On this machine that counter is in mA·s, not µAh. energy_now rose 0.7–0.85 Wh across these short sleeps, as you warned.

withdrawn in sleep avg
nothing 1987 mW
0xd (pmp-dispext0) 2012 mW
0xc + 0xd (pmp-disp0 too) 1945 mW (current 163.0 → 161.6 mA, so mostly voltage)

So on T600x the display requests don't hold s2idle power up the way DISPEXT does on T6030. Marking report@c or report@d apple,release-for-sleep wouldn't buy power here, and keeping patch 6 scoped to T6030 looks right.

You listed an M1/M2 suspend for patch 1 as untested. I can build the series for J314s and run lid, power-button and keyboard wakes if that's still useful.

@scottjones scottjones left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this, the DISPEXT release is a big win and the write-up made it easy to follow. We read through all six patches at 12aec36 and checked what reaches our other Macs (J613, J700, J414c). Short version: we'd like to take the sleep win and the RTC alarm soon, and hold the panel hardware-off and the dark-display veto change for after the RC.

Scope on our other machines looks clean. The SPMI wake flag changes nothing today: no SPMI child on T8122, T8140 or T6021 calls enable_irq_wake (tipd only does with wakeup-source, which no Apple DT sets, and the J700 Baku RTC uses IRQF_NO_SUSPEND), and the T6021 SPMI node isn't an interrupt controller. rtc-macsmc is inert without the cells. The release-for-sleep property is only added through gate_dcpext_one, which is T6030 only, and the veto change only touches the native path (external_native is T6030 only).

Things we'd like changed:

  • rtc-macsmc.c:218, :227, :236 write the whole control byte. Clearing the event writes 0x02, which drops MONITOR and ALARM_EN for a moment, and disarm/arm write 0x01/0x41 over anything else firmware keeps in 0x2000. Could this be a read-modify-write?
  • Thanks for moving to devm_device_init_wakeup / devm_pm_set_wake_irq and for setting drvdata before the IRQ. rtc-macsmc.c:381 still requests the IRQ before devm_rtc_register_device (:449), and nothing disarms an alarm firmware left armed. The Baku driver found its firmware does that. Disarming at probe, before the request, would cover it.
  • The Baku layout (rtc-apple-pmu.c:17-23) has a separate event register at +0xc. Here the event is bit 1 of 0x2000 and there's no cell at 0x200c. The new binding and commit message describe it, which helps, but where did the Chapel layout come from? rtcwake waking on time shows the edge works but not the handler, since a wake happens even if the handler returns IRQ_NONE. A run of tools/testing/selftests/rtc/rtctest, or an awake echo +10 > wakealarm with /proc/interrupts going up and wakealarm clearing, would settle it.
  • iomfb_v14_7.c:3398-3406: if A472(1) is refused, panel_off stays set and we still restore the backlight, so the user gets a lit backlight over a dark panel until another DPMS cycle. Nothing triggers that cycle. We'd rather skip the backlight restore and retry, or at least not leave it like that silently.
  • iomfb_v14_7.c:1159-1174: the new callback answers aren't gated to J514S/J516S. They apply to every 14.7 native session, including the J613 panel and the T6030 DCPEXT, and D574 with in[0] > 1 now returns -EINVAL on DCPEXT where it used to be answered with zeros.
  • Thanks for the new host cases in t6030-pmp-graph-tests: an unacknowledged request left alone, the re-assert after sleep, and the release-for-sleep mark are all covered now, each with a mutant that fails, and the pmp-report.c:1378 comment reads right with patch 6. t6030-display-gate.c:1464 still says no external display is attached in sleep, so that one wants the same update. resume_noirq failing at pmp-report.c:1429 still only logs, and the external display is then gone until reboot. A retry, or a hotplug kick to the DCPEXT, would help there.

On patch 4 in sleep: your own numbers show it saves nothing in s2idle (1.354 vs 1.313 W), but it adds an A472 off and on to every suspend and resume through the DRM helpers. We'd be happier with it on DPMS only, or held until we've seen it on J516S too.

On patch 6: prepare still refuses a lit display, and on Omarchy hypridle only locks before sleep and doesn't DPMS off. So for our users this mostly won't change anything yet. The testing was on KWin with one TV. Before we drop that veto we'd want unplug and plug while asleep, two displays, and J516S.

One small thing in the description: the table has a measured 1.386 W with the HDMI TV attached, but "Not covered" says sleep power with a display attached wasn't measured.

For the RC we'd suggest splitting out 1-3 (with the RTC fixes) and 5. Patch 5 stands on its own, since without 6 nothing sleeps with a display attached. We'd test that on J514S with the rtc selftest and repeated rtcwake cycles, PTD readback and hotplug after resume, and one suspend each on J613, J700 and J414c. 4 and 6 can follow after the RC.

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
samucarre pushed a commit to samucarre/waybian that referenced this pull request Oct 29, 2026
Suspend goes from ❌ to 🟡 in both READMEs: suspend-to-idle works when
started by hand, with the GPU running, and the RTC alarm wakes the
machine (three out of three in testing); it is not wired to the lid yet
and its battery drain is not measured. docs/hardware.md gets a Suspend
section (what was ported from omacom/linux-aurora#274, what this board
needed on top, what was tested and what is not done). The kernel lineage
and the GRUB table list 0.5 to 0.9, with 0.9 as the default.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013Fj1pF1Aokg7nBQoCUUWum
@erykwieliczko
erykwieliczko force-pushed the feat/m3-pro-sleep-power branch from 12aec36 to b28cb3a Compare October 29, 2026 23:41
@erykwieliczko erykwieliczko changed the title Make M3 Pro sleep use 1.4 W instead of 2.6 W Make M3 Pro sleep use 1.4 W instead of 2.6 W, and add the RTC wake alarm Oct 29, 2026
@erykwieliczko

erykwieliczko commented Oct 29, 2026 •

Copy link
Copy Markdown
Collaborator Author

@scottjones thanks, that's a fair split. Done that way: this PR is now your 1–3 (with the RTC fixes) and 5, plus the binding patch, still on 63a0580a. The panel power-off and the dark-display sleep change moved to #283, stacked on this one.

RTC (patch 3)

  • Control byte. Every update is a read-modify-write now. A latched event is cleared by writing the byte back as read, so MONITOR and ALARM_EN stay as they are. Arm and disarm change only ALARM_EN and MONITOR, and never write the event bit as 1.

  • Alarm left armed by firmware. The probe disarms it, and clears a latched event, before it requests the interrupt.

  • Where the layout came from. It was worked out on this machine for j375c (Mac Studio M1 Max): rear Thunderbolt ports never become USB hosts #380, and I have no written source for it. So I dumped 0x2000–0x200f on J514S (table in the description):

    • armed: 0x2000 = 0x41;
    • fired with the driver unbound: 0x2000 = 0x03. The hardware clears ALARM_EN and latches the event in bit 1;
    • 0x200c stays 00 throughout;
    • the rebind's probe wrote the byte back, which cleared the event and left 0x01.

    Unlike Baku, there's no separate event register here.

  • Handler, not just the wake.

    • rtctest: 8/8 pass;
    • awake echo +10 > wakealarm: the interrupt count went 0 → 1, wakealarm read empty afterwards, and the control byte was back at 0x01;
    • 3/3 RTC wakes from s2idle through the new code, on time.

pmp-report (patch 5)

  • A failed re-assert on resume. It's written again, up to three times, each with the usual 50 ms wait for the acknowledgement. The target bit is set by the first write either way, so a late acknowledgement also recovers. New host case with a fake PMP that acknowledges late, plus a mutant that drops the retry (killed): 21 cases, 16 mutants.
  • t6030-display-gate.c:1464. Left as is here, because with the veto change gone the comment holds again: nothing sleeps with an external display attached. The follow-up updates it with the veto change.

Moved to the follow-up

  • Panel power-off (A472):
    • DPMS only now. Suspend only blanks, through pm_suspend_in_progress();
    • a refused power on is retried once. If the controller stays off, the backlight stays off and the next power on tries again;
    • KUnit covers both.
  • Callback scope. The new answers are limited to the J514S/J516S panel. One correction, though: D574 with in[0] > 1 didn't change on DCPEXT. dcp_v14_external_callback() answers D574 before the shared handler, and returned -EINVAL for that at 63a0580a already. My earlier reply had that wrong. What did change on DCPEXT was D700 and D593, from zero answers with a ratelimited warning to zero answers without one. With the gating, that's back as it was too.
  • Veto change. It waits for the tests you listed: unplug and replug while asleep, two displays, J516S. Agreed on hypridle too: on Omarchy it does nothing until displays get turned off before sleep.

Description

Fixed the 1.386 W contradiction. Those rows went with the follow-up anyway.

Testing

@erykwieliczko

Copy link
Copy Markdown
Collaborator Author

@CoreyH thanks, that's a useful negative result. It confirms the DISPEXT win is T6030-specific, and patch 5 (the withdrawal) stays scoped there.

Yes, an M1/M2 suspend run would help. On J314s only patch 1 (IRQCHIP_SKIP_SET_WAKE on the SPMI controller) changes anything. The RTC alarm is inert without the T6030 DT, and the rest is T6030-only. Lid, power-button and keyboard wakes, plus a few quick wake→sleep cycles, would cover it. Any set_wake/enable_irq_wake warnings in dmesg would be worth noting too.

@samucarre

samucarre commented Oct 30, 2026 •

Copy link
Copy Markdown

Thanks for this series. A data point from an M3 Max (T6031, MacBook Pro 16" J516C), running v3 of it (12aec36c2267, before the split) on integration 2f725546d6ac, plus my local J516C patches.

The T6031 RTC alarm in the device tree, the counterpart of patch 4 (same registers, SPMI interrupt 2), is in #285, against this branch.

rtcwake -s 30 + s2idle on the J516C. Its boot loader's device tree predates the alarm, so a local patch added the same properties at boot; the DTB built from that branch has the same ones.

  • GPU driver not loaded: 3/3 woke on the alarm and resumed with no PM errors.
  • GPU loaded: 3/3, woken by the alarm's SPMI interrupt (so patch 1 does its job on T6031 too), with the GPU running jobs after each resume. That needs a separate G15 change that keeps the T6031 GFX domain on through sleep: drm/asahi: g15: keep the T6031 GFX domain on through system sleep #288.

For #283: once #259 gives the J516C record panel levels, dcp_v14_board_nits() admits the J516C panel too, so DPMS off would stop its display controller, which hasn't been tried there. A board flag that keeps the A472 path to the J514S/J516S is in #284, on top of #283.

Not measured: sleep power on T6031. Not run on T6031: patch 3's v4 fixes, though v4 reads the same device tree properties. Not covered: external displays, since T6031 HDMI isn't in Aurora yet, so patch 5 has nothing to act on there.

Prepared with an LLM (Claude Code) and tested on the hardware.

@chriskearney chriskearney left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving b28cb3a for integration/aurora after exact-head source review, CI and bounded independent CPU checks. The RTC control writes preserve unrelated bits; driver state is initialized and a firmware alarm disarmed before requesting the IRQ; wakeup cleanup is managed. The PMP withdrawal/reassertion path has bounded retry and preserves error reporting. The panel hardware-off and dark-external-display admission changes were split to #283 and are not included in this approval.

Independent source checks passed 21 PMP graph scenarios with all 16 compiled mutants rejected, plus RTC program-alarm controls covering 512 initial-byte/arm combinations, nine I/O failures and short reads. These are host checks, not silicon wake or power measurements. The ignored disarm-error result in the IRQ handler remains a recorded inherited limitation.

I accept this scoped sleep/RTC change for integration with the remaining qualification explicitly outstanding: we have not run this exact #274-only J516S live-DT/awake-alarm/s2idle sequence or the planned M1 shared-path control. Package DTBs alone do not establish delivery of the RTC cells to the running bootloader DT. The author's stacked hardware results are not independent isolated qualification, and we do not claim the power figure as our measurement. Further hardware work retains its separate recovery and restore requirements. Proceed through the normal merge queue and required CI.

@chriskearney
chriskearney dismissed maralcbr’s stale review October 30, 2026 02:40

Superseded by reviewed b28cb3a: mutex/drvdata initialization precedes IRQ setup, firmware alarm is disarmed before request_threaded_irq, managed wakeup/wake-IRQ cleanup is present, and the RTC binding is updated. Panel hardware-off and dark-external-display admission were split out to #283. Maintainer approval 5707470045 records independent host checks and explicitly unrun isolated hardware qualification. Dismissing the older-revision request after verifying these source changes.

@chriskearney
chriskearney added this pull request to the merge queue Oct 30, 2026
@CoreyH

CoreyH commented Oct 30, 2026

Copy link
Copy Markdown

J314s (M1 Pro) suspend run for patch 1

I ran this series' CI package (linux-aurora 7.2.11.aurora10-1.382, built from 12aec36c2 before the split) on a J314s, installed beside the regular kernel. spmi-apple-controller.c is identical in the merged ba46b626e. No external display, on battery.

  • Lid: closed about 60 s, woke on opening, clean resume.
  • Power button: woke, clean.
  • Quick wake→sleep cycles: back-to-back 5 s sleeps by lid, each resumed.
  • Keyboard: doesn't wake, but it doesn't on 7.2.11.aurora9 either. On the M1 family the internal keyboard is SPI HID, and spi2.0 has no power/wakeup at all (separate issue to follow). Not a regression from this series.
  • After resume: brightness keys, keyboard backlight (set and read back), trackpad and Wi-Fi all fine.
  • dmesg: no set_wake/enable_irq_wake warnings. macsmc-rtc probes, with no wakealarm, as expected without the T6030 DT. The only resume noise is brcmf_p2p "failed to update device address -52", which aurora9 logs on every resume too.

So patch 1 changes nothing visible on J314s and regresses nothing.

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.

6 participants