Repository navigation
drm/apple: m4: Physically power the J713 display domains down while blanked - #1059
RyanTheTide merged 13 commits into
Conversation
|
I tested this on my J713, works great with 2x TB4 displays connected through both DPMS off and s2 idle. |
RyanTheTide
left a comment
There was a problem hiding this comment.
This no longer applies to integration/aurora. Patch 5 (pmgr-pwrstate consumer-switched states) conflicts in pmgr-pwrstate.c, pmgr-pwrstate-test.c and include/linux/soc/apple/pmgr-pwrstate.h with 55facfa (no PS reset recovery during system sleep) and a10defe (latched bit 11 PS reset on power-on). rtkit.c also conflicts with 0630fab (10 s handshake wait), and t8132-dcp.dtsi / t8132-j713-pmgr.dtsi with 76b6d84 and 108975e. Could you rebase it, and fold in the two rtkit retained-wake fixups that j713/s2r-wip carries on top of this series? The failing SEP source check (aurora-sep PKGBUILD sums) comes from the old base and should clear with the rebase.
An adopted J713 external display DART can be unpowered across sleep, so its registers are used only with its power confirmed: apple_dart_dispext_get() counts a use, an invalidation waits for the uses and marks the DART unconfirmed until a state check after power-up. The internal display DARTs can lose power the same way once their DCP powers its domains down. Give the gate its own flag, power_gated, set for the external display DARTs as before, and rename its state and helpers from dispext_* to pm_*. The external display DART's adoption, frontends, fault handling and state check stay as they are. No functional change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Bartosz Nitka <niteria@gmail.com>
On T8132 a power state register of a display power domain is guarded by a "clock protection" slice in the PS_PROT register bank. While the slice is enabled, a write that powers the domain's CPU down faults. The native DART power-down disables the DART's streams, flushes it and then clears the slice; power-up sets it again. Add apple,clock-protection to the DART binding: a phandle to the PS_PROT bank and the DART's slice in it. The bank is shared by several DARTs, so describe it as a syscon and add its compatible to the syscon binding. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Bartosz Nitka <niteria@gmail.com>
The J713 internal display DARTs are borrowed from the running DCP and stay powered: the DCP keeps the display domains on. To let the DCP power the domains down while the display is off, add apple_dart_dcp_power_off() and apple_dart_dcp_power_on() for a DART that has a clock protection slice (apple,clock-protection). Power-down, with the DCP CPU stopped and the DART's masters idle, marks the DART's power unconfirmed (apple_dart_pm_get() refuses from then on and the uses in flight are waited for) and gates its commands. Then, as the native DART power-down does, it requires no fault, waits up to 1 ms for the TLB command and outstanding transactions to drain, disables the enabled streams, flushes with the power-down TLB command (V2 | STT) and clears the clock protection slice. It saves the translations, their lock and the stream enables first. apple_dart_dcp_power_check() says, changing nothing, whether a power-down can run, so that the DCP can ask before it starts. Power-up restores the slice, requires the streams still disabled and the translations and their lock as saved (a DART that lost them is not rebuilt), flushes, enables the saved streams and confirms the power. A failure in either direction retires the DART's borrowed streams, as other display DART failures do; nothing is retried. While the DART is down its generic DMA streams, the display's SID0 and the DCP's SID23, keep working on their page tables: unmap, the flush after a map and translation use the tables in memory and no register, and the power-up flushes before it enables the streams. The DART's powered-down state changes only with the locks of all its borrowed streams held, so the power-up's flush and its confirmed power are one step for those updates. System resume leaves a powered-down DART alone; its gated commands already keep suspend and the fault handler off its registers. KUnit: a full cycle against RAM registers and a RAM-backed PS_PROT bank, a DART that comes back without its translations, refusals for a locked bank, a stream that stays enabled, a transaction that does not drain and a pending fault, and a SID0 stream whose unmap, flush and translation use only its page tables while the DART is down. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Bartosz Nitka <niteria@gmail.com>
The J713 display coprocessor's CPU, frontend and system power states are firmware-owned: the OS keeps them on and never touches their registers. macOS powers them off while the display is asleep, after stopping the coprocessor, and on again before restarting it, with a retention workaround on the CPU state. Add apple,consumer-switched for a firmware-owned state that its consumer may power off and on through the PMGR driver, and apple,retention-workaround for the PMGR registers to set while it is off: triplets of offset, mask and value. A chain of such states powers off child first and on parent first. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Bartosz Nitka <niteria@gmail.com>
…states Add apple_pmgr_ps_consumer_off() and apple_pmgr_ps_consumer_on() for the chain of apple,consumer-switched power states that starts at a consumer's power domain: off child first, on parent first. genpd still sees the states as firmware-owned and always on; the consumer has stopped its coprocessor before power-off and restarts it after power-on. Each state follows the native sequence. Power-off requires the state fully on and not in reset, disables automatic power management while staying on (when it was enabled), sets the apple,retention-workaround registers and then power gates, clearing the history bits. Power-on powers on with automatic power management disabled, gives the retention registers' workaround bits back the values they had (no other bit: power-off changed none) and then automatic power management as it was. Every write is read back or waited for; a failure leaves the state failed until reboot, and nothing is retried. A state that is not fully on is refused without being touched. apple_pmgr_ps_consumer_check() says, changing nothing, whether the chain can be powered off at all: consumer-switched, not failed and not off. Whether a state is fully on is known only at power-off: under automatic power management the J713 DCP CPU state reads level 0 while its CPU idles. KUnit: the J713 DCP CPU state reproduces the native power-off and power-on writes as captured on macOS, and keeps retention bits outside the workaround that change while it is off; a state without automatic power management or retention keeps its other bits; and a state that is not on, one whose actual state does not follow and a retention register that does not take its value are refused or failed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Bartosz Nitka <niteria@gmail.com>
A display pipe's BWR and boost interface enables are described as a power domain between the pipe's SYS and FE power states. The J713 internal display's power states are firmware-owned and on from boot, while the boot loader leaves only its boost enable set, so its enables cannot be such a domain; macOS clears them before powering the pipe down and sets them again after powering it up. Add apple,consumer-switched for an interface node that is not a power domain: the display coprocessor that refers to it clears its bits before it powers the pipe down and restores them after it powers the pipe up. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Bartosz Nitka <niteria@gmail.com>
An interface node with apple,consumer-switched keeps its BWR and boost bits reserved but is not a genpd domain. Add apple_pmc_interface_consumer_off() and apple_pmc_interface_consumer_on() for the consumer that names it in apple,pmc-interface: off saves both bits and clears the boost, then the BWR enable, as a pipe's power-down does; on gives both back the values they had. If the BWR enable does not take the change on the way off, the boost enable gets its old value back. Nothing is retried, and the bits of other pipes are left alone. As for the other interface domains, the bits return to their values at probe when the PMC goes away. apple_pmc_interface_consumer_check() says, changing nothing, whether the bits can be cleared now. KUnit: the internal display's bits with the boost enable only (as Linux finds them) and with both set (as macOS keeps them), next to dispext0's boost bit, and a BWR enable that does not clear. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Bartosz Nitka <niteria@gmail.com>
The J713 DCP takes its PMP DISP vote (power command 0x0f, logical device 8, state 1) when it activates, and the vote is held until reboot. macOS releases it after stopping the DCP and before powering the display domains down, and takes it again before powering them up. Add apple_pmp_j713_display_put(): the same power command with state 0, sent through the PMP owner's serialized request path (which consumes the exact reply), after which apple_pmp_j713_display_get() takes the vote again. The Rust owner forwards the release word to apple_pmp_set_device_power(), which already handles state 0. One request is in flight at a time, as before. A release that was not transmitted leaves the vote held; one whose outcome is unknown leaves it neither held nor available again, so the display cannot come back up on a vote that may be gone. Other accepted votes are kept. apple_pmp_j713_display_check_release() says, changing nothing, whether the vote can be released now and taken again later: held, nothing in flight, and admission open, as the later get is a new request and a failed untagged request closes admission. The DCP asks before it powers down. Host test: release and retake, a release with nothing held, one while another request is pending, one that was not sent and one without a reply, and the release check, also with admission closed by another request. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Bartosz Nitka <niteria@gmail.com>
apple_rtkit_idle() leaves a co-processor's firmware, endpoints and shared buffers in place, but waking it again goes through INIT and starts the system endpoints anew. The J713 DCP is to stop its CPU after the idle handshake, power its domains down and start the CPU again later. Its firmware then says HELLO by itself, and macOS answers the endpoint map with the endpoints that are still running, without INIT and without starting any endpoint. Add apple_rtkit_wake_retained(). From the idle state, with RX held by the caller since the idle, it restores the mailbox (apple_mbox_restore(), as its power domain may have been off), resumes RX and lets the caller start the CPU. While it runs, the endpoint map is answered with the endpoints started in this session, which RTKit now records, and nothing is started again; a started endpoint missing from the map fails the wake. A co-processor that first reports QUIESCED gets one ON request, any other IOP state before ON fails the wake. The usual boot wait then requires IOP ON and requests AP ON. Nothing is allocated, freed or reinitialized. A signal to the caller does not interrupt the wake's waits: the CPU is running by then, and nothing would undo that. An unexpected IOP state ends whichever wait the wake is in, the map or IOP ON after it. A last map part cannot echo endpoint 32 * base, as bit 0 of the reply is also MORE: such a wake fails. The DCP's map has two parts, with its endpoint 0 in the first. Also accept management message 0xc, which some co-processors send once idle after the IOP IDLE request, instead of warning about it. apple_mbox_restore() sets the mailbox-level enables of a started mailbox again, as apple_mbox_start() does. Host test: the retained endpoint map replies for the DCP's map, a missing started endpoint, a last part that cannot be echoed, QUIESCED before ON, an unexpected state before and after the map, the unchanged acknowledgment outside a retained wake, and which waits a signal can interrupt. The OSLog test's RTKit stub gains the started endpoint bitmap. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Bartosz Nitka <niteria@gmail.com>
On DPMS off the J713 internal display gets setPowerState(0) (A473), which stops its pipe, but the DCP keeps running and its CPU, frontend and system power domains stay on. macOS goes further for display sleep: it releases the AP link, idles RTKit, stops the DCP CPU and powers the three domains off, and on wake starts the CPU again on the retained firmware. Do the same once the display has been blanked for appledrm.power_gate_ms (0, the default, never), and before system sleep while blanked. First, changing nothing, ask every provider whether it can take part: both display DARTs, the PMP vote, the PMC interface and the PMGR states. If one cannot, the display just stays logically off. Then: A500, drain the callbacks, apple_rtkit_idle() and hold RX, stop the CPU (after its idle status), take in anything the firmware sent meanwhile (anything there fails the session, before a power-down), power down both display DARTs (streams off, power-down flush, clock protection off), release the PMP DISP vote, clear the PMC interface enables and power the consumer-switched CPU, frontend and system states off. The first frame afterwards reverses it before setPowerState(1), in macOS's order: domains, PMP vote, PMC enables, DARTs (state checked, streams back), then RTKit's retained wake, which restarts the CPU and gets HELLO, the endpoint map and IOP/AP ON without INIT, and the display preparation macOS sends (A414, A412). A frame cancels a power-down not yet started; a power-down under way finishes first. While the domains are down no register of the DCP, its mailbox or its DARTs is used: health checks, status and resume leave them alone, and resume leaves the display down until the next frame. Any failure fails the session, as other DCP failures do, but none refuses system sleep: the power-down is a saving, not a condition of sleep. Setting power_gate_ms to 0 also stops a power-down already scheduled. Only an internal J713 DCP whose node names its PMC interface (apple,pmc-interface) does this. debugfs iomfb_m4 shows the gate state, the number of power-downs and the last power-down and power-up times. On a J713 with power_gate_ms=3000, 204 power-downs and power-ups in one boot completed without an error, one of them started by a suspend 1.5 s after the blank: the display domains read back level 0 while off, stayed off across suspend-to-idle, and the display came back with a completed frame each time. Awake with DPMS off it draws about 15 mW less than setPowerState(0) alone (8 alternating pairs); one pair of suspend-to-idle runs differed by about 10 mW. It is a prerequisite for deeper SoC sleep with the display off. Host test: m4-dpms-retirement covers the power-down scheduling, its absence when not enabled, the power-up before setPowerState(1) and a power-up that fails; m4-power-gate the order of both sequences, a provider that refuses (nothing changes), a late firmware message (the session fails before any power-down) and power_gate_ms turned off after the blank. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Bartosz Nitka <niteria@gmail.com>
A DCP that powers its display domains down while the display is off clears its pipe's PMC interface enables first and restores them after powering the domains up. Add apple,pmc-interface, a phandle to the pipe's apple,consumer-switched PMC interface node. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Bartosz Nitka <niteria@gmail.com>
Describe what a power-down of the internal display domains needs on T8132: - the PMGR register block up to the display CPU's power-gate retention registers at 0x2504c and 0x25070; - the PS_PROT bank of clock protection slices, as a syscon; - the internal display pipe's PMC interface enables (BWR and boost bit 0), as a consumer-switched interface, since its power states are firmware-owned rather than a power domain. No consumer uses them yet. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Bartosz Nitka <niteria@gmail.com>
…down Let the internal DCP power its display down while blanked (appledrm.power_gate_ms): - the display CPU, frontend and system power states are consumer-switched, the CPU with the retention workaround macOS sets while it is off (0x3 in 0x2504c, 0x0 in 0x25070); - the DCP firmware DART and the display frontend DART name their clock protection slices, 42 and 8, as the ADT does; - the DCP names the internal pipe's PMC interface. The power states are in the policy the J715 shares, where nothing uses them yet: only the J713 names the PMC interface, and the J715 is untested. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Bartosz Nitka <niteria@gmail.com>
decc825 to
90eb784
Compare
|
Rebased onto 9260e1f and force-pushed. Conflicts:
On the two rtkit fixups, there's nothing to fold in. They only repair a bad conflict resolution I made when porting this series onto #1208, which emptied Re-tested on a J713 with this branch's kernel: 135 power-downs and power-ups in one boot with no errors, including a power-down triggered by s2idle. The host tests, KUnit, DT checks and each commit's build are re-run too; the description has the details. |
0099cf0
into
aurora-silicon:integration/aurora
integration/aurora now has the J713 display DART power gating (#1059), whose register definitions sit where this series' DART commit adds the v2.2/v2.4 error, client-partition and context-cache definitions. Keep both blocks; they define different registers. Signed-off-by: Diljit Singh <hey@djdev.me>
Rebased onto integration/aurora 9260e1f, and tested there (below).
Since #830, DPMS off on the J713 (MacBook Air 13" M4, T8132) sends
setPowerState(0)(A473), a logical power-down. The display co-processor (DCP), its DARTs and the display power states stay powered. This series powers them down: after a blank, it stops the DCP, quiesces its DARTs, releases its PMP DISP vote, and switches off the pipe's PMC interface and the three display power states (ps_disp_sys/fe/cpu). It brings them back before the first present. It is opt-in (appledrm.power_gate_ms, default 0) and lays the groundwork for deeper sleep states.Why it needs the clock-protection slice
Clearing the DCP CPU power state's AUTO bit took the whole machine down until the dart-dcp clock protection (GAPF / PS_PROT) slice was cleared first. Slice 42 of the PS_PROT bank at 0x38079c000 (its control word is at 0x38079cb80) guards 0x380710000, the CPU state itself; it reads 0x12 while on and 0x2 while off. On T8140, Apple's DART power-down in SPTM clears the slice the same way, and Aura hit the identical fault on the J700. The DART power-down therefore disables streams, flushes with V2|STT (0xa000), and clears the slice. Power-up restores the slice, checks that the DART kept its translations, flushes with V2 (0x8000) and re-enables the streams.
Order
The steps cross five drivers, and genpd cannot express the handshakes between them. The display domains also stay firmware-owned for every other consumer. So
iomfb_m4sequences the steps. Before the first one it asks every provider, changing nothing, whether it can take part (apple_*_check()); if one cannot, the display just stays logically off, as with #830. After that, each step is a narrow call: DT-gated with exact readback for the DART, PMGR and PMC, a release of the DISP vote for PMP, and a retained wake for RTKit. Every step fails closed (no retry; a failure quarantines the session as today), but none refuses system sleep.While the domains are down, the display's and the DCP's generic DMA (SID0, SID23) keep working on the DARTs' page tables in memory, without registers; the power-up flushes before it enables the streams.
Commits
apple,clock-protection+apple,t8132-ps-protsysconapple_dart_dcp_power_check()(+4 KUnit cases, including DMA while powered down)apple,consumer-switched,apple,retention-workaroundapple_mbox_restore()(new host testtools/asahi/rtkit-retained-tests.py)power_gate_ms, debugfsgate_*; host testsm4-dpms-retirement,m4-power-gate)apple,pmc-interfaceps_protsyscon, PMCinterface-disp0Testing (J713)
Tests:
apple-dcp/run.pypasses (88 ok, including the newm4-power-gate), and so do the RTKit host tests, including the newrtkit-retained-tests.py. The fulltools/asahi/run-host-tests.pypasses 187 of 200 locally; the same 13 jobs (asahi-m4, avd-av1-tail, avd-codec-tail, tunnel-route) fail on a pristine 9260e1f.scripts/dtc/apple-dt-check.sh(all 137 Apple device trees, base against head) reports no new warnings, and the five changed bindings passdt_binding_check.--strictflags only theCo-Authored-By:trailer form and, on commit 9, the new-file MAINTAINERS note.m4-error-paths.py: test 1 (pmp-vote-isolation), which carries the PMP release cases, passes. Test 2 (borrowed-dart-fault) also fails to compile on a pristine 9260e1f, because the harness lacksapple_dart_fail_borrowed; it is not from this series.Hardware, on this branch (J713):
t8132-j713.dtb(compared node by node), so this exercises the same description but not this branch's DTB file. Once merged, the DTB reaches the J713 through a rebuild of Aura's stage-2 bundle.power_gate_ms=3000), all witherror=0andhealth=0, and no kernel faults. Over 100 consecutive DPMS off/on cycles with the default idle cache, the power-down took 641/974/1130 µs and the power-up 767/840/1217 µs (min/median/max). The display was gated 3.27–3.53 s after DPMS off and back 0.38–0.44 s after DPMS on. Another 33 cycles ran withidle_cache_ms=0.power_gate_ms=600000, so only suspend could power them down) gated the domains on the way into s2idle (726 µs). They were still gated after resume, and the display came back 0.42 s after DPMS on; a normal 3 s cycle then passed.Hardware, before the rebase (05ae9bf):
j713-dcp-gate-test, on niteria:j713/dcp-physical-power-test).power_gate_ms=3000), all witherror=0andhealth=0. Over 200 consecutive DPMS off/on cycles, the power-down took 629/962/3805 µs (two cycles above 1.5 ms) and the power-up 746/819/1171 µs (min/median/max), on top of drm/apple: m4: Power the display down for system sleep, while blanked and while static #830's A473 calls (about 129 ms off, 55 ms on). The display was gated 3.24–3.41 s after DPMS off and back 0.37–0.39 s after DPMS on. An earlier revision of the series, before the review fixes, ran 231 cycles the same way.power_gate_ms=600000, so only suspend could power them down) gated the domains on the way into s2idle (761 µs). They were still gated after resume, and the display came back 0.4 s after DPMS on; a normal 3 s cycle then passed. With the earlier revision, the domains also stayed gated through a 20-minute s2idle.Power (battery, DPMS off, same boot; logical =
power_gate_ms=0; measured with the earlier revision, whose registers while gated are the same):power_nowPhysical was lower in all 8 awake pairs, by 14.8 ± 4.9 mW (95% CI). The coarser coulomb counter agrees within its noise (9 ± 11 mW over the same pairs), and an earlier run with two phases of each mode could not resolve a difference. The single s2idle pair differs by about 10 mW, consistent with the awake result. The saving today is small. The point of the series is that the display domains can now be off, which deeper sleep states will need.
🤖 Generated with Claude Code