Skip to content

drm/apple: T6041 (M4 Pro/Max) displays on desktop 26.6 firmware: J616s internal panel, HDMI and USB-C DisplayPort - #445

Closed
fbalazsy wants to merge 9 commits into
aurora-silicon:integration/aurorafrom
fbalazsy:j616s/display-t6041
Closed

fbalazsy wants to merge 9 commits into
aurora-silicon:integration/aurorafrom
fbalazsy:j616s/display-t6041

Conversation

@fbalazsy

Copy link
Copy Markdown

The M4 Pro/Max display coprocessors (T6041: the internal dcp and the external dcpext0/dcpext1) run DCP firmware 26.6.2, but that firmware is not the H17 protocol of the A18 Pro/M5 (iomfb_h17p.c): it continues the 14.7 IOMFB of the earlier desktop chips (A029 setup_video_limits, A407 swap_submit with the 0x1bd8-byte request, A473 on the command channel, compact EPIC on every auxiliary endpoint). This adds it to the driver as its own template instance, DCP_FIRMWARE_V_26_6_DESKTOP (not the J613 DCPLink DCP_FIRMWARE_V_26_6), and drives the J616s internal panel, the HDMI output (dcpext0, fixed DP PHY on ATC3 → DP-to-HDMI converter) and USB-C DisplayPort (dcpext1, Type-C route chosen from the PHY in DP alt mode) with it.

Externals follow the J700 model where it does not depend on the protocol: one DRM device, each dcpext an extra CRTC, connectors with fwnode for out-of-band hotplug, a dcpext that fails to probe or start becomes an unusable pipe and never takes the panel down (no hard coupling as for t8140-dcpext). The Type-C display fabric (typec-routes) is not used yet for T6041; the route comes from the DT's apple,dptx-phys list and tipd's DP alt mode.

Change. Nine commits:

  1. phy: apple: T6050 fixed DisplayPort PHY register sequences, 2. dt-bindings: phy: apple,t6040-atcphy: DisplayPort mode switch and fixed DP PHY, 3. phy: apple: t6040: DisplayPort mode — phy: apple: t6040: DisplayPort mode (Type-C alt mode and the fixed HDMI PHY) #444 unchanged, folded in here (the drm side needs apple_t6040_phy_dp_alt()); phy: apple: t6040: DisplayPort mode (Type-C alt mode and the fixed HDMI PHY) #444 is closed in favour of this PR.
  2. drm/apple: tell the H17 26.x lineage apart from the desktop one — DCP_FW_H17() / DCP_FW_DESKTOP in version_utils.h; the H17 branches of the template key on it. No functional change: iomfb_h17p.o, iomfb_v12_3.o, iomfb_v13_3.o and iomfb_v14_7_tpl.o keep byte-identical code and data sections (compared section by section, also after every later commit).
  3. drm/apple: add the desktop 26.6 IOMFB template instance for T6041 — iomfb_v26_6_desktop.c/.h: method and callback tables, the 26.6 wire structs with size asserts, power-on/off/sleep sequences, modeset, swap, gamma.
  4. drm/apple: afk: speak compact EPIC on desktop 26.x endpoints — compact framing with fragment reassembly (afk-fragment.h, lengths bounded, ring pointers validated), only for the desktop firmware.
  5. drm/apple: drive the T6041 internal panel on desktop 26.6 firmware — apple,t6041-dcp match, exact 26.6.2 firmware gate, endpoint graph as advertised, takeover trial (boot mode first).
  6. drm/apple: compact DPTX and DPAVSERV for desktop 26.x external displays — DPTX on EPIC2C 0x2c, ports bound by unit, set_hpd status in word 0, HBR3.
  7. drm/apple: drive the T6041 external displays on desktop 26.6 firmware — apple,t6041-dcpext, HDMI HPD with the cold-plug pulse, Type-C route select/release under hpd_mutex, re-power of a replugged pipe, no keepalive across a disconnect. New module parameter t6041_dcpext (default 1, 0 keeps the dcpext out of the component set).

Commits 5–8 port the 26.6 work of Niklas Sheth (T8132 hotplug, compact AFK), Josh Rodd (J715 on 26.6.2), Cody Ho (firmware gate) and Jonathan Goyvaerts (J616s internal panel), credited with Co-developed-by/Signed-off-by.

Dependencies. Runtime: the T6041 crossbar of #435 (externals; the panel does not need it) and the DISPEXT power states of #436. Device tree: the internal panel uses the dcp node already in t6040.dtsi; the J616s dcpext0/1, atcphy3, crossbar, DART and DISPEXT nodes are not part of this PR — they follow once the loader enables them (today they are switched on at runtime here).

Sign-off note (@RyanTheTide, @DjDeveloperr). Commit 1 is Dj's atc-t6050-dp-data.h from #248 (440f79f) unchanged with his authorship; the original has no Signed-off-by from Dj, so checkpatch reports the missing author sign-off. Dj, could you add yours (or should this wait for #248)? The other commits have all sign-offs.

Hardware tested on: MacBook Pro 16" M4 Pro (J616s, T6040/T6041), DCP firmware 26.6.2, Samsung G95NC on HDMI and USB-C (PBP), Hyprland 0.56. Local kernel 7.3.0-m4.79 = the daily J616s tree (integration/aurora 420a1c8 + J616s series) with drivers/gpu/drm/apple = integration/aurora 653c489 + exactly these drm commits, the #435 crossbar and #444 PHY.

How tested:

Result
Internal panel eDP-1 3456x2234@60 (120 Hz offered), backlight 0–600 nits, all advertised endpoints started
HDMI (dcpext0) HDMI-A-1 1920x2160@59.99 from boot; cold plug and replug light up
USB-C DP (dcpext1) USB-1 1920x2160@119.98 from boot (HBR3); unplug/replug: hotplug, power-on, modeset
All three at once eDP-1 + HDMI-A-1 + USB-1, picture confirmed on each
s2idle with both externals (7 min) all three back after resume, DPMS on, no DCP errors
Other firmware paths the four existing template objects byte-identical; appledrm W=1 clean per commit (bisectable)
KUnit (kunit.py --arch=arm64, DRM_APPLE=y, PHY_APPLE_T6040_USB2=n) links; 322 tests, 321 passed, 1 skipped (S35_dual_retry_off, covered by W3)
checkpatch.pl commit 1 as above; DCP_FW_NAME()/DCP_FW_VER spacing and comparison reports on the template follow its existing style; MAINTAINERS note for the new files

Not tested: 7680x2160 on the G95NC (needs the firmware's dual-pipe path, max width 6720 per pipe), pin assignment D, HDMI/DP audio, VRR (no VRR timings offered on this firmware for now), HDCP.

DjDeveloperr and others added 9 commits October 25, 2026 13:03
The link sequences of the T6050 fixed DP PHY, taken unchanged from
aurora-silicon#248 ("phy: apple: Add T6050 fixed DP and four-clock
crossbar support", 440f79f) as the single file the T6040 Type-C PHY
needs for its DisplayPort mode: the T6050 ADT names its PHY
"atc-phy,t6050", "atc-phy,t6040", and the sequences run unchanged on the
T6040 PHY core. Only the data header is carried here; the T6050 PHY driver,
its binding and the crossbar changes of aurora-silicon#248 stay in that pull request. Once
aurora-silicon#248 lands, this commit disappears on rebase.

[fbalazsy: picked drivers/phy/apple/atc-t6050-dp-data.h alone from aurora-silicon#248]

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

The T6040 Type-C PHY also carries DisplayPort: the PHY of a USB-C port as
the alt mode switch of its connector (mode-switch), and the PHY of an HDMI
converter as a fixed DisplayPort link. The fixed instance has no USB
controller behind it, so no PIPE interface ("pipehandler" region), no reset
line and no connector port. Describe the DP phy type, the mode switch and
the ten-region variant, and require #reset-cells only where the PIPE
interface exists.

Signed-off-by: František Balázsy <frantisekbalazsy@gmail.com>
Add PHY_TYPE_DP to the T6040 Type-C PHY: PHY mode 3 of the paired
AppleT6040TypeCPhy driver, DisplayPort on all four lanes, for a Type-C port
whose controller selected DP alt mode with pin assignment C or E, and for
the fixed DP PHY of an HDMI converter, a node without the PIPE interface
and the DWC3 reset line, which now probes too.

Bring-up follows the paired driver's mode 3: wake the CFG block, apply the
generic ADT tunables and the DPTX tunables, set the lane crossbar to DP,
hold the common block and both TX lanes on by override (the PHY
coprocessor does not run in this mode), release the reset, then set the
lane mode and the AUX block. On a Type-C port the USB3 side is shut down
first. The per-rate link sequences (RBR to HBR3) and the drive-setting
presets are the T6050 fixed-DP tables, whose PHY the T6050 ADT marks as
T6040-compatible ("atc-phy,t6050", "atc-phy,t6040"); the T6050 clock shim
steps are replaced by the T6040 DP clock switch of the CFG window, run
after the lane program as the paired driver does. Pin assignment D (two DP
lanes next to USB3) is not supported.

The Type-C mode switch latches whether the port is in DP alt mode;
apple_t6040_phy_dp_alt() (include/linux/soc/apple/dp-tunnel.h) exposes it
for a DCP that lists several Type-C DP PHYs and routes to the one with a
sink plugged.

Signed-off-by: František Balázsy <frantisekbalazsy@gmail.com>
The IOMFB template keys its H17 (A18 Pro, M5) wire changes on
DCP_FW_VER >= 26.0. The desktop firmware of the M4 Pro/Max DCP (T6041)
also reports 26.x, but continues the 14.7 IOMFB.

Mark a desktop instance's version with DCP_FW_DESKTOP and key the H17
branches on DCP_FW_H17(). No functional change: the 12.3, 13.3, 14.7
and H17P template objects are unchanged.

Signed-off-by: František Balázsy <frantisekbalazsy@gmail.com>
The 26.6 firmware of the M4 Pro/Max DCP (T6041) continues the 14.7
IOMFB rather than the H17 protocol: most method numbers match H17P,
but the records and the start, power and mode-set flows are those of
14.7 with extensions. Add it as a template instance,
iomfb_v26_6_desktop.c, with firmware compatibility value
DCP_FIRMWARE_V_26_6_DESKTOP. Nothing selects it yet.

The instance carries this firmware's wire layout: a 0x588-byte swap
record with presents_surface, has_completion and the panel level in
nits as a binary64, a 0x1bd8-byte swap_submit with six surf2 entries,
swap_start with an opaque client cookie, set_power_state with four
transition flags, and the padded abort_swaps, read_edt_data, hotplug,
swap intent and frame_sync records. DCP_FW_LAYOUT_14_7() covers the
desktop 26.x versions, and static assertions pin the record sizes.

Power-on is A414, first_client_open, A474, is_main_display and
set_power_state(1), then set_display_device; power-off clears the
swap and sets power state 0. A mode set selects the timing through
get_mode_info (A364) before set_digital_out_mode. A pipe without a
live mode is bootstrapped through the timing's best virtual color
element, which the parser now records. GAMMA_LUT is composed with the
firmware's gamma table (A420/A421). VRR timings are not offered.

The per-DCP desktop state is allocated only for this firmware, so
other instances keep their struct apple_dcp size and the 12.3, 13.3,
14.7 and H17P template objects are unchanged.

Co-developed-by: Niklas Sheth <hi@niklassheth.com>
Signed-off-by: Niklas Sheth <hi@niklassheth.com>
Co-developed-by: Josh Rodd <josh@rodd.us>
Signed-off-by: Josh Rodd <josh@rodd.us>
Co-developed-by: Jonathan Goyvaerts <jonathan.goyvaerts@gmail.com>
Signed-off-by: Jonathan Goyvaerts <jonathan.goyvaerts@gmail.com>
Signed-off-by: František Balázsy <frantisekbalazsy@gmail.com>
The desktop 26.6 firmware of the T6041 DCP speaks a compact EPIC on
its system and auxiliary endpoints: an 8-byte transport header
(sequence, flags, interface, total length), the timestamp, an 8-byte
sub-header and the payload. Messages longer than 1028 bytes arrive in
fragments that repeat the header. Services announce themselves with
type 0x11 and are opened with type 0x12; commands, replies and calls
carry a status/length descriptor.

On DCP_FIRMWARE_V_26_6_DESKTOP endpoints, switch to it on the first
compact message: reassemble fragments, register and open announced
services, answer calls, deliver reports and match replies to pending
commands. Send commands and replies the same way, fragmented when
needed, waiting for ring space with WAIT_FOR_SPACE, and answer the
firmware's WAIT_FOR_SPACE after draining the receive ring. Call
replies are sent from a separate workqueue so that a full transmit
ring cannot stall reception. Ring pointers read from shared memory
are validated before use; a send that fails part-way fails the
commands in flight without shutting the endpoint down.

Other firmware never takes these paths.

Co-developed-by: Niklas Sheth <hi@niklassheth.com>
Signed-off-by: Niklas Sheth <hi@niklassheth.com>
Signed-off-by: František Balázsy <frantisekbalazsy@gmail.com>
Match apple,t6041-dcp and apple,t6041-dcpext and drive the internal
DCP of the M4 Pro/Max (J616s) through the desktop 26.6 IOMFB instance.

Probe accepts a T6041 processor only with apple,firmware-version and
apple,firmware-compat both 26.6.2, and the internal one only if its
register windows, iommus, mboxes and piodma match the ADT. The clock
is optional there, and without apple,max-brightness the backlight
maximum defaults to the panel's 600 nits.

The firmware does not tolerate being left running with its endpoints
unserviced, so bind sets the coprocessor running and dcp_start()
wakes RTKit once every handler is in place. It starts the system
endpoint, the IOMFB transport and the auxiliary endpoints the firmware
advertises, routes their messages, and binds the IOMFB shared memory
last. An auxiliary endpoint that fails to start is skipped.

Before the boot framebuffer is evicted, the panel goes through a
takeover trial: DCP ready, modes known, panel connected, power-on and
one mode set, boot-like mode first. If it fails, the DCP is left
running, the DRM device is kept, shutdown and resume leave the DCP
alone and suspend is refused, and the boot framebuffer keeps the
screen.

The T6041 external processors stay out of the DRM device unless
t6041_dcpext=1, and then as unusable pipes.

Co-developed-by: Cody Ho <codyho@stanford.edu>
Signed-off-by: Cody Ho <codyho@stanford.edu>
Co-developed-by: Jonathan Goyvaerts <jonathan.goyvaerts@gmail.com>
Signed-off-by: Jonathan Goyvaerts <jonathan.goyvaerts@gmail.com>
Signed-off-by: František Balázsy <frantisekbalazsy@gmail.com>
The T6041 external display processors publish their DPTX port services
as dcpdptx-port-epic:0 and :1 in compact EPIC on EPIC2C (0x2c). Bind
them by unit, open port 0 last, and follow the firmware's own client
where the compact ABI differs:

- set_hpd returns an IOReturn status in its first word;
- GET_SUPPORTS_HPD and GET_VIRTUAL_DEVICE_MODE are answered with 0;
- GET_MAX_LINK_RATE offers HBR3;
- the crossbar link is taken down before a link configuration change
  and brought up after it;
- group 1 calls 3 and 4 keep the first 32 bytes of their record and
  clear the result word.

The DPAVSERV EDID request asks for 2 KiB there, as a 32 KiB inline
request does not fit the compact ring, and dcpdp-service-epic is not
opened.

Co-developed-by: Niklas Sheth <hi@niklassheth.com>
Signed-off-by: Niklas Sheth <hi@niklassheth.com>
Signed-off-by: František Balázsy <frantisekbalazsy@gmail.com>
The two external display processors of the M4 Pro/Max (J616s) join the
panel's DRM device as further pipelines; t6041_dcpext=0 leaves them
alone. dcpext0 drives the HDMI port through its fixed route (atcphy3
and its crossbar), dcpext1 the USB-C ports through a route chosen at
connect from the PHY that entered DP alt mode.

- probe powers the fixed route's DP PHY and lists the dynamic routes
  (dp-phyN, dp-xbarN, apple,dptx-phys, apple,dptx-mux-index);
- start brings up the advertised auxiliary endpoints, the DPTX port
  services, DPAVSERV, EXPERT and EPIC25, and connects DPTX port 0;
- a connect asserts HPD after the display request and does not fail
  when no display is attached;
- an HDMI hotplug on a connected port pulses HPD, so the firmware
  exports the modes of a display plugged in after boot;
- a USB-C hotplug selects and releases the route through the
  connector's out-of-band hotplug under hpd_mutex; a failed connect
  and unbind release it;
- a CRTC kept active across a replug is powered on and set up again on
  its next flush.

A dcpext that is absent or fails to probe or bind is kept as an
unusable pipe and never holds up the panel.

apple_t6040_phy_dp_alt() gets a stub, and DRM_APPLE cannot be built in
with the T6040 USB2 PHY as a module.

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.

I read the whole series against integration/aurora 653c489. The shared paths look safe for the other machines on the universal kernel. I preprocessed iomfb_template.c for the v12.3, v13.3, v14.7 and H17P instances before and after. They only differ by the DCP_FW_SWAP() spelling, the moved swap_req declaration and the surf2_null size, so DCP_FW_H17 still matches only H17P. The compact EPIC paths in afk.c, dcp.c, dptxep.c and dpavservep.c are all gated on the desktop firmware or the t6041 compatibles. The fragment reassembly bounds look right to me (total capped at 1 MiB, each piece checked against what is left, seq and interface continuity), and so do the TX ring wrap and space checks.

The attribution is in order. Every Co-developed-by sign-off matches the original commits, and the Gravity files these come from carry the same GPL-2.0-only OR MIT licence.

Things to fix before this can go in:

  1. Commit 1 doesn't need to wait for #248. Its file is byte-identical to atc-t6050-dp-data.h in 440f79f, and that commit carries "Signed-off-by: Dj hey@djdev.me". So the original does have the author's sign-off, and you can carry it over above yours with a line pointing at 440f79f, the usual way for a file picked from a signed commit. #248 itself is stacked on the APIF branch and doesn't apply on integration/aurora, so landing it first isn't practical.

  2. dptxep.c dptxport_compact_xbar_link() calls apple_dpxbar_link_up() and apple_dpxbar_link_down() directly. DRM_APPLE has no dependency on MUX_APPLE_DPXBAR, so DRM_APPLE=y with the crossbar as a module fails to link. In the universal config both are modules, so appledrm.ko now pulls in the crossbar module on every machine. dcp-fabric.c already has dcp_dpxbar_link(), which uses symbol_get() for exactly this reason. Making that non-static and calling it here would keep the crossbar optional.

  3. The same applies to apple_t6040_phy_dp_alt() in dcp.c together with the new "depends on PHY_APPLE_T6040_USB2 || !PHY_APPLE_T6040_USB2". It builds, but appledrm.ko now needs the T6040 PHY module on every SoC, and DRM_APPLE=y is quietly demoted to m wherever the PHY is a module. A symbol_get() lookup like the crossbar one would avoid both.

  4. t6040-usb2.c, a stale ERR_PTR tunable. When t6040_usb3_probe() fails to parse a tunable, it leaves the ERR_PTR in tphy->tunables[i] and returns 0. t6040_dp_probe() then treats the node as DP only, and for an absent property it hits continue without clearing that slot. t6040_dp_phy_init() then passes the ERR_PTR to apple_tunable_apply(), which only checks for NULL. Nothing in the tree reaches this yet, since no DT consumes the DP PHY, but clearing the slot on every skip and error path would close it. It also means a Type-C PHY whose USB3 setup failed is now classed as DP only, which may not be what you want.

  5. t6040-usb2.c, USB3 and DP ownership. t6040_dp_power_on() shuts the USB3 side down, but t6040_dp_power_off() only clears dp_up and doesn't bring USB3 back. Nothing stops t6040_usb3_power_on() from re-initialising the PHY under a live DP mode either. After a Type-C DP session ends, USB3 on that port looks like it stays down until dwc3 power cycles the PHY. This is T6041 only, and not reachable with the current DT.

Smaller notes. The desktop callbacks in iomfb.c don't validate in_len/out_len the way iomfb_check_callback_h17p() does for H17P. The u16 offset keeps them inside the shared memory, so this isn't an overflow, but a size check would be cheap. Also, dcp_desktop_dyn_route_select_locked() takes the drvdata of whatever dp-phyN points at. A compatible check there would guard against a DT that names a different PHY.

I'm building the full series together with #435, #436, #427 and #428 on the universal recipe with WERROR, and running KUnit, the apple-dcp host selftests and the DT checks. I'll add the results and a J616s internal panel boot here once they're done.

@RyanTheTide

Copy link
Copy Markdown
Contributor

Thanks a lot for this series! It's now carried in #446 with your eight commits unchanged, your authorship, sign-off and the co-developer credit intact. Commit 1 is now Dj's own signed commit from #248, and there are three small fixes on top (runtime lookups for the crossbar and DP alt helpers, the stale tunable pointer, and the host selftest fixtures). The internal panel came up on our J616s with it. Closing this one in favour of #446.

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