From a6f4e8ca3024d598cece6abe50c2b4a5b3cdb23a Mon Sep 17 00:00:00 2001 From: AxxzyWasTaken Date: Sat, 7 Nov 2026 12:31:54 +0000 Subject: [PATCH 1/3] arm64: dts: apple: t6041: describe the USB4 resources, disabled The J616C has the same ACIO, NHI, DART and PCIe-C blocks as T6040 at the same addresses and interrupts, checked against its ADT. Take the T6040 description and stop deleting the power domains and mailboxes it needs. Everything stays disabled. Signed-off-by: AxxzyWasTaken --- arch/arm64/boot/dts/apple/t6041.dtsi | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/arch/arm64/boot/dts/apple/t6041.dtsi b/arch/arm64/boot/dts/apple/t6041.dtsi index 094a7c4bd8fa6..87e1718a22b12 100644 --- a/arch/arm64/boot/dts/apple/t6041.dtsi +++ b/arch/arm64/boot/dts/apple/t6041.dtsi @@ -23,9 +23,6 @@ /delete-node/ &jpeg0_dart; /delete-node/ &jpeg1_dart; /delete-node/ &scaler0_dart; -/delete-node/ &usb4_0_mbox; -/delete-node/ &usb4_1_mbox; -/delete-node/ &usb4_2_mbox; /delete-node/ &sio_mbox; /delete-node/ &pinctrl_smc; /delete-node/ &ps_gpx; @@ -128,19 +125,7 @@ /delete-node/ &ps_dispext0_fe; /delete-node/ &ps_dispext0_cpu; /delete-node/ &ps_pcpu0_3; -/delete-node/ &ps_atc0_cio; -/delete-node/ &ps_atc0_pcie; -/delete-node/ &ps_atc0_cio_pcie; -/delete-node/ &ps_atc0_cio_usb; -/delete-node/ &ps_atc1_cio; -/delete-node/ &ps_atc1_pcie; -/delete-node/ &ps_atc1_cio_pcie; -/delete-node/ &ps_atc1_cio_usb; -/delete-node/ &ps_atc2_cio; -/delete-node/ &ps_atc2_pcie; -/delete-node/ &ps_atc2_cio_pcie; /delete-node/ &ps_pcpu0_4; -/delete-node/ &ps_atc2_cio_usb; /delete-node/ &ps_atc3_cio; /delete-node/ &ps_atc3_pcie; /delete-node/ &ps_atc3_cio_pcie; @@ -358,6 +343,8 @@ /delete-node/ &opp_p0; /delete-node/ &opp_p1; +#include "t6040-usb4.dtsi" + / { opp_p0: opp-table-p0 { compatible = "operating-points-v2"; From cf727761e6f91c535383cab76827f51e820d2970 Mon Sep 17 00:00:00 2001 From: AxxzyWasTaken Date: Sat, 7 Nov 2026 12:40:12 +0000 Subject: [PATCH 2/3] phy: apple: t6040: add the CIO (USB4/Thunderbolt) mode Take the lanes for USB4 or the Thunderbolt alternate mode: the common tunables and the CIO ones, the crossbar with orientation in one bit, both lanes released, then the same PHY firmware as USB3. USB3 stays parked until the lanes come back. The order is AppleT6040TypeCPhy mode 2 from the 25G83 kernelcache and matches the T6050 recipe, except for the AHB clock request at CFG +0x28. Not run on hardware. Signed-off-by: AxxzyWasTaken --- drivers/phy/apple/t6040-usb2.c | 214 +++++++++++++++++++++++++++++---- 1 file changed, 193 insertions(+), 21 deletions(-) diff --git a/drivers/phy/apple/t6040-usb2.c b/drivers/phy/apple/t6040-usb2.c index cb4146d91faf1..73b687686294d 100644 --- a/drivers/phy/apple/t6040-usb2.c +++ b/drivers/phy/apple/t6040-usb2.c @@ -49,6 +49,8 @@ #include #include #include +#include +#include #define T6040_USB2_USBCTL 0x00 #define T6040_USB2_USBCTL_MODE GENMASK(2, 0) @@ -120,7 +122,10 @@ #define T6040_CORE_CMN_OVRD_SLEEP_B_BIG BIT(0) #define T6040_CORE_CMN_OVRD_SLEEP_B_SML BIT(2) #define T6040_CORE_CMN_OVRD_CLAMP BIT(4) +#define T6040_CORE_LANE_RX 0x10 /* CFG1_TOP */ #define T6040_CORE_SLP_CTRL 0x120 /* CFG_SLP_CTRL */ +#define T6040_CORE_LN0_RX_TOP_CIO 0x935c +#define T6040_CORE_LN1_RX_TOP_CIO 0x1935c #define T6040_CORE_CMN_BIAS 0xa00 /* AUS40CMN_SHM CMN_BIAS_REG */ #define T6040_CORE_CMN_BIAS_BGBIAS_EN_OV BIT(1) #define T6040_CORE_UC_CTRL 0x260 /* ACIOPHY_TOP_BLK_UC_CTRL_REG */ @@ -241,6 +246,7 @@ struct t6040_usb2_phy { bool usb3_pipe; /* dwc3 asked for the PIPE switch this power cycle */ bool usb3_lent; /* DP mode took the lanes of this Type-C port */ bool pipe_switched; /* the controller's PIPE runs on this PHY */ + bool cio_up; /* the lanes run USB4/Thunderbolt */ /* DisplayPort; dp_ok is false when the DT lacks a DP route */ struct phy *dp; @@ -598,14 +604,15 @@ static int t6040_usb3_cpu_start(struct t6040_usb2_phy *tphy) * every detach; each start makes the firmware tune the PHY. Then release the * PHY reset (D). */ -static int t6040_usb3_run_firmware(struct t6040_usb2_phy *tphy, bool swap) +static int t6040_usb3_run_firmware(struct t6040_usb2_phy *tphy, bool usb, bool swap) { void __iomem *cpu = t6040_cpu(tphy); u32 uc_before; int ret; - t6040_usb2_mask32(t6040_cfg(tphy) + T6040_CFG_SPARE0, T6040_CFG_SPARE0_USB_LN_SWAP, - swap ? T6040_CFG_SPARE0_USB_LN_SWAP : 0); + if (usb) + t6040_usb2_mask32(t6040_cfg(tphy) + T6040_CFG_SPARE0, T6040_CFG_SPARE0_USB_LN_SWAP, + swap ? T6040_CFG_SPARE0_USB_LN_SWAP : 0); fsleep(20); t6040_usb2_set32(cpu + T6040_CPU_IDLE_CTRL, T6040_CPU_IDLE_CTRL_EN); fsleep(10); @@ -750,24 +757,12 @@ static void t6040_usb3_bist_off(struct t6040_usb2_phy *tphy) * aciophy_phy_shutdown_partA: the PHY's USB link side down (the same BIST * handshake as the PIPE switch), both TX lanes, then the common block. */ -static void t6040_usb3_shutdown_lanes(struct t6040_usb2_phy *tphy) +/* aciophy_tx_powerdown(0), (1), then aciophy_cmn_shutdown: USB and CIO alike. */ +static void t6040_phy_tx_cmn_down(struct t6040_usb2_phy *tphy) { - void __iomem *bist = t6040_bist(tphy); void __iomem *core = t6040_core(tphy); unsigned int lane; - t6040_usb2_set32(bist + T6040_BIST_CFG0, BIT(0)); - t6040_usb2_clear32(bist + T6040_BIST_OVRD0, 0x100000); - t6040_usb2_mask32(bist + T6040_BIST_OVRD1, 0x60, 0x20); - t6040_usb3_bist_pulse(tphy, T6040_BIST_CFG0, BIT(3)); - t6040_usb3_bist_wait(tphy, T6040_BIST_STATUS_PHY_2, true, 1000); - t6040_usb2_mask32(bist + T6040_BIST_OVRD0, ~0xe1ffffffu, 0x4000000); - t6040_usb2_mask32(bist + T6040_BIST_OVRD1, 0x600, 0x200); - t6040_usb3_bist_pulse(tphy, T6040_BIST_CFG0, BIT(3)); - t6040_usb3_bist_wait(tphy, T6040_BIST_STATUS_PHY_2, true, 1000); - t6040_usb2_clear32(bist + T6040_BIST_CFG0, BIT(0)); - - /* aciophy_tx_powerdown(0), (1) */ for (lane = 0; lane < 2; lane++) { t6040_usb2_set32(core + T6040_CORE_SLP_CTRL, 0x100 << lane); udelay(1); @@ -786,6 +781,25 @@ static void t6040_usb3_shutdown_lanes(struct t6040_usb2_phy *tphy) udelay(1); t6040_usb2_clear32(core + T6040_CORE_CMN_OVRD, T6040_CORE_CMN_OVRD_SLEEP_B_SML); udelay(1); +} + + +static void t6040_usb3_shutdown_lanes(struct t6040_usb2_phy *tphy) +{ + void __iomem *bist = t6040_bist(tphy); + + t6040_usb2_set32(bist + T6040_BIST_CFG0, BIT(0)); + t6040_usb2_clear32(bist + T6040_BIST_OVRD0, 0x100000); + t6040_usb2_mask32(bist + T6040_BIST_OVRD1, 0x60, 0x20); + t6040_usb3_bist_pulse(tphy, T6040_BIST_CFG0, BIT(3)); + t6040_usb3_bist_wait(tphy, T6040_BIST_STATUS_PHY_2, true, 1000); + t6040_usb2_mask32(bist + T6040_BIST_OVRD0, ~0xe1ffffffu, 0x4000000); + t6040_usb2_mask32(bist + T6040_BIST_OVRD1, 0x600, 0x200); + t6040_usb3_bist_pulse(tphy, T6040_BIST_CFG0, BIT(3)); + t6040_usb3_bist_wait(tphy, T6040_BIST_STATUS_PHY_2, true, 1000); + t6040_usb2_clear32(bist + T6040_BIST_CFG0, BIT(0)); + + t6040_phy_tx_cmn_down(tphy); fsleep(200); } @@ -896,8 +910,8 @@ static void t6040_usb3_up_locked(struct t6040_usb2_phy *tphy) lockdep_assert_held(&tphy->lock); if (!tphy->usb3_ok || tphy->usb3_up) return; - /* DP mode owns all four lanes; USB3 comes back in t6040_usb3_reclaim_locked(). */ - if (tphy->dp_up) { + /* DP and CIO own all four lanes; USB3 comes back in t6040_usb3_reclaim_locked(). */ + if (tphy->dp_up || tphy->cio_up) { dev_dbg(tphy->dev, "USB3: lanes held by DP, USB2 only for now\n"); return; } @@ -922,7 +936,7 @@ static void t6040_usb3_up_locked(struct t6040_usb2_phy *tphy) goto sleep; /* the coprocessor block may be unclocked: never touch it */ t6040_usb3_tunables(tphy); t6040_usb3_lanes(tphy, swap); - ret = t6040_usb3_run_firmware(tphy, swap); + ret = t6040_usb3_run_firmware(tphy, true, swap); if (ret) goto usb2_only; @@ -1409,7 +1423,7 @@ static void t6040_dp_drive(struct t6040_usb2_phy *tphy, unsigned int lane, u32 p static void t6040_usb3_reclaim_locked(struct t6040_usb2_phy *tphy) { lockdep_assert_held(&tphy->lock); - if (!tphy->usb3_lent || tphy->dp_up || tphy->dp_alt) + if (!tphy->usb3_lent || tphy->dp_up || tphy->dp_alt || tphy->cio_up) return; tphy->usb3_lent = false; if (!tphy->usb3_on || tphy->usb3_up) @@ -1422,6 +1436,156 @@ static void t6040_usb3_reclaim_locked(struct t6040_usb2_phy *tphy) dev_info(tphy->dev, "USB3 %s after DP\n", tphy->usb3_up ? "back" : "not restored"); } +/* ---- CIO: USB4 and Thunderbolt on both lanes ---- */ + +/* The ADT tables of the common set, then the CIO ones; no DPTX writes. */ +static void t6040_cio_tunables(struct t6040_usb2_phy *tphy) +{ + void __iomem *core = t6040_core(tphy); + size_t i; + + for (i = 0; i < T6040_USB3_NR_TUNABLES; i++) + apple_tunable_apply(tphy->regs[t6040_usb3_tunable_list[i].region], + tphy->tunables[i]); + /* tunable_LN0/1_RX_TOP_CIO_DFLT; UC_REGS_CIO and RX_EQ_CIO are empty. */ + t6040_usb2_clear32(core + T6040_CORE_LN0_RX_TOP_CIO, 0xe00); + t6040_usb2_clear32(core + T6040_CORE_LN1_RX_TOP_CIO, 0xe00); +} + +/* aciophy_xbar_config, mode 2: orientation is one crossbar bit. */ +static void t6040_cio_xbar(struct t6040_usb2_phy *tphy, bool flip) +{ + void __iomem *core = t6040_core(tphy); + + t6040_usb2_clear32(core + T6040_CORE_XBAR, 0xfc); + t6040_usb2_mask32(core + T6040_CORE_XBAR_LANES, 0x1f, flip ? 1 : 0); + t6040_usb2_mask32(core + T6040_CORE_XBAR, 0x3, 0x2); +} + +/* aciophy_slpctrl_init, mode 2: both lanes, whatever the orientation. */ +static void t6040_cio_lanes_on(struct t6040_usb2_phy *tphy) +{ + static const struct { u32 off, bit; bool set; } steps[] = { + { T6040_CORE_LANE_RX, 0x10, true }, { T6040_CORE_LANE_RX, 0x40, true }, + { T6040_CORE_LANE_RX, 0x1, true }, { T6040_CORE_LANE_RX, 0x4, true }, + { T6040_CORE_CMN_OVRD, 0x400, true }, { T6040_CORE_CMN_OVRD, 0x1000, true }, + { T6040_CORE_CMN_OVRD, 0x40, true }, { T6040_CORE_CMN_OVRD, 0x100, true }, + { T6040_CORE_CMN_OVRD, 0x4000, false }, { T6040_CORE_CMN_OVRD, 0x10000, true }, + { T6040_CORE_SLP_CTRL, 0x40, false }, { T6040_CORE_SLP_CTRL, 0x4, false }, + { T6040_CORE_SLP_CTRL, 0x400, false }, + }; + void __iomem *core = t6040_core(tphy); + unsigned int lane; + size_t i; + + t6040_usb2_clear32(core + T6040_CORE_CMN_OVRD, 0x2a); + udelay(1); + for (lane = 0; lane < 2; lane++) { + for (i = 0; i < ARRAY_SIZE(steps); i++) { + if (steps[i].set) + t6040_usb2_set32(core + steps[i].off, steps[i].bit << lane); + else + t6040_usb2_clear32(core + steps[i].off, steps[i].bit << lane); + udelay(1); + } + } +} + +/* aciophy_rx_powerdown(0), (1): CIO powers the receivers down, USB does not. */ +static void t6040_cio_rx_down(struct t6040_usb2_phy *tphy) +{ + void __iomem *core = t6040_core(tphy); + unsigned int lane; + + for (lane = 0; lane < 2; lane++) { + t6040_usb2_set32(core + T6040_CORE_CMN_OVRD, 0x4000 << lane); + udelay(1); + t6040_usb2_clear32(core + T6040_CORE_CMN_OVRD, 0x40 << lane); + udelay(1); + t6040_usb2_clear32(core + T6040_CORE_CMN_OVRD, 0x400 << lane); + udelay(1); + t6040_usb2_clear32(core + T6040_CORE_LANE_RX, 0x1 << lane); + udelay(1); + t6040_usb2_clear32(core + T6040_CORE_LANE_RX, 0x10 << lane); + udelay(1); + } +} + +/* The Type-C state that needs CIO: USB4 entered, or the Thunderbolt alt mode. */ +static bool t6040_cio_state(const struct typec_mux_state *state) +{ + if (state->alt) + return state->alt->svid == USB_TYPEC_TBT_SID; + if (state->mode != TYPEC_MODE_USB4 || !state->data) + return false; + return FIELD_GET(EUDO_USB_MODE_MASK, + ((const struct enter_usb_data *)state->data)->eudo) == EUDO_USB_MODE_USB4; +} + +static int t6040_cio_up_locked(struct t6040_usb2_phy *tphy) +{ + bool flip; + int ret; + + lockdep_assert_held(&tphy->lock); + if (!tphy->usb3_ok || tphy->dp_up) + return -EBUSY; + switch (tphy->orientation) { + case TYPEC_ORIENTATION_NORMAL: + flip = false; + break; + case TYPEC_ORIENTATION_REVERSE: + flip = true; + break; + default: + dev_warn(tphy->dev, "CIO: no plug orientation\n"); + return -EINVAL; + } + if (tphy->usb3_up || tphy->pipe_switched) + t6040_usb3_down_locked(tphy); + tphy->usb3_lent = tphy->usb3_on; + + ret = pm_runtime_resume_and_get(tphy->dev); + if (ret) + return ret; + ret = t6040_usb3_wake(tphy); + if (ret) + goto sleep; /* the coprocessor block may be unclocked: never touch it */ + t6040_cio_tunables(tphy); + t6040_cio_xbar(tphy, flip); + t6040_cio_lanes_on(tphy); + ret = t6040_usb3_run_firmware(tphy, false, flip); + if (ret) { + t6040_usb3_firmware_power_down(tphy); + goto sleep; + } + tphy->cio_up = true; + dev_info(tphy->dev, "CIO PHY up, %s orientation\n", flip ? "reverse" : "normal"); + return 0; + +sleep: + t6040_usb3_sleep(tphy); + pm_runtime_put(tphy->dev); + dev_warn(tphy->dev, "CIO PHY bring-up failed: %d\n", ret); + return ret; +} + +static void t6040_cio_down_locked(struct t6040_usb2_phy *tphy) +{ + lockdep_assert_held(&tphy->lock); + if (!tphy->cio_up) + return; + t6040_phy_tx_cmn_down(tphy); + t6040_cio_rx_down(tphy); + fsleep(200); + t6040_usb3_firmware_power_down(tphy); + t6040_usb3_sleep(tphy); + tphy->cio_up = false; + pm_runtime_put(tphy->dev); + dev_info(tphy->dev, "CIO PHY asleep\n"); + t6040_usb3_reclaim_locked(tphy); +} + /* ---- DP phy_ops ---- */ static int t6040_dp_power_on(struct phy *phy) @@ -1432,6 +1596,8 @@ static int t6040_dp_power_on(struct phy *phy) guard(mutex)(&tphy->lock); if (!tphy->dp_ok || tphy->dp_up) return 0; + if (tphy->cio_up) + return -EBUSY; /* Pin D shares the running PHY with USB3. Keep its PIPE and firmware. */ if (tphy->dp_two_lanes) { if (!tphy->usb3_ok || !tphy->dp_alt) @@ -1581,6 +1747,12 @@ static int t6040_dp_mux_set(struct typec_mux_dev *mux, struct typec_mux_state *s bool alt = false; guard(mutex)(&tphy->lock); + if (t6040_cio_state(state)) { + if (tphy->cio_up) + return 0; + return t6040_cio_up_locked(tphy); + } + t6040_cio_down_locked(tphy); tphy->dp_flip = (tphy->orientation == TYPEC_ORIENTATION_REVERSE); /* * Latch the DP alt mode state for the DCP's Type-C route. Pin From 02e6c3f1aee87c461a214b41730733cd21187fc7 Mon Sep 17 00:00:00 2001 From: AxxzyWasTaken Date: Sat, 7 Nov 2026 12:43:00 +0000 Subject: [PATCH 3/3] arm64: dts: apple: t6041: add the CIO reset and LSTX pins Reset: ADT pmgr reg[44] +0x2024, the T6000 layout, as macOS uses it. The slot per port and the LSTX pin function are inferred. Signed-off-by: AxxzyWasTaken --- arch/arm64/boot/dts/apple/t6041.dtsi | 52 ++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t6041.dtsi b/arch/arm64/boot/dts/apple/t6041.dtsi index 87e1718a22b12..5be2b85208ec9 100644 --- a/arch/arm64/boot/dts/apple/t6041.dtsi +++ b/arch/arm64/boot/dts/apple/t6041.dtsi @@ -345,6 +345,58 @@ #include "t6040-usb4.dtsi" +/ { + /* ADT pmgr reg[44]: AppleT6041PMGR::enableCioReconfig at +0x2024. */ + pmgr_cio: power-management@502820000 { + compatible = "apple,t6041-pmgr-cio", "syscon", "simple-mfd"; + #address-cells = <1>; + #size-cells = <1>; + reg = <0x5 0x02820000 0 0x4000>; + + cio_reset: reset-controller@2024 { + compatible = "apple,t6041-cio-reset", "apple,t6000-cio-reset"; + reg = <0x2024 0x4>; + #reset-cells = <1>; + }; + }; +}; + +&pinctrl_aop { + /* ADT acioN gpio-lstx. */ + usb4_0_lstx_pins: usb4-0-lstx-pins { + pinmux = ; + }; + + usb4_1_lstx_pins: usb4-1-lstx-pins { + pinmux = ; + }; + + usb4_2_lstx_pins: usb4-2-lstx-pins { + pinmux = ; + }; +}; + +&usb4_0_acio { + resets = <&cio_reset 0>; + pinctrl-names = "lstx"; + pinctrl-0 = <&usb4_0_lstx_pins>; + lstx-gpios = <&pinctrl_aop 49 GPIO_ACTIVE_HIGH>; +}; + +&usb4_1_acio { + resets = <&cio_reset 1>; + pinctrl-names = "lstx"; + pinctrl-0 = <&usb4_1_lstx_pins>; + lstx-gpios = <&pinctrl_aop 51 GPIO_ACTIVE_HIGH>; +}; + +&usb4_2_acio { + resets = <&cio_reset 2>; + pinctrl-names = "lstx"; + pinctrl-0 = <&usb4_2_lstx_pins>; + lstx-gpios = <&pinctrl_aop 53 GPIO_ACTIVE_HIGH>; +}; + / { opp_p0: opp-table-p0 { compatible = "operating-points-v2";