Repository navigation
Make M3 Pro sleep use 1.4 W instead of 2.6 W, and add the RTC wake alarm - #274
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>
0d60926 to
acd5f18
Compare
maralcbr
left a comment
There was a problem hiding this comment.
@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, thenRTC_FEATURE_ALARMis cleared quietly. The M1/M2 SMC RTC nodes only listrtc_offset. - Patches 3, 5 and 6 only reach T6030:
- the DT is
t6030.dtsi; apple,release-for-sleepis added only bygate_ext_apply()(t6030-display-gate.c:1466), and onlygate_t6030sets.dcpext;- the veto change is on the
external_nativepath.
- the DT is
- Patch 4's hardware off only reaches J514S/J516S, through
dcp_v14_board_nits()anddcp_v14_panel_nits_admit(). The newD(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:
- The handler runs. It gets
rtcthrough its cookie, so the handler itself is fine. - It calls
rtc_update_irq()(:323), which queuesrtc_timer_do_work(). - That work calls
ops->read_timeon the parent device. macsmc_rtc_get_time()then readsdev_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 andreadq_poll_timeout_atomic(), so calling it underrcu_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_offstays set and the nextpowerontries again. A transport failure latchesfailed(:493-501), and then the panel stays dark until reboot, like any other dead session.
- Off runs under
-
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)
- The smc-rtc binding needs updating.
apple,smc-rtc.yaml:21-30allows one cell and one name, and it hasadditionalProperties: false. The t6030 node now adds three cell names,interrupts-extendedandwakeup-source, sodtbs_checkwill reject it. Please add them as optional, with the alarm cells all-or-nothing. - For the release notes. An explicit
systemctl suspendwith a lit external display is still refused. On Omarchy, hypridle'sbefore_sleep_cmdonly 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. - Alarm precision. The alarm uses
floor(off / 2^15), butget_timeusesfloor((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>
acd5f18 to
12aec36
Compare
|
@maralcbr thanks. Pushed, still on Blocker: the alarm interrupt is live before drvdata (patch 3)
Notes
|
|
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 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 Each run was a lid-closed s2idle on battery, about 11–14 minutes, averaged from
So on T600x the display requests don't hold s2idle power up the way DISPEXT does on T6030. Marking report@c or report@d 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
left a comment
There was a problem hiding this comment.
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 > wakealarmwith /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
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
12aec36 to
b28cb3a
Compare
|
@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 RTC (patch 3)
pmp-report (patch 5)
Moved to the follow-up
DescriptionFixed the 1.386 W contradiction. Those rows went with the follow-up anyway. Testing
|
|
@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 ( |
|
Thanks for this series. A data point from an M3 Max (T6031, MacBook Pro 16" J516C), running v3 of it ( The T6031 RTC alarm in the device tree, the counterpart of patch 4 (same registers, SPMI interrupt 2), is in #285, against this branch.
For #283: once #259 gives the J516C record panel levels, 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
left a comment
There was a problem hiding this comment.
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.
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.
|
J314s (M1 Pro) suspend run for patch 1 I ran this series' CI package (
So patch 1 changes nothing visible on J314s and regresses nothing. |
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
rtcwakeand timed wake-ups didn't work.Patches
IRQCHIP_SKIP_SET_WAKE; the controller is always on).wakeup-source.wakeup-source).suspend_noirqand asserted again inresume_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 themapple,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:
min-statefloor 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):panel_hw_off=0)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_noware 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:
010001410000030000010001The 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)
30a65186with 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 onto63a0580awithout conflicts.tools/testing/selftests/rtc/rtctest: 8/8 pass;echo +10 > wakealarm: the interrupt count went 0 → 1,wakealarmcleared, and the control byte went back to01;03), and the rebind's probe cleared and disarmed it;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.rtcwake -s N+systemctl suspendwoke on time in every scripted run (more than 25, 20–300 s). Unbind/rebind twice, without-EEXIST.t6030-pmp-graph-tests.py) runs the sleep callbacks against its fake PMP:63a0580a:aurora.kunitconfig, with M3 Pro: keep HDMI audio working across pause, rate changes and output switches; add USB-C DisplayPort audio #265 v3 and the follow-up stacked): 1591 run, 1588 pass, 3 expected skips;check-commits.pyand checkpatch are clean on all 5 commits;W=1;dt_binding_checkpasses, anddtbs_checkwith the smc-rtc schema passes on all 115 Apple DTBs.Not covered:
🤖 Generated with Claude Code