Skip to content

J616s (M4 Pro): external displays (Type-C through the display fabric, HDMI and USB-C DisplayPort behind the T6041 display gate) and the third loaded GPU image - #451

Merged
RyanTheTide merged 28 commits into
aurora-silicon:integration/aurorafrom
fbalazsy:j616s/display-t6041-fabric
Oct 27, 2026

Conversation

@fbalazsy

@fbalazsy fbalazsy commented Oct 25, 2026 •

Copy link
Copy Markdown

Follow-up to #446 (merged). This PR now carries the whole J616s external display work as one series (it folds #436, which the display gate needs, the device tree and gate, and the GPU image digest that was #500), merged with the current integration/aurora (9cbdd07) without rewriting the earlier commits.

Series.

  1. J616s (M4 Pro): external displays (Type-C through the display fabric, HDMI and USB-C DisplayPort behind the T6041 display gate) and the third loaded GPU image #451's own eight commits: the T6041 Type-C displays through the display fabric (typec-routes under dcpext1, one mode switch per port), the callback record check, USB3 lanes given back after DP, the review items.

  2. dt-bindings: power: apple: add apple,irq-safe to the PMP report entries and pmdomain: apple: pmp-report: IRQ-safe entries (was pmdomain: apple: IRQ-safe PMP report entries #436): the DISPEXT power domains sit below the PMP report entries and are IRQ safe, so their parent must be.

  3. dt-bindings: display: apple,dcp: add apple,t6041-dcpext: the driver already matches it.

  4. arm64: dts: apple: t6040-j616s: external display chain: dcpext0 behind HDMI (atc3 crossbar, atcphy3, the DP-to-HDMI converter), dcpext1 behind the Type-C ports, the DISPEXT domains with their PMP acknowledgement entries, DARTs, mailboxes, the dcpext firmware memory as disabled placeholders the boot loader fills from the ADT. Every node is disabled: powering DISPEXT without the PMP acknowledgement hangs the machine before root.

  5. soc: apple: t6041-display-gate: power up the J616s external displays: the existing display gate enables the chain once both external processors have their firmware memory (the dcpext memory-region nodes are available): Type-C routes before any device exists with the display subsystem held back; after the PMP firmware is bound the HDMI chain, the Type-C chain, and last both processors and the display subsystem together. It waits for the crossbar and PHY drivers (modules) first, because with a deferred probe timeout of 0 a processor that finds its crossbar missing is given up for good, and probes the Type-C port controllers again once dcpext1 is bound (the Type-C core treats a mode switch below a disabled node as absent at their first probe). If the PMP does not come within 20 s, or a step fails, the routes are withdrawn and only the internal panel stays. Without the firmware memory, with nomodeset, or on another machine nothing changes.

  6. drm/asahi: admit a third loaded T6040 25G83 image (was drm/asahi: admit a third loaded T6040 25G83 image #500, a follow-up to drm/asahi: admit the alternate T6040 loaded 25G83 image #453): this J616s loads a third normalized T6040 TEXT identity (TEXT 0x60000, DATA 0x138000, the same entropy offsets, sha256 d725264d4831b95a5b8a375abf2fb5cdfbd6d7cbc7f1baa2cc8b37ee5d6e5e98). Without it the probe stops with unsupported GPU firmware build ... GPU firmware image not admitted: Error(22) and the device has no render node. The digest is added to the 26.6.2 T6040 list and to the KUnit test that runs every listed image through the lookup used by identify(); digest only, no Apple bytes.

Loader contract. The gate's condition is the firmware memory, not a loader name: m4-loader (jgoyvaerts/m4-loader#4) fills the placeholders from the ADT. m1n1 does not do that today; with m1n1 the externals stay off. The m1n1 side is a contract this PR neither implements nor tests.

Hardware tested on: MacBook Pro 16" M4 Pro (J616s), DCP firmware 26.6.2, m4-loader rev19-j616s-1, Samsung G95NC on HDMI and USB-C, a USB-C dock with USB3 storage and Ethernet. Kernel 7.3.0-m4.116 = integration/aurora 9cbdd07 + this series + a local J616s platform layer for m4-loader (NVMe write guard, DART filter, device tree, this unit's GPU image digest); the tree of this branch is identical to it outside those local files.

Result
Boot eDP, HDMI and USB-C DP from boot, no command line switch or userspace step; processors enabled ~3 s after the PMP; 0 axi_rd_err/WARN/Oops
HDMI replug display back
USB-C DP replug, all three ports display back on each; USB3 on the same port with a dock
Clamshell externals stay on, internal panel back after lid open
s2idle (38 s) HDMI and DP back
dtbs_check (aurora + this series) no diagnostics for the new nodes
GPU image (commit 6) three boots (m4.114, .115, .116) admit it: renderD128 present, vulkaninfo lists Apple M4 Pro (G16S B1), the Hyprland session runs on the GPU; the kernel without the entry (m4.113, one boot) shows the error above; no asahi fault, error or timeout in the logs
Build W=1 clean for t6041-display-gate.c

Not done for commit 6: KUnit and the Buildbox gates were not run (the test edit is formatted with the pinned rustfmt but not compiled in a KUnit build) and the image was not compared byte for byte with the IPSW firmware as #453 did. Not tested: two USB-C displays at once; m1n1 as the boot loader; a kernel with the crossbar and PHY built in. The DISPEXT1_SYS and DISPEXT1_FE pmgr power-state devices never bind here (silent probe deferral, cause not found); the displays work regardless.


Earlier description of the fabric part (unchanged):

Follow-up to #446 (merged).

The T6041 Type-C routes from #446 use the private dynamic route from the original series (apple,dptx-phys, dp-phyN/dp-xbarN, apple_t6040_phy_dp_alt()). The display fabric already routes the T602x, T6030 and J700 Type-C ports, so this moves desktop 26.x onto it and closes the remaining notes from the #445 review.

Change. Three commits:

  1. drm/apple: route the T6041 Type-C displays through the display fabric: typec-routes under dcpext1, one mode switch per port, driven by the port controller. The private route and apple_t6040_phy_dp_alt() go away. Three things differ from the other fabric users:
    • the T6040 DP PHY is powered when the display connects, not when a route is claimed (the PHY's mode switch must have seen DP alt mode and the plug orientation first);
    • the compact DPTX takes the crossbar link down and up around a link configuration through dcp->xbar, and the fabric's direct-link helper is T6030 only, so the route sets dcp->xbar while it is active (without it the link fails training at 4, 2 and 1 lanes);
    • there is a single Type-C pipeline, so it is claimed on HPD only: a port in DP alt mode without a sink no longer holds it against the port with a display.
  2. drm/apple: check the desktop 26.6 callback records against their handlers: the size check from the review notes.
  3. phy: apple: t6040: give the Type-C lanes back to USB3 after DP: review item 5.

Hardware tested on: MacBook Pro 16" M4 Pro (J616s), DCP firmware 26.6.2, Samsung G95NC on HDMI and USB-C (PBP). Local kernel 7.3.0-m4.93 = the daily J616s tree with the same driver logic (the J616s device tree with typec-routes on dcpext1 is local, as for #446).

Result
USB-C DP through the fabric DP-1 1920x2160@119.98, 25 modes, PHY 0 claimed; deterministic on 4 boots
Second port in DP alt mode without a sink (pin assignment D) does not take the pipeline; the display port does
Port controller rebind (replug) display back on the same route
Device suspend (pm_test=devices) eDP-1, HDMI-A-1 and DP-1 back
Build W=1 clean on the touched files for DRM_APPLE=m and DRM_APPLE=y (crossbar and PHY as modules)

Not tested: the other two Type-C ports with a display, two USB-C displays at once (one pipeline), s2idle with the fabric route.

Adaptive sync (commits 16 and 17: internal panel, external displays)

The desktop 26.6 mode set (get_mode_info, then set_digital_out_mode) never sent IOMFBParameter 14, so the ProMotion panel had no VRR and its VRR modes were hidden. The commit sends adaptive sync (minRR in 16.16 Hz, 0 when VRR_ENABLED is off) before the mode is selected, makes a VRR_ENABLED change a mode set, and offers VRR on the internal panel only.

Tested on the J616s, DCP firmware 26.6.2, kernel 7.3.0-m4.124 = integration/aurora 9cbdd07 + this PR + the J616s platform layer, no module options:

Result
vrr_capable eDP-1 1 (3456x2234@120, range 24-120 from the parser), externals 0
Firmware IOMFBParameter_adaptive_sync Req minRR = 0x180000, then 3456x2234@120 Hz DSC=NO VRR=YES; VRR=NO with minRR 0 when VRR is switched off
Swaps keep completing; a 24/30/48/60 fps client presents at 41.67/33.33/20.83/16.67 ms
VRR on/off, DPMS off/on clean, 0 kernel errors

Commit 17 sends the same parameter on the external pipes and stops hiding their VRR range. Kernel 7.3.0-m4.125 (= the above + commit 17), Samsung G95NC on USB-C DP (3840x2160@60, range 48-60 Hz from the EDID), Hyprland misc:vrr 1:

Result
vrr_capable eDP-1 1, DP-1 1
Firmware (dcpext1) IOMFBParameter_adaptive_sync Req minRR = 0x300000, 3840x2160@60 Hz DSC=NO VRR=YES
54 fps client on DP-1 VRR on: every 18.5 ms (98% off the 60 Hz grid); VRR off: 16.7/33.3 ms
DPMS off/on eDP-1 120 Hz and DP-1 keep VRR, 0 kernel errors
HDMI (same monitor on the HDMI port, 3840x2160@60, hotplug) vrr_capable 1, dcpext0 minRR = 0x300000 and VRR=YES; 54 fps client every 18.5 ms with VRR (84% off the 60 Hz grid), 16.7/33.3 ms without; the monitor OSD reports Adaptive-Sync on

On the internal panel swap completion follows the content rate with VRR off too, so the numbers there do not prove a scanout difference; the panel was checked by eye. Not tested: s2idle with VRR on.

The dcpext of a T6041 listed the DP PHY and crossbar of every Type-C port
and picked the port whose PHY had entered DP alt mode at connect time.
The display fabric already routes the T602x, T6030 and J700 Type-C ports:
a typec-routes node under the DCP, one mode switch per port, driven by
the port controller.  Use it for desktop 26.x as well and drop the
private route (apple,dptx-phys, dp-phyN/dp-xbarN, apple_t6040_phy_dp_alt()).

Three things differ from the other fabric users:

- The T6040 DP PHY must be powered when the display connects, not when
  a route is claimed.  The port controller calls the PHY's and the
  route's mode switch in connector order, and the PHY has to have seen
  DP alt mode and the plug orientation before its lanes are programmed.
- The compact DPTX of desktop 26.x takes the crossbar link down and up
  around a link configuration through dcp->xbar, while the fabric's own
  direct-link helper is T6030 only.  The route sets dcp->xbar for the
  time it is active; without it the link never trains.
- There is a single Type-C pipeline.  A port in DP alt mode without a
  sink (a dock, or a pin assignment the PHY cannot drive) must not hold
  it against the port that has a display: claim it on HPD only.

Tested on a J616s (Mac16,7) with a USB-C display on one port: attach at boot,
rebind of the port controller (replug) and a device suspend test (pm_test=devices).  The
other ports and a second display are not tested.

Signed-off-by: František Balázsy <frantisekbalazsy@gmail.com>
…lers

The H17P callbacks are checked against the record sizes the firmware
sends; the desktop 26.6 ones were not. The u16 offset keeps a record
inside the shared memory, but a handler could still read past a short
request or write past a short reply into the next record.

Give every desktop 26.6 handler the request and reply size it reads and
writes, and refuse a callback whose record is shorter, or whose lengths
do not fit the message, the way H17P refuses a malformed one. Longer
records stay accepted.

Signed-off-by: František Balázsy <frantisekbalazsy@gmail.com>
t6040_dp_power_on() shuts the USB3 side down, but t6040_dp_power_off()
only cleared dp_up, and t6040_usb3_power_on() could re-initialise the PHY
under a live DP mode. Once a Type-C DP session ended with the plug still
in, USB3 on that port stayed down until dwc3 power cycled the PHY.

Track whether dwc3 holds the USB3 phy on and asked for the PIPE switch.
USB3 bring-up now waits while DP mode is programmed, and when DP mode
ends and the port has left DP alt mode, from the DP power-off or the
mode switch, whichever comes last, USB3 is brought up again and the
PIPE goes back to the PHY. On an unplug or a DP mode change tipd drops
the USB role first, dwc3 powers the USB3 phy off, and nothing changes
there.

Signed-off-by: František Balázsy <frantisekbalazsy@gmail.com>

@RyanTheTide RyanTheTide left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nice cleanup, and moving onto the fabric is the right call. I read all three commits against integration/aurora 3b77f0f. Nothing changes for the other fabric users: every new branch in dcp-fabric.c is gated on the desktop firmware or on phy_powered, and other SoCs keep their routes[0].phy and fixed xbar. No hard module dependency comes back either. The crossbar still goes through dcp_dpxbar_link(), and apple_t6040_phy_dp_alt() is gone along with its stub. The callback size table matches cb_handlers[] tag for tag, and the bound can't overflow. Thanks for closing item 5 as well.

A few things I'd like to see fixed before it goes in, all on the T6041 path:

  1. PHY power on connect isn't ordered against route release. dcp_typec_phy_on() checks active_typec_route under hpd_mutex only, because the desktop connect skips the fabric session the other SoCs take. Meanwhile dcp_typec_route_phy_off() runs under tb_lock inside the fabric lock. An OOB hotplug connect racing a route deactivate can power the PHY after the crossbar is deselected and leave phy_powered set, and then the USB3 give-back in commit 3 never runs. #446 kept this ordered through hpd_mutex ("which also orders dcp->phy against a connect"). Taking the fabric lock around the phy_on, or another lock deactivate also holds, would restore that.

  2. The start-up gate in dcp_start() changed from !dcp->phy && !d->ndyn to !dcp->phy. At probe, route registration points dcp->phy at routes[0], so the first start is fine. But after an unplug the desktop deactivate sets dcp->phy to NULL, so a later dcp_start() (master rebind) would skip iBoot, DPTX, EXPERT and EPIC25, and Type-C would stay down until reboot. Separately, the startup dcp_dptx_connect(dcp, 0) now runs with dcp->phy on route 0, an unpowered PHY with no crossbar selected, which goes against the new "an idle pipeline must not point at one" comment. Gating on dcp->phy || dcp->nr_typec_routes, and skipping the startup connect while no route is active, would cover both.

  3. Claiming on HPD only covers the claim but not the release. When HPD drops while the port stays in DP alt mode, the fabric calls dcp_dptx_disconnect_oob() but keeps port->owner, and dcp_desktop_dptx_disconnect_oob() no longer releases the route or the PHY. A dock whose monitor gets unplugged keeps the only pipeline, its crossbar and a powered DP PHY, and a display on another port then gets dcp_typec_port_busy. So a sinkless DP alt port does still hold the pipeline, once it has had a display.

  4. The USB3 give-back switches PIPE back while xHCI is already running, where set_mode() says the switch belongs after core init and before xHCI starts. There's no USB3-after-DP row in the test table. A USB3 device on that port after a DP session (speed and enumeration) would settle it.

Smaller ones. dcp->xbar is overwritten on route activate and only cleared again on the USB-only branch, and it's cleared outside tb_lock while dptxport_compact_xbar_link() reads it unlocked. That's harmless with routes only on dcpext1, but worth tidying. In the new record check, hdr->tag is read before length < sizeof(*hdr) is checked, and unlike H17P it doesn't check offset + length against the shared buffer.

I'm running the universal WERROR build, KUnit and the apple-dcp host selftests on this branch now and will add the results here. USB-C display behaviour can only be checked on hardware with a monitor plugged in, which is a morning job on our J616s.

The comment in dcp_is_typec_output() still described the private route
the previous commit removed. Desktop 26.x now takes its Type-C route
from the fabric and only drives the link through its own compact DPTX
connect.

Signed-off-by: František Balázsy <frantisekbalazsy@gmail.com>
@RyanTheTide

Copy link
Copy Markdown
Contributor

Gate results on integration/aurora 3b77f0f with this branch and #394:

  • The universal recipe (Fedora asahi base, asahi.config, LLVM=1, CONFIG_DRM_WERROR=y) builds Image, modules and DTBs with no warnings.
  • The apple-dcp host selftests drop from 63/63 to 55/63. Eight groups no longer compile: dispatcher-scope, actual-dcp-normal-safe, actual-dcp-finalizer-recovery, provider-order-releases-failed-lease, failed-safe-allows-new-dp-admission, altmode-safe-skips-quiescence, concurrent-prepare-set-stale-quiescence and mux-release-stages. The fixtures extract the changed fabric and iomfb functions but don't know DCP_FIRMWARE_V_26_6_DESKTOP, dcp_typec_route_phy_off() or iomfb_check_callback_v26_6_desktop(). It's the same kind of fixture follow-up as the selftests commit in drm/apple: T6041 (M4 Pro/Max) displays on desktop 26.6 firmware (carries #445) #446, which can serve as the pattern: a define nothing uses, plus stubs that assert if a non-desktop test reaches them. The fixture fix belongs in this PR. Run it with python3 tools/testing/selftests/drivers/gpu/apple-dcp/run.py --output <dir> from a clean tmpfs.

RyanTheTide added a commit that referenced this pull request Oct 25, 2026
After a Type-C route is released, #451 parks dcp->phy at NULL. A later firmware start must still discover the DPTX services for the registered routes; testing only the current PHY skips them and leaves later hotplug without a service. Keep endpoint discovery enabled for a Type-C pipeline, with link admission still requiring an active route.

Exercise the real startup function with no route, parked Type-C routes, and a fixed HDMI PHY in a hardware-free host fixture.

Assisted-by: LLM
Signed-off-by: Ryan Murray <ryan@aurorasilicon.org>
@jgoyvaerts

Copy link
Copy Markdown
Contributor

Thanks for this series. I'd like to test it on a second J616s with a USB-C monitor (DP alt mode, pin assignment C), and to follow up with the PHY unplug sequence from a macOS capture (dptx_main_shutdown, aux_shutdown, then the mode-3 PHY shutdown, without the coprocessor).

Two pieces are only on your machine so far, as far as I can see:

  1. The J616s device tree that enables all of this: dcpext0/dcpext1 with their DARTs and mailboxes, the atcN-dpxbar crossbars, the DISPEXT power states, the typec-routes on dcpext1, the mode-switch links from the PD controllers, and the ATC3 fixed DP PHY for HDMI.
  2. The boot loader side for dcpext0/dcpext1: reserving their firmware, the apple,firmware-version/apple,firmware-compat properties and the data carveouts. m1n1's dt_set_display() still skips T6041 for the external processors, and thunderbolt: apple: avoid lock inversion during controller teardown #51 only covers the internal DCP.

Could you publish them, or point me at a branch? If you'd rather not, I'll write the device tree from the ADT and open it as a PR, but yours is the tested one.

What I can add from macOS 26.6.2 (25G83) on the J616s, in case it helps the review:

  • Your monitor case matches macOS. Pin assignment C runs PHY mode 3 with no coprocessor. The HPM reports USB3 first, then DP about 1.1 s later, and macOS tears USB3 down when the DP status arrives. HPD follows another ~1.1 s later.
  • macOS picks the dispext dynamically: connect 0:dispext0,0 -> atc0,0, mux_select=0, pclk=PCLK1. The link was HBR at 4 lanes for a 1080p60 panel.
  • aciophy_configure_dp_clks is only reached from the Thunderbolt tunnel path in the AppleT6040TypeCPhy disassembly, not from the DP alt mode setLinkRate. It works for you, so I may be misreading it. Do you know whether the alt-mode link needs the clock switch?

…peline with the sink

Review of the Type-C fabric series found four holes on the T6041 path.

dcp_typec_phy_on() looked at the active route under hpd_mutex only, while
the deactivate powers the PHY off and releases the crossbar under tb_lock.
A connect racing a release could power the PHY after the crossbar was
deselected and leave phy_powered set. Take tb_lock around the check and the
power-on, and refuse a route whose crossbar is already released.

The start-up gate in dcp_start_desktop() is again satisfied by a Type-C
route, so a restart after an unplug (dcp->phy is NULL then) still brings
the DPTX services up, and the start-up link is only connected when a fixed
PHY or an active route exists: an idle pipeline has no powered PHY and no
crossbar selection to connect on.

When HPD drops while the port stays in DP alt mode, the route, its crossbar
and the PHY were kept. Release them with the sink, as a mode change does,
so a display on another port is not refused as busy; the route is claimed
again when HPD returns.

Check that a desktop callback record lies inside the shared buffer before
its header is read, and access dcp->xbar with READ_ONCE()/WRITE_ONCE() as
it is read by the DPTX worker without a lock.

Signed-off-by: František Balázsy <frantisekbalazsy@gmail.com>
The fabric extraction now reaches dcp_typec_route_phy_off() and the
DCP_FIRMWARE_V_26_6_DESKTOP tests, and the dispatcher extraction the
desktop callback check. Add the define and, as for the other desktop
paths, a stub that asserts if a non-desktop scenario reaches the check; the
PHY power-off is a no-op there as the real one returns for an unpowered
PHY. Seven of the eight units that stopped compiling build and pass again;
mux-release-stages needs -Wno-unused-but-set-global with clang 23.

Signed-off-by: František Balázsy <frantisekbalazsy@gmail.com>
@fbalazsy

Copy link
Copy Markdown
Author

Thanks for testing it. Answers in order.

1. Device tree. It is on a branch of my fork: https://github.com/fbalazsy/linux/tree/j616s/dt-t6041-extdisp (one commit on top of
integration/aurora 3b77f0f, only t6040-j616s.dts plus four new files):

  • t6040-j616s-extdisp.dtsi: dcpext0/dcpext1 with DARTs and mailboxes, DISPEXT0/1 power states and the PMP
    device-state acknowledgements, the atcN-dpxbar crossbars, pinctrl_aop.
  • t6040-j616s-extdisp-hdmi.dtsi: the HDMI path (dcpext0 -> atc3 crossbar -> atcphy3 fixed DP PHY -> DP-to-HDMI converter).
  • t6040-j616s-extdisp-usbc.dtsi: mode-switch on atcphy0..2, the port@3 links from the PD controllers, and
    typec-routes on dcpext1.
  • t6040-j616s-extdisp-mem.dtsi: the dcpext firmware memory and apple,firmware-version/compat.

This tree builds t6040-j616s.dtb cleanly but was not booted; the same nodes run on my daily tree, which is based on an older integration/aurora. Every node is disabled. The chain has to be enabled in order after boot (enabling the DISPEXT power chain before
the PMP acknowledgement hung the Mac before root; HDMI and Type-C chains have to be enabled in two steps, all at once
crashed dcpext0). The routes must be enabled together with dcpext1, because tipd looks for display routes only at
probe. I do that with a small gate module on my daily tree; it is not part of this branch.
The values are those of one unit (macOS 25G83), read from its ADT, and go stale after a macOS or iBoot update.

2. Boot loader side. Today nothing on my side writes anything for dcpext0/dcpext1: my loader build does not
touch them, so the memory and version properties are the static values in -extdisp-mem.dtsi. The generic
dt_reserve_dcpext_firmware() in m1n1's kboot.c would reserve the firmware and set apple,firmware-version/compat,
but it also sets status = "okay", which would enable DISPEXT before the PMP acknowledgement, and as you say
dt_set_display() skips T6041 for the external processors. What I would want from a loader, derived from the ADT on
every boot: reserved-memory nodes from iop-dcpextN-nub segment-ranges (no-map, iommu-addresses = remap address -
0x100_0000_0000, size rounded to 16 KiB, __OS_LOG left out as for the internal DCP), memory-region(-names) and the
firmware version on the still disabled dcpext0/dcpext1, and a handoff marker once their DART mappings are checked,
with the enabling left to the kernel (as t6030-display-gate). That is a written proposal only, nothing of it is
implemented or tested.

3. aciophy_configure_dp_clks. My T6040 PHY driver runs the clock-switch handshake
(t6040_dp_configure_clks(), cfg+0x14/0x18, both switches) in the mode-3 bring-up, for the fixed HDMI PHY and for
Type-C DP alt mode alike, and both work on the J616s (DP alt mode to a monitor at 1920x2160@120, and the HDMI link).
I have not tried the alt-mode link without it, so I cannot say whether it is required there or only harmless; if the
macOS path really skips it for alt mode, dropping it would be a one-line test on the same hardware.

On the second J616s with a USB-C monitor and the unplug sequence: happy to run any test you want on this one.

@jgoyvaerts

Copy link
Copy Markdown
Contributor

Thanks, that's a lot of hard-won detail. The DISPEXTn_CPU and FAB7_SOC notes alone would have cost me a few resets.

I checked your tree against this unit's ADT (macOS 25G83 too). The dcpext0/1, mailbox, DART, crossbar, PMGR and ATC3 values match. The iop-dcpextN-nub segment ranges are identical to your static -extdisp-mem.dtsi as well: data 0x10bd0e20000 / 0x10bcfb5c000, heap 0x10bcfe20000 / 0x10bceb5c000, shared __TEXT at 0x100002f4000. So the static file works here as long as both stay on 25G83.

The missing piece for a second machine is the enabling step. Could you push the gate module, even as a WIP branch? I'd like to run your order exactly (PMP acknowledgement first, then the HDMI chain, then dcpext1 with its routes and the tipd rebind) rather than reinvent it. Longer term, your loader proposal (segment ranges, memory-region and the version on the disabled nodes, plus a handoff marker) together with a T6041 entry in t6030-display-gate.c sounds like the right shape to me, and I can take the loader half in m4-loader.

Test offer accepted, thanks. I'll post my results here: all three ports, both orientations, USB3 after a DP session (Ryan's item 4), and later the unplug sequence with and without t6040_dp_configure_clks().

@fbalazsy

Copy link
Copy Markdown
Author

The gate is on https://github.com/fbalazsy/linux/tree/j616s/t6041-display-gate-wip, one WIP commit on top of the DT branch: drivers/soc/apple/t6041-display-gate.c (Kconfig, Makefile) plus tools/j616s-extdisp/ with the boot hook and the tipd rebind that run around it. It compiles with W=1 on current integration/aurora plus the DT branch; it has not been booted in that form, the same code runs on my daily tree.

The order, which tools/j616s-extdisp/README also describes: the module is loaded with enable=1 before appledrm. Stage 1 enables the HDMI chain (the genpd parents in the DT order the PMP DISPEXT0 acknowledgement and FAB7_SOC before DISPEXT0_SYS), then it waits 3 s and stage 2 enables the Type-C chain with dcpext1 and its routes in one changeset. appledrm then binds, and m4-extdisp-rebind-tipd rebinds the SPMI usb-pd devices once the route mode switches exist.

Built in, it stays off unless apple_t6041_display.enable=1 is on the command line. I have only run it as a module, loaded after the initramfs has set boot-pending. A pre-root hang does not recover on its own, so I would not try the built-in variant on a second machine without someone at the Mac. A T6041 entry in t6030-display-gate.c and the loader half in m4-loader is the shape I would like too.

@fbalazsy

Copy link
Copy Markdown
Author

Pushed two commits on top for the review (361d633).

  1. dcp_typec_phy_on() now checks the route and powers the PHY under tb_lock, the lock the deactivate holds around dcp_typec_route_phy_off() and the crossbar release, and refuses a route whose crossbar is already released.
  2. The dcp_start_desktop_external() gate is !dcp->phy && !d->ndyn && !dcp->nr_typec_routes again, and the start-up dcp_dptx_connect() only runs with a fixed PHY or an active route, so an idle pipeline no longer connects on an unpowered PHY with no crossbar selected.
  3. When HPD drops while the port stays in DP alt mode, the route, its crossbar and the PHY are released together with the sink (as a mode change does) and dcp_typec_pipeline_freed() runs; the route is claimed again when HPD returns.
  4. The record is checked against the shared buffer before hdr->tag is read, and dcp->xbar uses READ_ONCE()/WRITE_ONCE().
  5. The selftest fixtures know DCP_FIRMWARE_V_26_6_DESKTOP, dcp_typec_route_phy_off() and iomfb_check_callback_v26_6_desktop() as in drm/apple: T6041 (M4 Pro/Max) displays on desktop 26.6 firmware (carries #445) #446 (a define plus stubs that assert if a non-desktop scenario reaches them).

Tested on the J616s (kernel with these two commits, plus a gate module and the DT on a local base): clean boot with eDP, HDMI and DP at 1920x2160@120, a rebind of the port controller (the route is released and claimed again, the display comes back), and a pm_test=devices suspend cycle with all three outputs back. Not tested yet: the HPD drop with the port staying in DP alt mode (needs the monitor input switched by hand) and your item 4, a USB3 device after a DP session; I will post both. I changed nothing for item 4 until that test shows what the PIPE switch does.

Selftests: the eight units that stopped compiling build again and pass, except mux-release-stages, which does not build here with clang 23 because of -Wunused-but-set-global on the fixture's own xbar_error; with that warning off its normal and late cases pass. service-rebind, cpuidle-s2idle, probe-mux-pm-dispatch, dwc3-teardown and same-handle-srcu-index fail the same way without these commits.

@fbalazsy
fbalazsy requested a review from RyanTheTide October 25, 2026 18:18
…tput session

A desktop 26.x Type-C display took its route from the fabric but was
connected by dcp_desktop_dptx_connect(), outside the session the other
SoCs use: no attach and detach bookkeeping (cable flag, generation,
firmware metadata, unplug count), no relink, no reconnect retry and none of
the IRQ_HPD handling the fabric already dispatches to it.

dcp_is_typec_output() is no longer false for the desktop firmware, and
dcp_dptx_connect(), dcp_dptx_connect_oob() and dcp_dptx_disconnect_oob()
take the common path for it. What compact DPTX does differently stays
where it differs:
 - the connection validation call does not exist, so it is skipped;
 - the PHY is powered when the display connects, before the DPTX connect;
 - a link configuration that has not arrived when the wait ends is not an
   error, it comes once a sink arrives;
 - a disconnect marks the external pipe as shut down by the firmware.
The fixed HDMI output (dcpext0) has no Type-C session and keeps
dcp_desktop_dptx_connect().

Signed-off-by: František Balázsy <frantisekbalazsy@gmail.com>
dcp_dptx_connect_raw() now asks the DPTX endpoint whether it is compact
and has the route power the PHY. This scenario's endpoint is the classic
one and its PHY needs no route: report not compact and stub the PHY
power-on.

Signed-off-by: František Balázsy <frantisekbalazsy@gmail.com>
@fbalazsy

fbalazsy commented Oct 25, 2026 •

Copy link
Copy Markdown
Author

Pushed two more commits (6dc79aa): the desktop 26.x Type-C display now connects through the common Type-C output session instead of dcp_desktop_dptx_connect(), which was the remaining difference from the other SoCs you pointed at with the fabric session.

dcp_is_typec_output() is no longer false for the desktop firmware. dcp_dptx_connect(), dcp_dptx_connect_oob() and dcp_dptx_disconnect_oob() take the common path for dcpext1: attach/detach bookkeeping (cable flag, generation, firmware metadata, unplug count), relink attach, sessions in dcp_dptx_connect_raw(), reconnect retry, and the IRQ_HPD handling the fabric already dispatched to it. What compact DPTX does differently stays where it differs: no connection validation call (skipped), the route's PHY powered at connect (dcp_typec_phy_on()), a link configuration that has not arrived when the wait ends is not an error, and a disconnect marks the external pipe as shut down by the firmware. The fixed HDMI output (dcpext0) has no Type-C session and keeps dcp_desktop_dptx_connect().

Tested on the J616s on top of integration/aurora 85c1ce7 (with #452) plus this branch and my local DT and gate: clean boot with eDP, HDMI and DP at 1920x2160@120; a rebind of the port controller (route released, claimed again, display back, no dcp_dptx_connect errors); DPMS off and on of the DP output; a pm_test=devices suspend cycle with all three outputs back; Vulkan on the same boot. The apple-dcp host selftests are as before, with the same six units that do not build with clang 23 here (fast-replug-bounce now passes after a fixture follow-up in the second commit).

Still not tested, as it needs the cable moved by hand: HPD dropping while the port stays in DP alt mode, and a USB3 device on the port after a DP session (item 4). On the port of this monitor the USB devices behind the monitor's hub stay at USB2 during DP; a USB3 dock on another port enumerates at 5000M after that port's PIPE switch (USB3 PIPE on the PHY), which is the up path, not the give-back after DP.

@fbalazsy

Copy link
Copy Markdown
Author

Hardware results for the open items, on 7.3.0-m4.106 (= integration/aurora ff1510c + this PR's head 6dc79aa + #454) on the J616s, DCP firmware 26.6.2, with the m4-loader (rev 19 + dcpext firmware memory from the ADT):

  1. HPD drop with the port staying in DP alt: the Type-C monitor was unplugged and plugged back twice on the same port (dcpext1 → atc1 crossbar, PHY 1). Each time: dcp_dptx_disconnect, route released and re-allocated, new compact dcpdp-device-epic service, IRQ_HPD from the PD controller, mode back without a modeset error, no axi_rd_err, no WARN.
  2. USB3 after DP: a USB3 mass-storage device (uas, 5 Gb/s) and a SuperSpeed hub enumerate on the same dock while the DP tunnel is up.

Also checked: the monitor on a second USB-C port (DP-2) and on the first (DP-1), HDMI (dcpext0, 1920x2160) at the same time as the Type-C output, lid close/open with the externals up, and the Samsung G95NC with PBP off (single 16:9 image over USB-C). The internal panel keeps its 120 Hz mode throughout.

@fbalazsy

Copy link
Copy Markdown
Author

@jgoyvaerts the loader half is up: jgoyvaerts/m4-loader#5 fills the dcpext0/1 firmware memory from the ADT into disabled placeholders of board.dtb (no handoff marker, the loader does not touch the dcpext DARTs; /chosen m4,dcpext records the decision). The DT branch j616s/dt-t6041-extdisp has the matching t6040-j616s-extdisp-mem-loader.dtsi on top (d31caf8) and the gate on j616s/t6041-display-gate-wip now refuses a stage whose dcpext has no available memory-region (34484eb). The loader image is installed here and boots the static DT unchanged; the placeholder DT boot is my next step.

@fbalazsy

Copy link
Copy Markdown
Author

Correction and the real result for item 4. My earlier note on item 4 described a USB3 device enumerating while a DP tunnel was up on another port; that is not the give-back you asked about. Tested it properly now, same branch head (6dc79aa) on 7.3.0-m4.108:

  • Type-C monitor on atc0 (dcpext1, PHY 0), DP session up. Monitor unplugged: dcp_dptx_disconnect(port=0), the atc0 crossbar route released.
  • The port's xHCI is removed and added again, the PHY switches back with USB3 PIPE on the PHY (382a90000), and a USB3 mass-storage device plugged into that same port enumerates 1 s later as SuperSpeed (uas, 5 Gb/s). No descriptor errors, no warnings.

So the PIPE give-back after DP works on the J616s: the switch happens with xHCI torn down and re-created around it, not under a running controller. Afterwards the monitor on a different port (atc1, PHY 1) came up again.

apple,irq-safe on a PMP report entry marks an entry that parents the
IRQ-safe PMGR power states of a coprocessor: genpd requires the parent
of an IRQ-safe subdomain to be IRQ safe as well.

Signed-off-by: František Balázsy <frantisekbalazsy@gmail.com>
A PMP device-state entry that parents PMGR power states (which are IRQ
safe) must be IRQ safe too, or genpd refuses the subdomain. Setting the
state takes a spinlock and polls the acknowledgment atomically, so the
entry may run with interrupts disabled. Opt in with apple,irq-safe.

Tested on a MacBook Pro M4 Pro (J616s): the DISPEXT0 and DISPEXT1
device-state entries parent the dispext0/1 PMGR chains that power the
HDMI and USB-C DisplayPort controllers.

Signed-off-by: František Balázsy <frantisekbalazsy@gmail.com>
The M4 Pro (T6041) external display processors use the generic dcpext
fallback like the other dcpext variants; the driver already matches
apple,t6041-dcpext.

Signed-off-by: František Balázsy <frantisekbalazsy@gmail.com>
Describe the J616s external display chain, all disabled: dcpext0 behind the
HDMI port (atc3 display crossbar, atcphy3, the DP-to-HDMI converter),
dcpext1 behind the three Type-C ports (display fabric routes, atc0..2
crossbars), their DISPEXT power domains with the PMP acknowledgement
entries, the display DARTs and mailboxes, and the dcpext firmware memory as
disabled placeholders that the boot loader fills from the ADT.

Nothing is enabled by the device tree: powering the DISPEXT chain without
the PMP acknowledgement hangs the machine before root, and a boot loader
that does not provide the firmware memory leaves the placeholders disabled.
A gate enables the chain (next commit).

Signed-off-by: František Balázsy <frantisekbalazsy@gmail.com>
Extend the T6041 display gate to the external displays. When both external
display processors have their firmware memory (the memory-region nodes of the
dcpext are available: the boot loader filled the placeholders), enable the
chain after the PMP firmware is bound: the Type-C routes before any device
exists (so the port controllers wait for dcpext1's mode switches) with the
display subsystem held back, then the HDMI chain, the Type-C chain, and last
both processors and the display subsystem together, so the display master is
created once with every DART in its iommus. The port controllers are probed
again once dcpext1 is bound, as the Type-C core treats a mode switch below a
disabled node as absent at their first probe. If the PMP does not come within
20 s, or a step fails, the routes are withdrawn and only the internal panel
stays. The processors are enabled only once the infrastructure devices
have their drivers (the crossbar and PHY are modules), as a processor that
finds one missing is given up for good.

Without the firmware memory, with nomodeset, or on another machine, nothing
changes. Tested on a J616s with m4-loader rev19-j616s-1 (which fills the
memory from the ADT): HDMI and USB-C DisplayPort from boot, eDP unchanged.

Signed-off-by: František Balázsy <frantisekbalazsy@gmail.com>
…utes on dcpext0

The desktop 26.6 firmware names the display pipes by their display index
(endpoint 0 the internal panel, 1 dcpext0, 2 dcpext1) when two of them
drive one display: apple,dcp-endpoint records it, separate from
apple,dcp-index, which the driver also uses for the doorbell and the PHY
mode.

The two external pipes merge upwards only, so a display wider than one
pipe (the Samsung Odyssey G95NC, 7680x2160) needs dcpext0 as the primary.
dcpext0 therefore also gets a route to every Type-C port next to its fixed
HDMI path (a hybrid pipeline, as the T6030 J514s): crossbar state 0 on
atcphy0..2 (the ADT's dp-switch-dfp-endpoint 0 = dispext0, as its HDMI
route on atcphy3), the connectors' port@3 with an endpoint per pipe. The
routes are disabled with the rest of the chain; the display gate enables
them in the HDMI stage.
RyanTheTide added a commit that referenced this pull request Oct 27, 2026
The g5 line already validates exact record lengths for its desktop 26.6
callbacks. #451 adds a minimum-length table for the full handler set, so a
straight replay defines the checker twice and fails the WERROR build.

Keep the g5 exact checks as a helper, fill the four g5 handlers absent from
the new table, then require both checks. The table now covers every
registered desktop handler. Point the synthetic desktop fixtures at the
exact helper; production dispatch uses the combined checker.

The original #451 commits remain attributed to František Balázsy. This is a
g5 integration fixup.

Assisted-by: LLM
Signed-off-by: Ryan Murray <ryan@aurorasilicon.org>
…pipes

One DISPEXT pipe of the desktop 26.6 firmware drives modes up to 6720 px
wide ("IOMFB removing mode: 7680 x 2160, max width: 6720"); a 7680x2160
sink lost its native mode. The firmware supports a two-pipe display: the
primary pipe and a peer merge their frame timing generators and each
encodes half of the line, as the T6050 firmware-27 peer (aurora-silicon#485) does.

The primary answers the display allocation in SET_TILED_DISPLAY_HINTS
with its own and the peer's endpoint IDs when the sink is wider than one
pipe and an idle pipe above it exists (apple,dcp-endpoint; the pipes
merge upwards only: dcpext0 is the primary, dcpext1 its peer; its sinkless
HDMI link does not make dcpext0 busy, that link is released before the
pair comes up). With the pair reserved the hotplug record is echoed as
on firmware 27, so both pipes know each other's display ID.

A DRM mode set wider than one pipe then runs the merge on both pipes:
the primary's video data (A364) is split into a half-width derived mode
inserted on both (A365, A366), both pipes are powered as a pair (A473),
their master swap IDs aligned (A360/A361), and the paired mode set
(A413) run with the tiling rendezvous the firmware drives through D115
(read) and D116 (write): membership, phase, display IDs, offsets; the
DSC capabilities, bits per pixel and slice hints are copied from the
primary to the peer in between (A367..A373). The same mode set again
keeps the pair (DRM repeats it right after the connector came up, and
the merged pipe no longer resolves the original timing).

Each frame is the primary's swap: its left tile stays in the swap, the
right tile goes to the peer as its own swap_start/swap_submit chain with
the primary's swap ID (the firmware does not use the IOMFB transaction
queue on this firmware); the peer's completion is not a DRM event. A
merged pair is stopped as a pair before the primary's power-off or sleep:
the single-pipe power-off of a merged primary timed out and the peer's
firmware looped.

Adaptive sync follows the CRTC on the pair: IOMFBParameter 14 goes to
both pipes with the same minimum rate before the paired mode set. A pipe
keeps the parameter across a replug, so a peer that last ran a VRR mode
alone merged with VRR on while the primary ran without it, and its
firmware crashed. A changed rate (VRR switched on or off) stops the pair
and installs it again, as the mode set applies it on one pipe.
The hybrid pipeline (a fixed output plus Type-C routes, dcpext0 on the
J616s) scores last for a Type-C port, kept for the fixed display. On the
J616s it is also the only pipe that can be the primary of a two-pipe
display, so a 7680 px sink on USB-C can only be driven through it.
appledrm.typec_hybrid_first drops that penalty: a Type-C port is planned
onto the hybrid pipeline first and dcpext1 stays free as its peer. The
HDMI display then has no pipe while the Type-C display is up; the
default keeps today's order.
A pair's peer stayed marked as its owner's peer after the owner's display
was gone: after system sleep, or a cable change, the port could move to
the peer pipe, and the peer then ran its own display with the owner
recorded. dcp_peer_swap_complete_26_6() takes the swaps of a reserved
peer for the pair's and discarded the display's own swaps: the picture
stuttered and the CRTC retrained in a loop.

The reservation is released when the primary disconnects its DPTX link
(a merged pair is stopped first by the power-off, and a pair that still
runs is kept); the next allocation reserves a peer again. A peer's swap
is only taken for the pair's while the peer has no route of its own. A
pipe that was unbound is no longer refused as a peer after it binds
again.
…he DT, no debounce without a sink

apple,typec-hybrid-first on dcpext0 (the J616s: the only pipe that can be
the primary of a display wider than one pipe) makes the fabric plan a
Type-C port onto the hybrid pipeline first, as appledrm.typec_hybrid_first
does, so a 7680 px sink on USB-C is paired at boot without a module
parameter. The pipeline is only available while its fixed output has no
sink, so an HDMI display plugged in first keeps it.

The HDMI presence debounce (ten seconds) delayed a sample that finds no
sink where there was none before, at start-up and after system sleep.
The pipeline still counted as taken when the port was planned at
start-up, or re-planned after the sink dropped its link in sleep, and
the port went to the other pipe (a flattened 3840x2160, and after sleep
no pair). Such a sample now stays absent; a sample after a real unplug is
debounced as before.
…lug finds no mode

A Type-C display that wakes from system sleep may list only its base modes
at first: its EDID is not ready yet. The retrain after that hotplug then
finds no mode for the active CRTC (the sink's 7680x2160 is not listed),
and nothing reads the EDID again until the next HPD event, which can be
a minute away; the compositor stays at a fallback mode (a 3840x2160
picture stretched over a 7680x2160 monitor).

Disconnect and connect the link again, as a replug does, two seconds
after such a retrain failed, unless a mode set completed in between; at
most four times per resume.

The work goes with the other Type-C works: disabled at probe, while
unbound and across system sleep, so that nothing arms it behind a
cancel.
The pair was installed first with adaptive sync off, because the CRTC
still had VRR disabled, and the compositor then switched VRR on: that
stopped the pair and installed it again.  When the sink drops its link
in between (a monitor waking up or switching its input layout), the
second install fails (set_digital_out_mode 0x6) and the display stays
dark until the next replug.

Install the pair with the mode's minimum refresh rate from the start,
whatever the CRTC asks.  A VRR toggle then finds the pair with the same
timing, colour and rate and keeps it.  Swaps on a VRR mode carry their
timestamps either way, so the compositor still paces the frames.
…m its Type-C route's

On desktop 26.x a Type-C route sets dcp->xbar to its own crossbar control,
for the compact DPTX link configuration.  dcp->xbar was also the fixed
HDMI output's control, so on the hybrid dcpext0 every later use for HDMI
went to the Type-C port's control instead: when the route was let go
with an HDMI display present, handing the pipeline back to HDMI selected
the Type-C port's control with the HDMI index.  The next route of that
port, on dcpext1, then waited in mux_control_select() for good, holding
the fabric lock, with the port controller's update work and interrupt
thread stuck behind it (seen with a G95NC on USB-C and HDMI, switching
its picture-by-picture layout).

Keep the fixed output's control in dcp->fixed_xbar and select, release
and check the fixed route with it only; dcp->xbar stays the control a
link configuration takes up and down, and points at the fixed one again
when the pipeline is handed back to HDMI.  A Type-C route's control is
taken with mux_control_try_select(): only a route of the same port can
hold it, and that one lets it go under the fabric lock held here, so a
wait could never end.
…ps HPD

When the sink of a desktop 26.x Type-C route drops HPD, the route gives
its pipeline back and the freed pipeline goes to a port left waiting.
The port's own DP state still said HPD at that point, so it was taken
as waiting itself and routed again at once, onto the same pipeline or,
with an HDMI display on the hybrid, onto the other one, and its
connection was replayed against a sink without HPD.

Clear the port's HPD before the pipeline is freed (the pairing pass
still sees what it counted before), connect a live HDMI display the
hybrid has gone back to, as the DP-exit path does, and leave a parked
hybrid without a link crossbar: the released route's control may now
belong to the other pipeline.  The error path that restores the fixed
route points the link crossbar at it again.
…splay onto the freed hybrid

On a desktop 26.x processor the HDMI HPD handler left disconnects to the
firmware.  The J616s's DP-to-HDMI converter (a Parade DP855) keeps its DP
link up without a sink, so the firmware never reports an unplug: the
HDMI connector stayed connected and the hybrid pipeline busy until the
next plug.  Debounce both edges for 500 ms; when HPD stays low, report
the unplug as a CRTC power-off with HPD low does, and start the HDMI
presence hold, which frees the hybrid when it expires.

A Type-C display wider than one pipe that came while HDMI held the
hybrid sits on the Type-C-only pipeline, which pairs with nothing above
it.  When the hybrid becomes free again, move such a display onto it,
as a replug does, so that it gets both pipes.

While the hybrid drives a Type-C display, an HDMI plug only waits: the
cold-plug HPD pulse would have hit the Type-C display's link.
…e-C display

A monitor on both the HDMI port and a USB-C port of a J616s (a Samsung
G95NC at 7680x2160) can have both display pipes on one input only: the
hybrid dcpext0 is the HDMI output's pipeline and the only primary of a
pair.  The monitor does not tell which input it shows: both keep HPD
up, and the unselected DP input still answers for its EDID.  So
typec_hybrid_first says who gets the hybrid: -1 follows the device tree
(the default), 0 puts an HDMI display first, 1 a Type-C display.  A
write applies at once, so that the desktop can follow the monitor's
input.

 - Type-C first: a Type-C display that asked for two pipes and found no
   peer takes the hybrid.  The HDMI display is reported gone, its link
   released (retried while the firmware is busy with a link nobody
   answers) and it waits; it gets the hybrid back when the Type-C
   display sets a mode within one pipe (picture by picture) or leaves.
 - HDMI first: an HDMI display keeps the hybrid.  One wider than one
   pipe also takes the other pipe from a Type-C display, which waits,
   routed nowhere, until the HDMI display fits one pipe or leaves.

A hand-over is an unplug and replug: at most one per ten seconds, and
no more than two failed takes until the next plug.

Peer reservations that outlived their pair are dropped: when a Type-C
route takes a pipeline reserved as another display's peer, and when a
display asks for a pair while the pipe it reserved, or the pipe it is
reserved for, drives a display of its own.
@fbalazsy

Copy link
Copy Markdown
Author

Commits since 562eb4c (562eb4c..0d43a7f): a monitor wider than one pipe (Samsung Odyssey G95NC, 7680x2160@120) runs on two desktop 26.6 pipes, over USB-C and HDMI, at boot, after system sleep and after a replug.

Why. One DISPEXT pipe of this firmware drives modes up to 6720 px wide; the Samsung Odyssey G95NC's 7680x2160 was dropped ("IOMFB removing mode: 7680 x 2160, max width: 6720") and the monitor ran 5120x1440. The firmware has the two-pipe display the T6050 firmware-27 peer in #485 uses: the primary and a peer merge their frame timing generators and each encodes half of the line.

Commits up to 50ba71c (pushed before, no comment yet).

  1. arm64: dts: apple: t6040-j616s: firmware display endpoints, Type-C routes on dcpext0: apple,dcp-endpoint (the firmware's display index: 1 dcpext0, 2 dcpext1; binding entry added), and Type-C routes on dcpext0 next to its HDMI path (a hybrid pipeline, as the J514s). The two pipes merge upwards only, so dcpext0 is the only possible primary. Routes are disabled with the chain and enabled by the gate's HDMI stage.
  2. drm/apple: drive a display wider than one pipe with two desktop 26.6 pipes: the allocation answer in SET_TILED_DISPLAY_HINTS, the hotplug record echo, the paired mode set (A364 video data, half-width derived mode inserted on both pipes A365/A366, paired power A473, master swap IDs A360/A361, A413 on both with the tiling rendezvous through D115 (read) and D116 (write), DSC caps/bpp/slice hints copied to the peer A367..A373). Per frame the primary's swap keeps the left tile and the peer gets the right tile as its own swap_start/swap_submit chain with the primary's swap ID (this firmware never advances the IOMFB transaction queue). A merged pair is stopped as a pair before the primary's power-off/sleep. Adaptive sync follows the CRTC on the pair: parameter 14 goes to both pipes with the same minimum rate before the paired mode set; a pipe keeps it across a replug, and a peer merged with VRR on while the primary ran without it crashed its firmware. (Commit 4 below installs the pair with the mode's VRR range, so a VRR on/off keeps it.) dcp-peer-v26_6.{h,inc}, included from dcp.c like the fw27 peer.
  3. drm/apple: fabric: let a Type-C port take the hybrid pipeline first: appledrm.typec_hybrid_first drops the +100 hybrid penalty in dcp_fabric_score(), so a Type-C port is planned onto dcpext0 and dcpext1 stays free as the peer. Default off (today's order: HDMI keeps dcpext0).

Commits on top of 50ba71c.

  1. drm/apple: dual pipe: give the peer back with the primary's link: the peer reservation is released with the primary's link; a peer swallows only the swaps of a display it does not drive itself (a stale reservation made the lock screen stutter).
  2. drm/apple: fabric: plan a Type-C port onto the hybrid pipeline from the DT, no debounce without a sink: apple,typec-hybrid-first on the J616s dcpext0 replaces the module default; an HDMI sample that finds no sink where there was none before skips the 10 s presence hold, so a USB-C-only boot pairs at once.
  3. drm/apple: connect a Type-C display again when a retrain after a hotplug finds no mode: after s2idle the sink first lists only its base modes; the link is connected again once, 2 s after the failed retrain.
  4. drm/apple: dual pipe: install the pair with the mode's VRR range: a VRR on/off no longer stops and re-installs the pair (a re-install racing a sink HPD drop left the display dark).
  5. drm/apple: fabric: keep the hybrid pipeline's HDMI crossbar apart from its Type-C route's: on desktop 26.x dcp->xbar was overwritten with the Type-C route's control, so handing dcpext0 back to HDMI selected the Type-C port's control; the port's next route blocked in mux_control_select() under the fabric lock, with the port controller's update work and IRQ thread stuck behind it (and a reboot hanging in device_shutdown()). Type-C routes now use mux_control_try_select().
  6. drm/apple: fabric: do not route a Type-C port again when its sink drops HPD: the pipeline was freed while the port still read HPD, so the same port was routed again at once.
  7. drm/apple: desktop HDMI: report the unplug, move a two-pipe Type-C display onto the freed hybrid: the J616s DP-to-HDMI converter (Parade DP855) keeps its DP link without a sink and the firmware reports no unplug; the debounced HPD falling edge now reports it.
  8. drm/apple: fabric: hand the hybrid between an HDMI and a two-pipe Type-C display: one monitor on both inputs cannot tell which input it shows (both keep HPD, the unselected DP input still answers for its EDID), so appledrm.typec_hybrid_first (now -1 from the DT, 0 HDMI first, 1 Type-C first) decides and applies at once when written; stale peer reservations are dropped.

Hardware tested on: J616s, DCP firmware 26.6.2, m4-loader rev19-j616s-1, Samsung Odyssey G95NC on USB-C (DP80 cable; Left Back and Left Front ports) and HDMI. Kernel 7.3.0-m4.163 = this branch head plus the local J616s platform layer.

Result
Boot, USB-C only and USB-C + HDMI (Type-C first) 7680x2160@120 on two pipes, paired mode set once with adaptive sync (48 Hz minimum), then kept; picture and VRR confirmed on the monitor (OSD Adaptive-Sync On, testufo 120 fps)
VRR off/on in the compositor pair kept, no re-install
Picture-by-picture on/off on: USB-C half on dcpext1, HDMI half on dcpext0 within ~3 s; off: pair within ~5 s
typec_hybrid_first 1 <-> 0 following the monitor's input full width moves to the selected input
s2idle, picture by picture and full width, several cycles back after the wake
DPMS off/on peer stop before power-off: 0, pair set again
HDMI unplugged and plugged live detected; with Type-C first the USB-C display pairs ~10 s after the unplug
USB-C unplugged and plugged with HDMI connected pair within ~4 s
USB-C on another port (Left Front) pair
Build every commit W=1 clean, KUnit objects included (KUnit not run)
dtbs_check (display/apple,dcp.yaml, arm64 defconfig) no diagnostics

Known limits. 7680x2160@240 stays out of reach (the firmware clamps the ATC port to HBR3 and allows 4 DSC slices per pipe; macOS does not offer it on this machine either). The monitor reports no selected input, hence the parameter; a hand-over between the inputs is an unplug and replug, so the monitor blinks. After s2idle the first enumeration is partial and the link is connected again once. Re-pairing after an unpair caused by a failed frame fails A364 of the original timing (a clean stop and re-pair works). Not tested: two different monitors on HDMI and USB-C that both want two pipes.

RyanTheTide added a commit that referenced this pull request Oct 27, 2026
…c-carry

J616s external displays: carry #451 with a FORTIFY build fix and selftest fixtures
@RyanTheTide
RyanTheTide merged commit 8aeca5e into aurora-silicon:integration/aurora Oct 27, 2026
@RyanTheTide

Copy link
Copy Markdown
Contributor

Thank you, this is now in integration/aurora through #526, with all 28 of your commits unchanged under your authorship.

Two things turned up while testing it, and #526 carries the fixes:

  • dcp.o did not build with CONFIG_FORTIFY_SOURCE=y. dcp_peer_hints_26_6() clears the allocation block with memset(&r->primary_id, 0, 32), which FORTIFY treats as a write past primary_id. The fix groups the allocation fields with struct_group_attr() and clears the group.
  • Nine apple-dcp host selftests needed their fixtures updated for the VRR and hybrid hand-over changes. No assertions changed.

It was tested on a J616s through a DP Alt Mode dock (mode switches up to 1080p180, DPMS, GPU) and on a Neo (boot and display regression). Details are in #526. Closing this one as merged there.

erikwb pushed a commit to erikwb/linux that referenced this pull request Oct 28, 2026
The g5 line already has the internal-panel adaptive-sync policy and failure
fallback that overlap the last two commits of aurora-silicon#451. Keep that panel path,
admit VRR advertised by external sink modes, and program the desktop sync
parameter when an external pipe enters or leaves VRR.

Ported from the external-display intent of fbalazsy's aurora-silicon#451 while preserving
its preceding commits.

Assisted-by: LLM
Signed-off-by: Ryan Murray <ryan@aurorasilicon.org>
erikwb pushed a commit to erikwb/linux that referenced this pull request Oct 28, 2026
The g5 lifecycle fixture already extracts the real desktop 26.6 callback
checker. The aurora-silicon#451 fixture follow-up adds an assert stub for its older base,
which makes three g5 host scenarios fail to compile with a duplicate symbol.
Keep the extracted checker and remove only that redundant stub.

The original aurora-silicon#451 commits remain attributed to František Balázsy. This is a
g5 integration fixup.

Assisted-by: LLM
Signed-off-by: Ryan Murray <ryan@aurorasilicon.org>
erikwb pushed a commit to erikwb/linux that referenced this pull request Oct 28, 2026
Publish the entered partner DisplayPort alternate mode, consume SPMI events latched before IRQ registration, expose pin E, and provide the connector fwnode fallback. The tether route gate follow-up belongs with aurora-silicon#451 and is included in the batch3 candidate.

Assisted-by: LLM
erikwb pushed a commit to erikwb/linux that referenced this pull request Oct 28, 2026
…ora-silicon#518, aurora-silicon#516, aurora-silicon#519 and aurora-silicon#451

This is the tree of the J616s test kernel 7.3.0-aurora-j616s-batch5, built
from these parents:

- wip/j616s-batch1-20261027 (e1a25b5): the J616s bring-up series
- integration/aurora 647f084 (includes aurora-silicon#498, aurora-silicon#512, aurora-silicon#523 and DJ's G17 merges)
- aurora-silicon#518 ad14f2d: 25G83 tiler growth fix (Jonathan Goyvaerts)
- aurora-silicon#516 31340ab: T6040 two-lane DP pin D beside USB3
- aurora-silicon#519 d769aaf: tipd reconcile of firmware-entered J616s DP Alt Mode
- aurora-silicon#451 0d43a7f: J616s external displays (František Balázsy)

Resolution notes:
- aurora-silicon#451's display code supersedes the older aurora-silicon#451 copy carried by batch1,
  including its desktop 26.6 adaptive sync. Batch1's callback guard,
  ProMotion presentation timestamps, minRR=0 restore and swap watchdog stay.
- The display gate finds a Type-C route's PHY by its typecN name, so aurora-silicon#451's
  dcpext0 route@0 is not enabled on a tether whose ATC0 PHY is disabled.
- aurora-silicon#516's pin D and aurora-silicon#519's tipd reconcile are kept as in the J616s series.
  Batch2's selftest fixture reconcile for the G17 owner and CLPC fixtures is
  part of this tree.

Tested on the J616s with the follow-up commits: boots with 0 WARN, external
display at 1920x1080@180 through DP Alt Mode, 20/20 mode switches and
15/15 DPMS cycles with no new kernel errors.

Assisted-by: LLM
Signed-off-by: Ryan Murray <ryan@aurorasilicon.org>
erikwb pushed a commit to erikwb/linux that referenced this pull request Oct 28, 2026
…nc in batch5

The batch5 merge takes aurora-silicon#451's desktop 26.6 adaptive sync and removes the
J616s series' parameter sequence (dcp_modeset_sync(), its fixed-refresh
fallback on a rejected VRR mode set) and its admission gate
(dcp_desktop_vrr_check(), dcp_desktop_vrr_supported()).

Retire panel-adaptive-fallback, which drove only that removed sequence;
vrr-rejection now pins aurora-silicon#451's VRR_ENABLED-to-mode-set admission.
Drop the stubs other fixtures kept for the removed functions
(desktop-unknown-modeset-recovery, vrr-rejection), which no longer
compile as unused. In the publication fixture, take lookup_mode() by
its definition: the J616s takeover check starts a line with a
lookup_mode() call, which the line-start pattern matched first.

panel-adaptive-watchdog is unchanged: the fixed-refresh latch after an
adaptive swap times out remains.

Assisted-by: LLM
Signed-off-by: Ryan Murray <ryan@aurorasilicon.org>
erikwb pushed a commit to erikwb/linux that referenced this pull request Oct 28, 2026
Brings the J616s batch5 kernel (the tree running on the M4 Pro) up to the
current integration/aurora, which adds aurora-silicon#524 (J713 T8132 energy model), aurora-silicon#526
(the T6041 display fabric carry, whose peer allocation field group batch5
already has) and aurora-silicon#529 (J713 USB-C idle power).

Conflict resolution:
- selftests apple-dcp rate.py: keep batch5's version. Its fixtures follow
  aurora-silicon#451's desktop adaptive sync and the T6041 external EDID. The
  integration/aurora side only changed how the lookup_mode() definition is
  found; its line-start pattern also matches the indented
  "lookup_mode(dcp, ..." call that batch5's iomfb.c has before the
  definition, while batch5's 'bool lookup_mode(' marker finds the
  definition in either form.

aurora-silicon#529's ATC and dwc3 changes merge cleanly beside aurora-silicon#516's two-lane DP pin D.

Assisted-by: LLM
Signed-off-by: Ryan Murray <ryan@aurorasilicon.org>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.

3 participants