Skip to content

J716c: Thunderbolt 5 and USB4 on the right USB-C port - #946

Closed
erikwb wants to merge 18 commits into
j716c/t6050-platformfrom
j716c/thunderbolt
Closed

erikwb wants to merge 18 commits into
j716c/t6050-platformfrom
j716c/thunderbolt

Conversation

@erikwb

@erikwb erikwb commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

The M5 Max MacBook Pro's right-hand USB-C port has no USB4/Thunderbolt: the T6050 combo PHY has no CIO mode, and the host router, its DART and the PCIe-C tunnel port all start differently from earlier SoCs. With this series a CalDigit TS5 dock links at 40 Gb/s ×2, and its USB3 and PCIe come through the tunnels.

What it changes

  • Combo PHY: a CIO (USB4/Thunderbolt) mode. The PHY firmware's data is restored before every restart, otherwise every second start never completes. The PIPE switch tolerates the missing lock ACK on this port and parks from any dummy producer. In CIO mode USB3 is carried over the router's tunnel.
  • CIO reset: the T6050 reconfig request. It waits for the request and done bits to clear before and after, since the router's CPU wrapper takes an SError until then.
  • Host router (thunderbolt: apple):
    • the router's registers are left alone until its CPU runs;
    • the CPU is released with a plain store;
    • LSTX is handed over once RTKit is up;
    • the PCIe router AP firmware tunable is applied;
    • Gen4 cables are described with the Gen4 bit alone;
    • the NHI's parity interrupt is accounted for.
  • DART: a T8110 DART that nothing set up gets its tunables, bypass streams, full error reporting and context-cache flush from the DT.
  • DROM: entries for ports the router doesn't implement are skipped instead of failing the host DROM.
  • PCIe-C: a t6050-pciec variant (256-entry RID2SID, its MSI layout, kernel cold init), which thunderbolt: apple recognises as the tunnel host.
  • Bindings and DT: bindings for each of the above; the J716c right port, its router, DART, NHI and PCIe tunnel. The router node is named acio@: the boot loader disables a cio@ node whose first register isn't its ADT register.

Base: this PR is stacked on the T6050 PRs. Its base, j716c/t6050-platform, is those PRs merged in order: #934 (build fixes), #935 (T6050 device tree), #936 (NVMe), #937 (PCIe), #938 (N1), #939 (combo PHY and DWC3), #940 (dptx), #941 (SMC), #942 (RTKit crash logs), #943 (DockChannel), #481 (cpufreq) and #945 (J716c device tree). Only the last 18 commits are this PR.

Testing (J716c, M5 Max, CalDigit TS5)

  • On this code: the TS5 links at 40 Gb/s ×2 at boot, USB3 runs at 10 Gb/s through the dock, and its I225 Ethernet comes up at 2.5 GbE over the PCIe tunnel.
  • Earlier builds of the same series, the same apart from comments and removed bring-up switches, also passed hotplug, unplug/replug, bolt authorization (IOMMU policy) and a USB-C hub in DisplayPort alternate mode.

Not done: DisplayPort tunnels, and recovery from the router's Dead End state.

Checks:

  • Every commit builds on its own.
  • The router, NHI, DART, PCIe tunnel and PHY nodes validate against their bindings. A full dtbs_check can't run on this base: pci/apple,pcie.yaml has a duplicate key.

Created with Claude Opus 5.5 in Claude Code.

@erikwb
erikwb force-pushed the j716c/t6050-platform branch from d4bc832 to 704fe6a Compare November 5, 2026 01:09
@erikwb
erikwb force-pushed the j716c/thunderbolt branch 2 times, most recently from 7ba73fd to 0cd5c49 Compare November 5, 2026 01:22
@erikwb
erikwb force-pushed the j716c/t6050-platform branch from 704fe6a to 3bfc832 Compare November 5, 2026 01:22
@erikwb
erikwb force-pushed the j716c/thunderbolt branch from 0cd5c49 to 8394a67 Compare November 5, 2026 01:24
@erikwb
erikwb force-pushed the j716c/t6050-platform branch from 3bfc832 to 3180de6 Compare November 5, 2026 01:24
erikwb added 18 commits November 4, 2026 22:13
Describe the right-hand USB-C port: its SN201202x PD controller, the
ATC2 combo PHY, usb-drd2 and its DARTs, and their power domains.

On J716c the port's HPM is ADT /arm-io/nub-spmi-a1/hpm2 (SPMI address
0xc, port-location "right"), on its own SPMI bus, not on nub-spmi-a0
at address 8 as on J775D; on J716c that address is hpm5 (port-type
0x11), not a port's controller. Add nub-spmi-a1 to t6050-max.dtsi,
polled as nub-spmi-a0 is, put the HPM on it, and enable the ATC2 chain
that J775D leaves to the display handoff.

Add the atcphy2 alias so that the boot loader fills the PHY's tunables
from /arm-io/atc-phy2, and turn dcpext0's Type-C route off with
dcpext0: a mode-switch endpoint whose mux is never registered would
defer the PD controller forever.

Developed with Claude Code.

Signed-off-by: Erik Bourget <erik@ebourget.net>
On J716c the PIPE handler does not acknowledge the lock request when
the controller switches from the dummy producer to USB3, even with the
dummy PHY behind the mux enabled. The switch then fails and the port
falls back to USB2.

The switch works without the ACK. Wait 6 ms for the lock ACK, and for
an unlock ACK to clear, then log the PIPE handler state if it is
missing and complete the switch. With this, USB3 comes up on that
port.

Developed with Claude Code.

Signed-off-by: Erik Bourget <erik@ebourget.net>
On T6050 the CPU wrapper of each USB4 router is clocked through the CIO
reconfig request (the router CPU's clock gate CIOn_RECONFIG_V). Until it
has run, any access to the wrapper faults.

The request register uses the T8142 bit positions (request BIT(2 * port),
four ports) and sits at offset 0x60 of a page in the die's PMGR block,
0x280620000 on die 0. Its odd bit is a done flag rather than a busy
flag: it is set once the request is taken, then request and done clear
by themselves about 4 ms later. The wrapper takes an SError when it is
accessed before they clear.

Wait for both bits to clear (a reconfiguration still under way), store
the request, wait for the done flag, then wait for both bits to clear
again.

Developed with Claude Code.

Signed-off-by: Erik Bourget <erik@ebourget.net>
The CIO (USB4/Thunderbolt) mode has its own firmware register and
receiver tunables. They are optional: an absent one is skipped.

Developed with Claude Code.

Signed-off-by: Erik Bourget <erik@ebourget.net>
The T6050 combo PHY rejects TYPEC_MODE_USB4 and the Thunderbolt alternate
mode, so a USB4 dock that the PD controller has already entered USB4 with
gets no link: the PHY is never configured and DWC3 cannot start.

Add the CIO mode as a step recipe in the style of the USB ones:

- the mode-independent power-up;
- the common tunables, then the CIO set (AUS40CMN_SHM, AUSPLL_CORE,
  CIO4PLL_CORE) and the CIO80 set (UC_REGS_CIO, LN0/LN1_RX_TOP_CIO,
  LN0/LN1_RX_EQ_CIO), with no USB or DP lane masks;
- the crossbar in CIO, where orientation only selects TOP +0x78 bit 0;
- the sleep-control release of both lanes;
- the firmware start, UC_TNBL_DONE and the ACIOPHY reset release, without
  the PIPE BIST and the ATCPHY_CFG +0x8 orientation bit.

The shutdown powers both transmitters and receivers down, shuts the common
block down, stops the firmware and runs the usual sleep, clamp and reset
sequence.

USB2 runs beside the link, so the USB2 PHY is brought up as for USB. USB3
arrives through a tunnel later; in CIO the USB3 PHY set_mode leaves DWC3
on its dummy PIPE producer until a tunnel exists.

The five CIO80 tunables are optional DT properties
(apple,tunable-uc-regs-cio, -lane0/1-rx-top-cio, -lane0/1-rx-eq-cio); a
missing one is skipped. On J716c only the RX_TOP pair is non-empty.

Developed with Claude Code.

Signed-off-by: Erik Bourget <erik@ebourget.net>
The T6050 host router has the T8132 MxWrap CPU wrapper and LSTX pin,
four ACIO power domains instead of three, and a PCIe router AP
firmware block with its own tunables. Its NHI has a single register
range, one DART stream per ring, and an SRAM parity interrupt after
its 24 ring interrupts.

Developed with Claude Code.

Signed-off-by: Erik Bourget <erik@ebourget.net>
The M5 Max (T6050) host routers are Type 7 routers with an MxWrap CPU
wrapper, close to the M3 ones. They differ at start:

- The wrapper is only clocked once the CIO reset (the reconfig request)
  has run; any access before it faults. The reset is required, as on
  the other generations, and runs before the wrapper is touched.
- The CPU is released with a plain store of the RUN value rather than
  a read-modify-write, and halted again when the router stops.
- Both the rc block and the wrapper take an external abort when read
  before the CPU runs, so the M3_CTRL start of the other generations is
  skipped and their state is not logged. The rc block is first touched
  to hand LSTX over, once RTKit is up.
- The router takes the LSTX pin over only once its RTKit is up: write
  the LSTX control from the level the AP held, then mux the pin to the
  router. While the router is off, hold the pin as a GPIO at the level
  the router last drove.
- The PCIe router AP firmware tunable is applied with the other router
  tunables once the firmware is ready.
- The router firmware trains Gen4 links only when the cable word says
  the cable is Gen4. For a USB4 Gen4 cable from Enter_USB it expects
  0x801 (present and Gen4), without the 20 Gb/s bit.
- The NHI has a named SRAM parity interrupt after its 24 ring
  interrupts, which is not a ring.

Match apple,t6050-usb4-acio and apple,t6050-usb4-nhi. DisplayPort
tunnels stay off on T6050 for now.

Developed with Claude Code.

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

A T8110 DART that no boot loader sets up, such as a USB4 router's or
its PCIe-C tunnel's, needs the device tree to describe what the boot
loader otherwise does: its tunables, its bypass streams, every error
reported and the context cache flushed.

Add apple,full-error-reporting and apple,ctc-flush, and extend
apple,bypass-sids beyond the PCIe-C tunnel DARTs.

Developed with Claude Code.

Signed-off-by: Erik Bourget <erik@ebourget.net>
A T8110 DART that the boot loader leaves alone, such as the M5 Max
USB4 router's, comes up with its tunables unapplied, its errors
masked and no stream in bypass. Its own driver otherwise only clears
the streams, enables them and flushes the TLBs.

Let the device tree describe the rest, for non-tunneled T8110 DARTs:

- apple,tunable: the DART's own tunables, applied at every reset before
  the first stream is configured. Client partition registers
  (0x800..0xfff) are skipped unless PARAMS1 bit 29 says they exist.
- apple,bypass-sids: streams set to bypass translation at every reset,
  outside any domain.
- apple,full-error-reporting: unmask every error and enable the error
  interrupt for every stream with the registers of the DART's revision
  (the error mask moved to 0x4060 in 2.2; status, enable and the
  per-stream enables moved from 0x700 to 0x4200 in 2.4), and leave the
  DART unlocked on error. On 2.4 the interrupt handler also reports
  and acknowledges the error status.
- apple,ctc-flush: where PARAMS2 bit 4 says there is a context cache,
  flush it (0x600) after a V2 stream flush, which leaves it alone.

Log the identification registers of a DART set up this way at probe.

Developed with Claude Code.

Signed-off-by: Erik Bourget <erik@ebourget.net>
Describe the USB4/Thunderbolt host router behind the right-hand USB-C
port (ADT /arm-io/acio2, acio-cpu2 and dart-acio2). In t6050-max.dtsi:

- the ATC2_CIO, ATC2_CIO_PCIE, ATC2_CIO_USB and ATC2_CIO_AUSS power
  domains;
- the router CPU's mailbox and the router's LSTX pin function on the
  AOP GPIO controller;
- the router itself, with its DART and NHI as children, so that both
  are populated only once the router runs: nothing is touched at boot;
- the DART's stream 15 in bypass, full error reporting and the context
  cache flush.

In the board: AUSS, kept as the boot loader leaves it, the CIO reconfig
request (port 2 of the die's CIO reset), the AOP GPIO controller with
only the LSTX pin exposed, the PHY's CIO80 RX_TOP tunables, the link
from the PD controller to the router, and, until the boot loader fills
them, this board's router, NHI and DART tunables and host DROM, from its
ADT.

The router node is named acio@: the boot loader disables a cio@ node
unless its first register is the third register of the matching
/arm-io/acioN, which on T6050 is not the router's rc block.

Developed with Claude Code.

Signed-off-by: Erik Bourget <erik@ebourget.net>
At the first controller reset the USB3 PIPE mux is expected unset (0),
on the dummy producer (2) or already parked (0x42). When nothing has
reset the controller domain since boot, as on a boot without the
SPTM, the mux reads 0x22: the dummy producer on another clock. The
park then refused it and dwc3 failed to probe.

The park clears the clock field before it selects the dummy producer
and its clock, so start from any clock while the producer is the
dummy one.

Developed with Claude Code.
Signed-off-by: Erik Bourget <erik@ebourget.net>
The host DROM of the M5 Max USB4 router lists port 8, which the router
reports as not implemented. Reading its port config fails with -ENODEV,
which made the whole DROM unusable and left the host router without
one, so the NHI failed to probe.

Skip such entries, as is already done for entries past the last port.

Developed with Claude Code.
Signed-off-by: Erik Bourget <erik@ebourget.net>
The PHY firmware is preloaded by the boot loader, data segment
included, and keeps its state in that segment. Restarted after a
stop, it ran on what its last run had left there and never finished
calibrating the lanes: UC_TNBL_DONE did not come, and every second
connection of a port failed, in USB as in CIO mode, until reboot.

Save the data SRAM when the firmware is first started and put it back
before every later start.

Developed with Claude Code.
Signed-off-by: Erik Bourget <erik@ebourget.net>
In CIO mode the lanes carry USB4/TBT, and USB3 reaches DWC3 through
the router's USB3 adapter. DWC3 was left on its dummy PIPE producer,
so a USB3 tunnel came up with nothing on the host side.

When DWC3 asks for USB3 in CIO mode, move its PIPE mux to the tunnel
producer (0x21: PIPE mode 1, clock 2) under the PIPE lock, as the
older Apple PHYs do in USB4 mode. A lock that is not acknowledged is
not fatal, as for native USB3. On power-off the PIPE goes back to the
dummy producer while the CIO PHY is left to the router.

Developed with Claude Code.
Signed-off-by: Erik Bourget <erik@ebourget.net>
The T6050 PCIe-C root port has the T600x register layout and is always
initialized by the kernel.

Developed with Claude Code.

Signed-off-by: Erik Bourget <erik@ebourget.net>
T6050 PCIe-C is the T8103 tunnel root port with a larger RID2SID table,
a different MSI layout and no reset sequence of its own:

- RID2SID has 256 entries, which run over the T8103 tunnel block: the
  posted request counters, TUNCTRL and TUNSTAT move up by 0x700, to
  0x1080..0x108c. The release, assert and acknowledge paths use them.
- The MSI doorbell takes its high word at 0x16c. MSIs go through the
  T602x map, indexed by the vector the endpoint sends: the bridge's slice
  of the window it shares with the internal host, vector n at AIC
  1824 + n. Endpoints get base + i as their MSI data.
- Nothing hands the port over, so it is always cold-initialized, as the
  T600x/T602x ports are with pcie_apple.tunnel_kernel_init: debug, fabric
  and port tunables, PERST and APPCLK, RUN, then the root port's
  tunables, before the router populates the DART. There is no port reset
  table to replay and no PMGR reset; only RID2SID is cleared.
- The root port's ACK latency limit (0x700) and flow control update
  timer (0x73c) depend on the max payload size. Set them after every
  link-up and whenever the root port's DevCtl is written.

The parity error interrupt is left alone.

Developed with Claude Code.

Signed-off-by: Erik Bourget <erik@ebourget.net>
A router finds its PCIe-C host among the children of its
apple,pcie-tunnel node by compatible: to tell whether the port gets
initialized, to map its Intr2AXI bridge, to find its reset and to find
the bound host once a tunnel is up. Accept apple,t6050-pciec there too.

The T6050 port has no reset of its own, and the kernel cold-initializes
it, which apple_pcie_tunnel_needs_cold_init() reports. Other SoCs see no
change.

Developed with Claude Code.

Signed-off-by: Erik Bourget <erik@ebourget.net>
Describe the PCIe-C root port behind the right port's router and its
DART, in a node the router points at with apple,pcie-tunnel and
populates once it has enabled a PCIe tunnel, and the ATC2_PCIE power
state they sit in, in t6050-max.dtsi. The router powers ATC2_PCIE with
its other domains, so the port can be cold-initialized before its
children are populated.

Nothing initialises the DART, so like the router's it carries full error
reporting and its two bypass streams, 253 and 255, on which the port
issues its own writes, MSIs included; the board gives its tunables and
the port's. Its container is not named usb4-pcie-tunnel-N, which would
give it the fixed 64-stream topology of the older tunnel DARTs instead
of the 256 streams it reports. The bridge's MSIs are vectors 192-223 of
the window shared with the internal host.

Developed with Claude Code.

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

Copy link
Copy Markdown
Contributor

For the boot loader comment in the J716c DT (the boot loader disables a cio@ node unless its first register is the third register of the matching /arm-io/acioN): aurora-silicon/aura#103 fixes that in aura. The register index now follows the ADT router generation: reg 2 for acio, reg 1 for acio,v2 (T8142, T6041, T6050, M3 Ultra; checked against 60 board ADTs). The same PR also uses the optional hi_up NHI table for every v2 router, not only T8142. Once it ships, the router can be a normal cio@ node and take its tuning and DROM from the loader. It is proven on the M2 so far and has not been tested on a T6050.

🤖 Generated with Claude Code

@DjDeveloperr DjDeveloperr 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.

Approved: no regression on the M5 Pro (J714sAP). The J716c parts are reviewed statically.

All 18 commits are patch-identical (git range-diff, 18 of 18 =) to their copies in ewb/j716c-usb4-public-base. Our round-1 USB4 kernel was built on that base and ran on the M5 Pro:

  • both combo PHYs and both DWC3s bound (6f93def's 0x22 park included), and the debug link (ATC0) stayed up;
  • the ATC2 router mailbox bound, and 381ac0000.acio defers, because J714s describes no ports or DROM;
  • no oops or WARN.

DT: the J716c and J775D DTBs build on this PR's base. Once this is merged with integration's other T6050 boards, t6050-max.dtsi's nub_spmi_a1 and apciec2_dart clash with the copies in t6050-peripherals.dtsi, and 9 board DTBs fail to build (J714s among them). #1308 keys those guards to a T6050_MAX_DTSI macro instead.

Not tested: Thunderbolt/USB4 itself (there's no dock on the M5).

@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.

The base j716c/t6050-platform has no PR and is not contained in integration/aurora (about 40 of its commits are not in integration), and a test merge of this branch into integration/aurora conflicts in about 28 files (arch/arm64/boot/dts/apple, drivers/net/wireless/apple/centauri, drivers/thunderbolt, drivers/pci/controller, drivers/mux, drivers/mfd, thunderbolt bindings). Please rebase onto integration/aurora and retarget there, so the PR carries only its own commits.

@RyanTheTide

Copy link
Copy Markdown
Contributor

Rebased onto integration/aurora as #1323; the series there carries these commits unchanged apart from the conflict resolutions listed in its description.

@DjDeveloperr

Copy link
Copy Markdown
Contributor

Closing: merged as part of #1323 (f030e40), which carries all 18 commits rebased onto integration/aurora (resolutions R3-R9 in its description; everything this adds to t6050-max.dtsi is J716C-only there). The old base j716c/t6050-platform isn't needed. On the M5 Pro the merged code runs with no regression, but J716c's USB4 itself wasn't testable here (no dock).

🤖 Generated with Claude Code

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