Skip to content

pmdomain: apple: pmc: enable the M4 external display boost, for 4K at 120 Hz - #1006

Merged
RyanTheTide merged 3 commits into
aurora-silicon:integration/aurorafrom
erikwb:j713/pmc-dispext
Nov 6, 2026
Merged

RyanTheTide merged 3 commits into
aurora-silicon:integration/aurorafrom
erikwb:j713/pmc-dispext

Conversation

@erikwb

@erikwb erikwb commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

On the M4 (T8132) the PMC ignores an external display pipe's bandwidth-request (BWR) and boost votes until the pipe's bits are set in two interface enable words. With them clear, the pipe's display clock stays at its lowest performance state. Everything up to about 772 Mpixel/s works, but a 3840x2160 120 Hz mode (1188 MHz) shows only the first lines of each frame and the rest stays dark. macOS sets the bits when the pipe's SYS power state powers on and clears them before it powers off. This PR does the same, through genpd.

Enable Word dispext0 dispext1
BWR PMC + 0xac020 ("rails" + 0x2020) bit 3 bit 4
boost PMC + 0xac028 ("rails" + 0x2028) bit 1 bit 2
  • dt-bindings: a new interface-* child node of the PMC, provided as a power domain, with power-domains (the power state the enables need), apple,bwr-bit and apple,boost-bit. Only the T8132 PMC may have them.
  • Driver:
    • The per-SoC layout gets the two words' offsets, 0 where a SoC has none (interface nodes are rejected there).
    • Each interface node is a power domain joined to its parent, as the cluster domains are. Power on sets boost, then BWR; power off clears them. Each is a read-modify-write under the PMC's raw spinlock and is read back. A word that doesn't take the change restores the earlier boost bit and fails with -EIO.
    • The domains are IRQ safe, because the PMGR power states under them are.
    • Boot state is taken from the hardware: on only if both bits are set. A bit the boot loader left set is never cleared before the pipe powers off. iBoot leaves boost 0x1 (bit 0 is the internal display) and BWR 0x0.
    • Release restores the probe values.
    • Both words are added to the debugfs status.
  • DT:
    • The two interface nodes go under the PMC in t8132-memory.dtsi, each with its pipe's SYS power state as parent.
    • On the J713, where the PMC is enabled, each pipe's FE power state depends on them as well as on SYS. genpd then orders power on as SYS, the enables, FE, and power off in reverse. The other T8132 boards keep the PMC disabled, so the dependency lives in the board DT.
    • The legacy-loader adapter drops the copied nodes, whose parents don't resolve there.

Together with the drm/apple realtime bandwidth and DSC PR (#1007), this makes 3840x2160 at 120 Hz work on an external monitor through a Thunderbolt dock.

Not done here: macOS also sets a third enable (DVMR) and tells the PMP the pipe's power state. Neither was needed for the result above.

Testing:

  • KUnit (apple-pmc suite, arm64): 13/13, including bit setting and clearing next to the internal display's boost bit, a word that doesn't take the change, the boot states, and the domain linked between IRQ-safe SYS and FE power states, powered through a device on FE.

  • make dt_binding_check (apple,pmc.yaml) and dtbs_check for every T8132 board: no new messages.

  • scripts/checkpatch.pl --strict: clean on all three commits.

  • J713 with a CalDigit TS5 dock and two 3840x2160 monitors, with the drm/apple PR (drm/apple: 4K at 120 Hz on the M4 MacBook Air external displays #1007):

    • After boot the debugfs status shows BWR 0x18 and boost 0x7, from 0x0 and 0x1. Both interface domains power on when the FE power states do.
    • 3840x2160 at 120 Hz with DSC shows the whole picture at 120 frames a second, next to 3840x2160 at 60 Hz on the second monitor.
    • Hotplug of either monitor, docked s2idle, and the dock on either Type-C port: both monitors come back in their modes, no warnings. The internal panel is unaffected.
    • The pipes' power states stay on through hotplug and s2idle, so on hardware only the power-on path ran. Clearing at power-off is covered by the KUnit tests.

This was developed and tested by Claude (Anthropic), working with me on the J713.

On T8132 a display pipe votes for memory bandwidth and for a faster
display clock through two PMC interfaces: bandwidth request (BWR) and
boost. Each interface has an enable word with one bit per agent, and the
PMC ignores the votes of a pipe whose bits are clear. macOS sets a pipe's
two bits when the pipe's SYS power state powers on, and clears them
before it powers off.

Describe such a pair of bits as an interface-* child node of the PMC,
provided as a power domain whose parent is the power state the enables
need. The bits are set after the parent powers on and cleared before it
powers off. Only the T8132 PMC has these words, so other PMCs may not
have interface nodes.

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

The T8132 PMC ignores an external display pipe's bandwidth and boost
votes until the pipe's bits are set in the BWR and boost interface
enable words. With them clear, the pipe's display clock stays at its
lowest state, and a 4K mode at 120 Hz shows only the first lines of
each frame. macOS sets the bits when the pipe's SYS power state powers
on, and clears them before it powers off.

Add the offsets of the two enable words in the "rails" region to the
per-SoC layout: 0x2020 and 0x2028 on T8132, and 0 elsewhere, where an
interface node is rejected. Provide each interface-* child node as a
power domain that joins its parent domain like the cluster domains do.

Power on sets the pipe's boost bit and then its BWR bit, and power off
clears them, each with a read-modify-write under the PMC lock that is
read back. If the BWR word does not take the change, the boost bit gets
back the value it had before, and the call fails with -EIO. A bit
claimed by two domains is rejected.

The domain is IRQ safe: the PMGR power states that depend on it are,
and genpd refuses an IRQ-safe subdomain under a parent that is not. The
callbacks only take the PMC's raw spinlock and access MMIO.

Like a voter domain, an interface domain starts from the hardware
state: it is on if both of its bits are already set. With only one bit
set it starts off and that bit is left alone until the domain powers
on, which sets the other one; a failed power on keeps it as well. So the
driver never drops a vote the boot loader left before the pipe powers
off. When the PMC goes away, the domains' bits get back their values at
probe, as the rail enables do. System sleep needs nothing extra: genpd
powers the domain off and on with the domains that depend on it, and
the words, like the rail enables, are not saved and restored.

Show both words in the debugfs status, and add KUnit cases for setting
and clearing a pipe's bits next to the internal display's boost bit, for
a BWR word that does not take the change, for the boot states, and for
the domain linked between IRQ-safe SYS and FE power states the way the
PMGR driver links them, powered through a device on FE.

Signed-off-by: Erik Bourget <erik@ebourget.net>
The PMC ignores the bandwidth and boost votes of an external display
pipe until the pipe's BWR and boost interface enables are set, so the
pipe's display clock stays at its lowest state and a 4K mode at 120 Hz
shows only the first lines of each frame.

Describe the enables of dispext0 (BWR bit 3, boost bit 1) and dispext1
(BWR bit 4, boost bit 2) under the PMC, each with the pipe's SYS power
state as its parent. On the J713, where the PMC is enabled, make each
pipe's FE power state depend on them as well as on SYS. genpd then
powers on SYS, the enables and FE in that order, and powers them off in
reverse, so the enables are only set while SYS is on.

The other T8132 boards keep the PMC disabled, so the dependency lives in
the J713 DT and not in t8132-pmgr.dtsi, where it would hold their FE
power states back for good.

The legacy-loader adapter copies the t8132-memory.dtsi nodes, phandles
included, into a loader tree that lacks them. The interface nodes'
parents do not resolve there, and the loader's PMGR states do not use
the nodes, so drop them from the adapter's data.

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

Copy link
Copy Markdown
Contributor

Merge train (MTR, 06/11): merging. An independent adversarial review found it strictly T8132-gated: interface nodes need bwr_enable/boost_enable, which only the T8132 PMC layout sets, and are refused elsewhere. Both read-modify-writes run under the PMC raw spinlock with read-back and rollback. J616s (t6040 PMC) and Neo (t8140) show no change. We have no J713, so the hardware proof is your 4K120 run. Note: J713 external display FE domains now depend on APPLE_PMC, which the universal config has.

Gates: test merge 2d3579ef = integration/aurora 11eccbe + #1005 #994 #930 #658 #992 #1006 #1007 #1008 #531 (#992 has since landed on its own); base 11eccbe.

  • KUnit: drm/apple 112/113 (1 skip); Rust (asahi, incl. asahi_g17_validation_pages) 200/201 (1 skip); arm64 apple-isp-*, apple-pmc, apple-pmgr-pwrstate and thunderbolt 137/138 (1 skip).
  • W=1 on drm/apple, media/apple/isp, pmdomain/apple, thunderbolt and nvmem/apple-spmi-nvmem: 0 new warnings.
  • dt_binding_check (apple,dcp, apple,dart, apple,t8132-isp, apple,spmi-nvmem, apple,pmc): 0 new.
  • Host tests run-host-tests.py 185/185; apple-dcp selftests 86/86.

Hardware (7.3.0-aurora-mtr1106a, one-shot boots, fleet recipe):

  • J616s (M4 Pro) vs fleet17: 0 kernel errors, 0 AVC, 0 failed units; eDP + DP-2, Wi-Fi, Vulkan G16S. Camera NV12 1080p and 720p, and P010 720p, streamed 30 frames each, with no ISP errors. vkcube OK; glmark2 6617/6738/6683 vs fleet17 6646/6555/6664.
  • Neo (A18 Pro), two boots vs fleet16: 0 kernel errors, 0 AVC, os_uuid pin present; eDP, Wi-Fi, Vulkan G17P. Camera NV12 1080p and 720p, no ISP errors. vkcube OK; glmark2 2718/2729/2726/2725 vs fleet16 2043–2108; 0 GPU faults.
  • Both Macs were restored to their default kernels afterwards.

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.

2 participants