From b49b7a52f9b9580a221d9b2b7210e8d16ddfc430 Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Mon, 24 Aug 2026 13:14:09 -0400 Subject: [PATCH 01/51] phy: apple: atc: Don't treat the pipehandler lock as fatal in USB4 mode atcphy_configure_pipehandler_usb4() aborts the mux switch when atcphy_pipehandler_lock() times out. That lock cannot succeed in this path: the LOCK_PIPE_IF handshake FSM is clocked by the DWC3 PIPE clock, and once the connector's lanes are routed to the USB4 controller that clock is no longer sourced the way a USB3 bring-up sources it. The ACK never asserts, so every routed-USB4 mux switch fails before PIPEHANDLER_MUX_CTRL is ever written and the port silently stays on the previous backend. The handshake is advisory in this path. It reports that the PIPE interface has quiesced, but the mux switch does not depend on that once the lanes have already moved to the USB4 controller. Linux already encodes that policy everywhere else, just through control flow rather than a shared fall-through. The dummy path only warns on both the lock and the unlock, and the sole fatal acquire is the USB3 host BIST sequence, which genuinely needs the PIPE quiesced. Make the USB4 path warn and continue like the dummy path, and say why in a comment so the next refactor doesn't "fix" the missing error check back in. Observed on a MacBook Pro 14" (M2 Pro, J414s, T6020), where the ACK poll deadlocked the mux switch deterministically. Signed-off-by: DjDeveloperr --- drivers/phy/apple/atc.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/drivers/phy/apple/atc.c b/drivers/phy/apple/atc.c index 06896da59cd6dc..2e822a3e37c3ac 100644 --- a/drivers/phy/apple/atc.c +++ b/drivers/phy/apple/atc.c @@ -1222,11 +1222,17 @@ static int atcphy_configure_pipehandler_usb4(struct apple_atcphy *atcphy) set32(atcphy->regs.pipehandler + PIPEHANDLER_OVERRIDE, PIPEHANDLER_OVERRIDE_RXVALID); set32(atcphy->regs.pipehandler + PIPEHANDLER_OVERRIDE, PIPEHANDLER_OVERRIDE_RXDETECT); + /* + * The lock is best-effort here: the LOCK_PIPE_IF handshake is clocked by the + * DWC3 PIPE clock, which is not running once the lanes are routed to the USB4 + * controller, so the ACK legitimately never asserts. The handshake is + * advisory in this path, and atcphy_configure_pipehandler_dummy() below + * already relies on the same warn-and-continue behaviour. Only the USB3 host + * BIST sequence genuinely needs the PIPE quiesced and keeps the lock fatal. + */ ret = atcphy_pipehandler_lock(atcphy); - if (ret) { - dev_err(atcphy->dev, "Failed to lock pipehandler\n"); - return ret; - } + if (ret) + dev_warn(atcphy->dev, "Failed to lock pipehandler\n"); /* Configure PIPE mux to the USB4/Thunderbolt controller */ atcphy_pipehandler_set_mux(atcphy, PIPEHANDLER_MUX_CTRL_DATA_USB4, From 77137b9c3e4cdd4c3937e41a304081d6702874a3 Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Mon, 24 Aug 2026 13:02:12 -0400 Subject: [PATCH 02/51] thunderbolt: apple: Preserve the other M3 control bits when starting the core apple_cio_start() releases the ACIO Cortex-M3 with a bare store of APPLE_CIO_M3_CTRL_START, which clears every other bit of the RC-window control register at 0x0c. Apple's own enableEmbeddedCPU reads that register first, logs its current value ("Current LSTX State") and then does a read-modify-write, so the register is known to carry at least one other live field. On t6020 it has been observed reading 0x3 before the kernel touches it, i.e. iBoot leaves bit 0 set and the bare store clears it. Nothing here needs those bits cleared, and their meaning is undocumented, so read-modify-write instead: unknown-and-preserved beats unknown-and-cleared. Signed-off-by: DjDeveloperr --- drivers/thunderbolt/apple.c | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/drivers/thunderbolt/apple.c b/drivers/thunderbolt/apple.c index 8786a9c26678d5..9f935c05bc08ad 100644 --- a/drivers/thunderbolt/apple.c +++ b/drivers/thunderbolt/apple.c @@ -604,7 +604,7 @@ static int apple_cio_start(struct apple_cio *acio) { struct device_link *link; int i, ret; - u32 state; + u32 state, val; lockdep_assert_held(&acio->lock); @@ -629,8 +629,16 @@ static int apple_cio_start(struct apple_cio *acio) goto remove_links; } - /* Start and wait for the co-processor to boot */ - writel(APPLE_CIO_M3_CTRL_START, acio->rc_base + APPLE_CIO_M3_CTRL); + /* + * Start and wait for the co-processor to boot. + * + * iBoot can leave other bits of this register set (0x3 has been observed + * on t6020 before the kernel touches it). The + * remaining fields are undocumented, so preserve them rather than clearing + * them with a bare store. + */ + val = readl(acio->rc_base + APPLE_CIO_M3_CTRL); + writel(val | APPLE_CIO_M3_CTRL_START, acio->rc_base + APPLE_CIO_M3_CTRL); acio->rtk = apple_rtkit_init(acio->dev, acio, NULL, 0, &apple_cio_rtkit_ops); if (IS_ERR(acio->rtk)) { ret = PTR_ERR(acio->rtk); From d1ca2e5fec235b08df231519801f7878298d2690 Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Mon, 24 Aug 2026 13:10:17 -0400 Subject: [PATCH 03/51] thunderbolt: apple: Reject NHIs with more ring IRQs than status bits apple_cio_ring_index() maps TX ring n to bit n and RX ring n to bit n + n_rings, and apple_nhi_ring_interrupt_active() and apple_cio_ring_irq() then use BIT(idx) against the 32-bit IRQ enable and status registers. The index is derived from the interrupt count in the device tree, which is only checked for being non-zero and even; the later hop_count comparison is against that same firmware-provided number and masks off ten bits, so it does not bound it either. Nothing in the path stops a firmware description with more than sixteen ring pairs from shifting a u32 by 32 or more, which is undefined behaviour and would quietly enable and acknowledge the wrong ring. Reject it at probe instead. Real hardware reports twelve pairs, so this only rules out descriptions the register layout cannot express. Signed-off-by: DjDeveloperr --- drivers/thunderbolt/apple.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/drivers/thunderbolt/apple.c b/drivers/thunderbolt/apple.c index 9f935c05bc08ad..1d72760a6200bc 100644 --- a/drivers/thunderbolt/apple.c +++ b/drivers/thunderbolt/apple.c @@ -212,6 +212,17 @@ static int apple_nhi_probe_irqs(struct apple_nhi *anhi) dev_err(anhi->dev, "Invalid number of interrupts: %d must be even\n", n_irqs); return -EINVAL; } + /* + * TX ring n is addressed as bit n and RX ring n as bit n + n_rings of the 32 bit + * IRQ status and enable registers, so both halves have to fit into a single + * register. hop_count is only checked against n_rings later on, and both come + * from firmware, so bound this before anything shifts by the derived index. + */ + if (n_irqs > 32) { + dev_err(anhi->dev, "Invalid number of interrupts: %d exceeds 32 status bits\n", + n_irqs); + return -EINVAL; + } anhi->n_rings = n_irqs / 2; anhi->rx_irqs = devm_kcalloc(anhi->dev, anhi->n_rings, sizeof(*anhi->rx_irqs), GFP_KERNEL); From 5966e38ad96f7a4bee5169974708b50fbc96b54e Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Mon, 24 Aug 2026 13:39:35 -0400 Subject: [PATCH 04/51] reset: apple-cio: Wait for the shared request register before reconfiguring On t600x and t602x every port on a die shares one CIO reconfigure register, so a request issued for a different port can still be in flight when this one runs. The driver's mutex serialises its own callers but says nothing about the hardware state machine, and a request stored while the block is still busy is dropped. The block needs two waits rather than one: poll the busy bits until they clear, store the request bit, then poll again. The same shape applies across t600x and t602x, with only the register offset and the die split changing. Keep the first wait advisory and store the request either way. A timeout there means the block was busy for 192 ms, which is worth a message, but failing the reset would turn a slow reconfigure into a probe failure of the whole USB4 complex. Worth noting for later: t8103, t8112 and t8122 have no PMGR CIO reconfigure at all, which is why the t8103 variant here drives a dedicated register page instead. Signed-off-by: DjDeveloperr --- drivers/reset/reset-apple-cio.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/drivers/reset/reset-apple-cio.c b/drivers/reset/reset-apple-cio.c index 896b3460774bd6..4233f3efa53d49 100644 --- a/drivers/reset/reset-apple-cio.c +++ b/drivers/reset/reset-apple-cio.c @@ -41,6 +41,7 @@ struct apple_cio_reset_variant { struct apple_cio_reset { struct reset_controller_dev rcdev; const struct apple_cio_reset_variant *variant; + struct device *dev; struct regmap *regmap; u32 offset; struct mutex lock; @@ -69,6 +70,20 @@ static int t6000_cio_deassert(struct apple_cio_reset *priv, unsigned long id) guard(mutex)(&priv->lock); + /* + * Every port on the die shares this register, so a reconfiguration requested for + * another port can still be running. The busy bits must clear before the request + * is stored, and the result of that wait is advisory, so only + * warn here: waiting can only help, while failing would turn a slow reconfigure + * into a probe failure. + */ + ret = regmap_read_poll_timeout(priv->regmap, priv->offset, val, + !(val & T6000_CIO_CTRL_INIT_BUSY(id)), + APPLE_CIO_RESET_POLL_US, + APPLE_CIO_RESET_TIMEOUT_US); + if (ret) + dev_warn(priv->dev, "CIO %lu still busy before reconfigure request\n", id); + ret = regmap_write(priv->regmap, priv->offset, T6000_CIO_CTRL_INIT_REQ(id)); if (ret) return ret; @@ -119,6 +134,7 @@ static int apple_cio_reset_probe(struct platform_device *pdev) return -ENOMEM; priv->variant = of_device_get_match_data(dev); + priv->dev = dev; ret = devm_mutex_init(dev, &priv->lock); if (ret) From 9d306ff930a843992eae52e5c8e474a3e191c4d6 Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Mon, 24 Aug 2026 14:53:34 -0400 Subject: [PATCH 05/51] reset: apple-cio: Wait before the request on t8103 too, and record the layouts The t8103 variant has the same problem the t600x one did: it stores the request without first checking whether the block is still busy from an earlier one. Poll bit 1 of the port's control word until it clears, then set the request bit, keeping the wait advisory for the reason given on the t600x side. Bit 1 had no name here because nothing read it. Call it INIT_BUSY. On t8103 and t8112 the request can also be set with a read-modify-write rather than a plain store. Deliberately not done here: the existing poll waits for the register to read exactly INIT_DONE, which works on every machine this has run on, and preserving unknown bits could stop that value ever appearing. Noted in the code so the difference reads as a decision rather than an oversight. Record the known register layouts while they are in hand. The two variants here cover four SoCs and give no hint what the next one needs, so anyone extending this would otherwise redo the work. Three things in that table matter: The first argument selects a PMGR register map rather than an address. Only map 0 is pinned down, the PMGR block itself, which is why t8103 resolves to 0x23b784000 and t6020 to the pmgr node plus 0xa02c. t8122 and t6030 select map 0x62 and t8112 map 0x63, and those are unresolved. M3 is therefore blocked on identifying one map index rather than on finding a register: its offset and bit layout are already known and match t6030's. t6050 uses two bits per port instead of a request bit and a busy bit sixteen apart, so it needs a third variant rather than a new offset. t8132 and t8142 index a per-port table rather than forming an offset, and are not decoded. Signed-off-by: DjDeveloperr --- drivers/reset/reset-apple-cio.c | 50 +++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/drivers/reset/reset-apple-cio.c b/drivers/reset/reset-apple-cio.c index 4233f3efa53d49..452893bf92a3c8 100644 --- a/drivers/reset/reset-apple-cio.c +++ b/drivers/reset/reset-apple-cio.c @@ -9,6 +9,38 @@ * t8103 each port comes with a dedicated register page while t600x * uses a single register shared by all ports of a die inside the PMGR * MMIO region. + * + * Only the deassert direction is implemented: a request to assert returns + * immediately on every SoC, hence the missing .assert op here. The register + * layout is not shared between generations: + * + * SoC regmap offset request busy + * t8103 0 0x84000 + 0x4000 * port BIT(0), rmw BIT(1) + * t8112 0x63 0x8000 / 0x4000 BIT(0), rmw BIT(1) + * t8122 0x62 0x8 BIT(slot) BIT(16 + slot) + * t6000 0 0x1a034 BIT(port) BIT(16 + port) + * t6020 0 0xa02c BIT(port) BIT(16 + port) + * t6030 0x62 0x8 BIT(slot) BIT(16 + slot) + * t6031 ? 0x2028 BIT(port) BIT(16 + port) + * t6041 ? 0x2024 BIT(slot) BIT(16 + slot) + * t6050 ? 0x20060 BIT(2 * port) BIT(2 * port + 1) + * + * t6000, t6020, t6031 and t6050 split slot into a die index and a port with + * "port = slot % 4", which the device tree expresses as one controller node + * per die instead. The others index by slot directly. + * + * "regmap" is an ApplePMGR::RegMap enum rather than an address. Only 0 is + * pinned down, as the PMGR block itself: t8103 resolves to 0x23b784000 and + * t6020 to the pmgr node plus 0xa02c, both of which match this driver. The + * non-zero selectors are unresolved, so t8122 and t6030 cannot be wired up + * yet even though their offset and bit layout are known. + * + * t8132 and t8142 have a different shape again, indexing a per-port table out + * of a driver-private structure rather than forming an offset, and have not + * been decoded. + * + * Every implementation polls the busy bits, stores the request, then polls + * again, with a 192 ms budget. The first poll's result is discarded. */ #include @@ -25,6 +57,7 @@ #define T8103_CIO_CTRL_STRIDE 0x4000 #define T8103_CIO_CTRL_INIT_REQ BIT(0) +#define T8103_CIO_CTRL_INIT_BUSY BIT(1) #define T8103_CIO_CTRL_INIT_DONE BIT(2) #define T6000_CIO_CTRL_INIT_REQ(port) BIT(port) @@ -53,6 +86,23 @@ static int t8103_cio_deassert(struct apple_cio_reset *priv, unsigned long id) u32 val; int ret; + /* + * Wait for a reconfiguration already in flight, as Apple does, and like Apple + * carry on regardless of the result. See the t6000 variant for why this is only + * a warning. + * + * Apple also sets the request bit with a read-modify-write rather than a plain + * store. That is deliberately not copied here: the poll below waits for the + * whole register to read exactly INIT_DONE, which works on every machine this + * has run on, and preserving unknown bits could stop that value ever appearing. + */ + ret = regmap_read_poll_timeout(priv->regmap, offset, val, + !(val & T8103_CIO_CTRL_INIT_BUSY), + APPLE_CIO_RESET_POLL_US, + APPLE_CIO_RESET_TIMEOUT_US); + if (ret) + dev_warn(priv->dev, "CIO %lu still busy before reconfigure request\n", id); + ret = regmap_write(priv->regmap, offset, T8103_CIO_CTRL_INIT_REQ); if (ret) return ret; From 979c97fbb466793eb97328b208d43b5471799bb8 Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Mon, 24 Aug 2026 13:02:51 -0400 Subject: [PATCH 06/51] usb: typec: tipd: Use the CD321x active-cable bit when rebuilding the TBT VDO The reconstructed cable Discover Mode VDO takes TBT_CABLE_ACTIVE_PASSIVE from TPS_DATA_STATUS_ACTIVE_CABLE, which is TI's bit 2. CD321x does not use TI's layout for this register: the upper half is already known to differ, which is why CD321X_DATA_STATUS_USB4_CONNECTION sits at bit 23 where TI documents FORCE_LSX, and why HPD_IRQ/HPD_LEVEL need their own defines. On CD321x the active-cable state is bit 22 of the same register, and the cable speed field is bits [27:25], matching TPS_DATA_STATUS_TBT_CABLE_SPEED_MASK as already used here. This matters beyond the VDO itself. TBT_CABLE_ACTIVE_PASSIVE becomes TB_VSE_CAP_APPLE_CABLE_INFO_ACTIVE_CABLE in the host router's cable word and gates BIDIR_LSRX, so describing a passive cable as active programs the router incorrectly. Add a CD321x-specific define and use it in the cd321x path only; tps6598x keeps TI's bit 2. Verified passive on a J414s (T6020), where DATA_STATUS reads 0x96800011: bit 22 clear, speed field 3. An active cable is still worth testing against. Signed-off-by: DjDeveloperr --- drivers/usb/typec/tipd/core.c | 2 +- drivers/usb/typec/tipd/tps6598x.h | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/usb/typec/tipd/core.c b/drivers/usb/typec/tipd/core.c index 58285c511cfeff..d4e2cd7e18a863 100644 --- a/drivers/usb/typec/tipd/core.c +++ b/drivers/usb/typec/tipd/core.c @@ -612,7 +612,7 @@ static void cd321x_typec_update_mode(struct tps6598x *tps, struct cd321x_status tbt_data.cable_mode |= TBT_CABLE_OPTICAL; if (st->data_status & TPS_DATA_STATUS_ACTIVE_LINK_TRAIN) tbt_data.cable_mode |= TBT_CABLE_LINK_TRAINING; - if (st->data_status & TPS_DATA_STATUS_ACTIVE_CABLE) + if (st->data_status & CD321X_DATA_STATUS_ACTIVE_CABLE) tbt_data.cable_mode |= TBT_CABLE_ACTIVE_PASSIVE; tbt_data.device_mode = TBT_MODE | (u32)le16_to_cpu(st->intel_vid_status.device_mode) << 16; diff --git a/drivers/usb/typec/tipd/tps6598x.h b/drivers/usb/typec/tipd/tps6598x.h index 672233ecbfb97f..15a39f6ee1566d 100644 --- a/drivers/usb/typec/tipd/tps6598x.h +++ b/drivers/usb/typec/tipd/tps6598x.h @@ -204,6 +204,7 @@ /* modified TPS_REG_DATA_STATUS bits for CD321x (and likely also TPS65987DDK) */ #define CD321X_DATA_STATUS_HPD_IRQ BIT(14) #define CD321X_DATA_STATUS_HPD_LEVEL BIT(15) +#define CD321X_DATA_STATUS_ACTIVE_CABLE BIT(22) #define CD321X_DATA_STATUS_USB4_CONNECTION BIT(23) #define TPS_DATA_STATUS_DP_PIN_ASSIGNMENT_MASK GENMASK(11, 10) From 658d76cc716ab67420972f1e813d99260157886c Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Tue, 25 Aug 2026 14:35:38 -0400 Subject: [PATCH 07/51] dt-bindings: PCI: apple: add T8103-family PCIe-C Signed-off-by: DjDeveloperr --- .../devicetree/bindings/pci/apple,pcie.yaml | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/Documentation/devicetree/bindings/pci/apple,pcie.yaml b/Documentation/devicetree/bindings/pci/apple,pcie.yaml index 0bed9e126fdaa4..58ea6eb8d6073a 100644 --- a/Documentation/devicetree/bindings/pci/apple,pcie.yaml +++ b/Documentation/devicetree/bindings/pci/apple,pcie.yaml @@ -21,6 +21,10 @@ description: | separate from the port registers. In that case, there is one PHY register range per port register range. + The PCIe-C variant connects a single root port to a USB4 router instead of + a physical PCIe link. It has no external PHY or PERST# GPIO and follows the + router's cable-controlled power lifetime. + All root ports share a single ECAM space, but separate GPIOs are used to take the PCI devices on those ports out of reset. Therefore the standard "reset-gpios" and "max-link-speed" properties appear on @@ -45,6 +49,11 @@ properties: - const: apple,t8122-pcie - const: apple,t6020-pcie - const: apple,t6020-pcie + - const: apple,t8103-pciec + - items: + - const: apple,t8112-pciec + - const: apple,t8103-pciec + - const: apple,t6000-pciec reg: minItems: 3 @@ -141,6 +150,27 @@ allOf: properties: reg-names: minItems: 10 + - if: + properties: + compatible: + contains: + enum: + - apple,t8103-pciec + - apple,t6000-pciec + then: + properties: + reg: + minItems: 7 + maxItems: 7 + interrupts: + maxItems: 1 + ranges: + minItems: 3 + maxItems: 3 + else: + properties: + ranges: + maxItems: 2 examples: - | From d20d219f0946643fa7b6a5e6196843cffc133db6 Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Fri, 28 Aug 2026 00:02:34 -0400 Subject: [PATCH 08/51] dt-bindings: describe Apple tunneled PCIe-C resources Signed-off-by: DjDeveloperr --- .../devicetree/bindings/iommu/apple,dart.yaml | 27 +++++++++++++++++++ .../devicetree/bindings/pci/apple,pcie.yaml | 18 ++++++++++++- .../thunderbolt/apple,t8103-usb4-acio.yaml | 9 +++++-- 3 files changed, 51 insertions(+), 3 deletions(-) diff --git a/Documentation/devicetree/bindings/iommu/apple,dart.yaml b/Documentation/devicetree/bindings/iommu/apple,dart.yaml index e179199dbd3b54..ae6fd35f548c1c 100644 --- a/Documentation/devicetree/bindings/iommu/apple,dart.yaml +++ b/Documentation/devicetree/bindings/iommu/apple,dart.yaml @@ -54,6 +54,33 @@ properties: power-domains: maxItems: 1 + apple,dma-range: + $ref: /schemas/types.yaml#/definitions/uint64-array + minItems: 2 + maxItems: 2 + description: > + Inclusive device-visible DMA aperture, encoded as a start address and + size. The IOMMU DMA allocator restricts mappings to this range. + + apple,dma-offset: + $ref: /schemas/types.yaml#/definitions/uint64 + description: > + Offset added to device-visible DMA addresses when indexing the DART page + tables. This models host bridges which alias a low PCI bus aperture into + a high DART translation window. + + apple,tunable: + $ref: /schemas/types.yaml#/definitions/uint32-matrix + items: + items: + - description: Register offset + - description: Mask to be applied to the register value + - description: Bits to be set after applying the mask + description: > + List of (register offset, mask, value) tuples copied from Apple's Device + Tree by the bootloader and required to initialize dynamically powered + DART instances. + required: - compatible - reg diff --git a/Documentation/devicetree/bindings/pci/apple,pcie.yaml b/Documentation/devicetree/bindings/pci/apple,pcie.yaml index 58ea6eb8d6073a..e38960f7607ff1 100644 --- a/Documentation/devicetree/bindings/pci/apple,pcie.yaml +++ b/Documentation/devicetree/bindings/pci/apple,pcie.yaml @@ -160,8 +160,24 @@ allOf: then: properties: reg: - minItems: 7 + minItems: 5 maxItems: 7 + reg-names: + oneOf: + - items: + - const: config + - const: rc + - const: port0 + - const: fabric + - const: debug + - items: + - const: config + - const: rc + - const: port0 + - const: fabric + - const: debug + - const: ltssm + - const: intr2axi interrupts: maxItems: 1 ranges: diff --git a/Documentation/devicetree/bindings/thunderbolt/apple,t8103-usb4-acio.yaml b/Documentation/devicetree/bindings/thunderbolt/apple,t8103-usb4-acio.yaml index 6674c7dde2b514..34b8a8fa7c473c 100644 --- a/Documentation/devicetree/bindings/thunderbolt/apple,t8103-usb4-acio.yaml +++ b/Documentation/devicetree/bindings/thunderbolt/apple,t8103-usb4-acio.yaml @@ -60,7 +60,11 @@ properties: power-domains: minItems: 3 - maxItems: 3 + maxItems: 4 + description: | + The first three domains power the ACIO co-processor complex. When + apple,pcie-tunnel is present, the fourth domain powers its PCIe-C + controller and is enabled only after the USB4 PCIe tunnel is active. thunderbolt-switch: true @@ -162,7 +166,8 @@ examples: mboxes = <&usb4_1_mbox>; power-domains = <&ps_atc1_cio>, <&ps_atc1_cio_pcie>, - <&ps_atc1_cio_usb>; + <&ps_atc1_cio_usb>, + <&ps_atc1_pcie>; resets = <&cio_reset 1>; thunderbolt-switch; pinctrl-0 = <&acio1_pins>; From 6645cbc2c6e783642c0ed81dcff82337ae2382bd Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Sat, 29 Aug 2026 00:48:57 -0400 Subject: [PATCH 09/51] dt-bindings: apple: validate PCIe-C and USB4 topology Signed-off-by: DjDeveloperr --- .../devicetree/bindings/iommu/apple,dart.yaml | 27 +++++++++++-- .../devicetree/bindings/pci/apple,pcie.yaml | 38 ++++++++++++------- .../thunderbolt/apple,t8103-usb4-acio.yaml | 6 +++ 3 files changed, 55 insertions(+), 16 deletions(-) diff --git a/Documentation/devicetree/bindings/iommu/apple,dart.yaml b/Documentation/devicetree/bindings/iommu/apple,dart.yaml index ae6fd35f548c1c..3e93011ca00662 100644 --- a/Documentation/devicetree/bindings/iommu/apple,dart.yaml +++ b/Documentation/devicetree/bindings/iommu/apple,dart.yaml @@ -28,6 +28,11 @@ properties: - apple,t8103-usb4-dart - apple,t8110-dart - apple,t6000-dart + - items: + - enum: + - apple,t6000-dart + - apple,t8112-dart + - const: apple,t8110-dart - items: - enum: - apple,t6020-dart @@ -46,10 +51,11 @@ properties: Optional since not all IOMMUs are attached to a clock gate. '#iommu-cells': - const: 1 + enum: [1, 5] description: - Has to be one. The single cell describes the stream id emitted by - a master to the IOMMU. + Usually one cell describing the stream id emitted by a master. The + T6020 NHI DART uses five cells to describe a stream range and its + device-visible DMA window. power-domains: maxItems: 1 @@ -87,6 +93,21 @@ required: - '#iommu-cells' - interrupts +allOf: + - if: + properties: + compatible: + contains: + const: apple,t6020-dart + then: + properties: + '#iommu-cells': + enum: [1, 5] + else: + properties: + '#iommu-cells': + const: 1 + additionalProperties: false examples: diff --git a/Documentation/devicetree/bindings/pci/apple,pcie.yaml b/Documentation/devicetree/bindings/pci/apple,pcie.yaml index e38960f7607ff1..6a6608dca4d1ba 100644 --- a/Documentation/devicetree/bindings/pci/apple,pcie.yaml +++ b/Documentation/devicetree/bindings/pci/apple,pcie.yaml @@ -61,21 +61,11 @@ properties: reg-names: minItems: 3 - items: - - const: config - - const: rc - - const: port0 - - const: port1 - - const: port2 - - const: port3 - - const: phy0 - - const: phy1 - - const: phy2 - - const: phy3 + maxItems: 10 ranges: minItems: 2 - maxItems: 2 + maxItems: 3 interrupts: description: @@ -94,6 +84,8 @@ properties: power-domains: maxItems: 1 + dma-coherent: true + patternProperties: "^pci@": $ref: /schemas/pci/pci-bus.yaml# @@ -108,13 +100,16 @@ patternProperties: description: Optional GPIO to power on the device maxItems: 1 + interrupt-controller: true + required: - - reset-gpios - interrupt-controller - "#interrupt-cells" - interrupt-map-mask - interrupt-map + unevaluatedProperties: false + required: - compatible - reg @@ -185,8 +180,25 @@ allOf: maxItems: 3 else: properties: + reg-names: + minItems: 3 + items: + - const: config + - const: rc + - const: port0 + - const: port1 + - const: port2 + - const: port3 + - const: phy0 + - const: phy1 + - const: phy2 + - const: phy3 ranges: maxItems: 2 + patternProperties: + "^pci@": + required: + - reset-gpios examples: - | diff --git a/Documentation/devicetree/bindings/thunderbolt/apple,t8103-usb4-acio.yaml b/Documentation/devicetree/bindings/thunderbolt/apple,t8103-usb4-acio.yaml index 34b8a8fa7c473c..c83e00f4463140 100644 --- a/Documentation/devicetree/bindings/thunderbolt/apple,t8103-usb4-acio.yaml +++ b/Documentation/devicetree/bindings/thunderbolt/apple,t8103-usb4-acio.yaml @@ -68,6 +68,12 @@ properties: thunderbolt-switch: true + apple,pcie-tunnel: + $ref: /schemas/types.yaml#/definitions/phandle + description: + PCIe-C tunnel container populated after the USB4 router has enabled its + PCIe adapter. + apple,tunable-rc: $ref: /schemas/types.yaml#/definitions/uint32-matrix items: From 13d04836756576b8dfc7a24a7f06469b71085b9f Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Tue, 25 Aug 2026 14:35:44 -0400 Subject: [PATCH 10/51] arm64: dts: apple: add T8103 and T8112 PCIe-C tunnels Signed-off-by: DjDeveloperr --- arch/arm64/boot/dts/apple/t8103.dtsi | 136 +++++++++++++++++++++++++++ arch/arm64/boot/dts/apple/t8112.dtsi | 136 +++++++++++++++++++++++++++ 2 files changed, 272 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t8103.dtsi b/arch/arm64/boot/dts/apple/t8103.dtsi index d54ec8e5290c58..53dc2cc87cc8bc 100644 --- a/arch/arm64/boot/dts/apple/t8103.dtsi +++ b/arch/arm64/boot/dts/apple/t8103.dtsi @@ -1587,6 +1587,7 @@ <&ps_atc0_cio_usb>; thunderbolt-switch; + apple,pcie-tunnel = <&usb4_0_pcie_tunnel>; pinctrl-0 = <&acio0_pins>; pinctrl-names = "default"; @@ -1649,6 +1650,73 @@ }; }; + usb4_0_pcie_tunnel: usb4-pcie-tunnel-0 { + #address-cells = <2>; + #size-cells = <2>; + ranges; + + apciec0_dart: iommu@3a1008000 { + compatible = "apple,t8103-dart"; + reg = <0x3 0xa1008000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = ; + #iommu-cells = <1>; + }; + + apciec0: pcie@3b0000000 { + compatible = "apple,t8103-pciec"; + device_type = "pci"; + + reg = <0x3 0xb0000000 0x0 0x10000000>, + <0x3 0xa0000000 0x0 0x4000>, + <0x3 0xa1000000 0x0 0x8000>, + <0x3 0xa0004000 0x0 0x4000>, + <0x3 0xa0200000 0x0 0x4000>, + <0x3 0xa1010000 0x0 0x4000>, + <0x3 0xa100c000 0x0 0x4000>; + reg-names = "config", "rc", "port0", "common", + "fabric", "debug", "intr2axi"; + + interrupt-parent = <&aic>; + interrupts = ; + + msi-controller; + msi-parent = <&apciec0>; + msi-ranges = <&aic AIC_IRQ 738 IRQ_TYPE_EDGE_RISING 32>; + + iommu-map = <0x0 &apciec0_dart 0x0 0x40>; + iommu-map-mask = <0x3f>; + + bus-range = <0x0 0x7f>; + #address-cells = <3>; + #size-cells = <2>; + ranges = <0x43000000 0x4 0x0 + 0x4 0x0 0x0 0x80000000>, + <0x02000000 0x0 0x80000000 + 0x4 0x80000000 0x0 0x40000000>, + <0x42000000 0x0 0xc0000000 + 0x4 0xc0000000 0x0 0x40000000>; + + apciec0_port: pci@0,0 { + device_type = "pci"; + reg = <0x0 0x0 0x0 0x0 0x0>; + external-facing; + + #address-cells = <3>; + #size-cells = <2>; + ranges; + + interrupt-controller; + #interrupt-cells = <1>; + interrupt-map-mask = <0 0 0 7>; + interrupt-map = <0 0 0 1 &apciec0_port 0 0 0 0>, + <0 0 0 2 &apciec0_port 0 0 0 1>, + <0 0 0 3 &apciec0_port 0 0 0 2>, + <0 0 0 4 &apciec0_port 0 0 0 3>; + }; + }; + }; + dwc3_0: usb@382280000 { compatible = "apple,t8103-dwc3"; reg = <0x3 0x82280000 0x0 0xcd00>, <0x3 0x8228cd00 0x0 0x3200>; @@ -1736,6 +1804,7 @@ <&ps_atc1_cio_usb>; thunderbolt-switch; + apple,pcie-tunnel = <&usb4_1_pcie_tunnel>; pinctrl-0 = <&acio1_pins>; pinctrl-names = "default"; @@ -1798,6 +1867,73 @@ }; }; + usb4_1_pcie_tunnel: usb4-pcie-tunnel-1 { + #address-cells = <2>; + #size-cells = <2>; + ranges; + + apciec1_dart: iommu@521008000 { + compatible = "apple,t8103-dart"; + reg = <0x5 0x21008000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = ; + #iommu-cells = <1>; + }; + + apciec1: pcie@530000000 { + compatible = "apple,t8103-pciec"; + device_type = "pci"; + + reg = <0x5 0x30000000 0x0 0x10000000>, + <0x5 0x20000000 0x0 0x4000>, + <0x5 0x21000000 0x0 0x8000>, + <0x5 0x20004000 0x0 0x4000>, + <0x5 0x20200000 0x0 0x4000>, + <0x5 0x21010000 0x0 0x4000>, + <0x5 0x2100c000 0x0 0x4000>; + reg-names = "config", "rc", "port0", "common", + "fabric", "debug", "intr2axi"; + + interrupt-parent = <&aic>; + interrupts = ; + + msi-controller; + msi-parent = <&apciec1>; + msi-ranges = <&aic AIC_IRQ 818 IRQ_TYPE_EDGE_RISING 32>; + + iommu-map = <0x0 &apciec1_dart 0x0 0x40>; + iommu-map-mask = <0x3f>; + + bus-range = <0x0 0x7f>; + #address-cells = <3>; + #size-cells = <2>; + ranges = <0x43000000 0x5 0x80000000 + 0x5 0x80000000 0x0 0x80000000>, + <0x02000000 0x0 0x00100000 + 0x6 0x00100000 0x0 0x3ff00000>, + <0x42000000 0x0 0x40000000 + 0x6 0x40000000 0x0 0x40000000>; + + apciec1_port: pci@0,0 { + device_type = "pci"; + reg = <0x0 0x0 0x0 0x0 0x0>; + external-facing; + + #address-cells = <3>; + #size-cells = <2>; + ranges; + + interrupt-controller; + #interrupt-cells = <1>; + interrupt-map-mask = <0 0 0 7>; + interrupt-map = <0 0 0 1 &apciec1_port 0 0 0 0>, + <0 0 0 2 &apciec1_port 0 0 0 1>, + <0 0 0 3 &apciec1_port 0 0 0 2>, + <0 0 0 4 &apciec1_port 0 0 0 3>; + }; + }; + }; + dwc3_1: usb@502280000 { compatible = "apple,t8103-dwc3"; reg = <0x5 0x02280000 0x0 0xcd00>, <0x5 0x0228cd00 0x0 0x3200>; diff --git a/arch/arm64/boot/dts/apple/t8112.dtsi b/arch/arm64/boot/dts/apple/t8112.dtsi index 90e6bc2aa5148e..376aabe175477f 100644 --- a/arch/arm64/boot/dts/apple/t8112.dtsi +++ b/arch/arm64/boot/dts/apple/t8112.dtsi @@ -1852,6 +1852,7 @@ <&ps_atc0_cio_usb>; thunderbolt-switch; + apple,pcie-tunnel = <&usb4_0_pcie_tunnel>; pinctrl-0 = <&acio0_pins>; pinctrl-names = "default"; @@ -1915,6 +1916,73 @@ }; }; + usb4_0_pcie_tunnel: usb4-pcie-tunnel-0 { + #address-cells = <2>; + #size-cells = <2>; + ranges; + + apciec0_dart: iommu@3a1008000 { + compatible = "apple,t8110-dart"; + reg = <0x3 0xa1008000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = ; + #iommu-cells = <1>; + }; + + apciec0: pcie@3b0000000 { + compatible = "apple,t8112-pciec", "apple,t8103-pciec"; + device_type = "pci"; + + reg = <0x3 0xb0000000 0x0 0x10000000>, + <0x3 0xa0000000 0x0 0x4000>, + <0x3 0xa1000000 0x0 0x8000>, + <0x3 0xa0004000 0x0 0x4000>, + <0x3 0xa0200000 0x0 0x4000>, + <0x3 0xa1010000 0x0 0x4000>, + <0x3 0xa100c000 0x0 0x4000>; + reg-names = "config", "rc", "port0", "common", + "fabric", "debug", "intr2axi"; + + interrupt-parent = <&aic>; + interrupts = ; + + msi-controller; + msi-parent = <&apciec0>; + msi-ranges = <&aic AIC_IRQ 992 IRQ_TYPE_EDGE_RISING 32>; + + iommu-map = <0x0 &apciec0_dart 0x0 0x40>; + iommu-map-mask = <0x3f>; + + bus-range = <0x0 0x7f>; + #address-cells = <3>; + #size-cells = <2>; + ranges = <0x43000000 0x4 0x0 + 0x4 0x0 0x0 0x80000000>, + <0x02000000 0x0 0x80000000 + 0x4 0x80000000 0x0 0x40000000>, + <0x42000000 0x0 0xc0000000 + 0x4 0xc0000000 0x0 0x40000000>; + + apciec0_port: pci@0,0 { + device_type = "pci"; + reg = <0x0 0x0 0x0 0x0 0x0>; + external-facing; + + #address-cells = <3>; + #size-cells = <2>; + ranges; + + interrupt-controller; + #interrupt-cells = <1>; + interrupt-map-mask = <0 0 0 7>; + interrupt-map = <0 0 0 1 &apciec0_port 0 0 0 0>, + <0 0 0 2 &apciec0_port 0 0 0 1>, + <0 0 0 3 &apciec0_port 0 0 0 2>, + <0 0 0 4 &apciec0_port 0 0 0 3>; + }; + }; + }; + dwc3_0: usb@382280000 { compatible = "apple,t8112-dwc3", "apple,t8103-dwc3"; reg = <0x3 0x82280000 0x0 0xcd00>, <0x3 0x8228cd00 0x0 0x3200>; @@ -2002,6 +2070,7 @@ <&ps_atc1_cio_usb>; thunderbolt-switch; + apple,pcie-tunnel = <&usb4_1_pcie_tunnel>; pinctrl-0 = <&acio1_pins>; pinctrl-names = "default"; @@ -2065,6 +2134,73 @@ }; }; + usb4_1_pcie_tunnel: usb4-pcie-tunnel-1 { + #address-cells = <2>; + #size-cells = <2>; + ranges; + + apciec1_dart: iommu@521008000 { + compatible = "apple,t8110-dart"; + reg = <0x5 0x21008000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = ; + #iommu-cells = <1>; + }; + + apciec1: pcie@530000000 { + compatible = "apple,t8112-pciec", "apple,t8103-pciec"; + device_type = "pci"; + + reg = <0x5 0x30000000 0x0 0x10000000>, + <0x5 0x20000000 0x0 0x4000>, + <0x5 0x21000000 0x0 0x8000>, + <0x5 0x20004000 0x0 0x4000>, + <0x5 0x20200000 0x0 0x4000>, + <0x5 0x21010000 0x0 0x4000>, + <0x5 0x2100c000 0x0 0x4000>; + reg-names = "config", "rc", "port0", "common", + "fabric", "debug", "intr2axi"; + + interrupt-parent = <&aic>; + interrupts = ; + + msi-controller; + msi-parent = <&apciec1>; + msi-ranges = <&aic AIC_IRQ 1073 IRQ_TYPE_EDGE_RISING 32>; + + iommu-map = <0x0 &apciec1_dart 0x0 0x40>; + iommu-map-mask = <0x3f>; + + bus-range = <0x0 0x7f>; + #address-cells = <3>; + #size-cells = <2>; + ranges = <0x43000000 0x5 0x80000000 + 0x5 0x80000000 0x0 0x80000000>, + <0x02000000 0x0 0x00100000 + 0x6 0x00100000 0x0 0x3ff00000>, + <0x42000000 0x0 0x40000000 + 0x6 0x40000000 0x0 0x40000000>; + + apciec1_port: pci@0,0 { + device_type = "pci"; + reg = <0x0 0x0 0x0 0x0 0x0>; + external-facing; + + #address-cells = <3>; + #size-cells = <2>; + ranges; + + interrupt-controller; + #interrupt-cells = <1>; + interrupt-map-mask = <0 0 0 7>; + interrupt-map = <0 0 0 1 &apciec1_port 0 0 0 0>, + <0 0 0 2 &apciec1_port 0 0 0 1>, + <0 0 0 3 &apciec1_port 0 0 0 2>, + <0 0 0 4 &apciec1_port 0 0 0 3>; + }; + }; + }; + dwc3_1: usb@502280000 { compatible = "apple,t8112-dwc3", "apple,t8103-dwc3"; reg = <0x5 0x02280000 0x0 0xcd00>, <0x5 0x0228cd00 0x0 0x3200>; From 14dab35accbe73d1e84aa167b7d9fdb09cb216be Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Tue, 25 Aug 2026 14:43:51 -0400 Subject: [PATCH 11/51] arm64: dts: apple: route base M1/M2 displays to both Type-C ports Signed-off-by: DjDeveloperr --- arch/arm64/boot/dts/apple/t8103-jxxx.dtsi | 80 ++++++++++++++------- arch/arm64/boot/dts/apple/t8112-j413.dts | 2 - arch/arm64/boot/dts/apple/t8112-j415.dts | 2 - arch/arm64/boot/dts/apple/t8112-j473.dts | 81 +++++++++++++++------- arch/arm64/boot/dts/apple/t8112-j493.dts | 2 - arch/arm64/boot/dts/apple/t8112-jxxx.dtsi | 84 +++++++++++++++++------ 6 files changed, 177 insertions(+), 74 deletions(-) diff --git a/arch/arm64/boot/dts/apple/t8103-jxxx.dtsi b/arch/arm64/boot/dts/apple/t8103-jxxx.dtsi index df919d4889d248..2c3941e3d13daa 100644 --- a/arch/arm64/boot/dts/apple/t8103-jxxx.dtsi +++ b/arch/arm64/boot/dts/apple/t8103-jxxx.dtsi @@ -9,15 +9,11 @@ * Copyright The Asahi Linux Contributors */ -#define ENABLE_DCPEXT_TYPEC - / { aliases { bluetooth0 = &bluetooth0; dcp = &dcp; -#ifdef ENABLE_DCPEXT_TYPEC dcpext = &dcpext; -#endif disp0 = &display; disp0_piodma = &disp0_piodma; serial0 = &serial0; @@ -80,6 +76,7 @@ compatible = "usb-c-connector"; power-role = "dual"; data-role = "dual"; + displayport = <&dcpext>; ports { #address-cells = <1>; @@ -103,6 +100,13 @@ remote-endpoint = <&usb4_0_acio_tbt>; }; }; + + port@3 { + reg = <3>; + typec0_dcp: endpoint { + remote-endpoint = <&dcpext_typec0>; + }; + }; }; }; }; @@ -114,15 +118,11 @@ interrupts = <106 IRQ_TYPE_LEVEL_LOW>; interrupt-names = "irq"; - typec1: connector { - compatible = "usb-c-connector"; - power-role = "dual"; - data-role = "dual"; - -#ifdef ENABLE_DCPEXT_TYPEC - /* hacks */ + typec1: connector { + compatible = "usb-c-connector"; + power-role = "dual"; + data-role = "dual"; displayport = <&dcpext>; -#endif ports { #address-cells = <1>; @@ -146,12 +146,18 @@ remote-endpoint = <&usb4_1_acio_tbt>; }; }; + + port@3 { + reg = <3>; + typec1_dcp: endpoint { + remote-endpoint = <&dcpext_typec1>; + }; + }; }; }; }; }; -#ifdef ENABLE_DCPEXT_TYPEC &display { iommus = <&disp0_dart 0>, <&dispext0_dart 0>; }; @@ -170,25 +176,53 @@ &dcpext { status = "okay"; - apple,connector-type = "DP"; - - /* hacks */ - apple,dptx-phy = <1>; - phys = <&atcphy1 PHY_TYPE_DP>; - phy-names = "dp-phy"; - mux-controls = <&atcphy1_xbar 0>; - mux-control-names = "dp-xbar"; - mux-index = <0>; + apple,connector-type = "USB-C"; + phys = <&atcphy0 PHY_TYPE_DP>, <&atcphy1 PHY_TYPE_DP>; + phy-names = "typec0", "typec1"; + mux-controls = <&atcphy0_xbar 0>, <&atcphy1_xbar 0>; + mux-control-names = "typec0", "typec1"; + apple,typec-mux-indices = <0 0>; + apple,typec-dptx-phys = <0 1>; + + typec-routes { + #address-cells = <1>; + #size-cells = <0>; + + route@0 { + reg = <0>; + mode-switch; + + port { + dcpext_typec0: endpoint { + remote-endpoint = <&typec0_dcp>; + }; + }; + }; + + route@1 { + reg = <1>; + mode-switch; + + port { + dcpext_typec1: endpoint { + remote-endpoint = <&typec1_dcp>; + }; + }; + }; + }; }; &dpaudio1 { status = "okay"; }; +&ps_atc0_common { + apple,always-on; /* Needs to stay on for DP-alt suspend/resume */ +}; + &ps_atc1_common { apple,always-on; /* Needs to stay on for DP-alt suspend/resume */ }; -#endif /* USB controllers */ &dwc3_0 { diff --git a/arch/arm64/boot/dts/apple/t8112-j413.dts b/arch/arm64/boot/dts/apple/t8112-j413.dts index 82f54d603167f5..5ae973bc79fef4 100644 --- a/arch/arm64/boot/dts/apple/t8112-j413.dts +++ b/arch/arm64/boot/dts/apple/t8112-j413.dts @@ -9,8 +9,6 @@ /dts-v1/; -#define ENABLE_DCPEXT_TYPEC - #include "t8112.dtsi" #include "t8112-jxxx.dtsi" #include diff --git a/arch/arm64/boot/dts/apple/t8112-j415.dts b/arch/arm64/boot/dts/apple/t8112-j415.dts index c9f0d1a879452e..20a64ecc122d87 100644 --- a/arch/arm64/boot/dts/apple/t8112-j415.dts +++ b/arch/arm64/boot/dts/apple/t8112-j415.dts @@ -9,8 +9,6 @@ /dts-v1/; -#define ENABLE_DCPEXT_TYPEC - #include "t8112.dtsi" #include "t8112-jxxx.dtsi" #include diff --git a/arch/arm64/boot/dts/apple/t8112-j473.dts b/arch/arm64/boot/dts/apple/t8112-j473.dts index cb94c7faa03263..bd90405e7cfd6b 100644 --- a/arch/arm64/boot/dts/apple/t8112-j473.dts +++ b/arch/arm64/boot/dts/apple/t8112-j473.dts @@ -9,7 +9,7 @@ /dts-v1/; -#define ENABLE_DCPEXT_TYPEC +#define T8112_TYPEC_DCP #include "t8112.dtsi" #include "t8112-jxxx.dtsi" @@ -20,18 +20,11 @@ chassis-type = "desktop"; aliases { - bluetooth0 = &bluetooth0; - /delete-property/ dcp; - dcpext = &dcpext; ethernet0 = ðernet0; wifi0 = &wifi0; }; }; -&framebuffer0 { - power-domains = <&ps_dispext_cpu0>, <&ps_dptx_ext_phy>; -}; - &dptxphy { status = "okay"; }; @@ -67,13 +60,6 @@ apple,dptx-phy = <5>; }; -#ifdef ENABLE_DCPEXT_TYPEC -/ { - aliases { - dcp = &dcp; - }; -}; - &display { /* dispext0_dart needs to be first for m1n1's */ iommus = <&dispext0_dart 0>, <&disp0_dart 0>; @@ -85,15 +71,40 @@ &dcp { status = "okay"; - apple,connector-type = "DP"; + apple,connector-type = "USB-C"; + phys = <&atcphy0 PHY_TYPE_DP>, <&atcphy1 PHY_TYPE_DP>; + phy-names = "typec0", "typec1"; + mux-controls = <&atcphy0_xbar 0>, <&atcphy1_xbar 0>; + mux-control-names = "typec0", "typec1"; + apple,typec-mux-indices = <0 0>; + apple,typec-dptx-phys = <0 1>; + + typec-routes { + #address-cells = <1>; + #size-cells = <0>; + + route@0 { + reg = <0>; + mode-switch; + + port { + dcp_typec0: endpoint { + remote-endpoint = <&typec0_dcp>; + }; + }; + }; - /* hacks */ - apple,dptx-phy = <1>; - phys = <&atcphy1 PHY_TYPE_DP>; - phy-names = "dp-phy"; - mux-controls = <&atcphy1_xbar 0>; - mux-control-names = "dp-xbar"; - mux-index = <0>; + route@1 { + reg = <1>; + mode-switch; + + port { + dcp_typec1: endpoint { + remote-endpoint = <&typec1_dcp>; + }; + }; + }; + }; }; &dcpext { @@ -106,11 +117,31 @@ status = "okay"; }; +&typec0 { + displayport = <&dcp>; + + ports { + port@3 { + reg = <3>; + typec0_dcp: endpoint { + remote-endpoint = <&dcp_typec0>; + }; + }; + }; +}; + &typec1 { - /* hacks */ displayport = <&dcp>; + + ports { + port@3 { + reg = <3>; + typec1_dcp: endpoint { + remote-endpoint = <&dcp_typec1>; + }; + }; + }; }; -#endif /* * Keep the power-domains used for the HDMI port on. diff --git a/arch/arm64/boot/dts/apple/t8112-j493.dts b/arch/arm64/boot/dts/apple/t8112-j493.dts index ce64fd1f77f881..fd3953fbb2fa0b 100644 --- a/arch/arm64/boot/dts/apple/t8112-j493.dts +++ b/arch/arm64/boot/dts/apple/t8112-j493.dts @@ -9,8 +9,6 @@ /dts-v1/; -#define ENABLE_DCPEXT_TYPEC - #include "t8112.dtsi" #include "t8112-jxxx.dtsi" #include diff --git a/arch/arm64/boot/dts/apple/t8112-jxxx.dtsi b/arch/arm64/boot/dts/apple/t8112-jxxx.dtsi index a9088c17b76197..8edc9228939c91 100644 --- a/arch/arm64/boot/dts/apple/t8112-jxxx.dtsi +++ b/arch/arm64/boot/dts/apple/t8112-jxxx.dtsi @@ -14,6 +14,7 @@ atcphy0 = &atcphy0; atcphy1 = &atcphy1; dcp = &dcp; + dcpext = &dcpext; disp0 = &display; disp0_piodma = &disp0_piodma; usb4-0-acio = &usb4_0_acio; @@ -69,6 +70,9 @@ compatible = "usb-c-connector"; power-role = "dual"; data-role = "dual"; +#ifndef T8112_TYPEC_DCP + displayport = <&dcpext>; +#endif ports { #address-cells = <1>; @@ -92,6 +96,15 @@ remote-endpoint = <&usb4_0_acio_tbt>; }; }; + +#ifndef T8112_TYPEC_DCP + port@3 { + reg = <3>; + typec0_dcp: endpoint { + remote-endpoint = <&dcpext_typec0>; + }; + }; +#endif }; }; }; @@ -108,8 +121,7 @@ power-role = "dual"; data-role = "dual"; -#ifdef ENABLE_DCPEXT_TYPEC - /* hacks */ +#ifndef T8112_TYPEC_DCP displayport = <&dcpext>; #endif @@ -135,18 +147,20 @@ remote-endpoint = <&usb4_1_acio_tbt>; }; }; + +#ifndef T8112_TYPEC_DCP + port@3 { + reg = <3>; + typec1_dcp: endpoint { + remote-endpoint = <&dcpext_typec1>; + }; + }; +#endif }; }; }; }; -#ifdef ENABLE_DCPEXT_TYPEC -/ { - aliases { - dcpext = &dcpext; - }; -}; - &display { iommus = <&disp0_dart 0>, <&dispext0_dart 0>; }; @@ -163,32 +177,62 @@ status = "okay"; }; +#ifndef T8112_TYPEC_DCP &dcpext { status = "okay"; - apple,connector-type = "DP"; - - /* hacks */ - apple,dptx-phy = <1>; - phys = <&atcphy1 PHY_TYPE_DP>; - phy-names = "dp-phy"; - mux-controls = <&atcphy1_xbar 0>; - mux-control-names = "dp-xbar"; - mux-index = <2>; + apple,connector-type = "USB-C"; + phys = <&atcphy0 PHY_TYPE_DP>, <&atcphy1 PHY_TYPE_DP>; + phy-names = "typec0", "typec1"; + mux-controls = <&atcphy0_xbar 0>, <&atcphy1_xbar 0>; + mux-control-names = "typec0", "typec1"; + apple,typec-mux-indices = <2 2>; + apple,typec-dptx-phys = <0 1>; + + typec-routes { + #address-cells = <1>; + #size-cells = <0>; + + route@0 { + reg = <0>; + mode-switch; + + port { + dcpext_typec0: endpoint { + remote-endpoint = <&typec0_dcp>; + }; + }; + }; + + route@1 { + reg = <1>; + mode-switch; + + port { + dcpext_typec1: endpoint { + remote-endpoint = <&typec1_dcp>; + }; + }; + }; + }; }; +#endif /* remove once m1n1 enables sio nodes after setup */ &sio { - status = "okay"; + status = "okay"; }; &dpaudio1 { status = "okay"; }; +&ps_atc0_common { + apple,always-on; /* Needs to stay on for DP-alt suspend/resume */ +}; + &ps_atc1_common { apple,always-on; /* Needs to stay on for DP-alt suspend/resume */ }; -#endif /* USB controllers */ &dwc3_0 { From 5b41b7a12251e40e24e14f43c3d276ff4e60cb55 Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Tue, 25 Aug 2026 15:01:04 -0400 Subject: [PATCH 12/51] arm64: dts: apple: add non-Ultra T600x USB4 tunnel routing Signed-off-by: DjDeveloperr --- arch/arm64/boot/dts/apple/t6001.dtsi | 3 + .../arm64/boot/dts/apple/t600x-j314-j316.dtsi | 156 +++++++++- arch/arm64/boot/dts/apple/t600x-j375.dtsi | 195 ++++++++++++- arch/arm64/boot/dts/apple/t600x-pciec.dtsi | 274 ++++++++++++++++++ 4 files changed, 622 insertions(+), 6 deletions(-) create mode 100644 arch/arm64/boot/dts/apple/t600x-pciec.dtsi diff --git a/arch/arm64/boot/dts/apple/t6001.dtsi b/arch/arm64/boot/dts/apple/t6001.dtsi index 73053599d71d81..dc2d9f14fb060f 100644 --- a/arch/arm64/boot/dts/apple/t6001.dtsi +++ b/arch/arm64/boot/dts/apple/t6001.dtsi @@ -20,6 +20,8 @@ # define GPU_REPEAT(x) #endif +#define APPLE_T600X_NON_ULTRA + #include "t600x-common.dtsi" / { @@ -45,6 +47,7 @@ &{/soc} { #include "t600x-die0.dtsi" #include "t600x-dieX.dtsi" + #include "t600x-pciec.dtsi" #include "t600x-nvme.dtsi" }; diff --git a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi index 19010c3f654633..9d76d9ad34e919 100644 --- a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi +++ b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi @@ -24,7 +24,7 @@ bluetooth0 = &bluetooth0; dcp = &dcp; dcpext0 = &dcpext0; -#ifdef ENABLE_DCPEXT_TYPEC +#ifdef APPLE_T600X_NON_ULTRA dcpext1 = &dcpext1; #endif disp0 = &display; @@ -133,6 +133,115 @@ }; }; +#ifdef APPLE_T600X_NON_ULTRA +&display { + iommus = <&disp0_dart 0>, <&dispext0_dart 0>, <&dispext1_dart 0>; +}; + +&dispext1_dart { + status = "okay"; +}; + +&dcpext1_dart { + status = "okay"; +}; + +&dcpext1_mbox { + status = "okay"; +}; + +&dcpext1 { + status = "okay"; + apple,connector-type = "USB-C"; + phys = <&atcphy0 PHY_TYPE_DP>, <&atcphy1 PHY_TYPE_DP>, + <&atcphy2 PHY_TYPE_DP>; + phy-names = "typec0", "typec1", "typec2"; + mux-controls = <&atcphy0_xbar 0>, <&atcphy1_xbar 0>, + <&atcphy2_xbar 0>; + mux-control-names = "typec0", "typec1", "typec2"; + apple,typec-mux-indices = <2 2 2>; + apple,typec-dptx-phys = <0 1 2>; + + typec-routes { + #address-cells = <1>; + #size-cells = <0>; + + route@0 { + reg = <0>; + mode-switch; + + port { + dcpext1_typec0: endpoint { + remote-endpoint = <&typec0_dcp>; + }; + }; + }; + + route@1 { + reg = <1>; + mode-switch; + + port { + dcpext1_typec1: endpoint { + remote-endpoint = <&typec1_dcp>; + }; + }; + }; + + route@2 { + reg = <2>; + mode-switch; + + port { + dcpext1_typec2: endpoint { + remote-endpoint = <&typec2_dcp>; + }; + }; + }; + }; +}; + +&dpaudio2 { + status = "okay"; +}; + +&atcphy0_xbar { + status = "okay"; +}; + +&atcphy1_xbar { + status = "okay"; +}; + +&atcphy2_xbar { + status = "okay"; +}; + +&ps_atc0_common { + apple,always-on; +}; + +&ps_atc1_common { + apple,always-on; +}; + +&ps_atc2_common { + apple,always-on; +}; + +&usb4_0_acio { + apple,pcie-tunnel = <&usb4_0_pcie_tunnel>; +}; + +&usb4_1_acio { + apple,pcie-tunnel = <&usb4_1_pcie_tunnel>; +}; + +&usb4_2_acio { + apple,pcie-tunnel = <&usb4_2_pcie_tunnel>; +}; +#endif + /* remove once m1n1 enables sio nodes after setup */ &sio { status = "okay"; @@ -197,7 +306,7 @@ power-role = "dual"; data-role = "dual"; -#ifdef ENABLE_DCPEXT_TYPEC +#if defined(ENABLE_DCPEXT_TYPEC) && !defined(APPLE_T600X_NON_ULTRA) /* hacks */ displayport = <&dcpext1>; #endif @@ -263,7 +372,7 @@ }; }; -#ifdef ENABLE_DCPEXT_TYPEC +#if defined(ENABLE_DCPEXT_TYPEC) && !defined(APPLE_T600X_NON_ULTRA) &display { iommus = <&disp0_dart 0>, <&dispext0_dart 0>, <&dispext1_dart 0>; @@ -463,6 +572,47 @@ status = "okay"; }; +#ifdef APPLE_T600X_NON_ULTRA +&typec0 { + displayport = <&dcpext1>; + + ports { + port@3 { + reg = <3>; + typec0_dcp: endpoint { + remote-endpoint = <&dcpext1_typec0>; + }; + }; + }; +}; + +&typec1 { + displayport = <&dcpext1>; + + ports { + port@3 { + reg = <3>; + typec1_dcp: endpoint { + remote-endpoint = <&dcpext1_typec1>; + }; + }; + }; +}; + +&typec2 { + displayport = <&dcpext1>; + + ports { + port@3 { + reg = <3>; + typec2_dcp: endpoint { + remote-endpoint = <&dcpext1_typec2>; + }; + }; + }; +}; +#endif + /* USB controllers */ &dwc3_0 { ports { diff --git a/arch/arm64/boot/dts/apple/t600x-j375.dtsi b/arch/arm64/boot/dts/apple/t600x-j375.dtsi index a42983d9786fda..c91f6992da3f4d 100644 --- a/arch/arm64/boot/dts/apple/t600x-j375.dtsi +++ b/arch/arm64/boot/dts/apple/t600x-j375.dtsi @@ -20,10 +20,13 @@ bluetooth0 = &bluetooth0; #ifndef NO_DCP dcp = &dcp; +#ifdef APPLE_T600X_NON_ULTRA + dcpext1 = &dcpext1; +#endif disp0 = &display; disp0_piodma = &disp0_piodma; #endif -#ifdef ENABLE_DCPEXT_TYPEC +#if defined(ENABLE_DCPEXT_TYPEC) && !defined(APPLE_T600X_NON_ULTRA) dcpext1 = &dcpext1; #endif ethernet0 = ðernet0; @@ -70,6 +73,138 @@ apple,connector-type = "HDMI-A"; }; +#ifdef APPLE_T600X_NON_ULTRA +&display { + iommus = <&disp0_dart 0>, <&dispext1_dart 0>; +}; + +&dispext1_dart { + status = "okay"; +}; + +&dcpext1_dart { + status = "okay"; +}; + +&dcpext1_mbox { + status = "okay"; +}; + +&dcpext1 { + status = "okay"; + apple,connector-type = "USB-C"; + phys = <&atcphy0 PHY_TYPE_DP>, <&atcphy1 PHY_TYPE_DP>, + <&atcphy2 PHY_TYPE_DP>, <&atcphy3 PHY_TYPE_DP>; + phy-names = "typec0", "typec1", "typec2", "typec3"; + mux-controls = <&atcphy0_xbar 0>, <&atcphy1_xbar 0>, + <&atcphy2_xbar 0>, <&atcphy3_xbar 0>; + mux-control-names = "typec0", "typec1", "typec2", "typec3"; + apple,typec-mux-indices = <2 2 2 2>; + apple,typec-dptx-phys = <0 1 2 3>; + + typec-routes { + #address-cells = <1>; + #size-cells = <0>; + + route@0 { + reg = <0>; + mode-switch; + + port { + dcpext1_typec0: endpoint { + remote-endpoint = <&typec0_dcp>; + }; + }; + }; + + route@1 { + reg = <1>; + mode-switch; + + port { + dcpext1_typec1: endpoint { + remote-endpoint = <&typec1_dcp>; + }; + }; + }; + + route@2 { + reg = <2>; + mode-switch; + + port { + dcpext1_typec2: endpoint { + remote-endpoint = <&typec2_dcp>; + }; + }; + }; + + route@3 { + reg = <3>; + mode-switch; + + port { + dcpext1_typec3: endpoint { + remote-endpoint = <&typec3_dcp>; + }; + }; + }; + }; +}; + +&dpaudio2 { + status = "okay"; +}; + +&atcphy0_xbar { + status = "okay"; +}; + +&atcphy1_xbar { + status = "okay"; +}; + +&atcphy2_xbar { + status = "okay"; +}; + +&atcphy3_xbar { + status = "okay"; +}; + +&ps_atc0_common { + apple,always-on; +}; + +&ps_atc1_common { + apple,always-on; +}; + +&ps_atc2_common { + apple,always-on; +}; + +&ps_atc3_common { + apple,always-on; +}; + +&usb4_0_acio { + apple,pcie-tunnel = <&usb4_0_pcie_tunnel>; +}; + +&usb4_1_acio { + apple,pcie-tunnel = <&usb4_1_pcie_tunnel>; +}; + +&usb4_2_acio { + apple,pcie-tunnel = <&usb4_2_pcie_tunnel>; +}; + +&usb4_3_acio { + apple,pcie-tunnel = <&usb4_3_pcie_tunnel>; +}; +#endif + /* remove once m1n1 enables sio nodes after setup */ &sio { status = "okay"; @@ -122,7 +257,7 @@ power-role = "dual"; data-role = "dual"; -#ifdef ENABLE_DCPEXT_TYPEC +#if defined(ENABLE_DCPEXT_TYPEC) && !defined(APPLE_T600X_NON_ULTRA) /* hacks */ displayport = <&dcpext1>; #endif @@ -211,7 +346,7 @@ }; }; -#ifdef ENABLE_DCPEXT_TYPEC +#if defined(ENABLE_DCPEXT_TYPEC) && !defined(APPLE_T600X_NON_ULTRA) &display { iommus = <&disp0_dart 0>, <&dispext1_dart 0>; }; @@ -254,6 +389,60 @@ }; #endif +#ifdef APPLE_T600X_NON_ULTRA +&typec0 { + displayport = <&dcpext1>; + + ports { + port@3 { + reg = <3>; + typec0_dcp: endpoint { + remote-endpoint = <&dcpext1_typec0>; + }; + }; + }; +}; + +&typec1 { + displayport = <&dcpext1>; + + ports { + port@3 { + reg = <3>; + typec1_dcp: endpoint { + remote-endpoint = <&dcpext1_typec1>; + }; + }; + }; +}; + +&typec2 { + displayport = <&dcpext1>; + + ports { + port@3 { + reg = <3>; + typec2_dcp: endpoint { + remote-endpoint = <&dcpext1_typec2>; + }; + }; + }; +}; + +&typec3 { + displayport = <&dcpext1>; + + ports { + port@3 { + reg = <3>; + typec3_dcp: endpoint { + remote-endpoint = <&dcpext1_typec3>; + }; + }; + }; +}; +#endif + /* USB controllers */ &dwc3_0 { ports { diff --git a/arch/arm64/boot/dts/apple/t600x-pciec.dtsi b/arch/arm64/boot/dts/apple/t600x-pciec.dtsi new file mode 100644 index 00000000000000..f4cad7634f5880 --- /dev/null +++ b/arch/arm64/boot/dts/apple/t600x-pciec.dtsi @@ -0,0 +1,274 @@ +// SPDX-License-Identifier: GPL-2.0+ OR MIT +/* + * PCIe-C root complexes used by USB4 tunnels on T6000/T6001. + * + * Copyright The Asahi Linux Contributors + */ + + usb4_0_pcie_tunnel: usb4-pcie-tunnel-0 { + #address-cells = <2>; + #size-cells = <2>; + ranges; + + apciec0_dart: iommu@721008000 { + compatible = "apple,t6000-dart"; + reg = <0x7 0x21008000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = ; + #iommu-cells = <1>; + }; + + apciec0: pcie@730000000 { + compatible = "apple,t6000-pciec"; + device_type = "pci"; + + reg = <0x7 0x30000000 0x0 0x10000000>, + <0x7 0x20000000 0x0 0x4000>, + <0x7 0x21000000 0x0 0x8000>, + <0x7 0x20004000 0x0 0x4000>, + <0x7 0x20200000 0x0 0x4000>, + <0x7 0x21010000 0x0 0x4000>, + <0x7 0x2100c000 0x0 0x4000>; + reg-names = "config", "rc", "port0", "common", + "fabric", "debug", "intr2axi"; + + interrupt-parent = <&aic>; + interrupts = ; + + msi-controller; + msi-parent = <&apciec0>; + msi-ranges = <&aic AIC_IRQ 0 1614 IRQ_TYPE_EDGE_RISING 32>; + + iommu-map = <0x0 &apciec0_dart 0x0 0x40>; + iommu-map-mask = <0x3f>; + + bus-range = <0x0 0x7f>; + #address-cells = <3>; + #size-cells = <2>; + ranges = <0x43000000 0x8 0x0 + 0x8 0x0 0x2 0x0>, + <0x02000000 0x0 0x00100000 + 0xa 0x00100000 0x0 0x3ff00000>, + <0x42000000 0x0 0x40000000 + 0xa 0x40000000 0x0 0x40000000>; + + apciec0_port: pci@0,0 { + device_type = "pci"; + reg = <0x0 0x0 0x0 0x0 0x0>; + external-facing; + + #address-cells = <3>; + #size-cells = <2>; + ranges; + + interrupt-controller; + #interrupt-cells = <1>; + interrupt-map-mask = <0 0 0 7>; + interrupt-map = <0 0 0 1 &apciec0_port 0 0 0 0>, + <0 0 0 2 &apciec0_port 0 0 0 1>, + <0 0 0 3 &apciec0_port 0 0 0 2>, + <0 0 0 4 &apciec0_port 0 0 0 3>; + }; + }; + }; + + usb4_1_pcie_tunnel: usb4-pcie-tunnel-1 { + #address-cells = <2>; + #size-cells = <2>; + ranges; + + apciec1_dart: iommu@b21008000 { + compatible = "apple,t6000-dart"; + reg = <0xb 0x21008000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = ; + #iommu-cells = <1>; + }; + + apciec1: pcie@b30000000 { + compatible = "apple,t6000-pciec"; + device_type = "pci"; + + reg = <0xb 0x30000000 0x0 0x10000000>, + <0xb 0x20000000 0x0 0x4000>, + <0xb 0x21000000 0x0 0x8000>, + <0xb 0x20004000 0x0 0x4000>, + <0xb 0x20200000 0x0 0x4000>, + <0xb 0x21010000 0x0 0x4000>, + <0xb 0x2100c000 0x0 0x4000>; + reg-names = "config", "rc", "port0", "common", + "fabric", "debug", "intr2axi"; + + interrupt-parent = <&aic>; + interrupts = ; + + msi-controller; + msi-parent = <&apciec1>; + msi-ranges = <&aic AIC_IRQ 0 1646 IRQ_TYPE_EDGE_RISING 32>; + + iommu-map = <0x0 &apciec1_dart 0x0 0x40>; + iommu-map-mask = <0x3f>; + + bus-range = <0x0 0x7f>; + #address-cells = <3>; + #size-cells = <2>; + ranges = <0x43000000 0x8 0x0 + 0xc 0x0 0x2 0x0>, + <0x02000000 0x0 0x00100000 + 0xe 0x00100000 0x0 0x3ff00000>, + <0x42000000 0x0 0x40000000 + 0xe 0x40000000 0x0 0x40000000>; + + apciec1_port: pci@0,0 { + device_type = "pci"; + reg = <0x0 0x0 0x0 0x0 0x0>; + external-facing; + + #address-cells = <3>; + #size-cells = <2>; + ranges; + + interrupt-controller; + #interrupt-cells = <1>; + interrupt-map-mask = <0 0 0 7>; + interrupt-map = <0 0 0 1 &apciec1_port 0 0 0 0>, + <0 0 0 2 &apciec1_port 0 0 0 1>, + <0 0 0 3 &apciec1_port 0 0 0 2>, + <0 0 0 4 &apciec1_port 0 0 0 3>; + }; + }; + }; + + usb4_2_pcie_tunnel: usb4-pcie-tunnel-2 { + #address-cells = <2>; + #size-cells = <2>; + ranges; + + apciec2_dart: iommu@f21008000 { + compatible = "apple,t6000-dart"; + reg = <0xf 0x21008000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = ; + #iommu-cells = <1>; + }; + + apciec2: pcie@f30000000 { + compatible = "apple,t6000-pciec"; + device_type = "pci"; + + reg = <0xf 0x30000000 0x0 0x10000000>, + <0xf 0x20000000 0x0 0x4000>, + <0xf 0x21000000 0x0 0x8000>, + <0xf 0x20004000 0x0 0x4000>, + <0xf 0x20200000 0x0 0x4000>, + <0xf 0x21010000 0x0 0x4000>, + <0xf 0x2100c000 0x0 0x4000>; + reg-names = "config", "rc", "port0", "common", + "fabric", "debug", "intr2axi"; + + interrupt-parent = <&aic>; + interrupts = ; + + msi-controller; + msi-parent = <&apciec2>; + msi-ranges = <&aic AIC_IRQ 0 1678 IRQ_TYPE_EDGE_RISING 32>; + + iommu-map = <0x0 &apciec2_dart 0x0 0x40>; + iommu-map-mask = <0x3f>; + + bus-range = <0x0 0x7f>; + #address-cells = <3>; + #size-cells = <2>; + ranges = <0x43000000 0x8 0x0 + 0x10 0x0 0x2 0x0>, + <0x02000000 0x0 0x00100000 + 0x12 0x00100000 0x0 0x3ff00000>, + <0x42000000 0x0 0x40000000 + 0x12 0x40000000 0x0 0x40000000>; + + apciec2_port: pci@0,0 { + device_type = "pci"; + reg = <0x0 0x0 0x0 0x0 0x0>; + external-facing; + + #address-cells = <3>; + #size-cells = <2>; + ranges; + + interrupt-controller; + #interrupt-cells = <1>; + interrupt-map-mask = <0 0 0 7>; + interrupt-map = <0 0 0 1 &apciec2_port 0 0 0 0>, + <0 0 0 2 &apciec2_port 0 0 0 1>, + <0 0 0 3 &apciec2_port 0 0 0 2>, + <0 0 0 4 &apciec2_port 0 0 0 3>; + }; + }; + }; + + usb4_3_pcie_tunnel: usb4-pcie-tunnel-3 { + #address-cells = <2>; + #size-cells = <2>; + ranges; + + apciec3_dart: iommu@1321008000 { + compatible = "apple,t6000-dart"; + reg = <0x13 0x21008000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = ; + #iommu-cells = <1>; + }; + + apciec3: pcie@1330000000 { + compatible = "apple,t6000-pciec"; + device_type = "pci"; + + reg = <0x13 0x30000000 0x0 0x10000000>, + <0x13 0x20000000 0x0 0x4000>, + <0x13 0x21000000 0x0 0x8000>, + <0x13 0x20004000 0x0 0x4000>, + <0x13 0x20200000 0x0 0x4000>, + <0x13 0x21010000 0x0 0x4000>, + <0x13 0x2100c000 0x0 0x4000>; + reg-names = "config", "rc", "port0", "common", + "fabric", "debug", "intr2axi"; + + interrupt-parent = <&aic>; + interrupts = ; + + msi-controller; + msi-parent = <&apciec3>; + msi-ranges = <&aic AIC_IRQ 0 1710 IRQ_TYPE_EDGE_RISING 32>; + + iommu-map = <0x0 &apciec3_dart 0x0 0x40>; + iommu-map-mask = <0x3f>; + + bus-range = <0x0 0x7f>; + #address-cells = <3>; + #size-cells = <2>; + ranges = <0x43000000 0x8 0x0 + 0x14 0x0 0x2 0x0>, + <0x02000000 0x0 0x00100000 + 0x16 0x00100000 0x0 0x3ff00000>, + <0x42000000 0x0 0x40000000 + 0x16 0x40000000 0x0 0x40000000>; + + apciec3_port: pci@0,0 { + device_type = "pci"; + reg = <0x0 0x0 0x0 0x0 0x0>; + external-facing; + + #address-cells = <3>; + #size-cells = <2>; + ranges; + + interrupt-controller; + #interrupt-cells = <1>; + interrupt-map-mask = <0 0 0 7>; + interrupt-map = <0 0 0 1 &apciec3_port 0 0 0 0>, + <0 0 0 2 &apciec3_port 0 0 0 1>, + <0 0 0 3 &apciec3_port 0 0 0 2>, + <0 0 0 4 &apciec3_port 0 0 0 3>; + }; + }; + }; From fc1b73b30e5af63fd3169b3b285ceead6f44ea4e Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Tue, 25 Aug 2026 15:12:56 -0400 Subject: [PATCH 13/51] arm64: dts: apple: complete non-Ultra T602x USB4 and DCP routes Signed-off-by: DjDeveloperr --- arch/arm64/boot/dts/apple/t6020-j474s.dts | 4 - arch/arm64/boot/dts/apple/t6021-j475c.dts | 4 - arch/arm64/boot/dts/apple/t6021.dtsi | 2 + arch/arm64/boot/dts/apple/t602x-dieX.dtsi | 70 +++++ .../arm64/boot/dts/apple/t602x-j414-j416.dtsi | 239 +++++++++++++++++- .../arm64/boot/dts/apple/t602x-j474-j475.dtsi | 190 ++++++++++++++ 6 files changed, 498 insertions(+), 11 deletions(-) diff --git a/arch/arm64/boot/dts/apple/t6020-j474s.dts b/arch/arm64/boot/dts/apple/t6020-j474s.dts index cfe7f8ac730641..3fbe45cd48585e 100644 --- a/arch/arm64/boot/dts/apple/t6020-j474s.dts +++ b/arch/arm64/boot/dts/apple/t6020-j474s.dts @@ -87,11 +87,7 @@ status = "disabled"; }; &display { -#ifdef ENABLE_DCPEXT_TYPEC iommus = <&dispext0_dart 0>, <&dispext1_dart 0>; -#else - iommus = <&dispext0_dart 0>; -#endif }; &dispext0_dart { status = "okay"; diff --git a/arch/arm64/boot/dts/apple/t6021-j475c.dts b/arch/arm64/boot/dts/apple/t6021-j475c.dts index ce08d5a42761b9..a43e32d0839658 100644 --- a/arch/arm64/boot/dts/apple/t6021-j475c.dts +++ b/arch/arm64/boot/dts/apple/t6021-j475c.dts @@ -83,11 +83,7 @@ status = "disabled"; }; &display { -#ifdef ENABLE_DCPEXT_TYPEC iommus = <&dispext0_dart 0>, <&dispext1_dart 0>; -#else - iommus = <&dispext0_dart 0>; -#endif }; &dispext0_dart { status = "okay"; diff --git a/arch/arm64/boot/dts/apple/t6021.dtsi b/arch/arm64/boot/dts/apple/t6021.dtsi index bb0e66851f1b59..889728010c84d8 100644 --- a/arch/arm64/boot/dts/apple/t6021.dtsi +++ b/arch/arm64/boot/dts/apple/t6021.dtsi @@ -23,6 +23,8 @@ # define GPU_DIE_REPEAT(x) #endif +#define APPLE_T602X_NON_ULTRA + #include "t602x-common.dtsi" / { diff --git a/arch/arm64/boot/dts/apple/t602x-dieX.dtsi b/arch/arm64/boot/dts/apple/t602x-dieX.dtsi index 6cd6a58d1e7594..2e44b0aab9cd16 100644 --- a/arch/arm64/boot/dts/apple/t602x-dieX.dtsi +++ b/arch/arm64/boot/dts/apple/t602x-dieX.dtsi @@ -1027,6 +1027,76 @@ }; }; +#ifdef APPLE_T602X_NON_ULTRA + DIE_NODE(usb4_3_pcie_tunnel): usb4-pcie-tunnel-3 { + #address-cells = <2>; + #size-cells = <2>; + ranges; + + DIE_NODE(apciec3_dart): iommu@1321008000 { + compatible = "apple,t8110-dart"; + reg = <0x13 0x21008000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = ; + #iommu-cells = <1>; + }; + + DIE_NODE(apciec3): pcie@1330000000 { + compatible = "apple,t6000-pciec"; + device_type = "pci"; + + reg = <0x13 0x30000000 0x0 0x10000000>, + <0x13 0x20000000 0x0 0x4000>, + <0x13 0x21000000 0x0 0x8000>, + <0x13 0x20004000 0x0 0x4000>, + <0x13 0x20200000 0x0 0x4000>, + <0x13 0x21010000 0x0 0x4000>, + <0x13 0x2100c000 0x0 0x4000>; + reg-names = "config", "rc", "port0", "common", + "fabric", "debug", "intr2axi"; + + interrupt-parent = <&aic>; + interrupts = ; + + msi-controller; + msi-parent = <&DIE_NODE(apciec3)>; + msi-ranges = <&aic AIC_IRQ DIE_NO 1929 + IRQ_TYPE_EDGE_RISING 32>; + + iommu-map = <0x0 &DIE_NODE(apciec3_dart) 0x0 0x40>; + iommu-map-mask = <0x3f>; + + bus-range = <0x0 0x7f>; + #address-cells = <3>; + #size-cells = <2>; + ranges = <0x43000000 0x8 0x0 + 0x14 0x0 0x2 0x0>, + <0x02000000 0x0 0x00100000 + 0x16 0x00100000 0x0 0x3ff00000>, + <0x42000000 0x0 0x40000000 + 0x16 0x40000000 0x0 0x40000000>; + + DIE_NODE(apciec3_port): pci@0,0 { + device_type = "pci"; + reg = <0x0 0x0 0x0 0x0 0x0>; + external-facing; + + #address-cells = <3>; + #size-cells = <2>; + ranges; + + interrupt-controller; + #interrupt-cells = <1>; + interrupt-map-mask = <0 0 0 7>; + interrupt-map = <0 0 0 1 &DIE_NODE(apciec3_port) 0 0 0 0>, + <0 0 0 2 &DIE_NODE(apciec3_port) 0 0 0 1>, + <0 0 0 3 &DIE_NODE(apciec3_port) 0 0 0 2>, + <0 0 0 4 &DIE_NODE(apciec3_port) 0 0 0 3>; + }; + }; + }; +#endif + DIE_NODE(dwc3_3): usb@1302280000 { compatible = "apple,t6020-dwc3", "apple,t8103-dwc3"; reg = <0x13 0x02280000 0x0 0xcd00>, <0x13 0x0228cd00 0x0 0x3200>; diff --git a/arch/arm64/boot/dts/apple/t602x-j414-j416.dtsi b/arch/arm64/boot/dts/apple/t602x-j414-j416.dtsi index 6b6b0225301d9a..bff9abdfe9f9b2 100644 --- a/arch/arm64/boot/dts/apple/t602x-j414-j416.dtsi +++ b/arch/arm64/boot/dts/apple/t602x-j414-j416.dtsi @@ -19,17 +19,250 @@ / { aliases { + dcp = &dcp; + dcpext0 = &dcpext0; + dcpext1 = &dcpext1; + disp0 = &display; + disp0_piodma = &disp0_piodma; keyboard = &keyboard; + sio = &sio; }; }; &framebuffer0 { power-domains = <&ps_disp0_cpu0>, <&ps_dptx_phy_ps>; + panel = <&panel>; + post-init-providers = <&panel>; }; -/* HACK: keep dptx_phy_ps power-domain always-on - * it is unclear how to sequence with dcp for the integrated display - */ +&dcp { + panel: panel { + compatible = "apple,panel"; + apple,max-brightness = <500>; + }; +}; + +&disp0_dart { + status = "okay"; +}; + +&display { + iommus = <&disp0_dart 0>, <&dispext0_dart 0>, <&dispext1_dart 0>; +}; + +&dispext0_dart { + status = "okay"; +}; + +&dcpext0_dart { + status = "okay"; +}; + +&dcpext0_mbox { + status = "okay"; +}; + +&dcpext0 { + status = "okay"; + apple,connector-type = "HDMI-A"; + hdmi-hpd-gpios = <&pinctrl_aop 25 GPIO_ACTIVE_HIGH>; + hdmi-pwren-gpios = <&smc_gpio 23 GPIO_ACTIVE_HIGH>; + dp2hdmi-pwren-gpios = <&smc_gpio 6 GPIO_ACTIVE_HIGH>; + phys = <&atcphy3 PHY_TYPE_DP>; + phy-names = "dp-phy"; + mux-controls = <&atcphy3_xbar 0>; + mux-control-names = "dp-xbar"; + mux-index = <0>; + apple,dptx-phy = <3>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@1 { + reg = <1>; + dcpext0_dpout: endpoint { + remote-endpoint = <&atcphy3_dp>; + }; + }; + }; +}; + +&dispext1_dart { + status = "okay"; +}; + +&dcpext1_dart { + status = "okay"; +}; + +&dcpext1_mbox { + status = "okay"; +}; + +&dcpext1 { + status = "okay"; + apple,connector-type = "USB-C"; + phys = <&atcphy0 PHY_TYPE_DP>, <&atcphy1 PHY_TYPE_DP>, + <&atcphy2 PHY_TYPE_DP>; + phy-names = "typec0", "typec1", "typec2"; + mux-controls = <&atcphy0_xbar 0>, <&atcphy1_xbar 0>, + <&atcphy2_xbar 0>; + mux-control-names = "typec0", "typec1", "typec2"; + apple,typec-mux-indices = <2 2 2>; + apple,typec-dptx-phys = <0 1 2>; + + typec-routes { + #address-cells = <1>; + #size-cells = <0>; + + route@0 { + reg = <0>; + mode-switch; + + port { + dcpext1_typec0: endpoint { + remote-endpoint = <&typec0_dcp>; + }; + }; + }; + + route@1 { + reg = <1>; + mode-switch; + + port { + dcpext1_typec1: endpoint { + remote-endpoint = <&typec1_dcp>; + }; + }; + }; + + route@2 { + reg = <2>; + mode-switch; + + port { + dcpext1_typec2: endpoint { + remote-endpoint = <&typec2_dcp>; + }; + }; + }; + }; +}; + +&typec0 { + displayport = <&dcpext1>; + + ports { + port@3 { + reg = <3>; + typec0_dcp: endpoint { + remote-endpoint = <&dcpext1_typec0>; + }; + }; + }; +}; + +&typec1 { + displayport = <&dcpext1>; + + ports { + port@3 { + reg = <3>; + typec1_dcp: endpoint { + remote-endpoint = <&dcpext1_typec1>; + }; + }; + }; +}; + +&typec2 { + displayport = <&dcpext1>; + + ports { + port@3 { + reg = <3>; + typec2_dcp: endpoint { + remote-endpoint = <&dcpext1_typec2>; + }; + }; + }; +}; + +&sio { + status = "okay"; +}; + +&dpaudio1 { + status = "okay"; +}; + +&dpaudio2 { + status = "okay"; +}; + +&atcphy0_xbar { + status = "okay"; +}; + +&atcphy1_xbar { + status = "okay"; +}; + +&atcphy2_xbar { + status = "okay"; +}; + +&atcphy3_xbar { + status = "okay"; +}; + +&atcphy3 { + status = "okay"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@2 { + reg = <2>; + atcphy3_dp: endpoint { + remote-endpoint = <&dcpext0_dpout>; + }; + }; + }; +}; + +&ps_atc0_common { + apple,always-on; +}; + +&ps_atc1_common { + apple,always-on; +}; + +&ps_atc2_common { + apple,always-on; +}; + +&ps_atc3_common { + apple,always-on; +}; + +&usb4_0_acio { + apple,pcie-tunnel = <&usb4_0_pcie_tunnel>; +}; + +&usb4_1_acio { + apple,pcie-tunnel = <&usb4_1_pcie_tunnel>; +}; + +&usb4_2_acio { + apple,pcie-tunnel = <&usb4_2_pcie_tunnel>; +}; + +/* Keep dptx_phy_ps on until DCP can sequence it during display bring-up. */ &ps_dptx_phy_ps { apple,always-on; }; diff --git a/arch/arm64/boot/dts/apple/t602x-j474-j475.dtsi b/arch/arm64/boot/dts/apple/t602x-j474-j475.dtsi index 287348628eb177..7e4069b929a81c 100644 --- a/arch/arm64/boot/dts/apple/t602x-j474-j475.dtsi +++ b/arch/arm64/boot/dts/apple/t602x-j474-j475.dtsi @@ -21,6 +21,196 @@ power-domains = <&ps_dispext0_cpu0>, <&ps_dptx_phy_ps>; }; +#ifdef APPLE_T602X_NON_ULTRA +/ { + aliases { + dcpext1 = &dcpext1; + }; +}; + +&display { + iommus = <&dispext0_dart 0>, <&dispext1_dart 0>; +}; + +&dispext1_dart { + status = "okay"; +}; + +&dcpext1_dart { + status = "okay"; +}; + +&dcpext1_mbox { + status = "okay"; +}; + +&dcpext1 { + status = "okay"; + apple,connector-type = "USB-C"; + phys = <&atcphy0 PHY_TYPE_DP>, <&atcphy1 PHY_TYPE_DP>, + <&atcphy2 PHY_TYPE_DP>, <&atcphy3 PHY_TYPE_DP>; + phy-names = "typec0", "typec1", "typec2", "typec3"; + mux-controls = <&atcphy0_xbar 0>, <&atcphy1_xbar 0>, + <&atcphy2_xbar 0>, <&atcphy3_xbar 0>; + mux-control-names = "typec0", "typec1", "typec2", "typec3"; + apple,typec-mux-indices = <2 2 2 2>; + apple,typec-dptx-phys = <0 1 2 3>; + + typec-routes { + #address-cells = <1>; + #size-cells = <0>; + + route@0 { + reg = <0>; + mode-switch; + + port { + dcpext1_typec0: endpoint { + remote-endpoint = <&typec0_dcp>; + }; + }; + }; + + route@1 { + reg = <1>; + mode-switch; + + port { + dcpext1_typec1: endpoint { + remote-endpoint = <&typec1_dcp>; + }; + }; + }; + + route@2 { + reg = <2>; + mode-switch; + + port { + dcpext1_typec2: endpoint { + remote-endpoint = <&typec2_dcp>; + }; + }; + }; + + route@3 { + reg = <3>; + mode-switch; + + port { + dcpext1_typec3: endpoint { + remote-endpoint = <&typec3_dcp>; + }; + }; + }; + }; +}; + +&typec0 { + displayport = <&dcpext1>; + + ports { + port@3 { + reg = <3>; + typec0_dcp: endpoint { + remote-endpoint = <&dcpext1_typec0>; + }; + }; + }; +}; + +&typec1 { + displayport = <&dcpext1>; + + ports { + port@3 { + reg = <3>; + typec1_dcp: endpoint { + remote-endpoint = <&dcpext1_typec1>; + }; + }; + }; +}; + +&typec2 { + displayport = <&dcpext1>; + + ports { + port@3 { + reg = <3>; + typec2_dcp: endpoint { + remote-endpoint = <&dcpext1_typec2>; + }; + }; + }; +}; + +&typec3 { + displayport = <&dcpext1>; + + ports { + port@3 { + reg = <3>; + typec3_dcp: endpoint { + remote-endpoint = <&dcpext1_typec3>; + }; + }; + }; +}; + +&dpaudio2 { + status = "okay"; +}; + +&atcphy0_xbar { + status = "okay"; +}; + +&atcphy1_xbar { + status = "okay"; +}; + +&atcphy2_xbar { + status = "okay"; +}; + +&atcphy3_xbar { + status = "okay"; +}; + +&ps_atc0_common { + apple,always-on; +}; + +&ps_atc1_common { + apple,always-on; +}; + +&ps_atc2_common { + apple,always-on; +}; + +&ps_atc3_common { + apple,always-on; +}; + +&usb4_0_acio { + apple,pcie-tunnel = <&usb4_0_pcie_tunnel>; +}; + +&usb4_1_acio { + apple,pcie-tunnel = <&usb4_1_pcie_tunnel>; +}; + +&usb4_2_acio { + apple,pcie-tunnel = <&usb4_2_pcie_tunnel>; +}; + +&usb4_3_acio { + apple,pcie-tunnel = <&usb4_3_pcie_tunnel>; +}; +#endif + &hpm0 { interrupts = <44 IRQ_TYPE_LEVEL_LOW>; }; From 78724f5704b80d9d7ba7498251e7751d4e6e335a Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Tue, 25 Aug 2026 14:16:41 -0400 Subject: [PATCH 14/51] arm64: dts: apple: add M2 Pro USB4 and display topology Signed-off-by: DjDeveloperr --- arch/arm64/boot/dts/apple/t602x-common.dtsi | 6 +- arch/arm64/boot/dts/apple/t602x-die0.dtsi | 232 ++++++++++++++++++++ arch/arm64/boot/dts/apple/t602x-dieX.dtsi | 207 +++++++++++++++++ 3 files changed, 442 insertions(+), 3 deletions(-) diff --git a/arch/arm64/boot/dts/apple/t602x-common.dtsi b/arch/arm64/boot/dts/apple/t602x-common.dtsi index 2fa6b93a14165f..c985e0e8d1c3ff 100644 --- a/arch/arm64/boot/dts/apple/t602x-common.dtsi +++ b/arch/arm64/boot/dts/apple/t602x-common.dtsi @@ -557,7 +557,7 @@ clk_disp0: clock-disp0 { compatible = "fixed-clock"; #clock-cells = <0>; - clock-frequency = <257142848>; /* TODO: check */ + clock-frequency = <257142848>; clock-output-names = "clk_disp0"; }; @@ -568,7 +568,7 @@ clock-output-names = "clk_dispext0"; }; - clk_dispext0_die1: clock-dispext0_die1 { + clk_dispext0_die1: clock-dispext0-die1 { compatible = "fixed-clock"; #clock-cells = <0>; clock-frequency = <0>; @@ -582,7 +582,7 @@ clock-output-names = "clk_dispext1"; }; - clk_dispext1_die1: clock-dispext1_die1 { + clk_dispext1_die1: clock-dispext1-die1 { compatible = "fixed-clock"; #clock-cells = <0>; clock-frequency = <0>; diff --git a/arch/arm64/boot/dts/apple/t602x-die0.dtsi b/arch/arm64/boot/dts/apple/t602x-die0.dtsi index 852b33a88b8136..e6ac57f1da4889 100644 --- a/arch/arm64/boot/dts/apple/t602x-die0.dtsi +++ b/arch/arm64/boot/dts/apple/t602x-die0.dtsi @@ -393,6 +393,167 @@ status = "disabled"; }; + dispext0_dart: iommu@289304000 { + compatible = "apple,t6020-dart", "apple,t8110-dart"; + reg = <0x2 0x89304000 0x0 0x4000>; + #iommu-cells = <1>; + interrupt-parent = <&aic>; + interrupts = ; + power-domains = <&ps_dispext0_cpu0>; + apple,dma-range = <0x100 0x0 0x10 0x0>; + status = "disabled"; + }; + + dcpext0_dart: iommu@28930c000 { + compatible = "apple,t6020-dart", "apple,t8110-dart"; + reg = <0x2 0x8930c000 0x0 0x4000>; + #iommu-cells = <1>; + interrupt-parent = <&aic>; + interrupts = ; + power-domains = <&ps_dispext0_cpu0>; + apple,dma-range = <0x100 0x0 0x10 0x0>; + status = "disabled"; + }; + + dcpext0_mbox: mailbox@289c08000 { + compatible = "apple,t6020-asc-mailbox", "apple,asc-mailbox-v4"; + reg = <0x2 0x89c08000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = , + , + , + ; + interrupt-names = "send-empty", "send-not-empty", + "recv-empty", "recv-not-empty"; + #mbox-cells = <0>; + power-domains = <&ps_dispext0_cpu0>; + resets = <&ps_dispext0_cpu0>; + status = "disabled"; + }; + + dcpext0: dcp@289c00000 { + compatible = "apple,t6020-dcpext", "apple,dcpext"; + mboxes = <&dcpext0_mbox>; + mbox-names = "mbox"; + iommus = <&dcpext0_dart 5>; + reg = <0x2 0x89c00000 0x0 0x4000>, + <0x2 0x88000000 0x0 0x4000000>, + <0x2 0x89320000 0x0 0x4000>, + <0x2 0x89344000 0x0 0x4000>, + <0x2 0x89800000 0x0 0x800000>; + reg-names = "coproc", "disp-0", "disp-1", "disp-2", "disp-3"; + apple,bw-scratch = <&pmgr_dcp 0 4 0x1210>; + apple,iomfb-surfaces = <1 1 0 1>; + power-domains = <&ps_dispext0_cpu0>; + resets = <&ps_dispext0_cpu0>; + clocks = <&clk_dispext0>; + apple,dcp-index = <1>; + #address-cells = <2>; + #size-cells = <2>; + status = "disabled"; + + piodma { + iommus = <&dispext0_dart 4>; + }; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + dcpext0_audio: endpoint { + remote-endpoint = <&dpaudio1_dcp>; + }; + }; + }; + }; + + dispext1_dart: iommu@315304000 { + compatible = "apple,t6020-dart", "apple,t8110-dart"; + reg = <0x3 0x15304000 0x0 0x4000>; + #iommu-cells = <1>; + interrupt-parent = <&aic>; + interrupts = ; + power-domains = <&ps_dispext1_cpu0>; + apple,dma-range = <0x100 0x0 0x10 0x0>; + status = "disabled"; + }; + + dcpext1_dart: iommu@31530c000 { + compatible = "apple,t6020-dart", "apple,t8110-dart"; + reg = <0x3 0x1530c000 0x0 0x4000>; + #iommu-cells = <1>; + interrupt-parent = <&aic>; + interrupts = ; + power-domains = <&ps_dispext1_cpu0>; + apple,dma-range = <0x100 0x0 0x10 0x0>; + status = "disabled"; + }; + + dcpext1_mbox: mailbox@315c08000 { + compatible = "apple,t6020-asc-mailbox", "apple,asc-mailbox-v4"; + reg = <0x3 0x15c08000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = , + , + , + ; + interrupt-names = "send-empty", "send-not-empty", + "recv-empty", "recv-not-empty"; + #mbox-cells = <0>; + power-domains = <&ps_dispext1_cpu0>; + resets = <&ps_dispext1_cpu0>; + status = "disabled"; + }; + + dcpext1: dcp@315c00000 { + compatible = "apple,t6020-dcpext", "apple,dcpext"; + mboxes = <&dcpext1_mbox>; + mbox-names = "mbox"; + iommus = <&dcpext1_dart 5>; + reg = <0x3 0x15c00000 0x0 0x4000>, + <0x3 0x14000000 0x0 0x4000000>, + <0x3 0x15320000 0x0 0x4000>, + <0x3 0x15344000 0x0 0x4000>, + <0x3 0x15800000 0x0 0x800000>; + reg-names = "coproc", "disp-0", "disp-1", "disp-2", "disp-3"; + apple,bw-scratch = <&pmgr_dcp 0 4 0x1218>; + apple,iomfb-surfaces = <1 1 0 1>; + power-domains = <&ps_dispext1_cpu0>; + resets = <&ps_dispext1_cpu0>; + clocks = <&clk_dispext1>; + apple,dcp-index = <2>; + #address-cells = <2>; + #size-cells = <2>; + status = "disabled"; + + piodma { + iommus = <&dispext1_dart 4>; + }; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + dcpext1_audio: endpoint { + remote-endpoint = <&dpaudio2_dcp>; + }; + }; + }; + }; + + sio_dart: iommu@39b008000 { + compatible = "apple,t6020-dart", "apple,t8110-dart"; + reg = <0x3 0x9b008000 0x0 0x8000>; + interrupt-parent = <&aic>; + interrupts = ; + #iommu-cells = <1>; + power-domains = <&ps_sio_cpu>; + }; + aop_dart: iommu@2a6808000 { compatible = "apple,t6020-dart", "apple,t8110-dart"; reg = <0x2 0xa6808000 0x0 0x4000>; @@ -693,6 +854,32 @@ #mbox-cells = <0>; }; + sio_mbox: mailbox@39bc08000 { + compatible = "apple,t6020-asc-mailbox", "apple,asc-mailbox-v4"; + reg = <0x3 0x9bc08000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = , + , + , + ; + interrupt-names = "send-empty", "send-not-empty", + "recv-empty", "recv-not-empty"; + #mbox-cells = <0>; + power-domains = <&ps_sio_cpu>; + }; + + sio: sio@39bc00000 { + compatible = "apple,t6020-sio", "apple,sio"; + reg = <0x3 0x9bc00000 0x0 0x8000>; + dma-channels = <128>; + #dma-cells = <1>; + mboxes = <&sio_mbox>; + iommus = <&sio_dart 0>; + power-domains = <&ps_sio_cpu>; + resets = <&ps_sio_cpu>; + status = "disabled"; + }; + fpwm0: pwm@39b030000 { compatible = "apple,t6020-fpwm", "apple,s5l-fpwm"; reg = <0x3 0x9b030000 0x0 0x4000>; @@ -911,6 +1098,7 @@ ports { #address-cells = <1>; #size-cells = <0>; + port@0 { reg = <0>; dpaudio0_dcp: endpoint { @@ -920,6 +1108,50 @@ }; }; + dpaudio1: audio-controller@39b540000 { + compatible = "apple,t6020-dpaudio", "apple,dpaudio"; + reg = <0x3 0x9b540000 0x0 0x4000>; + dmas = <&sio 0x66>; + dma-names = "tx"; + power-domains = <&ps_dpa1>; + reset-domains = <&ps_dpa1>; + status = "disabled"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + dpaudio1_dcp: endpoint { + remote-endpoint = <&dcpext0_audio>; + }; + }; + }; + }; + + dpaudio2: audio-controller@39b580000 { + compatible = "apple,t6020-dpaudio", "apple,dpaudio"; + reg = <0x3 0x9b580000 0x0 0x4000>; + dmas = <&sio 0x68>; + dma-names = "tx"; + power-domains = <&ps_dpa2>; + reset-domains = <&ps_dpa2>; + status = "disabled"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + dpaudio2_dcp: endpoint { + remote-endpoint = <&dcpext1_audio>; + }; + }; + }; + }; + mca: mca@39b600000 { compatible = "apple,t6020-mca", "apple,t8103-mca", "apple,mca"; reg = <0x3 0x9b600000 0x0 0x10000>, diff --git a/arch/arm64/boot/dts/apple/t602x-dieX.dtsi b/arch/arm64/boot/dts/apple/t602x-dieX.dtsi index 2e44b0aab9cd16..28d86f6cb67a60 100644 --- a/arch/arm64/boot/dts/apple/t602x-dieX.dtsi +++ b/arch/arm64/boot/dts/apple/t602x-dieX.dtsi @@ -504,6 +504,7 @@ <&DIE_NODE(ps_atc0_cio_usb)>; thunderbolt-switch; + apple,pcie-tunnel = <&DIE_NODE(usb4_0_pcie_tunnel)>; pinctrl-0 = <&DIE_NODE(acio0_pins)>; pinctrl-names = "default"; @@ -568,6 +569,74 @@ }; }; + DIE_NODE(usb4_0_pcie_tunnel): usb4-pcie-tunnel-0 { + #address-cells = <2>; + #size-cells = <2>; + ranges; + + DIE_NODE(apciec0_dart): iommu@721008000 { + compatible = "apple,t8110-dart"; + reg = <0x7 0x21008000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = ; + #iommu-cells = <1>; + }; + + DIE_NODE(apciec0): pcie@730000000 { + compatible = "apple,t6000-pciec"; + device_type = "pci"; + + reg = <0x7 0x30000000 0x0 0x10000000>, + <0x7 0x20000000 0x0 0x4000>, + <0x7 0x21000000 0x0 0x8000>, + <0x7 0x20004000 0x0 0x4000>, + <0x7 0x20200000 0x0 0x4000>, + <0x7 0x21010000 0x0 0x4000>, + <0x7 0x2100c000 0x0 0x4000>; + reg-names = "config", "rc", "port0", "common", + "fabric", "debug", "intr2axi"; + + interrupt-parent = <&aic>; + interrupts = ; + + msi-controller; + msi-parent = <&DIE_NODE(apciec0)>; + msi-ranges = <&aic AIC_IRQ DIE_NO 1833 + IRQ_TYPE_EDGE_RISING 32>; + + iommu-map = <0x0 &DIE_NODE(apciec0_dart) 0x0 0x40>; + iommu-map-mask = <0x3f>; + + bus-range = <0x0 0x7f>; + #address-cells = <3>; + #size-cells = <2>; + ranges = <0x43000000 0x8 0x0 + 0x8 0x0 0x2 0x0>, + <0x02000000 0x0 0x00100000 + 0xa 0x00100000 0x0 0x3ff00000>, + <0x42000000 0x0 0x40000000 + 0xa 0x40000000 0x0 0x40000000>; + + DIE_NODE(apciec0_port): pci@0,0 { + device_type = "pci"; + reg = <0x0 0x0 0x0 0x0 0x0>; + external-facing; + + #address-cells = <3>; + #size-cells = <2>; + ranges; + + interrupt-controller; + #interrupt-cells = <1>; + interrupt-map-mask = <0 0 0 7>; + interrupt-map = <0 0 0 1 &DIE_NODE(apciec0_port) 0 0 0 0>, + <0 0 0 2 &DIE_NODE(apciec0_port) 0 0 0 1>, + <0 0 0 3 &DIE_NODE(apciec0_port) 0 0 0 2>, + <0 0 0 4 &DIE_NODE(apciec0_port) 0 0 0 3>; + }; + }; + }; + DIE_NODE(dwc3_0): usb@702280000 { compatible = "apple,t6020-dwc3", "apple,t8103-dwc3"; reg = <0x7 0x02280000 0x0 0xcd00>, <0x7 0x0228cd00 0x0 0x3200>; @@ -657,6 +726,7 @@ <&DIE_NODE(ps_atc1_cio_usb)>; thunderbolt-switch; + apple,pcie-tunnel = <&DIE_NODE(usb4_1_pcie_tunnel)>; pinctrl-0 = <&DIE_NODE(acio1_pins)>; pinctrl-names = "default"; @@ -721,6 +791,74 @@ }; }; + DIE_NODE(usb4_1_pcie_tunnel): usb4-pcie-tunnel-1 { + #address-cells = <2>; + #size-cells = <2>; + ranges; + + DIE_NODE(apciec1_dart): iommu@b21008000 { + compatible = "apple,t8110-dart"; + reg = <0xb 0x21008000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = ; + #iommu-cells = <1>; + }; + + DIE_NODE(apciec1): pcie@b30000000 { + compatible = "apple,t6000-pciec"; + device_type = "pci"; + + reg = <0xb 0x30000000 0x0 0x10000000>, + <0xb 0x20000000 0x0 0x4000>, + <0xb 0x21000000 0x0 0x8000>, + <0xb 0x20004000 0x0 0x4000>, + <0xb 0x20200000 0x0 0x4000>, + <0xb 0x21010000 0x0 0x4000>, + <0xb 0x2100c000 0x0 0x4000>; + reg-names = "config", "rc", "port0", "common", + "fabric", "debug", "intr2axi"; + + interrupt-parent = <&aic>; + interrupts = ; + + msi-controller; + msi-parent = <&DIE_NODE(apciec1)>; + msi-ranges = <&aic AIC_IRQ DIE_NO 1865 + IRQ_TYPE_EDGE_RISING 32>; + + iommu-map = <0x0 &DIE_NODE(apciec1_dart) 0x0 0x40>; + iommu-map-mask = <0x3f>; + + bus-range = <0x0 0x7f>; + #address-cells = <3>; + #size-cells = <2>; + ranges = <0x43000000 0x8 0x0 + 0xc 0x0 0x2 0x0>, + <0x02000000 0x0 0x00100000 + 0xe 0x00100000 0x0 0x3ff00000>, + <0x42000000 0x0 0x40000000 + 0xe 0x40000000 0x0 0x40000000>; + + DIE_NODE(apciec1_port): pci@0,0 { + device_type = "pci"; + reg = <0x0 0x0 0x0 0x0 0x0>; + external-facing; + + #address-cells = <3>; + #size-cells = <2>; + ranges; + + interrupt-controller; + #interrupt-cells = <1>; + interrupt-map-mask = <0 0 0 7>; + interrupt-map = <0 0 0 1 &DIE_NODE(apciec1_port) 0 0 0 0>, + <0 0 0 2 &DIE_NODE(apciec1_port) 0 0 0 1>, + <0 0 0 3 &DIE_NODE(apciec1_port) 0 0 0 2>, + <0 0 0 4 &DIE_NODE(apciec1_port) 0 0 0 3>; + }; + }; + }; + DIE_NODE(dwc3_1): usb@b02280000 { compatible = "apple,t6020-dwc3", "apple,t8103-dwc3"; reg = <0xb 0x02280000 0x0 0xcd00>, <0xb 0x0228cd00 0x0 0x3200>; @@ -810,6 +948,7 @@ <&DIE_NODE(ps_atc2_cio_usb)>; thunderbolt-switch; + apple,pcie-tunnel = <&DIE_NODE(usb4_2_pcie_tunnel)>; pinctrl-0 = <&DIE_NODE(acio2_pins)>; pinctrl-names = "default"; @@ -874,6 +1013,74 @@ }; }; + DIE_NODE(usb4_2_pcie_tunnel): usb4-pcie-tunnel-2 { + #address-cells = <2>; + #size-cells = <2>; + ranges; + + DIE_NODE(apciec2_dart): iommu@f21008000 { + compatible = "apple,t8110-dart"; + reg = <0xf 0x21008000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = ; + #iommu-cells = <1>; + }; + + DIE_NODE(apciec2): pcie@f30000000 { + compatible = "apple,t6000-pciec"; + device_type = "pci"; + + reg = <0xf 0x30000000 0x0 0x10000000>, + <0xf 0x20000000 0x0 0x4000>, + <0xf 0x21000000 0x0 0x8000>, + <0xf 0x20004000 0x0 0x4000>, + <0xf 0x20200000 0x0 0x4000>, + <0xf 0x21010000 0x0 0x4000>, + <0xf 0x2100c000 0x0 0x4000>; + reg-names = "config", "rc", "port0", "common", + "fabric", "debug", "intr2axi"; + + interrupt-parent = <&aic>; + interrupts = ; + + msi-controller; + msi-parent = <&DIE_NODE(apciec2)>; + msi-ranges = <&aic AIC_IRQ DIE_NO 1897 + IRQ_TYPE_EDGE_RISING 32>; + + iommu-map = <0x0 &DIE_NODE(apciec2_dart) 0x0 0x40>; + iommu-map-mask = <0x3f>; + + bus-range = <0x0 0x7f>; + #address-cells = <3>; + #size-cells = <2>; + ranges = <0x43000000 0x8 0x0 + 0x10 0x0 0x2 0x0>, + <0x02000000 0x0 0x00100000 + 0x12 0x00100000 0x0 0x3ff00000>, + <0x42000000 0x0 0x40000000 + 0x12 0x40000000 0x0 0x40000000>; + + DIE_NODE(apciec2_port): pci@0,0 { + device_type = "pci"; + reg = <0x0 0x0 0x0 0x0 0x0>; + external-facing; + + #address-cells = <3>; + #size-cells = <2>; + ranges; + + interrupt-controller; + #interrupt-cells = <1>; + interrupt-map-mask = <0 0 0 7>; + interrupt-map = <0 0 0 1 &DIE_NODE(apciec2_port) 0 0 0 0>, + <0 0 0 2 &DIE_NODE(apciec2_port) 0 0 0 1>, + <0 0 0 3 &DIE_NODE(apciec2_port) 0 0 0 2>, + <0 0 0 4 &DIE_NODE(apciec2_port) 0 0 0 3>; + }; + }; + }; + DIE_NODE(dwc3_2): usb@f02280000 { compatible = "apple,t6020-dwc3", "apple,t8103-dwc3"; reg = <0xf 0x02280000 0x0 0xcd00>, <0xf 0x0228cd00 0x0 0x3200>; From 83862086ca23b7d059b38ee8456a472c5f7928b8 Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Tue, 25 Aug 2026 16:52:38 -0400 Subject: [PATCH 15/51] arm64: dts: apple: keep USB4 disabled on M1/M2 Ultra Signed-off-by: DjDeveloperr --- arch/arm64/boot/dts/apple/t600x-dieX.dtsi | 8 ++++++++ arch/arm64/boot/dts/apple/t602x-dieX.dtsi | 8 +++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/arch/arm64/boot/dts/apple/t600x-dieX.dtsi b/arch/arm64/boot/dts/apple/t600x-dieX.dtsi index c5ae1fe877b4e3..68621b948f5b60 100644 --- a/arch/arm64/boot/dts/apple/t600x-dieX.dtsi +++ b/arch/arm64/boot/dts/apple/t600x-dieX.dtsi @@ -466,6 +466,7 @@ }; */ +#ifdef APPLE_T600X_NON_ULTRA DIE_NODE(usb4_0_mbox): mailbox@701100000 { compatible = "apple,t6000-m3-mailbox", "apple,m3-mailbox-v2"; @@ -557,6 +558,7 @@ apple,thunderbolt-drom = [00]; }; }; +#endif DIE_NODE(dwc3_0): usb@702280000 { compatible = "apple,t6000-dwc3", "apple,t8103-dwc3"; @@ -619,6 +621,7 @@ status = "disabled"; }; +#ifdef APPLE_T600X_NON_ULTRA DIE_NODE(usb4_1_mbox): mailbox@b01100000 { compatible = "apple,t6000-m3-mailbox", "apple,m3-mailbox-v2"; @@ -710,6 +713,7 @@ apple,thunderbolt-drom = [00]; }; }; +#endif DIE_NODE(dwc3_1): usb@b02280000 { compatible = "apple,t6000-dwc3", "apple,t8103-dwc3"; @@ -772,6 +776,7 @@ status = "disabled"; }; +#ifdef APPLE_T600X_NON_ULTRA DIE_NODE(usb4_2_mbox): mailbox@f01100000 { compatible = "apple,t6000-m3-mailbox", "apple,m3-mailbox-v2"; @@ -863,6 +868,7 @@ apple,thunderbolt-drom = [00]; }; }; +#endif DIE_NODE(dwc3_2): usb@f02280000 { compatible = "apple,t6000-dwc3", "apple,t8103-dwc3"; @@ -925,6 +931,7 @@ status = "disabled"; }; +#ifdef APPLE_T600X_NON_ULTRA DIE_NODE(usb4_3_mbox): mailbox@1301100000 { compatible = "apple,t6000-m3-mailbox", "apple,m3-mailbox-v2"; @@ -1016,6 +1023,7 @@ apple,thunderbolt-drom = [00]; }; }; +#endif DIE_NODE(dwc3_3): usb@1302280000 { compatible = "apple,t6000-dwc3", "apple,t8103-dwc3"; diff --git a/arch/arm64/boot/dts/apple/t602x-dieX.dtsi b/arch/arm64/boot/dts/apple/t602x-dieX.dtsi index 28d86f6cb67a60..899b21a6f54d88 100644 --- a/arch/arm64/boot/dts/apple/t602x-dieX.dtsi +++ b/arch/arm64/boot/dts/apple/t602x-dieX.dtsi @@ -476,6 +476,7 @@ reg = <0x4 0x4e80000 0 0x4000>; }; +#ifdef APPLE_T602X_NON_ULTRA DIE_NODE(usb4_0_mbox): mailbox@701108000 { compatible = "apple,t6020-asc-mailbox", "apple,asc-mailbox-v4"; @@ -636,6 +637,7 @@ }; }; }; +#endif DIE_NODE(dwc3_0): usb@702280000 { compatible = "apple,t6020-dwc3", "apple,t8103-dwc3"; @@ -698,6 +700,7 @@ status = "disabled"; }; +#ifdef APPLE_T602X_NON_ULTRA DIE_NODE(usb4_1_mbox): mailbox@b01108000 { compatible = "apple,t6020-asc-mailbox", "apple,asc-mailbox-v4"; @@ -858,6 +861,7 @@ }; }; }; +#endif DIE_NODE(dwc3_1): usb@b02280000 { compatible = "apple,t6020-dwc3", "apple,t8103-dwc3"; @@ -920,6 +924,7 @@ status = "disabled"; }; +#ifdef APPLE_T602X_NON_ULTRA DIE_NODE(usb4_2_mbox): mailbox@f01108000 { compatible = "apple,t6020-asc-mailbox", "apple,asc-mailbox-v4"; @@ -1080,6 +1085,7 @@ }; }; }; +#endif DIE_NODE(dwc3_2): usb@f02280000 { compatible = "apple,t6020-dwc3", "apple,t8103-dwc3"; @@ -1142,6 +1148,7 @@ status = "disabled"; }; +#ifdef APPLE_T602X_NON_ULTRA DIE_NODE(usb4_3_mbox): mailbox@1301108000 { compatible = "apple,t6020-asc-mailbox", "apple,asc-mailbox-v4"; @@ -1234,7 +1241,6 @@ }; }; -#ifdef APPLE_T602X_NON_ULTRA DIE_NODE(usb4_3_pcie_tunnel): usb4-pcie-tunnel-3 { #address-cells = <2>; #size-cells = <2>; From 82b9fa7601e0f189fabf75ee143f3fa97bd7e714 Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Tue, 25 Aug 2026 16:55:30 -0400 Subject: [PATCH 16/51] arm64: dts: apple: avoid duplicate M2 display nodes Signed-off-by: DjDeveloperr --- arch/arm64/boot/dts/apple/t602x-die0.dtsi | 232 ---------------------- 1 file changed, 232 deletions(-) diff --git a/arch/arm64/boot/dts/apple/t602x-die0.dtsi b/arch/arm64/boot/dts/apple/t602x-die0.dtsi index e6ac57f1da4889..852b33a88b8136 100644 --- a/arch/arm64/boot/dts/apple/t602x-die0.dtsi +++ b/arch/arm64/boot/dts/apple/t602x-die0.dtsi @@ -393,167 +393,6 @@ status = "disabled"; }; - dispext0_dart: iommu@289304000 { - compatible = "apple,t6020-dart", "apple,t8110-dart"; - reg = <0x2 0x89304000 0x0 0x4000>; - #iommu-cells = <1>; - interrupt-parent = <&aic>; - interrupts = ; - power-domains = <&ps_dispext0_cpu0>; - apple,dma-range = <0x100 0x0 0x10 0x0>; - status = "disabled"; - }; - - dcpext0_dart: iommu@28930c000 { - compatible = "apple,t6020-dart", "apple,t8110-dart"; - reg = <0x2 0x8930c000 0x0 0x4000>; - #iommu-cells = <1>; - interrupt-parent = <&aic>; - interrupts = ; - power-domains = <&ps_dispext0_cpu0>; - apple,dma-range = <0x100 0x0 0x10 0x0>; - status = "disabled"; - }; - - dcpext0_mbox: mailbox@289c08000 { - compatible = "apple,t6020-asc-mailbox", "apple,asc-mailbox-v4"; - reg = <0x2 0x89c08000 0x0 0x4000>; - interrupt-parent = <&aic>; - interrupts = , - , - , - ; - interrupt-names = "send-empty", "send-not-empty", - "recv-empty", "recv-not-empty"; - #mbox-cells = <0>; - power-domains = <&ps_dispext0_cpu0>; - resets = <&ps_dispext0_cpu0>; - status = "disabled"; - }; - - dcpext0: dcp@289c00000 { - compatible = "apple,t6020-dcpext", "apple,dcpext"; - mboxes = <&dcpext0_mbox>; - mbox-names = "mbox"; - iommus = <&dcpext0_dart 5>; - reg = <0x2 0x89c00000 0x0 0x4000>, - <0x2 0x88000000 0x0 0x4000000>, - <0x2 0x89320000 0x0 0x4000>, - <0x2 0x89344000 0x0 0x4000>, - <0x2 0x89800000 0x0 0x800000>; - reg-names = "coproc", "disp-0", "disp-1", "disp-2", "disp-3"; - apple,bw-scratch = <&pmgr_dcp 0 4 0x1210>; - apple,iomfb-surfaces = <1 1 0 1>; - power-domains = <&ps_dispext0_cpu0>; - resets = <&ps_dispext0_cpu0>; - clocks = <&clk_dispext0>; - apple,dcp-index = <1>; - #address-cells = <2>; - #size-cells = <2>; - status = "disabled"; - - piodma { - iommus = <&dispext0_dart 4>; - }; - - ports { - #address-cells = <1>; - #size-cells = <0>; - - port@0 { - reg = <0>; - dcpext0_audio: endpoint { - remote-endpoint = <&dpaudio1_dcp>; - }; - }; - }; - }; - - dispext1_dart: iommu@315304000 { - compatible = "apple,t6020-dart", "apple,t8110-dart"; - reg = <0x3 0x15304000 0x0 0x4000>; - #iommu-cells = <1>; - interrupt-parent = <&aic>; - interrupts = ; - power-domains = <&ps_dispext1_cpu0>; - apple,dma-range = <0x100 0x0 0x10 0x0>; - status = "disabled"; - }; - - dcpext1_dart: iommu@31530c000 { - compatible = "apple,t6020-dart", "apple,t8110-dart"; - reg = <0x3 0x1530c000 0x0 0x4000>; - #iommu-cells = <1>; - interrupt-parent = <&aic>; - interrupts = ; - power-domains = <&ps_dispext1_cpu0>; - apple,dma-range = <0x100 0x0 0x10 0x0>; - status = "disabled"; - }; - - dcpext1_mbox: mailbox@315c08000 { - compatible = "apple,t6020-asc-mailbox", "apple,asc-mailbox-v4"; - reg = <0x3 0x15c08000 0x0 0x4000>; - interrupt-parent = <&aic>; - interrupts = , - , - , - ; - interrupt-names = "send-empty", "send-not-empty", - "recv-empty", "recv-not-empty"; - #mbox-cells = <0>; - power-domains = <&ps_dispext1_cpu0>; - resets = <&ps_dispext1_cpu0>; - status = "disabled"; - }; - - dcpext1: dcp@315c00000 { - compatible = "apple,t6020-dcpext", "apple,dcpext"; - mboxes = <&dcpext1_mbox>; - mbox-names = "mbox"; - iommus = <&dcpext1_dart 5>; - reg = <0x3 0x15c00000 0x0 0x4000>, - <0x3 0x14000000 0x0 0x4000000>, - <0x3 0x15320000 0x0 0x4000>, - <0x3 0x15344000 0x0 0x4000>, - <0x3 0x15800000 0x0 0x800000>; - reg-names = "coproc", "disp-0", "disp-1", "disp-2", "disp-3"; - apple,bw-scratch = <&pmgr_dcp 0 4 0x1218>; - apple,iomfb-surfaces = <1 1 0 1>; - power-domains = <&ps_dispext1_cpu0>; - resets = <&ps_dispext1_cpu0>; - clocks = <&clk_dispext1>; - apple,dcp-index = <2>; - #address-cells = <2>; - #size-cells = <2>; - status = "disabled"; - - piodma { - iommus = <&dispext1_dart 4>; - }; - - ports { - #address-cells = <1>; - #size-cells = <0>; - - port@0 { - reg = <0>; - dcpext1_audio: endpoint { - remote-endpoint = <&dpaudio2_dcp>; - }; - }; - }; - }; - - sio_dart: iommu@39b008000 { - compatible = "apple,t6020-dart", "apple,t8110-dart"; - reg = <0x3 0x9b008000 0x0 0x8000>; - interrupt-parent = <&aic>; - interrupts = ; - #iommu-cells = <1>; - power-domains = <&ps_sio_cpu>; - }; - aop_dart: iommu@2a6808000 { compatible = "apple,t6020-dart", "apple,t8110-dart"; reg = <0x2 0xa6808000 0x0 0x4000>; @@ -854,32 +693,6 @@ #mbox-cells = <0>; }; - sio_mbox: mailbox@39bc08000 { - compatible = "apple,t6020-asc-mailbox", "apple,asc-mailbox-v4"; - reg = <0x3 0x9bc08000 0x0 0x4000>; - interrupt-parent = <&aic>; - interrupts = , - , - , - ; - interrupt-names = "send-empty", "send-not-empty", - "recv-empty", "recv-not-empty"; - #mbox-cells = <0>; - power-domains = <&ps_sio_cpu>; - }; - - sio: sio@39bc00000 { - compatible = "apple,t6020-sio", "apple,sio"; - reg = <0x3 0x9bc00000 0x0 0x8000>; - dma-channels = <128>; - #dma-cells = <1>; - mboxes = <&sio_mbox>; - iommus = <&sio_dart 0>; - power-domains = <&ps_sio_cpu>; - resets = <&ps_sio_cpu>; - status = "disabled"; - }; - fpwm0: pwm@39b030000 { compatible = "apple,t6020-fpwm", "apple,s5l-fpwm"; reg = <0x3 0x9b030000 0x0 0x4000>; @@ -1098,7 +911,6 @@ ports { #address-cells = <1>; #size-cells = <0>; - port@0 { reg = <0>; dpaudio0_dcp: endpoint { @@ -1108,50 +920,6 @@ }; }; - dpaudio1: audio-controller@39b540000 { - compatible = "apple,t6020-dpaudio", "apple,dpaudio"; - reg = <0x3 0x9b540000 0x0 0x4000>; - dmas = <&sio 0x66>; - dma-names = "tx"; - power-domains = <&ps_dpa1>; - reset-domains = <&ps_dpa1>; - status = "disabled"; - - ports { - #address-cells = <1>; - #size-cells = <0>; - - port@0 { - reg = <0>; - dpaudio1_dcp: endpoint { - remote-endpoint = <&dcpext0_audio>; - }; - }; - }; - }; - - dpaudio2: audio-controller@39b580000 { - compatible = "apple,t6020-dpaudio", "apple,dpaudio"; - reg = <0x3 0x9b580000 0x0 0x4000>; - dmas = <&sio 0x68>; - dma-names = "tx"; - power-domains = <&ps_dpa2>; - reset-domains = <&ps_dpa2>; - status = "disabled"; - - ports { - #address-cells = <1>; - #size-cells = <0>; - - port@0 { - reg = <0>; - dpaudio2_dcp: endpoint { - remote-endpoint = <&dcpext1_audio>; - }; - }; - }; - }; - mca: mca@39b600000 { compatible = "apple,t6020-mca", "apple,t8103-mca", "apple,mca"; reg = <0x3 0x9b600000 0x0 0x10000>, From fd0e20b09c165ea5a2aba528cb73071b67e33e83 Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Tue, 25 Aug 2026 16:56:42 -0400 Subject: [PATCH 17/51] arm64: dts: apple: gate Mac Studio USB4 routes Signed-off-by: DjDeveloperr --- arch/arm64/boot/dts/apple/t600x-j375.dtsi | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t600x-j375.dtsi b/arch/arm64/boot/dts/apple/t600x-j375.dtsi index c91f6992da3f4d..b427ee7e668e0f 100644 --- a/arch/arm64/boot/dts/apple/t600x-j375.dtsi +++ b/arch/arm64/boot/dts/apple/t600x-j375.dtsi @@ -32,6 +32,7 @@ ethernet0 = ðernet0; serial0 = &serial0; sio = &sio; +#if defined(APPLE_T600X_NON_ULTRA) || defined(APPLE_T602X_NON_ULTRA) usb4-0-acio = &usb4_0_acio; usb4-0-nhi = &usb4_0_nhi; usb4-1-acio = &usb4_1_acio; @@ -40,6 +41,7 @@ usb4-2-nhi = &usb4_2_nhi; usb4-3-acio = &usb4_3_acio; usb4-3-nhi = &usb4_3_nhi; +#endif wifi0 = &wifi0; }; @@ -733,6 +735,7 @@ status = "okay"; }; +#if defined(APPLE_T600X_NON_ULTRA) || defined(APPLE_T602X_NON_ULTRA) &typec0 { ports { port@2 { @@ -861,6 +864,7 @@ /* 6: DP1 */ }; }; +#endif #include "spi1-nvram.dtsi" From 843e3ca94451e5fea2b8be306cefd01f25721aeb Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Tue, 25 Aug 2026 17:46:43 -0400 Subject: [PATCH 18/51] arm64: dts: apple: add complete USB4 handoff aliases Signed-off-by: DjDeveloperr --- arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi | 7 +++++++ arch/arm64/boot/dts/apple/t600x-j375.dtsi | 8 ++++++++ arch/arm64/boot/dts/apple/t8103-jxxx.dtsi | 4 ++++ arch/arm64/boot/dts/apple/t8112-jxxx.dtsi | 4 ++++ 4 files changed, 23 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi index 9d76d9ad34e919..1c46a3ea241a0c 100644 --- a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi +++ b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi @@ -33,10 +33,17 @@ sio = &sio; usb4-0-acio = &usb4_0_acio; usb4-0-nhi = &usb4_0_nhi; + usb4-0-pcie-adapter = &usb4_0_acio; + usb4-0-pcie = &apciec0; usb4-1-acio = &usb4_1_acio; usb4-1-nhi = &usb4_1_nhi; + usb4-1-pcie-adapter = &usb4_1_acio; + usb4-1-pcie = &apciec1; usb4-2-acio = &usb4_2_acio; usb4-2-nhi = &usb4_2_nhi; + usb4-2-pcie-adapter = &usb4_2_acio; + usb4-2-pcie = &apciec2; + usb4-3-pcie = &apciec3; wifi0 = &wifi0; }; diff --git a/arch/arm64/boot/dts/apple/t600x-j375.dtsi b/arch/arm64/boot/dts/apple/t600x-j375.dtsi index b427ee7e668e0f..b9bd2c6a5527b3 100644 --- a/arch/arm64/boot/dts/apple/t600x-j375.dtsi +++ b/arch/arm64/boot/dts/apple/t600x-j375.dtsi @@ -35,12 +35,20 @@ #if defined(APPLE_T600X_NON_ULTRA) || defined(APPLE_T602X_NON_ULTRA) usb4-0-acio = &usb4_0_acio; usb4-0-nhi = &usb4_0_nhi; + usb4-0-pcie-adapter = &usb4_0_acio; + usb4-0-pcie = &apciec0; usb4-1-acio = &usb4_1_acio; usb4-1-nhi = &usb4_1_nhi; + usb4-1-pcie-adapter = &usb4_1_acio; + usb4-1-pcie = &apciec1; usb4-2-acio = &usb4_2_acio; usb4-2-nhi = &usb4_2_nhi; + usb4-2-pcie-adapter = &usb4_2_acio; + usb4-2-pcie = &apciec2; usb4-3-acio = &usb4_3_acio; usb4-3-nhi = &usb4_3_nhi; + usb4-3-pcie-adapter = &usb4_3_acio; + usb4-3-pcie = &apciec3; #endif wifi0 = &wifi0; }; diff --git a/arch/arm64/boot/dts/apple/t8103-jxxx.dtsi b/arch/arm64/boot/dts/apple/t8103-jxxx.dtsi index 2c3941e3d13daa..2a2e386e3e608c 100644 --- a/arch/arm64/boot/dts/apple/t8103-jxxx.dtsi +++ b/arch/arm64/boot/dts/apple/t8103-jxxx.dtsi @@ -23,8 +23,12 @@ atcphy1 = &atcphy1; usb4-0-acio = &usb4_0_acio; usb4-0-nhi = &usb4_0_nhi; + usb4-0-pcie-adapter = &usb4_0_acio; + usb4-0-pcie = &apciec0; usb4-1-acio = &usb4_1_acio; usb4-1-nhi = &usb4_1_nhi; + usb4-1-pcie-adapter = &usb4_1_acio; + usb4-1-pcie = &apciec1; }; chosen { diff --git a/arch/arm64/boot/dts/apple/t8112-jxxx.dtsi b/arch/arm64/boot/dts/apple/t8112-jxxx.dtsi index 8edc9228939c91..208503f996c2fd 100644 --- a/arch/arm64/boot/dts/apple/t8112-jxxx.dtsi +++ b/arch/arm64/boot/dts/apple/t8112-jxxx.dtsi @@ -19,8 +19,12 @@ disp0_piodma = &disp0_piodma; usb4-0-acio = &usb4_0_acio; usb4-0-nhi = &usb4_0_nhi; + usb4-0-pcie-adapter = &usb4_0_acio; + usb4-0-pcie = &apciec0; usb4-1-acio = &usb4_1_acio; usb4-1-nhi = &usb4_1_nhi; + usb4-1-pcie-adapter = &usb4_1_acio; + usb4-1-pcie = &apciec1; serial0 = &serial0; serial2 = &serial2; sio = &sio; From 69ad698827bfb4cba94c81ed6fd31f60d0525f8e Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Fri, 28 Aug 2026 00:03:23 -0400 Subject: [PATCH 19/51] arm64: dts: apple: wire PCIe-C on non-Ultra M1 and M2 SoCs Signed-off-by: DjDeveloperr --- arch/arm64/boot/dts/apple/t600x-dieX.dtsi | 9 ++-- arch/arm64/boot/dts/apple/t600x-pciec.dtsi | 28 +++++++----- arch/arm64/boot/dts/apple/t600x-pmgr.dtsi | 12 +++++ arch/arm64/boot/dts/apple/t602x-dieX.dtsi | 53 ++++++++++++---------- arch/arm64/boot/dts/apple/t602x-pmgr.dtsi | 12 +++++ arch/arm64/boot/dts/apple/t8103-pmgr.dtsi | 2 + arch/arm64/boot/dts/apple/t8103.dtsi | 16 ++++--- arch/arm64/boot/dts/apple/t8112-pmgr.dtsi | 2 + arch/arm64/boot/dts/apple/t8112.dtsi | 16 ++++--- 9 files changed, 100 insertions(+), 50 deletions(-) diff --git a/arch/arm64/boot/dts/apple/t600x-dieX.dtsi b/arch/arm64/boot/dts/apple/t600x-dieX.dtsi index 68621b948f5b60..f1c59ea026063d 100644 --- a/arch/arm64/boot/dts/apple/t600x-dieX.dtsi +++ b/arch/arm64/boot/dts/apple/t600x-dieX.dtsi @@ -492,7 +492,8 @@ power-domains = <&DIE_NODE(ps_atc0_cio)>, <&DIE_NODE(ps_atc0_cio_pcie)>, - <&DIE_NODE(ps_atc0_cio_usb)>; + <&DIE_NODE(ps_atc0_cio_usb)>, + <&DIE_NODE(ps_atc0_pcie)>; thunderbolt-switch; @@ -647,7 +648,8 @@ power-domains = <&DIE_NODE(ps_atc1_cio)>, <&DIE_NODE(ps_atc1_cio_pcie)>, - <&DIE_NODE(ps_atc1_cio_usb)>; + <&DIE_NODE(ps_atc1_cio_usb)>, + <&DIE_NODE(ps_atc1_pcie)>; thunderbolt-switch; @@ -802,7 +804,8 @@ power-domains = <&DIE_NODE(ps_atc2_cio)>, <&DIE_NODE(ps_atc2_cio_pcie)>, - <&DIE_NODE(ps_atc2_cio_usb)>; + <&DIE_NODE(ps_atc2_cio_usb)>, + <&DIE_NODE(ps_atc2_pcie)>; thunderbolt-switch; diff --git a/arch/arm64/boot/dts/apple/t600x-pciec.dtsi b/arch/arm64/boot/dts/apple/t600x-pciec.dtsi index f4cad7634f5880..86cc1ac550e6ba 100644 --- a/arch/arm64/boot/dts/apple/t600x-pciec.dtsi +++ b/arch/arm64/boot/dts/apple/t600x-pciec.dtsi @@ -11,7 +11,7 @@ ranges; apciec0_dart: iommu@721008000 { - compatible = "apple,t6000-dart"; + compatible = "apple,t8110-dart"; reg = <0x7 0x21008000 0x0 0x4000>; interrupt-parent = <&aic>; interrupts = ; @@ -29,11 +29,12 @@ <0x7 0x20200000 0x0 0x4000>, <0x7 0x21010000 0x0 0x4000>, <0x7 0x2100c000 0x0 0x4000>; - reg-names = "config", "rc", "port0", "common", - "fabric", "debug", "intr2axi"; + reg-names = "config", "rc", "port0", "fabric", + "debug", "ltssm", "intr2axi"; interrupt-parent = <&aic>; interrupts = ; + power-domains = <&ps_atc0_pcie>; msi-controller; msi-parent = <&apciec0>; @@ -78,7 +79,7 @@ ranges; apciec1_dart: iommu@b21008000 { - compatible = "apple,t6000-dart"; + compatible = "apple,t8110-dart"; reg = <0xb 0x21008000 0x0 0x4000>; interrupt-parent = <&aic>; interrupts = ; @@ -96,11 +97,12 @@ <0xb 0x20200000 0x0 0x4000>, <0xb 0x21010000 0x0 0x4000>, <0xb 0x2100c000 0x0 0x4000>; - reg-names = "config", "rc", "port0", "common", - "fabric", "debug", "intr2axi"; + reg-names = "config", "rc", "port0", "fabric", + "debug", "ltssm", "intr2axi"; interrupt-parent = <&aic>; interrupts = ; + power-domains = <&ps_atc1_pcie>; msi-controller; msi-parent = <&apciec1>; @@ -145,7 +147,7 @@ ranges; apciec2_dart: iommu@f21008000 { - compatible = "apple,t6000-dart"; + compatible = "apple,t8110-dart"; reg = <0xf 0x21008000 0x0 0x4000>; interrupt-parent = <&aic>; interrupts = ; @@ -163,11 +165,12 @@ <0xf 0x20200000 0x0 0x4000>, <0xf 0x21010000 0x0 0x4000>, <0xf 0x2100c000 0x0 0x4000>; - reg-names = "config", "rc", "port0", "common", - "fabric", "debug", "intr2axi"; + reg-names = "config", "rc", "port0", "fabric", + "debug", "ltssm", "intr2axi"; interrupt-parent = <&aic>; interrupts = ; + power-domains = <&ps_atc2_pcie>; msi-controller; msi-parent = <&apciec2>; @@ -212,7 +215,7 @@ ranges; apciec3_dart: iommu@1321008000 { - compatible = "apple,t6000-dart"; + compatible = "apple,t8110-dart"; reg = <0x13 0x21008000 0x0 0x4000>; interrupt-parent = <&aic>; interrupts = ; @@ -230,11 +233,12 @@ <0x13 0x20200000 0x0 0x4000>, <0x13 0x21010000 0x0 0x4000>, <0x13 0x2100c000 0x0 0x4000>; - reg-names = "config", "rc", "port0", "common", - "fabric", "debug", "intr2axi"; + reg-names = "config", "rc", "port0", "fabric", + "debug", "ltssm", "intr2axi"; interrupt-parent = <&aic>; interrupts = ; + power-domains = <&ps_atc3_pcie>; msi-controller; msi-parent = <&apciec3>; diff --git a/arch/arm64/boot/dts/apple/t600x-pmgr.dtsi b/arch/arm64/boot/dts/apple/t600x-pmgr.dtsi index 833a59ecf37922..8b2c2cacce8c1e 100644 --- a/arch/arm64/boot/dts/apple/t600x-pmgr.dtsi +++ b/arch/arm64/boot/dts/apple/t600x-pmgr.dtsi @@ -509,6 +509,9 @@ #power-domain-cells = <0>; #reset-cells = <0>; label = DIE_LABEL(atc0_pcie); +#ifdef APPLE_T600X_NON_ULTRA + apple,always-on; +#endif power-domains = <&DIE_NODE(ps_atc0_common)>; }; @@ -527,6 +530,9 @@ #power-domain-cells = <0>; #reset-cells = <0>; label = DIE_LABEL(atc1_pcie); +#ifdef APPLE_T600X_NON_ULTRA + apple,always-on; +#endif power-domains = <&DIE_NODE(ps_atc1_common)>; }; @@ -545,6 +551,9 @@ #power-domain-cells = <0>; #reset-cells = <0>; label = DIE_LABEL(atc2_pcie); +#ifdef APPLE_T600X_NON_ULTRA + apple,always-on; +#endif power-domains = <&DIE_NODE(ps_atc2_common)>; }; @@ -563,6 +572,9 @@ #power-domain-cells = <0>; #reset-cells = <0>; label = DIE_LABEL(atc3_pcie); +#ifdef APPLE_T600X_NON_ULTRA + apple,always-on; +#endif power-domains = <&DIE_NODE(ps_atc3_common)>; }; diff --git a/arch/arm64/boot/dts/apple/t602x-dieX.dtsi b/arch/arm64/boot/dts/apple/t602x-dieX.dtsi index 899b21a6f54d88..455052a9bd35bb 100644 --- a/arch/arm64/boot/dts/apple/t602x-dieX.dtsi +++ b/arch/arm64/boot/dts/apple/t602x-dieX.dtsi @@ -502,7 +502,8 @@ power-domains = <&DIE_NODE(ps_atc0_cio)>, <&DIE_NODE(ps_atc0_cio_pcie)>, - <&DIE_NODE(ps_atc0_cio_usb)>; + <&DIE_NODE(ps_atc0_cio_usb)>, + <&DIE_NODE(ps_atc0_pcie)>; thunderbolt-switch; apple,pcie-tunnel = <&DIE_NODE(usb4_0_pcie_tunnel)>; @@ -581,6 +582,8 @@ interrupt-parent = <&aic>; interrupts = ; #iommu-cells = <1>; + apple,dma-range = <0x0 0x80000000 0x0 0x80000000>; + apple,dma-offset = <0x100 0x0>; }; DIE_NODE(apciec0): pcie@730000000 { @@ -591,14 +594,13 @@ <0x7 0x20000000 0x0 0x4000>, <0x7 0x21000000 0x0 0x8000>, <0x7 0x20004000 0x0 0x4000>, - <0x7 0x20200000 0x0 0x4000>, - <0x7 0x21010000 0x0 0x4000>, - <0x7 0x2100c000 0x0 0x4000>; - reg-names = "config", "rc", "port0", "common", - "fabric", "debug", "intr2axi"; + <0x7 0x20200000 0x0 0x4000>; + reg-names = "config", "rc", "port0", "fabric", + "debug"; interrupt-parent = <&aic>; interrupts = ; + power-domains = <&DIE_NODE(ps_atc0_pcie)>; msi-controller; msi-parent = <&DIE_NODE(apciec0)>; @@ -726,7 +728,8 @@ power-domains = <&DIE_NODE(ps_atc1_cio)>, <&DIE_NODE(ps_atc1_cio_pcie)>, - <&DIE_NODE(ps_atc1_cio_usb)>; + <&DIE_NODE(ps_atc1_cio_usb)>, + <&DIE_NODE(ps_atc1_pcie)>; thunderbolt-switch; apple,pcie-tunnel = <&DIE_NODE(usb4_1_pcie_tunnel)>; @@ -805,6 +808,8 @@ interrupt-parent = <&aic>; interrupts = ; #iommu-cells = <1>; + apple,dma-range = <0x0 0x80000000 0x0 0x80000000>; + apple,dma-offset = <0x100 0x0>; }; DIE_NODE(apciec1): pcie@b30000000 { @@ -815,14 +820,13 @@ <0xb 0x20000000 0x0 0x4000>, <0xb 0x21000000 0x0 0x8000>, <0xb 0x20004000 0x0 0x4000>, - <0xb 0x20200000 0x0 0x4000>, - <0xb 0x21010000 0x0 0x4000>, - <0xb 0x2100c000 0x0 0x4000>; - reg-names = "config", "rc", "port0", "common", - "fabric", "debug", "intr2axi"; + <0xb 0x20200000 0x0 0x4000>; + reg-names = "config", "rc", "port0", "fabric", + "debug"; interrupt-parent = <&aic>; interrupts = ; + power-domains = <&DIE_NODE(ps_atc1_pcie)>; msi-controller; msi-parent = <&DIE_NODE(apciec1)>; @@ -950,7 +954,8 @@ power-domains = <&DIE_NODE(ps_atc2_cio)>, <&DIE_NODE(ps_atc2_cio_pcie)>, - <&DIE_NODE(ps_atc2_cio_usb)>; + <&DIE_NODE(ps_atc2_cio_usb)>, + <&DIE_NODE(ps_atc2_pcie)>; thunderbolt-switch; apple,pcie-tunnel = <&DIE_NODE(usb4_2_pcie_tunnel)>; @@ -1029,6 +1034,8 @@ interrupt-parent = <&aic>; interrupts = ; #iommu-cells = <1>; + apple,dma-range = <0x0 0x80000000 0x0 0x80000000>; + apple,dma-offset = <0x100 0x0>; }; DIE_NODE(apciec2): pcie@f30000000 { @@ -1039,14 +1046,13 @@ <0xf 0x20000000 0x0 0x4000>, <0xf 0x21000000 0x0 0x8000>, <0xf 0x20004000 0x0 0x4000>, - <0xf 0x20200000 0x0 0x4000>, - <0xf 0x21010000 0x0 0x4000>, - <0xf 0x2100c000 0x0 0x4000>; - reg-names = "config", "rc", "port0", "common", - "fabric", "debug", "intr2axi"; + <0xf 0x20200000 0x0 0x4000>; + reg-names = "config", "rc", "port0", "fabric", + "debug"; interrupt-parent = <&aic>; interrupts = ; + power-domains = <&DIE_NODE(ps_atc2_pcie)>; msi-controller; msi-parent = <&DIE_NODE(apciec2)>; @@ -1252,6 +1258,8 @@ interrupt-parent = <&aic>; interrupts = ; #iommu-cells = <1>; + apple,dma-range = <0x0 0x80000000 0x0 0x80000000>; + apple,dma-offset = <0x100 0x0>; }; DIE_NODE(apciec3): pcie@1330000000 { @@ -1262,14 +1270,13 @@ <0x13 0x20000000 0x0 0x4000>, <0x13 0x21000000 0x0 0x8000>, <0x13 0x20004000 0x0 0x4000>, - <0x13 0x20200000 0x0 0x4000>, - <0x13 0x21010000 0x0 0x4000>, - <0x13 0x2100c000 0x0 0x4000>; - reg-names = "config", "rc", "port0", "common", - "fabric", "debug", "intr2axi"; + <0x13 0x20200000 0x0 0x4000>; + reg-names = "config", "rc", "port0", "fabric", + "debug"; interrupt-parent = <&aic>; interrupts = ; + power-domains = <&DIE_NODE(ps_atc3_pcie)>; msi-controller; msi-parent = <&DIE_NODE(apciec3)>; diff --git a/arch/arm64/boot/dts/apple/t602x-pmgr.dtsi b/arch/arm64/boot/dts/apple/t602x-pmgr.dtsi index 4ead781fea6893..fffd0ad79b9bbc 100644 --- a/arch/arm64/boot/dts/apple/t602x-pmgr.dtsi +++ b/arch/arm64/boot/dts/apple/t602x-pmgr.dtsi @@ -362,6 +362,9 @@ #power-domain-cells = <0>; #reset-cells = <0>; label = DIE_LABEL(atc0_pcie); +#ifdef APPLE_T602X_NON_ULTRA + apple,always-on; +#endif power-domains = <&DIE_NODE(ps_atc0_common)>; }; @@ -380,6 +383,9 @@ #power-domain-cells = <0>; #reset-cells = <0>; label = DIE_LABEL(atc1_pcie); +#ifdef APPLE_T602X_NON_ULTRA + apple,always-on; +#endif power-domains = <&DIE_NODE(ps_atc1_common)>; }; @@ -398,6 +404,9 @@ #power-domain-cells = <0>; #reset-cells = <0>; label = DIE_LABEL(atc2_pcie); +#ifdef APPLE_T602X_NON_ULTRA + apple,always-on; +#endif power-domains = <&DIE_NODE(ps_atc2_common)>; }; @@ -416,6 +425,9 @@ #power-domain-cells = <0>; #reset-cells = <0>; label = DIE_LABEL(atc3_pcie); +#ifdef APPLE_T602X_NON_ULTRA + apple,always-on; +#endif power-domains = <&DIE_NODE(ps_atc3_common)>; }; diff --git a/arch/arm64/boot/dts/apple/t8103-pmgr.dtsi b/arch/arm64/boot/dts/apple/t8103-pmgr.dtsi index 5d3846d44e3578..4a399bc8f0276b 100644 --- a/arch/arm64/boot/dts/apple/t8103-pmgr.dtsi +++ b/arch/arm64/boot/dts/apple/t8103-pmgr.dtsi @@ -864,6 +864,7 @@ #power-domain-cells = <0>; #reset-cells = <0>; label = "atc0_pcie"; + apple,always-on; power-domains = <&ps_atc0_common>; }; @@ -908,6 +909,7 @@ #power-domain-cells = <0>; #reset-cells = <0>; label = "atc1_pcie"; + apple,always-on; power-domains = <&ps_atc1_common>; }; diff --git a/arch/arm64/boot/dts/apple/t8103.dtsi b/arch/arm64/boot/dts/apple/t8103.dtsi index 53dc2cc87cc8bc..4682da893992ce 100644 --- a/arch/arm64/boot/dts/apple/t8103.dtsi +++ b/arch/arm64/boot/dts/apple/t8103.dtsi @@ -1584,7 +1584,8 @@ power-domains = <&ps_atc0_cio>, <&ps_atc0_cio_pcie>, - <&ps_atc0_cio_usb>; + <&ps_atc0_cio_usb>, + <&ps_atc0_pcie>; thunderbolt-switch; apple,pcie-tunnel = <&usb4_0_pcie_tunnel>; @@ -1674,11 +1675,12 @@ <0x3 0xa0200000 0x0 0x4000>, <0x3 0xa1010000 0x0 0x4000>, <0x3 0xa100c000 0x0 0x4000>; - reg-names = "config", "rc", "port0", "common", - "fabric", "debug", "intr2axi"; + reg-names = "config", "rc", "port0", "fabric", + "debug", "ltssm", "intr2axi"; interrupt-parent = <&aic>; interrupts = ; + power-domains = <&ps_atc0_pcie>; msi-controller; msi-parent = <&apciec0>; @@ -1801,7 +1803,8 @@ power-domains = <&ps_atc1_cio>, <&ps_atc1_cio_pcie>, - <&ps_atc1_cio_usb>; + <&ps_atc1_cio_usb>, + <&ps_atc1_pcie>; thunderbolt-switch; apple,pcie-tunnel = <&usb4_1_pcie_tunnel>; @@ -1891,11 +1894,12 @@ <0x5 0x20200000 0x0 0x4000>, <0x5 0x21010000 0x0 0x4000>, <0x5 0x2100c000 0x0 0x4000>; - reg-names = "config", "rc", "port0", "common", - "fabric", "debug", "intr2axi"; + reg-names = "config", "rc", "port0", "fabric", + "debug", "ltssm", "intr2axi"; interrupt-parent = <&aic>; interrupts = ; + power-domains = <&ps_atc1_pcie>; msi-controller; msi-parent = <&apciec1>; diff --git a/arch/arm64/boot/dts/apple/t8112-pmgr.dtsi b/arch/arm64/boot/dts/apple/t8112-pmgr.dtsi index ab8ec9bd4e4401..128a560dbac73e 100644 --- a/arch/arm64/boot/dts/apple/t8112-pmgr.dtsi +++ b/arch/arm64/boot/dts/apple/t8112-pmgr.dtsi @@ -866,6 +866,7 @@ #power-domain-cells = <0>; #reset-cells = <0>; label = "atc0_pcie"; + apple,always-on; power-domains = <&ps_atc0_common>; }; @@ -911,6 +912,7 @@ #power-domain-cells = <0>; #reset-cells = <0>; label = "atc1_pcie"; + apple,always-on; power-domains = <&ps_atc1_common>; }; diff --git a/arch/arm64/boot/dts/apple/t8112.dtsi b/arch/arm64/boot/dts/apple/t8112.dtsi index 376aabe175477f..76a9fa23709d09 100644 --- a/arch/arm64/boot/dts/apple/t8112.dtsi +++ b/arch/arm64/boot/dts/apple/t8112.dtsi @@ -1849,7 +1849,8 @@ power-domains = <&ps_atc0_cio>, <&ps_atc0_cio_pcie>, - <&ps_atc0_cio_usb>; + <&ps_atc0_cio_usb>, + <&ps_atc0_pcie>; thunderbolt-switch; apple,pcie-tunnel = <&usb4_0_pcie_tunnel>; @@ -1940,11 +1941,12 @@ <0x3 0xa0200000 0x0 0x4000>, <0x3 0xa1010000 0x0 0x4000>, <0x3 0xa100c000 0x0 0x4000>; - reg-names = "config", "rc", "port0", "common", - "fabric", "debug", "intr2axi"; + reg-names = "config", "rc", "port0", "fabric", + "debug", "ltssm", "intr2axi"; interrupt-parent = <&aic>; interrupts = ; + power-domains = <&ps_atc0_pcie>; msi-controller; msi-parent = <&apciec0>; @@ -2067,7 +2069,8 @@ power-domains = <&ps_atc1_cio>, <&ps_atc1_cio_pcie>, - <&ps_atc1_cio_usb>; + <&ps_atc1_cio_usb>, + <&ps_atc1_pcie>; thunderbolt-switch; apple,pcie-tunnel = <&usb4_1_pcie_tunnel>; @@ -2158,11 +2161,12 @@ <0x5 0x20200000 0x0 0x4000>, <0x5 0x21010000 0x0 0x4000>, <0x5 0x2100c000 0x0 0x4000>; - reg-names = "config", "rc", "port0", "common", - "fabric", "debug", "intr2axi"; + reg-names = "config", "rc", "port0", "fabric", + "debug", "ltssm", "intr2axi"; interrupt-parent = <&aic>; interrupts = ; + power-domains = <&ps_atc1_pcie>; msi-controller; msi-parent = <&apciec1>; From 97bf6478981b8762368529038954f08465e1bcaa Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Tue, 25 Aug 2026 14:16:33 -0400 Subject: [PATCH 20/51] PCI: apple: add cable-powered PCIe-C root complexes Signed-off-by: DjDeveloperr --- drivers/pci/controller/pcie-apple.c | 251 ++++++++++++++++++++++++---- drivers/thunderbolt/apple.c | 31 ++++ 2 files changed, 246 insertions(+), 36 deletions(-) diff --git a/drivers/pci/controller/pcie-apple.c b/drivers/pci/controller/pcie-apple.c index d962724750840d..cee9e2d4e5c5c3 100644 --- a/drivers/pci/controller/pcie-apple.c +++ b/drivers/pci/controller/pcie-apple.c @@ -159,6 +159,7 @@ struct hw_info { u32 port_rid2sid; u32 port_msimap; u32 max_rid2sid; + bool tunneled; }; static const struct hw_info t8103_hw = { @@ -184,6 +185,14 @@ static const struct hw_info t602x_hw = { .max_rid2sid = 512, }; +static const struct hw_info t6000_pciec_hw = { + .port_msiaddr = PORT_MSIADDR, + .port_perst = PORT_PERST, + .port_rid2sid = PORT_RID2SID, + .max_rid2sid = 64, + .tunneled = true, +}; + struct apple_pcie { struct mutex lock; struct device *dev; @@ -193,6 +202,7 @@ struct apple_pcie { struct list_head ports; struct completion event; struct irq_fwspec fwspec; + struct irq_domain *msi_domain; u32 nvecs; }; @@ -205,8 +215,11 @@ struct apple_pcie_port { struct irq_domain *domain; struct list_head entry; unsigned long *sid_map; + unsigned int irq; + unsigned int link_irqs[2]; int sid_map_sz; int idx; + bool started; }; static void rmw_set(u32 set, void __iomem *addr) @@ -257,8 +270,13 @@ static int apple_msi_domain_alloc(struct irq_domain *domain, unsigned int virq, fwspec.param[fwspec.param_count - 2] += hwirq; ret = irq_domain_alloc_irqs_parent(domain, virq, nr_irqs, &fwspec); - if (ret) + if (ret) { + mutex_lock(&pcie->lock); + bitmap_release_region(pcie->bitmap, hwirq, + order_base_2(nr_irqs)); + mutex_unlock(&pcie->lock); return ret; + } for (i = 0; i < nr_irqs; i++) { irq_domain_set_hwirq_and_chip(domain, virq + i, hwirq + i, @@ -404,27 +422,29 @@ static int apple_pcie_port_setup_irq(struct apple_pcie_port *port) { struct fwnode_handle *fwnode = &port->np->fwnode; struct apple_pcie *pcie = port->pcie; - unsigned int irq; u32 val = 0; /* FIXME: consider moving each interrupt under each port */ - irq = irq_of_parse_and_map(to_of_node(dev_fwnode(port->pcie->dev)), - port->idx); - if (!irq) + port->irq = irq_of_parse_and_map(to_of_node(dev_fwnode(port->pcie->dev)), + port->idx); + if (!port->irq) return -ENXIO; port->domain = irq_domain_create_linear(fwnode, 32, &apple_port_irq_domain_ops, port); - if (!port->domain) + if (!port->domain) { + irq_dispose_mapping(port->irq); + port->irq = 0; return -ENOMEM; + } /* Disable all interrupts */ writel_relaxed(~0, port->base + PORT_INTMSK); writel_relaxed(~0, port->base + PORT_INTSTAT); writel_relaxed(~0, port->base + PORT_LINKCMDSTS); - irq_set_chained_handler_and_data(irq, apple_port_irq_handler, port); + irq_set_chained_handler_and_data(port->irq, apple_port_irq_handler, port); /* Configure MSI base address */ BUILD_BUG_ON(upper_32_bits(DOORBELL_ADDR)); @@ -489,22 +509,109 @@ static int apple_pcie_port_register_irqs(struct apple_pcie_port *port) [0] = port_irqs[i].hwirq, }, }; - unsigned int irq; - int ret; + int irq, ret; irq = irq_domain_alloc_irqs(port->domain, 1, NUMA_NO_NODE, &fwspec); - if (WARN_ON(!irq)) - continue; + if (irq <= 0) + return irq ?: -ENOMEM; ret = request_irq(irq, apple_pcie_port_irq, 0, port_irqs[i].name, port); - WARN_ON(ret); + if (ret) { + irq_domain_free_irqs(irq, 1); + return ret; + } + + port->link_irqs[i] = irq; } return 0; } +static void apple_pcie_port_unregister_irqs(struct apple_pcie_port *port) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(port->link_irqs); i++) { + if (!port->link_irqs[i]) + continue; + + free_irq(port->link_irqs[i], port); + irq_domain_free_irqs(port->link_irqs[i], 1); + port->link_irqs[i] = 0; + } +} + +static void apple_pcie_tunnel_stop(struct apple_pcie_port *port) +{ + struct apple_pcie *pcie = port->pcie; + u32 stat; + int ret; + + /* + * PCIe-C has no external PERST# line. The USB4 router asserts the + * tunneled reset request, and the root port must acknowledge it before + * ACIO loses power. A cable disconnect requires that same ordering. + */ + rmw_set(PORT_TUNCTRL_PERST_ON, port->base + PORT_TUNCTRL); + ret = readl_relaxed_poll_timeout(port->base + PORT_TUNSTAT, stat, + stat & PORT_TUNSTAT_PERST_ON, + 1000, 100000); + if (ret) + dev_warn(pcie->dev, "port %pOF tunnel reset assertion timed out\n", + port->np); + + rmw_clear(PORT_PERST_OFF, port->base + pcie->hw->port_perst); + rmw_clear(PORT_LTSSMCTL_START, port->base + PORT_LTSSMCTL); + rmw_clear(PORT_APPCLK_EN, port->base + PORT_APPCLK); + + ret = readl_relaxed_poll_timeout(port->base + PORT_STATUS, stat, + !(stat & PORT_STATUS_READY), 10, 100000); + if (ret) + dev_warn(pcie->dev, "port %pOF disable timed out\n", port->np); + + rmw_set(PORT_TUNCTRL_PERST_ACK_REQ, port->base + PORT_TUNCTRL); + ret = readl_relaxed_poll_timeout(port->base + PORT_TUNSTAT, stat, + stat & PORT_TUNSTAT_PERST_ACK_PEND, + 1000, 1000000); + if (ret) + dev_warn(pcie->dev, "port %pOF tunnel reset acknowledgment timed out\n", + port->np); + rmw_clear(PORT_TUNCTRL_PERST_ACK_REQ, port->base + PORT_TUNCTRL); + port->started = false; +} + +static void apple_pcie_port_teardown(struct apple_pcie_port *port) +{ + if (port->base) + writel_relaxed(~0, port->base + PORT_INTMSK); + + if (port->pcie->hw->tunneled && port->started) + apple_pcie_tunnel_stop(port); + + apple_pcie_port_unregister_irqs(port); + + if (port->irq) { + irq_set_chained_handler_and_data(port->irq, NULL, NULL); + irq_dispose_mapping(port->irq); + port->irq = 0; + } + + if (port->domain) { + irq_domain_remove(port->domain); + port->domain = NULL; + } + + if (!list_empty(&port->entry)) + list_del_init(&port->entry); + + if (port->np) { + of_node_put(port->np); + port->np = NULL; + } +} + static int apple_pcie_setup_refclk(struct apple_pcie *pcie, struct apple_pcie_port *port) { @@ -650,7 +757,7 @@ static int apple_pcie_setup_port(struct apple_pcie *pcie, struct apple_pcie_port *port; struct resource *res; char name[16]; - u32 link_stat, idx; + u32 link_stat, stat, idx; int ret, i; port = devm_kzalloc(pcie->dev, sizeof(*port), GFP_KERNEL); @@ -668,44 +775,85 @@ static int apple_pcie_setup_port(struct apple_pcie *pcie, /* Use the first reg entry to work out the port index */ port->idx = idx >> 11; port->pcie = pcie; - port->np = np; + port->np = of_node_get(np); raw_spin_lock_init(&port->lock); + INIT_LIST_HEAD(&port->entry); snprintf(name, sizeof(name), "port%d", port->idx); res = platform_get_resource_byname(platform, IORESOURCE_MEM, name); if (!res) res = platform_get_resource(platform, IORESOURCE_MEM, port->idx + 2); + if (!res) { + ret = -ENODEV; + goto err_teardown; + } port->base = devm_ioremap_resource(&platform->dev, res); - if (IS_ERR(port->base)) - return PTR_ERR(port->base); + if (IS_ERR(port->base)) { + ret = PTR_ERR(port->base); + port->base = NULL; + goto err_teardown; + } - snprintf(name, sizeof(name), "phy%d", port->idx); - res = platform_get_resource_byname(platform, IORESOURCE_MEM, name); - if (res) - port->phy = devm_ioremap_resource(&platform->dev, res); - else - port->phy = pcie->base + CORE_PHY_DEFAULT_BASE(port->idx); + if (!pcie->hw->tunneled) { + snprintf(name, sizeof(name), "phy%d", port->idx); + res = platform_get_resource_byname(platform, IORESOURCE_MEM, name); + if (res) + port->phy = devm_ioremap_resource(&platform->dev, res); + else + port->phy = pcie->base + CORE_PHY_DEFAULT_BASE(port->idx); + if (IS_ERR(port->phy)) { + ret = PTR_ERR(port->phy); + port->phy = NULL; + goto err_teardown; + } + } + if (pcie->hw->tunneled) { + /* PCIe-C is reset through the internal USB4 adapter. */ + rmw_set(PORT_APPCLK_EN, port->base + PORT_APPCLK); + rmw_clear(PORT_TUNCTRL_PERST_ON | + PORT_TUNCTRL_PERST_ACK_REQ, + port->base + PORT_TUNCTRL); + rmw_clear(PORT_PERST_OFF, + port->base + pcie->hw->port_perst); + /* The minimal Tperst-clk value is 100us (PCIe CEM r5.0, 2.9.2). */ + usleep_range(100, 200); - /* link might be already brought up by u-boot, skip setup then */ - link_stat = readl_relaxed(port->base + PORT_LINKSTS); - if (!(link_stat & PORT_LINKSTS_UP)) { - ret = apple_pcie_setup_link(pcie, port, np); - if (ret) - return ret; + rmw_set(PORT_PERST_OFF, port->base + pcie->hw->port_perst); + port->started = true; + + /* Wait for 100ms after PERST# deassertion (PCIe r5.0, 6.6.1). */ + msleep(100); + + ret = readl_relaxed_poll_timeout(port->base + PORT_STATUS, stat, + stat & PORT_STATUS_READY, + 100, 250000); + if (ret < 0) { + dev_err(pcie->dev, + "port %pOF ready wait timeout\n", np); + goto err_teardown; + } + } else { + /* U-Boot may already have brought up a conventional root port. */ + link_stat = readl_relaxed(port->base + PORT_LINKSTS); + if (!(link_stat & PORT_LINKSTS_UP)) { + ret = apple_pcie_setup_link(pcie, port, np); + if (ret) + goto err_teardown; + } } if (pcie->hw->port_refclk) rmw_clear(PORT_REFCLK_CGDIS, port->base + pcie->hw->port_refclk); - else + else if (port->phy) rmw_set(PHY_LANE_CFG_REFCLKCGEN, port->phy + PHY_LANE_CFG); rmw_clear(PORT_APPCLK_CGDIS, port->base + PORT_APPCLK); ret = apple_pcie_port_setup_irq(port); if (ret) - return ret; + goto err_teardown; /* Reset all RID/SID mappings, and check for RAZ/WI registers */ for (i = 0; i < pcie->hw->max_rid2sid; i++) { @@ -721,11 +869,9 @@ static int apple_pcie_setup_port(struct apple_pcie *pcie, list_add_tail(&port->entry, &pcie->ports); init_completion(&pcie->event); - /* In the success path, we keep a reference to np around */ - of_node_get(np); - ret = apple_pcie_port_register_irqs(port); - WARN_ON(ret); + if (ret) + goto err_teardown; link_stat = readl_relaxed(port->base + PORT_LINKSTS); if (!(link_stat & PORT_LINKSTS_UP)) { @@ -744,6 +890,10 @@ static int apple_pcie_setup_port(struct apple_pcie *pcie, } return 0; + +err_teardown: + apple_pcie_port_teardown(port); + return ret; } static const struct msi_parent_ops apple_msi_parent_ops = { @@ -777,11 +927,14 @@ static int apple_msi_init(struct apple_pcie *pcie) ret = of_property_read_u32_index(to_of_node(fwnode), "msi-ranges", args.args_count + 1, &pcie->nvecs); - if (ret) + if (ret) { + of_node_put(args.np); return ret; + } of_phandle_args_to_fwspec(args.np, args.args, args.args_count, &pcie->fwspec); + of_node_put(args.np); pcie->bitmap = devm_bitmap_zalloc(pcie->dev, pcie->nvecs, GFP_KERNEL); if (!pcie->bitmap) @@ -793,13 +946,28 @@ static int apple_msi_init(struct apple_pcie *pcie) return -ENXIO; } - if (!msi_create_parent_irq_domain(&info, &apple_msi_parent_ops)) { + pcie->msi_domain = msi_create_parent_irq_domain(&info, &apple_msi_parent_ops); + if (!pcie->msi_domain) { dev_err(pcie->dev, "failed to create IRQ domain\n"); return -ENOMEM; } return 0; } +static void apple_pcie_cleanup(void *data) +{ + struct apple_pcie *pcie = data; + struct apple_pcie_port *port, *tmp; + + list_for_each_entry_safe(port, tmp, &pcie->ports, entry) + apple_pcie_port_teardown(port); + + if (pcie->msi_domain) { + irq_domain_remove(pcie->msi_domain); + pcie->msi_domain = NULL; + } +} + static struct apple_pcie *apple_pcie_lookup(struct device *dev) { return pci_host_bridge_priv(dev_get_drvdata(dev)); @@ -986,10 +1154,20 @@ static int apple_pcie_probe(struct platform_device *pdev) if (ret) return ret; + ret = devm_add_action_or_reset(dev, apple_pcie_cleanup, pcie); + if (ret) + return ret; + return pci_host_common_init(pdev, bridge, &apple_pcie_cfg_ecam_ops); } +static void apple_pcie_remove(struct platform_device *pdev) +{ + pci_host_common_remove(pdev); +} + static const struct of_device_id apple_pcie_of_match[] = { + { .compatible = "apple,t6000-pciec", .data = &t6000_pciec_hw }, { .compatible = "apple,t6020-pcie", .data = &t602x_hw }, { .compatible = "apple,pcie", .data = &t8103_hw }, { } @@ -998,6 +1176,7 @@ MODULE_DEVICE_TABLE(of, apple_pcie_of_match); static struct platform_driver apple_pcie_driver = { .probe = apple_pcie_probe, + .remove = apple_pcie_remove, .driver = { .name = "pcie-apple", .of_match_table = apple_pcie_of_match, diff --git a/drivers/thunderbolt/apple.c b/drivers/thunderbolt/apple.c index 1d72760a6200bc..8482dc1ca0fb02 100644 --- a/drivers/thunderbolt/apple.c +++ b/drivers/thunderbolt/apple.c @@ -111,6 +111,7 @@ struct apple_cio { struct device *dev; struct device_node *np; + struct device_node *pcie_tunnel_np; struct apple_rtkit *rtk; void __iomem *rc_base; @@ -134,6 +135,11 @@ struct apple_cio { struct typec_thunderbolt_switch_dev *tbt_switch; }; +static void apple_cio_of_node_put(void *data) +{ + of_node_put(data); +} + struct apple_nhi { struct device *dev; struct platform_device *pdev; @@ -694,6 +700,23 @@ static int apple_cio_start(struct apple_cio *acio) goto err_depopulate; } + /* + * The PCIe-C root complex is outside the ACIO MMIO window but shares + * its cable-controlled power lifetime. Instantiate it only after the + * router and NHI are live; parenting it to ACIO makes depopulation tear + * it down before the co-processor and power domains disappear. + */ + if (acio->pcie_tunnel_np) { + ret = of_platform_populate(acio->pcie_tunnel_np, NULL, NULL, + acio->dev); + if (ret) { + dev_err(acio->dev, + "failed to populate tunneled PCIe controller: %d\n", + ret); + goto err_depopulate; + } + } + acio->current_cable_info = acio->target_cable_info; return 0; @@ -806,6 +829,14 @@ static int apple_cio_probe(struct platform_device *pdev) init_completion(&acio->nhi_boot_completion); acio->dev = &pdev->dev; acio->np = dev->of_node; + acio->pcie_tunnel_np = + of_parse_phandle(dev->of_node, "apple,pcie-tunnel", 0); + if (acio->pcie_tunnel_np) { + ret = devm_add_action_or_reset(dev, apple_cio_of_node_put, + acio->pcie_tunnel_np); + if (ret) + return ret; + } acio->sram_res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "sram"); if (!acio->sram_res) From c2b4c790dffcea77008686d53fede80de82aee3d Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Tue, 25 Aug 2026 14:35:39 -0400 Subject: [PATCH 21/51] PCI: apple: support T8103-family PCIe-C tunnels Signed-off-by: DjDeveloperr --- drivers/pci/controller/pcie-apple.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/pci/controller/pcie-apple.c b/drivers/pci/controller/pcie-apple.c index cee9e2d4e5c5c3..c520e2eb2612cf 100644 --- a/drivers/pci/controller/pcie-apple.c +++ b/drivers/pci/controller/pcie-apple.c @@ -185,7 +185,7 @@ static const struct hw_info t602x_hw = { .max_rid2sid = 512, }; -static const struct hw_info t6000_pciec_hw = { +static const struct hw_info t8103_pciec_hw = { .port_msiaddr = PORT_MSIADDR, .port_perst = PORT_PERST, .port_rid2sid = PORT_RID2SID, @@ -1167,7 +1167,8 @@ static void apple_pcie_remove(struct platform_device *pdev) } static const struct of_device_id apple_pcie_of_match[] = { - { .compatible = "apple,t6000-pciec", .data = &t6000_pciec_hw }, + { .compatible = "apple,t8103-pciec", .data = &t8103_pciec_hw }, + { .compatible = "apple,t6000-pciec", .data = &t8103_pciec_hw }, { .compatible = "apple,t6020-pcie", .data = &t602x_hw }, { .compatible = "apple,pcie", .data = &t8103_hw }, { } From 6e26f21c887461b17d62d7752e5f9357a2547016 Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Fri, 28 Aug 2026 00:04:18 -0400 Subject: [PATCH 22/51] thunderbolt: apple: activate PCIe-C after the USB4 tunnel Signed-off-by: DjDeveloperr --- drivers/thunderbolt/apple.c | 423 +++++++++++++++++++++++++++++++++-- drivers/thunderbolt/nhi.h | 6 + drivers/thunderbolt/tunnel.c | 25 ++- 3 files changed, 436 insertions(+), 18 deletions(-) diff --git a/drivers/thunderbolt/apple.c b/drivers/thunderbolt/apple.c index 8482dc1ca0fb02..8ebeae5f0c32e1 100644 --- a/drivers/thunderbolt/apple.c +++ b/drivers/thunderbolt/apple.c @@ -53,15 +53,18 @@ #include #include +#include #include #include #include #include #include #include +#include #include #include #include +#include #include #include #include @@ -70,6 +73,7 @@ #include #include #include +#include #include "nhi.h" #include "tb.h" @@ -97,6 +101,10 @@ #define APPLE_CIO_SRAM_IOVA_BASE 0x10000000 #define APPLE_CIO_NHI_BOOT_TIMEOUT_MS 10000 +#define APPLE_CIO_PCIEC_READY_DELAY_MS 300 + +#define APPLE_CIO_PCIEC_INTR2AXI_CTRL 0x80 +#define APPLE_CIO_PCIEC_INTR2AXI_ENABLE BIT(0) #define TB_VSE_CAP_APPLE 0x00 #define TB_VSE_CAP_APPLE_CABLE_INFO 0x01 @@ -112,6 +120,8 @@ struct apple_cio { struct device *dev; struct device_node *np; struct device_node *pcie_tunnel_np; + bool pcie_tunnel_preinitialized; + void __iomem *pcie_intr2axi_base; struct apple_rtkit *rtk; void __iomem *rc_base; @@ -133,13 +143,115 @@ struct apple_cio { int nhi_boot_status; struct typec_thunderbolt_switch_dev *tbt_switch; + /* Serializes PCIe-C population with cable teardown. */ + struct mutex pcie_tunnel_lock; + struct delayed_work pcie_tunnel_work; + bool pcie_tunnel_requested; + bool pcie_tunnel_populated; }; +/* + * Temporary hardware bring-up gate. A non-zero value stops ACIO startup after + * the corresponding phase so each power/reset/MMIO transition can be tested + * independently on new SoCs without advancing into the next phase. + */ +static unsigned int apple_cio_debug_stage; +module_param_named(debug_stage, apple_cio_debug_stage, uint, 0644); +MODULE_PARM_DESC(debug_stage, "Stop ACIO startup after the selected debug phase"); + +static unsigned int apple_cio_pcie_tunnel_trace_delay_ms; +module_param_named(pcie_tunnel_trace_delay_ms, + apple_cio_pcie_tunnel_trace_delay_ms, uint, 0644); +MODULE_PARM_DESC(pcie_tunnel_trace_delay_ms, + "Delay before PCIe-C child population so its trace reaches persistent storage"); + +static bool apple_cio_pcie_intr2axi_prearmed; +module_param_named(pcie_intr2axi_prearmed, + apple_cio_pcie_intr2axi_prearmed, bool, 0644); +MODULE_PARM_DESC(pcie_intr2axi_prearmed, + "Diagnostic: consume a live userspace Intr2AXI pulse instead of pulsing again"); + +static bool apple_cio_defer_start; +module_param_named(defer_start, apple_cio_defer_start, bool, 0644); +MODULE_PARM_DESC(defer_start, + "Hold a detected USB4/TBT cable until ACIO is started through sysfs"); + +static int apple_cio_power_domain_limit = -1; +module_param_named(power_domain_limit, apple_cio_power_domain_limit, int, 0644); +MODULE_PARM_DESC(power_domain_limit, + "Enable this many traced ACIO power domains before returning (-1 = all)"); + static void apple_cio_of_node_put(void *data) { of_node_put(data); } +static bool +apple_cio_pcie_tunnel_is_preinitialized(struct device_node *parent) +{ + for_each_available_child_of_node_scoped(parent, child) { + u32 status; + + if (!of_device_is_compatible(child, "apple,t8103-pciec") && + !of_device_is_compatible(child, "apple,t6000-pciec")) + continue; + + return !of_property_read_u32(child, + "apple,pciec-preinit-status", + &status) && status == 1; + } + + return false; +} + +static void apple_cio_iounmap(void *data) +{ + iounmap((__force void __iomem *)data); +} + +static int apple_cio_map_pcie_intr2axi(struct apple_cio *acio) +{ + struct resource res; + int index, ret; + + for_each_available_child_of_node_scoped(acio->pcie_tunnel_np, child) { + if (!of_device_is_compatible(child, "apple,t8103-pciec") && + !of_device_is_compatible(child, "apple,t6000-pciec")) + continue; + + index = of_property_match_string(child, "reg-names", "intr2axi"); + if (index < 0) + return index; + + ret = of_address_to_resource(child, index, &res); + if (ret) + return ret; + + /* + * Intr2AXI controls PCIe configuration transaction forwarding and + * must therefore use non-posted Device-nGnRnE accesses. A regular + * ioremap() is Device-nGnRE on arm64 and can leave this pulse pending + * until the following DART access, which raises an asynchronous SError. + * + * Do not reserve the resource here: the PCIe-C child owns the same DT + * aperture and will claim it when the tunnel children are populated. + */ + acio->pcie_intr2axi_base = + ioremap_np(res.start, resource_size(&res)); + if (!acio->pcie_intr2axi_base) + return -ENOMEM; + + ret = devm_add_action_or_reset(acio->dev, apple_cio_iounmap, + (__force void *)acio->pcie_intr2axi_base); + if (ret) + return ret; + + return 0; + } + + return -ENODEV; +} + struct apple_nhi { struct device *dev; struct platform_device *pdev; @@ -398,12 +510,203 @@ static void apple_nhi_ring_configure(struct tb_ring *ring, u32 flags, u32 e2e_fl writel(flags | e2e_flags, options); } +static int apple_cio_populate_pcie_tunnel(struct apple_cio *acio) +{ + /* + * PCIe-C was cold-initialized by m1n1 before the ACIO M3 started, but + * enabling ACIO's cable-powered PCIe domain closes the Intr2AXI bridge + * again. The port enable sequence pulses this register immediately + * before forcing its DART active. Do the same after ACIO has + * enabled both tunnel adapters and before either child can touch DART MMIO. + * The bit self-clears after opening the bridge. + */ + if (READ_ONCE(apple_cio_pcie_intr2axi_prearmed)) { + WRITE_ONCE(apple_cio_pcie_intr2axi_prearmed, false); + dev_info(acio->dev, + "PCIe-C Intr2AXI bridge accepted from live diagnostic handoff\n"); + } else { + writel(APPLE_CIO_PCIEC_INTR2AXI_ENABLE, + acio->pcie_intr2axi_base + APPLE_CIO_PCIEC_INTR2AXI_CTRL); + /* Complete the pulse without reading the transient register. */ + mb(); + dev_info(acio->dev, "PCIe-C Intr2AXI bridge enabled\n"); + } + if (apple_cio_debug_stage == 12) { + dev_info(acio->dev, + "ACIO debug stage 12: Intr2AXI pulsed; tunnel children held\n"); + return -EAGAIN; + } + + dev_info(acio->dev, + "PCIe-C live handoff accepted; populating tunnel children\n"); + if (READ_ONCE(apple_cio_pcie_tunnel_trace_delay_ms)) + msleep(READ_ONCE(apple_cio_pcie_tunnel_trace_delay_ms)); + + return of_platform_populate(acio->pcie_tunnel_np, NULL, NULL, + acio->dev); +} + +static int apple_cio_activate_pcie_tunnel_locked(struct apple_cio *acio) +{ + int ret; + + lockdep_assert_held(&acio->pcie_tunnel_lock); + + if (!acio->pcie_tunnel_np || acio->pcie_tunnel_populated) + return 0; + if (!acio->pcie_tunnel_preinitialized) + return dev_err_probe(acio->dev, -ENODEV, + "PCIe-C requires a successful m1n1 preinit handoff\n"); + + if (acio->pd_list->num_pds < 4 || !acio->pd_list->pd_links[3]) + return dev_err_probe(acio->dev, -ENODEV, + "pre-ACIO PCIe-C initialization is not active\n"); + + if (apple_cio_debug_stage == 11) { + dev_info(acio->dev, + "ACIO debug stage 11: PCIe tunnel adapters held for manual population\n"); + return 0; + } + + ret = apple_cio_populate_pcie_tunnel(acio); + if (ret) + return dev_err_probe(acio->dev, ret, + "failed to populate tunneled PCIe controller\n"); + + acio->pcie_tunnel_populated = true; + dev_info(acio->dev, + "PCIe-C populated after USB4 PCIe tunnel adapter enable\n"); + return 0; +} + +static void apple_cio_pcie_tunnel_work(struct work_struct *work) +{ + struct apple_cio *acio = + container_of(to_delayed_work(work), struct apple_cio, + pcie_tunnel_work); + int ret; + + mutex_lock(&acio->pcie_tunnel_lock); + if (!READ_ONCE(acio->pcie_tunnel_requested)) { + mutex_unlock(&acio->pcie_tunnel_lock); + return; + } + + ret = apple_cio_activate_pcie_tunnel_locked(acio); + mutex_unlock(&acio->pcie_tunnel_lock); + if (ret && ret != -EAGAIN) + dev_err(acio->dev, "deferred PCIe-C activation failed: %d\n", ret); +} + +static int apple_cio_start(struct apple_cio *acio); + +static ssize_t acio_start_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct apple_cio *acio = dev_get_drvdata(dev); + int ret; + + if (!sysfs_streq(buf, "1")) + return -EINVAL; + + ret = mutex_lock_interruptible(&acio->lock); + if (ret) + return ret; + + if (acio->current_cable_info) { + ret = -EALREADY; + goto out_unlock; + } + if (!acio->target_cable_info) { + ret = -ENODEV; + goto out_unlock; + } + + dev_info(acio->dev, + "starting deferred ACIO cable transition %#x\n", + acio->target_cable_info); + ret = apple_cio_start(acio); + +out_unlock: + mutex_unlock(&acio->lock); + return ret ?: count; +} +static DEVICE_ATTR_WO(acio_start); + +static ssize_t pcie_tunnel_populate_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct apple_cio *acio = dev_get_drvdata(dev); + int ret; + + if (!sysfs_streq(buf, "1")) + return -EINVAL; + + ret = mutex_lock_interruptible(&acio->pcie_tunnel_lock); + if (ret) + return ret; + + ret = apple_cio_activate_pcie_tunnel_locked(acio); + mutex_unlock(&acio->pcie_tunnel_lock); + + return ret ?: count; +} +static DEVICE_ATTR_WO(pcie_tunnel_populate); + +static struct attribute *apple_cio_attrs[] = { + &dev_attr_acio_start.attr, + &dev_attr_pcie_tunnel_populate.attr, + NULL, +}; + +static const struct attribute_group apple_cio_group = { + .attrs = apple_cio_attrs, +}; + +static int apple_nhi_pci_tunnel_pre_activate(struct tb_nhi *nhi) +{ + struct apple_nhi *anhi = nhi_to_anhi(nhi); + struct apple_cio *acio = anhi->acio; + + /* + * Remember that the PCIe tunnel reached activation. PCIe-C must not be + * populated here: the tunnel paths and both adapters are still disabled. + */ + WRITE_ONCE(acio->pcie_tunnel_requested, true); + return 0; +} + +static int apple_nhi_pci_tunnel_post_activate(struct tb_nhi *nhi) +{ + struct apple_nhi *anhi = nhi_to_anhi(nhi); + struct apple_cio *acio = anhi->acio; + int ret; + + /* + * This is the Linux equivalent of Apple's startUsingTunnel() boundary. + * Only now can releaseResetFlow() clear the tunneled PERST state before + * the root port enables LTSSM. + */ + mutex_lock(&acio->pcie_tunnel_lock); + ret = apple_cio_activate_pcie_tunnel_locked(acio); + mutex_unlock(&acio->pcie_tunnel_lock); + + /* Stage 12 intentionally leaves the active tunnel paths in place. */ + if (ret == -EAGAIN && apple_cio_debug_stage == 12) + return 0; + return ret; +} + static const struct tb_nhi_ops apple_nhi_ops = { .request_ring_irq = apple_nhi_request_irq, .release_ring_irq = apple_nhi_release_irq, .ring_interrupt_active = apple_nhi_ring_interrupt_active, .ring_interrupt_mask = apple_nhi_ring_interrupt_mask, .ring_configure = apple_nhi_ring_configure, + .pci_tunnel_pre_activate = apple_nhi_pci_tunnel_pre_activate, + .pci_tunnel_post_activate = apple_nhi_pci_tunnel_post_activate, }; static const struct tb_nhi_ring_layout apple_nhi_ring_layout = { @@ -598,7 +901,12 @@ static void apple_cio_stop(struct apple_cio *acio) * After we shut down the ACIO co-processor we will no longer be able to access * the MMIO space of these so make sure nothing tries to do just that. */ + WRITE_ONCE(acio->pcie_tunnel_requested, false); + cancel_delayed_work_sync(&acio->pcie_tunnel_work); + mutex_lock(&acio->pcie_tunnel_lock); of_platform_depopulate(acio->dev); + acio->pcie_tunnel_populated = false; + mutex_unlock(&acio->pcie_tunnel_lock); /* Try to shut down and power off the co-processor gracefully */ ret = apple_rtkit_poweroff(acio->rtk); @@ -627,6 +935,17 @@ static int apple_cio_start(struct apple_cio *acio) /* Create device links to the power domains in order to power them on */ for (i = 0; i < acio->pd_list->num_pds; i++) { + if (READ_ONCE(apple_cio_power_domain_limit) >= 0) { + dev_emerg(acio->dev, + "ACIO power-domain access %d armed: enable %s\n", + i + 1, dev_name(acio->pd_list->pd_devs[i])); + if (i >= READ_ONCE(apple_cio_power_domain_limit)) { + ret = -EAGAIN; + goto remove_links; + } + dev_emerg(acio->dev, + "ACIO power-domain access %d executing\n", i + 1); + } link = device_link_add(acio->dev, acio->pd_list->pd_devs[i], DL_FLAG_STATELESS | DL_FLAG_PM_RUNTIME | DL_FLAG_RPM_ACTIVE); if (!link) { @@ -634,6 +953,20 @@ static int apple_cio_start(struct apple_cio *acio) goto remove_links; } acio->pd_list->pd_links[i] = link; + if (READ_ONCE(apple_cio_power_domain_limit) >= 0) + dev_emerg(acio->dev, + "ACIO power-domain access %d completed\n", i + 1); + dev_info(acio->dev, + "ACIO power domain %d (%s): runtime %s, usage %d\n", + i, dev_name(acio->pd_list->pd_devs[i]), + pm_runtime_active(acio->pd_list->pd_devs[i]) ? + "active" : "inactive", + atomic_read(&acio->pd_list->pd_devs[i]->power.usage_count)); + } + if (apple_cio_debug_stage == 1) { + dev_info(acio->dev, "ACIO debug stage 1: power domains enabled\n"); + ret = -EAGAIN; + goto remove_links; } /* @@ -645,6 +978,11 @@ static int apple_cio_start(struct apple_cio *acio) dev_err(acio->dev, "ACIO block failed to start: %d\n", ret); goto remove_links; } + if (apple_cio_debug_stage == 2) { + dev_info(acio->dev, "ACIO debug stage 2: reset deasserted\n"); + ret = -EAGAIN; + goto remove_links; + } /* * Start and wait for the co-processor to boot. @@ -655,19 +993,39 @@ static int apple_cio_start(struct apple_cio *acio) * them with a bare store. */ val = readl(acio->rc_base + APPLE_CIO_M3_CTRL); + if (apple_cio_debug_stage == 3) { + dev_info(acio->dev, "ACIO debug stage 3: M3 control read as %#x\n", val); + ret = -EAGAIN; + goto remove_links; + } writel(val | APPLE_CIO_M3_CTRL_START, acio->rc_base + APPLE_CIO_M3_CTRL); + if (apple_cio_debug_stage == 4) { + dev_info(acio->dev, "ACIO debug stage 4: M3 start requested\n"); + ret = -EAGAIN; + goto remove_links; + } acio->rtk = apple_rtkit_init(acio->dev, acio, NULL, 0, &apple_cio_rtkit_ops); if (IS_ERR(acio->rtk)) { ret = PTR_ERR(acio->rtk); dev_err(acio->dev, "Failed to initialize RTKit: %d\n", ret); goto remove_links; } + if (apple_cio_debug_stage == 5) { + dev_info(acio->dev, "ACIO debug stage 5: RTKit initialized\n"); + ret = -EAGAIN; + goto err_free_rtkit; + } ret = apple_rtkit_boot(acio->rtk); if (ret) { dev_err(acio->dev, "M3 RTKit failed to boot: %d\n", ret); goto err_free_rtkit; } + if (apple_cio_debug_stage == 6) { + dev_info(acio->dev, "ACIO debug stage 6: RTKit booted\n"); + ret = -EAGAIN; + goto err_shutdown_rtkit; + } ret = readl_poll_timeout(acio->rc_base + APPLE_CIO_M3_STAT, state, state & APPLE_CIO_M3_STAT_STATE, 100, 500000); @@ -675,8 +1033,18 @@ static int apple_cio_start(struct apple_cio *acio) dev_err(acio->dev, "M3 firmware failed to get ready: %d\n", ret); goto err_shutdown_rtkit; } + if (apple_cio_debug_stage == 7) { + dev_info(acio->dev, "ACIO debug stage 7: M3 ready state %#x\n", state); + ret = -EAGAIN; + goto err_shutdown_rtkit; + } apple_tunable_apply(acio->rc_base, acio->rc_tunable); + if (apple_cio_debug_stage == 8) { + dev_info(acio->dev, "ACIO debug stage 8: RC tunables applied\n"); + ret = -EAGAIN; + goto err_shutdown_rtkit; + } /* * Bring up devices which are part of ACIO and are now accessible by the main SoC @@ -688,6 +1056,11 @@ static int apple_cio_start(struct apple_cio *acio) dev_err(acio->dev, "failed to populate children: %d\n", ret); goto err_depopulate; } + if (apple_cio_debug_stage == 9) { + dev_info(acio->dev, "ACIO debug stage 9: child devices populated\n"); + ret = -EAGAIN; + goto err_depopulate; + } if (!wait_for_completion_timeout(&acio->nhi_boot_completion, msecs_to_jiffies(APPLE_CIO_NHI_BOOT_TIMEOUT_MS))) { @@ -699,22 +1072,10 @@ static int apple_cio_start(struct apple_cio *acio) ret = acio->nhi_boot_status; goto err_depopulate; } - - /* - * The PCIe-C root complex is outside the ACIO MMIO window but shares - * its cable-controlled power lifetime. Instantiate it only after the - * router and NHI are live; parenting it to ACIO makes depopulation tear - * it down before the co-processor and power domains disappear. - */ - if (acio->pcie_tunnel_np) { - ret = of_platform_populate(acio->pcie_tunnel_np, NULL, NULL, - acio->dev); - if (ret) { - dev_err(acio->dev, - "failed to populate tunneled PCIe controller: %d\n", - ret); - goto err_depopulate; - } + if (apple_cio_debug_stage == 10) { + dev_info(acio->dev, "ACIO debug stage 10: NHI is ready\n"); + ret = -EAGAIN; + goto err_depopulate; } acio->current_cable_info = acio->target_cable_info; @@ -805,6 +1166,13 @@ static int apple_cio_tbt_switch_set(struct typec_thunderbolt_switch_dev *sw, * Bring up or power down the ACIO complex * current_cable_info will be updated in the start/stop functions */ + if (acio->target_cable_info && READ_ONCE(apple_cio_defer_start)) { + dev_info(acio->dev, + "USB4/TBT cable transition %#x held for controlled ACIO start\n", + acio->target_cable_info); + return 0; + } + if (acio->target_cable_info) return apple_cio_start(acio); @@ -826,6 +1194,11 @@ static int apple_cio_probe(struct platform_device *pdev) ret = devm_mutex_init(dev, &acio->lock); if (ret) return ret; + ret = devm_mutex_init(dev, &acio->pcie_tunnel_lock); + if (ret) + return ret; + INIT_DELAYED_WORK(&acio->pcie_tunnel_work, + apple_cio_pcie_tunnel_work); init_completion(&acio->nhi_boot_completion); acio->dev = &pdev->dev; acio->np = dev->of_node; @@ -836,6 +1209,19 @@ static int apple_cio_probe(struct platform_device *pdev) acio->pcie_tunnel_np); if (ret) return ret; + + acio->pcie_tunnel_preinitialized = + apple_cio_pcie_tunnel_is_preinitialized( + acio->pcie_tunnel_np); + if (!acio->pcie_tunnel_preinitialized) { + dev_warn(dev, + "PCIe-C tunnel disabled: m1n1 handoff is not initialized\n"); + } else { + ret = apple_cio_map_pcie_intr2axi(acio); + if (ret) + return dev_err_probe(dev, ret, + "failed to map PCIe-C Intr2AXI registers\n"); + } } acio->sram_res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "sram"); @@ -882,6 +1268,10 @@ static int apple_cio_probe(struct platform_device *pdev) else if (ret < 3) return dev_err_probe(dev, -EINVAL, "Not enough PM domains\n"); + ret = devm_device_add_group(dev, &apple_cio_group); + if (ret) + return ret; + /* And finally register the OOB notification for Thunderbolt/USB4 cables */ struct typec_thunderbolt_switch_desc desc = { .fwnode = pdev->dev.fwnode, @@ -901,6 +1291,7 @@ static void apple_cio_remove(struct platform_device *pdev) struct apple_cio *acio = platform_get_drvdata(pdev); typec_thunderbolt_switch_unregister(acio->tbt_switch); + cancel_delayed_work_sync(&acio->pcie_tunnel_work); guard(mutex)(&acio->lock); if (acio->current_cable_info) diff --git a/drivers/thunderbolt/nhi.h b/drivers/thunderbolt/nhi.h index 6c1c35362fe975..da963354b2b7e8 100644 --- a/drivers/thunderbolt/nhi.h +++ b/drivers/thunderbolt/nhi.h @@ -76,6 +76,10 @@ struct tb_nhi_ring_layout { * @ring_configure: NHI specific hook to program the ring options registers * and enable the ring with the given flags. If not set * the standard USB4 NHI registers are used. + * @pci_tunnel_pre_activate: NHI specific hook run after a PCIe tunnel has + * been allocated but before its paths and adapters are enabled + * @pci_tunnel_post_activate: NHI specific hook run after a PCIe tunnel's + * paths and adapters have been enabled * @is_present: Whether the device is currently present on the parent bus * @init_interrupts: NHI specific interrupt initialization hook */ @@ -93,6 +97,8 @@ struct tb_nhi_ops { void (*ring_interrupt_active)(struct tb_ring *ring, bool active); void (*ring_interrupt_mask)(struct tb_ring *ring, bool mask); void (*ring_configure)(struct tb_ring *ring, u32 flags, u32 e2e_flags); + int (*pci_tunnel_pre_activate)(struct tb_nhi *nhi); + int (*pci_tunnel_post_activate)(struct tb_nhi *nhi); bool (*is_present)(struct tb_nhi *nhi); int (*init_interrupts)(struct tb_nhi *nhi); }; diff --git a/drivers/thunderbolt/tunnel.c b/drivers/thunderbolt/tunnel.c index bc0a2b38cdd142..e000718aad8e78 100644 --- a/drivers/thunderbolt/tunnel.c +++ b/drivers/thunderbolt/tunnel.c @@ -324,6 +324,24 @@ static int tb_pci_set_ext_encapsulation(struct tb_tunnel *tunnel, bool enable) return 0; } +static int tb_pci_pre_activate(struct tb_tunnel *tunnel) +{ + const struct tb_nhi_ops *ops = tunnel->tb->nhi->ops; + + if (ops && ops->pci_tunnel_pre_activate) + return ops->pci_tunnel_pre_activate(tunnel->tb->nhi); + return 0; +} + +static int tb_pci_post_activate(struct tb_tunnel *tunnel) +{ + const struct tb_nhi_ops *ops = tunnel->tb->nhi->ops; + + if (ops && ops->pci_tunnel_post_activate) + return ops->pci_tunnel_post_activate(tunnel->tb->nhi); + return 0; +} + static int tb_pci_activate(struct tb_tunnel *tunnel, bool activate) { int res; @@ -341,17 +359,18 @@ static int tb_pci_activate(struct tb_tunnel *tunnel, bool activate) if (res) return res; - if (activate) { res = tb_pci_port_enable(tunnel->src_port, activate); if (res) return res; + + return tb_pci_post_activate(tunnel); } else { /* Downstream router could be unplugged */ tb_pci_port_enable(tunnel->dst_port, activate); } - return activate ? 0 : tb_pci_set_ext_encapsulation(tunnel, activate); + return tb_pci_set_ext_encapsulation(tunnel, activate); } static int tb_pci_init_credits(struct tb_path_hop *hop) @@ -429,6 +448,7 @@ struct tb_tunnel *tb_tunnel_discover_pci(struct tb *tb, struct tb_port *down, return NULL; tunnel->activate = tb_pci_activate; + tunnel->pre_activate = tb_pci_pre_activate; tunnel->src_port = down; /* @@ -506,6 +526,7 @@ struct tb_tunnel *tb_tunnel_alloc_pci(struct tb *tb, struct tb_port *up, return NULL; tunnel->activate = tb_pci_activate; + tunnel->pre_activate = tb_pci_pre_activate; tunnel->src_port = down; tunnel->dst_port = up; From 148ccae151916667613e786aa9a94974f4cae66e Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Fri, 28 Aug 2026 00:05:16 -0400 Subject: [PATCH 23/51] PCI: apple: support live tunneled PCIe-C ports Signed-off-by: DjDeveloperr --- drivers/pci/controller/pcie-apple.c | 260 ++++++++++++++++++++-------- drivers/pci/iomap.c | 2 + 2 files changed, 193 insertions(+), 69 deletions(-) diff --git a/drivers/pci/controller/pcie-apple.c b/drivers/pci/controller/pcie-apple.c index c520e2eb2612cf..642e0bbc098549 100644 --- a/drivers/pci/controller/pcie-apple.c +++ b/drivers/pci/controller/pcie-apple.c @@ -232,6 +232,52 @@ static void rmw_clear(u32 clr, void __iomem *addr) writel_relaxed(readl_relaxed(addr) & ~clr, addr); } +/* + * PCIe-C lives behind the same tunneled fabric as its DART. A live Linux + * /dev/mem replay of the root-port setup sequence showed that the aperture is + * available, but each access needs a full completion barrier. Without it, + * back-to-back relaxed accesses can leave a transaction pending until a later + * access reports an asynchronous SError. Keep conventional root ports on the + * existing fast path. + */ +static inline void apple_pcie_port_writel(struct apple_pcie_port *port, + u32 value, u32 offset) +{ + writel_relaxed(value, port->base + offset); + if (port->pcie->hw->tunneled) { + mb(); + isb(); + } +} + +static inline u32 apple_pcie_port_readl(struct apple_pcie_port *port, + u32 offset) +{ + u32 value = readl_relaxed(port->base + offset); + + if (port->pcie->hw->tunneled) { + mb(); + isb(); + } + + return value; +} + +static void apple_pcie_port_rmw_set(struct apple_pcie_port *port, u32 set, + u32 offset) +{ + apple_pcie_port_writel(port, apple_pcie_port_readl(port, offset) | set, + offset); +} + +static void apple_pcie_port_rmw_clear(struct apple_pcie_port *port, u32 clear, + u32 offset) +{ + apple_pcie_port_writel(port, + apple_pcie_port_readl(port, offset) & ~clear, + offset); +} + static void apple_msi_compose_msg(struct irq_data *data, struct msi_msg *msg) { msg->address_hi = upper_32_bits(DOORBELL_ADDR); @@ -309,7 +355,7 @@ static void apple_port_irq_mask(struct irq_data *data) struct apple_pcie_port *port = irq_data_get_irq_chip_data(data); guard(raw_spinlock_irqsave)(&port->lock); - rmw_set(BIT(data->hwirq), port->base + PORT_INTMSK); + apple_pcie_port_rmw_set(port, BIT(data->hwirq), PORT_INTMSK); } static void apple_port_irq_unmask(struct irq_data *data) @@ -317,7 +363,7 @@ static void apple_port_irq_unmask(struct irq_data *data) struct apple_pcie_port *port = irq_data_get_irq_chip_data(data); guard(raw_spinlock_irqsave)(&port->lock); - rmw_clear(BIT(data->hwirq), port->base + PORT_INTMSK); + apple_pcie_port_rmw_clear(port, BIT(data->hwirq), PORT_INTMSK); } static bool hwirq_is_intx(unsigned int hwirq) @@ -330,7 +376,7 @@ static void apple_port_irq_ack(struct irq_data *data) struct apple_pcie_port *port = irq_data_get_irq_chip_data(data); if (!hwirq_is_intx(data->hwirq)) - writel_relaxed(BIT(data->hwirq), port->base + PORT_INTSTAT); + apple_pcie_port_writel(port, BIT(data->hwirq), PORT_INTSTAT); } static int apple_port_irq_set_type(struct irq_data *data, unsigned int type) @@ -410,7 +456,7 @@ static void apple_port_irq_handler(struct irq_desc *desc) chained_irq_enter(chip, desc); - stat = readl_relaxed(port->base + PORT_INTSTAT); + stat = apple_pcie_port_readl(port, PORT_INTSTAT); for_each_set_bit(i, &stat, 32) generic_handle_domain_irq(port->domain, i); @@ -440,31 +486,32 @@ static int apple_pcie_port_setup_irq(struct apple_pcie_port *port) } /* Disable all interrupts */ - writel_relaxed(~0, port->base + PORT_INTMSK); - writel_relaxed(~0, port->base + PORT_INTSTAT); - writel_relaxed(~0, port->base + PORT_LINKCMDSTS); + apple_pcie_port_writel(port, ~0, PORT_INTMSK); + apple_pcie_port_writel(port, ~0, PORT_INTSTAT); + apple_pcie_port_writel(port, ~0, PORT_LINKCMDSTS); irq_set_chained_handler_and_data(port->irq, apple_port_irq_handler, port); /* Configure MSI base address */ BUILD_BUG_ON(upper_32_bits(DOORBELL_ADDR)); - writel_relaxed(lower_32_bits(DOORBELL_ADDR), - port->base + pcie->hw->port_msiaddr); + apple_pcie_port_writel(port, lower_32_bits(DOORBELL_ADDR), + pcie->hw->port_msiaddr); if (pcie->hw->port_msiaddr_hi) - writel_relaxed(0, port->base + pcie->hw->port_msiaddr_hi); + apple_pcie_port_writel(port, 0, pcie->hw->port_msiaddr_hi); /* Enable MSIs, shared between all ports */ if (pcie->hw->port_msimap) { for (int i = 0; i < pcie->nvecs; i++) - writel_relaxed(FIELD_PREP(PORT_MSIMAP_TARGET, i) | - PORT_MSIMAP_ENABLE, - port->base + pcie->hw->port_msimap + 4 * i); + apple_pcie_port_writel(port, + FIELD_PREP(PORT_MSIMAP_TARGET, i) | + PORT_MSIMAP_ENABLE, + pcie->hw->port_msimap + 4 * i); } else { - writel_relaxed(0, port->base + PORT_MSIBASE); + apple_pcie_port_writel(port, 0, PORT_MSIBASE); val = ilog2(pcie->nvecs) << PORT_MSICFG_L2MSINUM_SHIFT; } - writel_relaxed(val | PORT_MSICFG_EN, port->base + PORT_MSICFG); + apple_pcie_port_writel(port, val | PORT_MSICFG_EN, PORT_MSICFG); return 0; } @@ -543,6 +590,27 @@ static void apple_pcie_port_unregister_irqs(struct apple_pcie_port *port) } } +static int apple_pcie_tunnel_release_reset(struct apple_pcie_port *port) +{ + u32 stat; + int ret; + + /* + * Starting the CIO PCIe tunnel leaves the tunneled root port in reset. + * Releasing it means writing zero to PORT_TUNCTRL, waiting for the + * reset-active indication to clear, and only then enabling the LTSSM. + */ + apple_pcie_port_writel(port, 0, PORT_TUNCTRL); + ret = read_poll_timeout_atomic(apple_pcie_port_readl, stat, + !(stat & PORT_TUNSTAT_PERST_ON), + 1000, 100000, false, port, PORT_TUNSTAT); + if (ret) + dev_err(port->pcie->dev, + "port %pOF tunnel reset release timed out\n", port->np); + + return ret; +} + static void apple_pcie_tunnel_stop(struct apple_pcie_port *port) { struct apple_pcie *pcie = port->pcie; @@ -554,38 +622,41 @@ static void apple_pcie_tunnel_stop(struct apple_pcie_port *port) * tunneled reset request, and the root port must acknowledge it before * ACIO loses power. A cable disconnect requires that same ordering. */ - rmw_set(PORT_TUNCTRL_PERST_ON, port->base + PORT_TUNCTRL); - ret = readl_relaxed_poll_timeout(port->base + PORT_TUNSTAT, stat, - stat & PORT_TUNSTAT_PERST_ON, - 1000, 100000); + apple_pcie_port_rmw_set(port, PORT_TUNCTRL_PERST_ON, PORT_TUNCTRL); + ret = read_poll_timeout_atomic(apple_pcie_port_readl, stat, + stat & PORT_TUNSTAT_PERST_ON, + 1000, 100000, false, port, PORT_TUNSTAT); if (ret) dev_warn(pcie->dev, "port %pOF tunnel reset assertion timed out\n", port->np); - rmw_clear(PORT_PERST_OFF, port->base + pcie->hw->port_perst); - rmw_clear(PORT_LTSSMCTL_START, port->base + PORT_LTSSMCTL); - rmw_clear(PORT_APPCLK_EN, port->base + PORT_APPCLK); + apple_pcie_port_rmw_clear(port, PORT_PERST_OFF, + pcie->hw->port_perst); + apple_pcie_port_rmw_clear(port, PORT_LTSSMCTL_START, PORT_LTSSMCTL); + apple_pcie_port_rmw_clear(port, PORT_APPCLK_EN, PORT_APPCLK); - ret = readl_relaxed_poll_timeout(port->base + PORT_STATUS, stat, - !(stat & PORT_STATUS_READY), 10, 100000); + ret = read_poll_timeout_atomic(apple_pcie_port_readl, stat, + !(stat & PORT_STATUS_READY), + 10, 100000, false, port, PORT_STATUS); if (ret) dev_warn(pcie->dev, "port %pOF disable timed out\n", port->np); - rmw_set(PORT_TUNCTRL_PERST_ACK_REQ, port->base + PORT_TUNCTRL); - ret = readl_relaxed_poll_timeout(port->base + PORT_TUNSTAT, stat, - stat & PORT_TUNSTAT_PERST_ACK_PEND, - 1000, 1000000); + apple_pcie_port_rmw_set(port, PORT_TUNCTRL_PERST_ACK_REQ, PORT_TUNCTRL); + ret = read_poll_timeout_atomic(apple_pcie_port_readl, stat, + stat & PORT_TUNSTAT_PERST_ACK_PEND, + 1000, 1000000, false, port, PORT_TUNSTAT); if (ret) dev_warn(pcie->dev, "port %pOF tunnel reset acknowledgment timed out\n", port->np); - rmw_clear(PORT_TUNCTRL_PERST_ACK_REQ, port->base + PORT_TUNCTRL); + apple_pcie_port_rmw_clear(port, PORT_TUNCTRL_PERST_ACK_REQ, + PORT_TUNCTRL); port->started = false; } static void apple_pcie_port_teardown(struct apple_pcie_port *port) { if (port->base) - writel_relaxed(~0, port->base + PORT_INTMSK); + apple_pcie_port_writel(port, ~0, PORT_INTMSK); if (port->pcie->hw->tunneled && port->started) apple_pcie_tunnel_stop(port); @@ -648,17 +719,19 @@ static int apple_pcie_setup_refclk(struct apple_pcie *pcie, return 0; } -static void __iomem *port_rid2sid_addr(struct apple_pcie_port *port, int idx) +static u32 port_rid2sid_offset(struct apple_pcie_port *port, int idx) { - return port->base + port->pcie->hw->port_rid2sid + 4 * idx; + return port->pcie->hw->port_rid2sid + 4 * idx; } static u32 apple_pcie_rid2sid_write(struct apple_pcie_port *port, int idx, u32 val) { - writel_relaxed(val, port_rid2sid_addr(port, idx)); + u32 offset = port_rid2sid_offset(port, idx); + + apple_pcie_port_writel(port, val, offset); /* Read back to ensure completion of the write */ - return readl_relaxed(port_rid2sid_addr(port, idx)); + return apple_pcie_port_readl(port, offset); } static int apple_pcie_setup_link(struct apple_pcie *pcie, @@ -757,7 +830,7 @@ static int apple_pcie_setup_port(struct apple_pcie *pcie, struct apple_pcie_port *port; struct resource *res; char name[16]; - u32 link_stat, stat, idx; + u32 link_stat, preinit_status, stat, idx; int ret, i; port = devm_kzalloc(pcie->dev, sizeof(*port), GFP_KERNEL); @@ -789,6 +862,14 @@ static int apple_pcie_setup_port(struct apple_pcie *pcie, goto err_teardown; } + /* + * PCIe-C register transactions are non-posted on the tunneled fabric. + * Mark the resource so devm_ioremap_resource() selects ioremap_np() on + * arm64, matching the successful /dev/mem Device-nGnRnE replay. + */ + if (pcie->hw->tunneled) + res->flags |= IORESOURCE_MEM_NONPOSTED; + port->base = devm_ioremap_resource(&platform->dev, res); if (IS_ERR(port->base)) { ret = PTR_ERR(port->base); @@ -810,25 +891,26 @@ static int apple_pcie_setup_port(struct apple_pcie *pcie, } } if (pcie->hw->tunneled) { - /* PCIe-C is reset through the internal USB4 adapter. */ - rmw_set(PORT_APPCLK_EN, port->base + PORT_APPCLK); - rmw_clear(PORT_TUNCTRL_PERST_ON | - PORT_TUNCTRL_PERST_ACK_REQ, - port->base + PORT_TUNCTRL); - rmw_clear(PORT_PERST_OFF, - port->base + pcie->hw->port_perst); - /* The minimal Tperst-clk value is 100us (PCIe CEM r5.0, 2.9.2). */ - usleep_range(100, 200); - - rmw_set(PORT_PERST_OFF, port->base + pcie->hw->port_perst); - port->started = true; - - /* Wait for 100ms after PERST# deassertion (PCIe r5.0, 6.6.1). */ - msleep(100); + /* + * m1n1 owns PCIe-C cold initialization. Replaying the port reset or + * tunnel-reset handshake against that live handoff raises an + * asynchronous SError on T6020. Accept only an explicitly successful + * handoff and begin with the read-only RUN state check used by m1n1. + */ + ret = of_property_read_u32(pcie->dev->of_node, + "apple,pciec-preinit-status", + &preinit_status); + if (ret || preinit_status != 1) { + dev_err(pcie->dev, + "PCIe-C requires a successful m1n1 preinit handoff\n"); + ret = -ENODEV; + goto err_teardown; + } - ret = readl_relaxed_poll_timeout(port->base + PORT_STATUS, stat, - stat & PORT_STATUS_READY, - 100, 250000); + ret = read_poll_timeout_atomic(apple_pcie_port_readl, stat, + stat & PORT_STATUS_READY, + 100, 250000, false, port, + PORT_STATUS); if (ret < 0) { dev_err(pcie->dev, "port %pOF ready wait timeout\n", np); @@ -836,7 +918,7 @@ static int apple_pcie_setup_port(struct apple_pcie *pcie, } } else { /* U-Boot may already have brought up a conventional root port. */ - link_stat = readl_relaxed(port->base + PORT_LINKSTS); + link_stat = apple_pcie_port_readl(port, PORT_LINKSTS); if (!(link_stat & PORT_LINKSTS_UP)) { ret = apple_pcie_setup_link(pcie, port, np); if (ret) @@ -849,7 +931,9 @@ static int apple_pcie_setup_port(struct apple_pcie *pcie, else if (port->phy) rmw_set(PHY_LANE_CFG_REFCLKCGEN, port->phy + PHY_LANE_CFG); - rmw_clear(PORT_APPCLK_CGDIS, port->base + PORT_APPCLK); + /* The preinitialized PCIe-C APPCLK state is part of the m1n1 handoff. */ + if (!pcie->hw->tunneled) + rmw_clear(PORT_APPCLK_CGDIS, port->base + PORT_APPCLK); ret = apple_pcie_port_setup_irq(port); if (ret) @@ -873,11 +957,18 @@ static int apple_pcie_setup_port(struct apple_pcie *pcie, if (ret) goto err_teardown; - link_stat = readl_relaxed(port->base + PORT_LINKSTS); + link_stat = apple_pcie_port_readl(port, PORT_LINKSTS); if (!(link_stat & PORT_LINKSTS_UP)) { unsigned long timeout, left; + + if (pcie->hw->tunneled) { + ret = apple_pcie_tunnel_release_reset(port); + if (ret) + goto err_teardown; + } + /* start link training */ - writel_relaxed(PORT_LTSSMCTL_START, port->base + PORT_LTSSMCTL); + apple_pcie_port_writel(port, PORT_LTSSMCTL_START, PORT_LTSSMCTL); timeout = link_up_timeout * HZ / 1000; left = wait_for_completion_timeout(&pcie->event, timeout); @@ -1001,10 +1092,22 @@ static struct apple_pcie_port *apple_pcie_get_port(struct pci_dev *pdev) static int apple_pcie_enable_device(struct pci_host_bridge *bridge, struct pci_dev *pdev) { + struct apple_pcie *pcie = pci_host_bridge_priv(bridge); + struct resource *res; u32 sid, rid = pci_dev_id(pdev); struct apple_pcie_port *port; int idx, err; + /* + * Endpoint BARs share PCIe-C's tunneled, non-posted MMIO fabric. Mark + * them before the function driver maps its BAR so pci_iomap() selects + * Device-nGnRnE on arm64. + */ + if (pcie->hw->tunneled) + pci_dev_for_each_resource(pdev, res) + if (res->flags & IORESOURCE_MEM) + res->flags |= IORESOURCE_MEM_NONPOSTED; + port = apple_pcie_get_port(pdev); if (!port) return 0; @@ -1049,7 +1152,8 @@ static void apple_pcie_disable_device(struct pci_host_bridge *bridge, struct pci for_each_set_bit(idx, port->sid_map, port->sid_map_sz) { u32 val; - val = readl_relaxed(port_rid2sid_addr(port, idx)); + val = apple_pcie_port_readl(port, + port_rid2sid_offset(port, idx)); if ((val & 0xffff) == rid) { apple_pcie_rid2sid_write(port, idx, 0); bitmap_release_region(port->sid_map, idx, 0); @@ -1120,17 +1224,31 @@ static int apple_pcie_probe_port(struct device_node *np) static int apple_pcie_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; + const struct hw_info *hw; struct pci_host_bridge *bridge; struct device_node *of_port; struct apple_pcie *pcie; int ret; - /* Check for probe dependencies for all ports first */ - for_each_available_child_of_node(dev->of_node, of_port) { - ret = apple_pcie_probe_port(of_port); - if (ret) { - of_node_put(of_port); - return dev_err_probe(dev, ret, "Port %pOF probe fail\n", of_port); + hw = of_device_get_match_data(dev); + if (!hw) + return -ENODEV; + + /* + * A tunneled PCIe-C port has no host GPIO for PERST#: the tunnel + * firmware and m1n1 handoff own its reset state. Requiring a + * reset-gpios provider here rejects the valid synthesized port before + * the tunneled setup path can consume that handoff. + */ + if (!hw->tunneled) { + /* Check for probe dependencies for all ports first */ + for_each_available_child_of_node(dev->of_node, of_port) { + ret = apple_pcie_probe_port(of_port); + if (ret) { + of_node_put(of_port); + return dev_err_probe(dev, ret, + "Port %pOF probe fail\n", of_port); + } } } @@ -1140,9 +1258,7 @@ static int apple_pcie_probe(struct platform_device *pdev) pcie = pci_host_bridge_priv(bridge); pcie->dev = dev; - pcie->hw = of_device_get_match_data(dev); - if (!pcie->hw) - return -ENODEV; + pcie->hw = hw; pcie->base = devm_platform_ioremap_resource(pdev, 1); if (IS_ERR(pcie->base)) return PTR_ERR(pcie->base); @@ -1154,11 +1270,17 @@ static int apple_pcie_probe(struct platform_device *pdev) if (ret) return ret; - ret = devm_add_action_or_reset(dev, apple_pcie_cleanup, pcie); + ret = pci_host_common_init(pdev, bridge, &apple_pcie_cfg_ecam_ops); + if (ret) + apple_pcie_cleanup(pcie); if (ret) return ret; - return pci_host_common_init(pdev, bridge, &apple_pcie_cfg_ecam_ops); + /* + * Port mappings are allocated by the ECAM init callback. Register cleanup + * afterwards so devres runs it before unmapping those registers. + */ + return devm_add_action_or_reset(dev, apple_pcie_cleanup, pcie); } static void apple_pcie_remove(struct platform_device *pdev) diff --git a/drivers/pci/iomap.c b/drivers/pci/iomap.c index ea86c282a3869b..2e35f0131fe8bf 100644 --- a/drivers/pci/iomap.c +++ b/drivers/pci/iomap.c @@ -50,6 +50,8 @@ void __iomem *pci_iomap_range(struct pci_dev *dev, len = maxlen; if (flags & IORESOURCE_IO) return __pci_ioport_map(dev, start, len); + if (flags & IORESOURCE_MEM_NONPOSTED) + return ioremap_np(start, len) ?: ioremap(start, len); if (flags & IORESOURCE_MEM) return ioremap(start, len); /* What? */ From e1d7775dd2d2e2d3623030af71caf5f1c9c18d05 Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Fri, 28 Aug 2026 00:05:55 -0400 Subject: [PATCH 24/51] iommu: apple-dart: support tunneled PCIe-C DMA aliases Signed-off-by: DjDeveloperr --- drivers/iommu/Kconfig | 1 + drivers/iommu/apple-dart.c | 551 +++++++++++++++++++++++++++++++++---- 2 files changed, 495 insertions(+), 57 deletions(-) diff --git a/drivers/iommu/Kconfig b/drivers/iommu/Kconfig index f86262b11416d1..8b0e5505516a8a 100644 --- a/drivers/iommu/Kconfig +++ b/drivers/iommu/Kconfig @@ -305,6 +305,7 @@ config APPLE_DART tristate "Apple DART IOMMU Support" depends on ARCH_APPLE || COMPILE_TEST depends on !GENERIC_ATOMIC64 # for IOMMU_IO_PGTABLE_DART + select APPLE_TUNABLE select IOMMU_API select IOMMU_IO_PGTABLE_DART help diff --git a/drivers/iommu/apple-dart.c b/drivers/iommu/apple-dart.c index 8b4452b07a54ec..ddc265ef74b342 100644 --- a/drivers/iommu/apple-dart.c +++ b/drivers/iommu/apple-dart.c @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -31,6 +32,7 @@ #include #include #include +#include #include #include @@ -39,6 +41,23 @@ #define DART_MAX_STREAMS 256 #define DART_MAX_TTBR 4 #define MAX_DARTS_PER_DEVICE 3 +#define DART_PCIEC_PAGE_SHIFT 14 +#define DART_PCIEC_ADDR_WIDTH 42 +#define DART_PCIEC_STREAMS 64 + +/* + * Temporary bring-up gate for the cable-powered DART behind PCIe-C. The + * normal storage, display and USB4 DARTs never consult this parameter. + */ +static unsigned int apple_dart_debug_stage; +module_param_named(debug_stage, apple_dart_debug_stage, uint, 0644); +MODULE_PARM_DESC(debug_stage, + "Stop tunneled PCIe-C DART setup after the selected debug phase"); + +static unsigned int apple_dart_trace_delay_ms; +module_param_named(tunnel_trace_delay_ms, apple_dart_trace_delay_ms, uint, 0644); +MODULE_PARM_DESC(tunnel_trace_delay_ms, + "Delay after PCIe-C DART trace boundaries (0 disables tracing)"); /* Common registers */ @@ -226,9 +245,12 @@ struct apple_dart { u32 supports_bypass : 1; u32 four_level : 1; u32 locked : 1; + u32 tunneled : 1; + struct apple_tunable *tunables; dma_addr_t dma_min; dma_addr_t dma_max; + dma_addr_t dma_offset; struct iommu_group *sid2group[DART_MAX_STREAMS]; struct iommu_device iommu; @@ -239,6 +261,66 @@ struct apple_dart { u64 *locked_ttbr[DART_MAX_STREAMS][DART_MAX_TTBR]; }; +/* + * PCIe-C is a cable-powered aperture and can report a failed transaction as + * an asynchronous external abort unless each access is completed before the + * next instruction retires. A live /dev/mem diagnostic using the same + * Device-nGnRnE attributes and the exact 64-SID reset sequence is stable when + * every access is followed by dsb sy + isb. ioremap_np() supplies the memory + * type, but writel() only supplies the normal I/O ordering barrier on arm64. + * Match the proven access semantics for the tunneled DART while leaving all + * always-on DARTs on the normal fast path. + */ +static inline void apple_dart_writel(struct apple_dart *dart, u32 value, + u32 offset) +{ + writel(value, dart->regs + offset); + if (unlikely(dart->tunneled)) { + mb(); + isb(); + } +} + +static inline u32 apple_dart_readl(struct apple_dart *dart, u32 offset) +{ + u32 value = readl(dart->regs + offset); + + if (unlikely(dart->tunneled)) { + mb(); + isb(); + } + + return value; +} + +static void apple_dart_apply_tunables(struct apple_dart *dart) +{ + size_t i; + + for (i = 0; i < dart->tunables->sz; ++i) { + u32 old_value, value; + + old_value = apple_dart_readl(dart, + dart->tunables->values[i].offset); + value = old_value & ~dart->tunables->values[i].mask; + value |= dart->tunables->values[i].value; + if (value != old_value) + apple_dart_writel(dart, value, + dart->tunables->values[i].offset); + } +} + +static void apple_dart_tunnel_trace(struct apple_dart *dart, + unsigned int step, const char *operation) +{ + if (!dart->tunneled || !READ_ONCE(apple_dart_trace_delay_ms)) + return; + + dev_info(dart->dev, "PCIe-C DART trace %u armed: %s\n", + step, operation); + msleep(READ_ONCE(apple_dart_trace_delay_ms)); +} + /* * Convenience struct to identify streams. * @@ -277,6 +359,7 @@ struct apple_dart_domain { bool finalized; u64 mask; + dma_addr_t dma_offset; struct mutex init_lock; struct apple_dart_atomic_stream_map stream_maps[MAX_DARTS_PER_DEVICE]; @@ -333,7 +416,7 @@ apple_dart_hw_enable_translation(struct apple_dart_stream_map *stream_map, int l WARN_ON(levels != 3 && levels != 4); WARN_ON(levels == 4 && !dart->four_level); for_each_set_bit(sid, stream_map->sidmap, dart->num_streams) - writel(tcr, dart->regs + DART_TCR(dart, sid)); + apple_dart_writel(dart, tcr, DART_TCR(dart, sid)); } static void apple_dart_hw_disable_dma(struct apple_dart_stream_map *stream_map) @@ -342,7 +425,8 @@ static void apple_dart_hw_disable_dma(struct apple_dart_stream_map *stream_map) int sid; for_each_set_bit(sid, stream_map->sidmap, dart->num_streams) - writel(dart->hw->tcr_disabled, dart->regs + DART_TCR(dart, sid)); + apple_dart_writel(dart, dart->hw->tcr_disabled, + DART_TCR(dart, sid)); } static void @@ -353,8 +437,8 @@ apple_dart_hw_enable_bypass(struct apple_dart_stream_map *stream_map) WARN_ON(!stream_map->dart->supports_bypass); for_each_set_bit(sid, stream_map->sidmap, dart->num_streams) - writel(dart->hw->tcr_bypass, - dart->regs + DART_TCR(dart, sid)); + apple_dart_writel(dart, dart->hw->tcr_bypass, + DART_TCR(dart, sid)); } static void apple_dart_hw_set_ttbr(struct apple_dart_stream_map *stream_map, @@ -365,9 +449,11 @@ static void apple_dart_hw_set_ttbr(struct apple_dart_stream_map *stream_map, WARN_ON(paddr & ((1 << dart->hw->ttbr_shift) - 1)); for_each_set_bit(sid, stream_map->sidmap, dart->num_streams) - writel(dart->hw->ttbr_valid | - (paddr >> dart->hw->ttbr_shift) << dart->hw->ttbr_addr_field_shift, - dart->regs + DART_TTBR(dart, sid, idx)); + apple_dart_writel(dart, + dart->hw->ttbr_valid | + (paddr >> dart->hw->ttbr_shift) << + dart->hw->ttbr_addr_field_shift, + DART_TTBR(dart, sid, idx)); } static void apple_dart_hw_clear_ttbr(struct apple_dart_stream_map *stream_map, @@ -377,7 +463,7 @@ static void apple_dart_hw_clear_ttbr(struct apple_dart_stream_map *stream_map, int sid; for_each_set_bit(sid, stream_map->sidmap, dart->num_streams) - writel(0, dart->regs + DART_TTBR(dart, sid, idx)); + apple_dart_writel(dart, 0, DART_TTBR(dart, sid, idx)); } static void @@ -400,7 +486,7 @@ apple_dart_hw_map_locked_ttbr(struct apple_dart_stream_map *stream_map, u8 idx) phys_addr_t phys; u64 *l1_tbl; - ttbr = readl(dart->regs + DART_TTBR(dart, sid, idx)); + ttbr = apple_dart_readl(dart, DART_TTBR(dart, sid, idx)); if (!(ttbr & dart->hw->ttbr_valid)) { dev_err(dart->dev, "Invalid ttbr[%u] for locked dart\n", @@ -469,23 +555,25 @@ static int apple_dart_t8020_hw_stream_command(struct apple_dart_stream_map *stream_map, u32 command) { + struct apple_dart *dart = stream_map->dart; unsigned long flags; int ret, i; u32 command_reg; - spin_lock_irqsave(&stream_map->dart->lock, flags); + spin_lock_irqsave(&dart->lock, flags); - for (i = 0; i < BITS_TO_U32(stream_map->dart->num_streams); i++) - writel(stream_map->sidmap[i], - stream_map->dart->regs + DART_T8020_STREAM_SELECT + 4 * i); - writel(command, stream_map->dart->regs + DART_T8020_STREAM_COMMAND); + for (i = 0; i < BITS_TO_U32(dart->num_streams); i++) + apple_dart_writel(dart, stream_map->sidmap[i], + DART_T8020_STREAM_SELECT + 4 * i); + apple_dart_writel(dart, command, DART_T8020_STREAM_COMMAND); - ret = readl_poll_timeout_atomic( - stream_map->dart->regs + DART_T8020_STREAM_COMMAND, command_reg, + ret = read_poll_timeout_atomic( + apple_dart_readl, command_reg, !(command_reg & DART_T8020_STREAM_COMMAND_BUSY), 1, - DART_STREAM_COMMAND_BUSY_TIMEOUT); + DART_STREAM_COMMAND_BUSY_TIMEOUT, false, dart, + DART_T8020_STREAM_COMMAND); - spin_unlock_irqrestore(&stream_map->dart->lock, flags); + spin_unlock_irqrestore(&dart->lock, flags); if (ret) { dev_err(stream_map->dart->dev, @@ -511,12 +599,13 @@ apple_dart_t8110_hw_tlb_command(struct apple_dart_stream_map *stream_map, for_each_set_bit(sid, stream_map->sidmap, dart->num_streams) { u32 val = FIELD_PREP(DART_T8110_TLB_CMD_OP, command) | FIELD_PREP(DART_T8110_TLB_CMD_STREAM, sid); - writel(val, dart->regs + DART_T8110_TLB_CMD); + apple_dart_writel(dart, val, DART_T8110_TLB_CMD); - ret = readl_poll_timeout_atomic( - dart->regs + DART_T8110_TLB_CMD, val, + ret = read_poll_timeout_atomic( + apple_dart_readl, val, !(val & DART_T8110_TLB_CMD_BUSY), 1, - DART_STREAM_COMMAND_BUSY_TIMEOUT); + DART_STREAM_COMMAND_BUSY_TIMEOUT, false, dart, + DART_T8110_TLB_CMD); if (ret) break; @@ -557,22 +646,134 @@ static int apple_dart_hw_reset(struct apple_dart *dart) stream_map.dart = dart; bitmap_zero(stream_map.sidmap, DART_MAX_STREAMS); bitmap_set(stream_map.sidmap, 0, dart->num_streams); + + apple_dart_tunnel_trace(dart, 20, "disable DMA for every stream"); apple_dart_hw_disable_dma(&stream_map); + apple_dart_tunnel_trace(dart, 21, "clear every stream TTBR"); apple_dart_hw_clear_all_ttbrs(&stream_map); /* enable all streams globally since TCR is used to control isolation */ + apple_dart_tunnel_trace(dart, 22, "enable all streams globally"); for (i = 0; i < BITS_TO_U32(dart->num_streams); i++) - writel(U32_MAX, dart->regs + dart->hw->enable_streams + 4 * i); + apple_dart_writel(dart, U32_MAX, + dart->hw->enable_streams + 4 * i); /* clear any pending errors before the interrupt is unmasked */ - writel(readl(dart->regs + dart->hw->error), dart->regs + dart->hw->error); + apple_dart_tunnel_trace(dart, 23, "read and clear DART error status"); + apple_dart_writel(dart, apple_dart_readl(dart, dart->hw->error), + dart->hw->error); + apple_dart_tunnel_trace(dart, 24, "clear T8110 error mask"); if (dart->hw->type == DART_T8110) - writel(0, dart->regs + DART_T8110_ERROR_MASK); + apple_dart_writel(dart, 0, DART_T8110_ERROR_MASK); + apple_dart_tunnel_trace(dart, 25, "invalidate every stream TLB"); return dart->hw->invalidate_tlb(&stream_map); } +static int apple_dart_debug_reset(struct apple_dart *dart) +{ + struct apple_dart_stream_map stream_map; + u32 value; + int i, sid; + + if (dart->hw->type != DART_T8110 || apple_dart_debug_stage < 30) + return 0; + + stream_map.dart = dart; + bitmap_zero(stream_map.sidmap, DART_MAX_STREAMS); + set_bit(0, stream_map.sidmap); + + apple_dart_writel(dart, U32_MAX, dart->hw->enable_streams); + dev_info(dart->dev, + "PCIe-C DART reset stage 30: stream word 0 enabled\n"); + if (apple_dart_debug_stage == 30) + return -EAGAIN; + + for (i = 1; i < BITS_TO_U32(dart->num_streams); i++) + apple_dart_writel(dart, U32_MAX, + dart->hw->enable_streams + 4 * i); + dev_info(dart->dev, + "PCIe-C DART reset stage 31: all stream words enabled\n"); + if (apple_dart_debug_stage == 31) + return -EAGAIN; + + value = apple_dart_readl(dart, DART_TCR(dart, 0)); + dev_info(dart->dev, + "PCIe-C DART reset stage 32: SID0 TCR initial value=%#x\n", + value); + if (apple_dart_debug_stage == 32) + return -EAGAIN; + + apple_dart_writel(dart, dart->hw->tcr_disabled, + DART_TCR(dart, 0)); + dev_info(dart->dev, + "PCIe-C DART reset stage 33: SID0 TCR disabled\n"); + if (apple_dart_debug_stage == 33) + return -EAGAIN; + + value = apple_dart_readl(dart, DART_TCR(dart, 0)); + dev_info(dart->dev, + "PCIe-C DART reset stage 34: SID0 TCR readback=%#x\n", + value); + if (apple_dart_debug_stage == 34) + return -EAGAIN; + + for (sid = 1; sid < dart->num_streams; sid++) + apple_dart_writel(dart, dart->hw->tcr_disabled, + DART_TCR(dart, sid)); + dev_info(dart->dev, + "PCIe-C DART reset stage 35: all %u TCRs disabled\n", + dart->num_streams); + if (apple_dart_debug_stage == 35) + return -EAGAIN; + + apple_dart_writel(dart, 0, DART_TTBR(dart, 0, 0)); + dev_info(dart->dev, + "PCIe-C DART reset stage 36: SID0 TTBR0 cleared\n"); + if (apple_dart_debug_stage == 36) + return -EAGAIN; + + value = apple_dart_readl(dart, DART_TTBR(dart, 0, 0)); + dev_info(dart->dev, + "PCIe-C DART reset stage 37: SID0 TTBR0 readback=%#x\n", + value); + if (apple_dart_debug_stage == 37) + return -EAGAIN; + + for (sid = 1; sid < dart->num_streams; sid++) + for (i = 0; i < dart->hw->ttbr_count; i++) + apple_dart_writel(dart, 0, + DART_TTBR(dart, sid, i)); + dev_info(dart->dev, + "PCIe-C DART reset stage 38: all TTBRs cleared\n"); + if (apple_dart_debug_stage == 38) + return -EAGAIN; + + value = apple_dart_readl(dart, dart->hw->error); + dev_info(dart->dev, + "PCIe-C DART reset stage 39: error status=%#x\n", value); + if (apple_dart_debug_stage == 39) + return -EAGAIN; + + apple_dart_writel(dart, value, dart->hw->error); + dev_info(dart->dev, + "PCIe-C DART reset stage 40: error status cleared\n"); + if (apple_dart_debug_stage == 40) + return -EAGAIN; + + apple_dart_writel(dart, 0, DART_T8110_ERROR_MASK); + dev_info(dart->dev, + "PCIe-C DART reset stage 41: error mask cleared\n"); + if (apple_dart_debug_stage == 41) + return -EAGAIN; + + i = dart->hw->invalidate_tlb(&stream_map); + dev_info(dart->dev, + "PCIe-C DART reset stage 42: SID0 TLB flush returned %d\n", i); + return i ?: -EAGAIN; +} + static void apple_dart_domain_flush_tlb(struct apple_dart_domain *domain) { int i, j; @@ -624,7 +825,9 @@ static phys_addr_t apple_dart_iova_to_phys(struct iommu_domain *domain, if (!ops) return 0; - return ops->iova_to_phys(ops, iova & dart_domain->mask); + return ops->iova_to_phys(ops, + (iova + dart_domain->dma_offset) & + dart_domain->mask); } static int apple_dart_map_pages(struct iommu_domain *domain, unsigned long iova, @@ -638,7 +841,9 @@ static int apple_dart_map_pages(struct iommu_domain *domain, unsigned long iova, if (!ops) return -ENODEV; - return ops->map_pages(ops, iova & dart_domain->mask, paddr, pgsize, + return ops->map_pages(ops, + (iova + dart_domain->dma_offset) & + dart_domain->mask, paddr, pgsize, pgcount, prot, gfp, mapped); } @@ -650,7 +855,9 @@ static size_t apple_dart_unmap_pages(struct iommu_domain *domain, struct apple_dart_domain *dart_domain = to_dart_domain(domain); struct io_pgtable_ops *ops = dart_domain->pgtbl_ops; - return ops->unmap_pages(ops, iova & dart_domain->mask, pgsize, pgcount, + return ops->unmap_pages(ops, + (iova + dart_domain->dma_offset) & + dart_domain->mask, pgsize, pgcount, gather); } @@ -665,7 +872,8 @@ apple_dart_setup_translation(struct apple_dart_domain *domain, /* enable all streams globally since TCR is used to control isolation */ for (i = 0; i < BITS_TO_U32(dart->num_streams); i++) - writel(U32_MAX, dart->regs + dart->hw->enable_streams + 4 * i); + apple_dart_writel(dart, U32_MAX, + dart->hw->enable_streams + 4 * i); for (i = 0; i < pgtbl_cfg->apple_dart_cfg.n_ttbrs; ++i) { u64 ttbr = virt_to_phys(pgtbl_cfg->apple_dart_cfg.ttbr[i]); @@ -703,7 +911,8 @@ static int apple_dart_finalize_domain(struct apple_dart_domain *dart_domain, struct apple_dart *dart = cfg->stream_maps[0].dart; struct io_pgtable_cfg pgtbl_cfg; dma_addr_t dma_max = dart->dma_max; - u32 ias = min_t(u32, dart->ias, fls64(dma_max)); + u32 ias = min_t(u32, dart->ias, + fls64(dma_max + dart->dma_offset)); int ret = 0; int i, j; @@ -740,13 +949,14 @@ static int apple_dart_finalize_domain(struct apple_dart_domain *dart_domain, sid = find_first_bit(sidmap, dart->num_streams); WARN_ON((sid < 0) || bitmap_weight(sidmap, dart->num_streams) > 1); - ttbr = readl(dart->regs + DART_TTBR(dart, sid, 0)); + ttbr = apple_dart_readl(dart, DART_TTBR(dart, sid, 0)); WARN_ON(!(ttbr & dart->hw->ttbr_valid)); /* If the DART is locked, we need to keep the translation level count. */ if (dart->hw->tcr_4level && dart->ias > 36) { - if (readl(dart->regs + DART_TCR(dart, sid)) & dart->hw->tcr_4level) { + if (apple_dart_readl(dart, DART_TCR(dart, sid)) & + dart->hw->tcr_4level) { if (ias < 37) { dev_info(dart->dev, "Expanded to ias=37 due to lock\n"); pgtbl_cfg.ias = 37; @@ -784,6 +994,7 @@ static int apple_dart_finalize_domain(struct apple_dart_domain *dart_domain, dart_domain->domain.geometry.aperture_start = dart->dma_min; dart_domain->domain.geometry.aperture_end = dma_max; dart_domain->domain.geometry.force_aperture = true; + dart_domain->dma_offset = dart->dma_offset; dart_domain->finalized = true; @@ -1226,10 +1437,10 @@ static irqreturn_t apple_dart_t8020_irq(int irq, void *dev) { struct apple_dart *dart = dev; const char *fault_name = NULL; - u32 error = readl(dart->regs + DART_T8020_ERROR); + u32 error = apple_dart_readl(dart, DART_T8020_ERROR); u32 error_code = FIELD_GET(DART_T8020_ERROR_CODE, error); - u32 addr_lo = readl(dart->regs + DART_T8020_ERROR_ADDR_LO); - u32 addr_hi = readl(dart->regs + DART_T8020_ERROR_ADDR_HI); + u32 addr_lo = apple_dart_readl(dart, DART_T8020_ERROR_ADDR_LO); + u32 addr_hi = apple_dart_readl(dart, DART_T8020_ERROR_ADDR_HI); u64 addr = addr_lo | (((u64)addr_hi) << 32); u8 stream_idx = FIELD_GET(DART_T8020_ERROR_STREAM, error); @@ -1255,7 +1466,7 @@ static irqreturn_t apple_dart_t8020_irq(int irq, void *dev) "translation fault: status:0x%x stream:%d code:0x%x (%s) at 0x%llx", error, stream_idx, error_code, fault_name, addr); - writel(error, dart->regs + DART_T8020_ERROR); + apple_dart_writel(dart, error, DART_T8020_ERROR); return IRQ_HANDLED; } @@ -1263,10 +1474,10 @@ static irqreturn_t apple_dart_t8110_irq(int irq, void *dev) { struct apple_dart *dart = dev; const char *fault_name = NULL; - u32 error = readl(dart->regs + DART_T8110_ERROR); + u32 error = apple_dart_readl(dart, DART_T8110_ERROR); u32 error_code = FIELD_GET(DART_T8110_ERROR_CODE, error); - u32 addr_lo = readl(dart->regs + DART_T8110_ERROR_ADDR_LO); - u32 addr_hi = readl(dart->regs + DART_T8110_ERROR_ADDR_HI); + u32 addr_lo = apple_dart_readl(dart, DART_T8110_ERROR_ADDR_LO); + u32 addr_hi = apple_dart_readl(dart, DART_T8110_ERROR_ADDR_HI); u64 addr = addr_lo | (((u64)addr_hi) << 32); u8 stream_idx = FIELD_GET(DART_T8110_ERROR_STREAM, error); @@ -1294,9 +1505,10 @@ static irqreturn_t apple_dart_t8110_irq(int irq, void *dev) "translation fault: status:0x%x stream:%d code:0x%x (%s) at 0x%llx", error, stream_idx, error_code, fault_name, addr); - writel(error, dart->regs + DART_T8110_ERROR); + apple_dart_writel(dart, error, DART_T8110_ERROR); for (int i = 0; i < BITS_TO_U32(dart->num_streams); i++) - writel(U32_MAX, dart->regs + DART_T8110_ERROR_STREAMS + 4 * i); + apple_dart_writel(dart, U32_MAX, + DART_T8110_ERROR_STREAMS + 4 * i); return IRQ_HANDLED; } @@ -1314,7 +1526,43 @@ static irqreturn_t apple_dart_irq(int irq, void *dev) static bool apple_dart_is_locked(struct apple_dart *dart) { - return !!(readl(dart->regs + dart->hw->lock) & dart->hw->lock_bit); + return !!(apple_dart_readl(dart, dart->hw->lock) & dart->hw->lock_bit); +} + +static void apple_dart_iounmap_np(void *data) +{ + iounmap((__force void __iomem *)data); +} + +static void __iomem * +apple_dart_platform_ioremap_np_resource(struct platform_device *pdev, + unsigned int index, + struct resource **res_out) +{ + struct device *dev = &pdev->dev; + struct resource *res; + void __iomem *regs; + int ret; + + res = platform_get_resource(pdev, IORESOURCE_MEM, index); + if (!res) + return ERR_PTR(-EINVAL); + + if (!devm_request_mem_region(dev, res->start, resource_size(res), + dev_name(dev))) + return ERR_PTR(-EBUSY); + + regs = ioremap_np(res->start, resource_size(res)); + if (!regs) + return ERR_PTR(-ENOMEM); + + ret = devm_add_action_or_reset(dev, apple_dart_iounmap_np, + (__force void *)regs); + if (ret) + return ERR_PTR(ret); + + *res_out = res; + return regs; } static int apple_dart_probe(struct platform_device *pdev) @@ -1325,6 +1573,18 @@ static int apple_dart_probe(struct platform_device *pdev) struct apple_dart *dart; struct device *dev = &pdev->dev; u64 dma_range[2]; + bool debug_stop = false; + bool tunneled = dev->of_node->parent && + of_node_name_prefix(dev->of_node->parent, + "usb4-pcie-tunnel-"); + + if (tunneled) { + dev_info(dev, "PCIe-C DART probe entered\n"); + if (apple_dart_debug_stage == 10) { + dev_info(dev, "PCIe-C DART debug stage 10: probe entry\n"); + return -EAGAIN; + } + } dart = devm_kzalloc(dev, sizeof(*dart), GFP_KERNEL); if (!dart) @@ -1332,16 +1592,43 @@ static int apple_dart_probe(struct platform_device *pdev) dart->dev = dev; dart->hw = of_device_get_match_data(dev); + dart->tunneled = tunneled; spin_lock_init(&dart->lock); + platform_set_drvdata(pdev, dart); - dart->regs = devm_platform_get_and_ioremap_resource(pdev, 0, &res); + /* + * PCIe-C is a cable-powered aperture whose writes must complete before + * the following transaction. The normal arm64 ioremap() mapping is + * Device-nGnRE and allowed the first posted DART reset write to surface as + * an asynchronous external abort at a later instruction. The identical + * reset sequence is stable through /dev/mem's Device-nGnRnE mapping, and + * PCI configuration forwarding already requires the same non-posted + * ordering. Keep ordinary always-on DARTs on their existing mapping. + */ + if (tunneled) + dart->regs = apple_dart_platform_ioremap_np_resource(pdev, 0, + &res); + else + dart->regs = devm_platform_get_and_ioremap_resource(pdev, 0, + &res); if (IS_ERR(dart->regs)) return PTR_ERR(dart->regs); + if (tunneled && apple_dart_debug_stage == 11) { + dev_info(dev, "PCIe-C DART debug stage 11: registers mapped\n"); + return -EAGAIN; + } if (resource_size(res) < 0x4000) { dev_err(dev, "MMIO region too small (%pr)\n", res); return -EINVAL; } + if (tunneled) { + dart->tunables = devm_apple_tunable_parse(dev, dev->of_node, + "apple,tunable", res); + if (IS_ERR(dart->tunables)) + return dev_err_probe(dev, PTR_ERR(dart->tunables), + "failed to parse PCIe-C DART tunables\n"); + } dart->irq = platform_get_irq(pdev, 0); if (dart->irq < 0) @@ -1352,6 +1639,7 @@ static int apple_dart_probe(struct platform_device *pdev) return ret; dart->num_clks = ret; + apple_dart_tunnel_trace(dart, 10, "enable PCIe-C DART clocks"); ret = clk_bulk_prepare_enable(dart->num_clks, dart->clks); if (ret) return ret; @@ -1363,9 +1651,51 @@ static int apple_dart_probe(struct platform_device *pdev) ret = devm_pm_runtime_enable(dev); if (ret) goto err_clk_disable; + if (tunneled && apple_dart_debug_stage == 12) { + dev_info(dev, + "PCIe-C DART debug stage 12: power and clocks enabled\n"); + ret = -EAGAIN; + debug_stop = true; + goto err_clk_disable; + } + if (tunneled) { + dev_info(dev, "applying %zu firmware PCIe-C DART tunables\n", + dart->tunables->sz); + apple_dart_tunnel_trace(dart, 11, "apply firmware DART tunables"); + apple_dart_apply_tunables(dart); + dev_info(dev, "firmware PCIe-C DART tunables applied\n"); + if (apple_dart_debug_stage == 13) { + dev_info(dev, + "PCIe-C DART debug stage 13: firmware tunables applied\n"); + ret = -EAGAIN; + debug_stop = true; + goto err_clk_disable; + } + } - dart_params[0] = readl(dart->regs + DART_PARAMS1); - dart_params[1] = readl(dart->regs + DART_PARAMS2); + /* + * The cable-powered T8110 DART does not provide a safely probeable + * parameter block at this point in PCIe tunnel activation. The SError + * raised by these reads is asynchronous, which made the following trace + * boundary look guilty on different boots. Apple instead publishes the + * complete immutable topology in ADT: 16 KiB pages, 42-bit addresses and + * 64 SIDs. Use that description before making any DART MMIO access. + */ + if (tunneled) { + dart->pgsize = 1 << DART_PCIEC_PAGE_SHIFT; + dart->supports_bypass = true; + dart->ias = DART_PCIEC_ADDR_WIDTH; + dart->oas = DART_PCIEC_ADDR_WIDTH; + dart->num_streams = DART_PCIEC_STREAMS; + dart->four_level = true; + dev_info(dev, + "PCIe-C DART using fixed topology: pagesize %#x, %u streams, AS %u -> %u\n", + dart->pgsize, dart->num_streams, dart->ias, dart->oas); + goto params_done; + } + + dart_params[0] = apple_dart_readl(dart, DART_PARAMS1); + dart_params[1] = apple_dart_readl(dart, DART_PARAMS2); dart->pgsize = 1 << FIELD_GET(DART_PARAMS1_PAGE_SHIFT, dart_params[0]); dart->supports_bypass = dart_params[1] & DART_PARAMS2_BYPASS_SUPPORT; @@ -1378,17 +1708,20 @@ static int apple_dart_probe(struct platform_device *pdev) break; case DART_T8110: - dart_params[2] = readl(dart->regs + DART_T8110_PARAMS3); - dart_params[3] = readl(dart->regs + DART_T8110_PARAMS4); + dart_params[2] = apple_dart_readl(dart, DART_T8110_PARAMS3); dart->ias = FIELD_GET(DART_T8110_PARAMS3_VA_WIDTH, dart_params[2]); dart->oas = FIELD_GET(DART_T8110_PARAMS3_PA_WIDTH, dart_params[2]); - dart->num_streams = FIELD_GET(DART_T8110_PARAMS4_NUM_SIDS, dart_params[3]); + dart_params[3] = apple_dart_readl(dart, DART_T8110_PARAMS4); + dart->num_streams = FIELD_GET(DART_T8110_PARAMS4_NUM_SIDS, + dart_params[3]); dart->four_level = dart->ias > 36; break; } +params_done: dart->dma_min = 0; dart->dma_max = DMA_BIT_MASK(dart->ias); + dart->dma_offset = 0; ret = of_property_read_u64_array(dev->of_node, "apple,dma-range", dma_range, 2); if (ret == -EINVAL) { @@ -1408,6 +1741,46 @@ static int apple_dart_probe(struct platform_device *pdev) &dart->dma_min, &dart->dma_max); } + ret = of_property_read_u64(dev->of_node, "apple,dma-offset", + &dart->dma_offset); + if (ret == -EINVAL) { + dart->dma_offset = 0; + ret = 0; + } else if (ret) { + goto err_clk_disable; + } else if (dart->dma_offset > DMA_BIT_MASK(dart->ias) || + dart->dma_max > DMA_BIT_MASK(dart->ias) - dart->dma_offset) { + dev_err(dev, "Invalid DMA offset for ias=%u\n", dart->ias); + ret = -EINVAL; + goto err_clk_disable; + } else { + dev_info(dev, + "aliasing DMA range %pad..%pad at DART offset %pad\n", + &dart->dma_min, &dart->dma_max, &dart->dma_offset); + } + + /* + * Older FDTs described the T602x PCIe-C DART-visible 1 TiB window + * directly. PCIe endpoints cannot issue those addresses: the host bridge + * accepts the upper 2 GiB of its 32-bit bus window and adds the 1 TiB + * alias before DART translation. Keep those FDTs bootable while returning + * the low bus address to DMA clients. + */ + if (!dart->dma_offset && tunneled && + (of_machine_is_compatible("apple,t6020") || + of_machine_is_compatible("apple,t6021")) && + ((dart->dma_min == 0 && + dart->dma_max == DMA_BIT_MASK(dart->ias)) || + (dart->dma_min == BIT_ULL(40) && + dart->dma_max == BIT_ULL(40) + BIT_ULL(32) - 1))) { + dart->dma_min = BIT_ULL(31); + dart->dma_max = DMA_BIT_MASK(32); + dart->dma_offset = BIT_ULL(40); + dev_info(dev, + "using T602x PCIe-C bus DMA window %pad..%pad at DART offset %pad\n", + &dart->dma_min, &dart->dma_max, &dart->dma_offset); + } + if (dart->num_streams > DART_MAX_STREAMS) { dev_err(&pdev->dev, "Too many streams (%d > %d)\n", dart->num_streams, DART_MAX_STREAMS); @@ -1415,28 +1788,83 @@ static int apple_dart_probe(struct platform_device *pdev) goto err_clk_disable; } - dart->locked = apple_dart_is_locked(dart); + /* + * The hot-plug PCIe-C DART is freshly made available for this tunnel and + * Apple's ADT does not describe a retained/locked translation context. + * Its T8110 PROTECT register is not readable from this activation state, + * so do not turn that optional observation into a fatal external abort. + */ + if (tunneled) { + dart->locked = false; + dev_info(dev, "PCIe-C DART treating fresh tunnel context as unlocked\n"); + } else { + dart->locked = apple_dart_is_locked(dart); + } + if (tunneled && apple_dart_debug_stage == 17) { + dev_info(dev, "PCIe-C DART debug stage 17: lock=%u\n", + dart->locked); + ret = -EAGAIN; + debug_stop = true; + goto err_clk_disable; + } + if (!dart->locked && tunneled && apple_dart_debug_stage >= 29) { + if (apple_dart_debug_stage == 29) { + dev_info(dev, + "PCIe-C DART debug stage 29: reset boundary reached\n"); + ret = -EAGAIN; + debug_stop = true; + goto err_clk_disable; + } + ret = apple_dart_debug_reset(dart); + debug_stop = ret == -EAGAIN; + goto err_clk_disable; + } if (!dart->locked) { ret = apple_dart_hw_reset(dart); if (ret) goto err_clk_disable; } + if (tunneled && apple_dart_debug_stage == 18) { + dev_info(dev, "PCIe-C DART debug stage 18: reset complete\n"); + ret = -EAGAIN; + debug_stop = true; + goto err_clk_disable; + } + apple_dart_tunnel_trace(dart, 30, "register DART fault IRQ"); ret = request_irq(dart->irq, apple_dart_irq, IRQF_SHARED, "apple-dart fault handler", dart); if (ret) goto err_clk_disable; + if (tunneled && apple_dart_debug_stage == 19) { + dev_info(dev, "PCIe-C DART debug stage 19: IRQ registered\n"); + ret = -EAGAIN; + debug_stop = true; + goto err_free_irq; + } - platform_set_drvdata(pdev, dart); - + apple_dart_tunnel_trace(dart, 31, "register DART sysfs device"); ret = iommu_device_sysfs_add(&dart->iommu, dev, NULL, "apple-dart.%s", dev_name(&pdev->dev)); if (ret) goto err_free_irq; + if (tunneled && apple_dart_debug_stage == 20) { + dev_info(dev, "PCIe-C DART debug stage 20: sysfs registered\n"); + ret = -EAGAIN; + debug_stop = true; + goto err_sysfs_remove; + } + apple_dart_tunnel_trace(dart, 32, "register DART with IOMMU core"); ret = iommu_device_register(&dart->iommu, &apple_dart_iommu_ops, dev); if (ret) goto err_sysfs_remove; + if (tunneled && apple_dart_debug_stage == 21) { + dev_info(dev, "PCIe-C DART debug stage 21: IOMMU registered\n"); + ret = -EAGAIN; + debug_stop = true; + goto err_iommu_unregister; + } pm_runtime_put(dev); @@ -1447,12 +1875,18 @@ static int apple_dart_probe(struct platform_device *pdev) dart->pgsize > PAGE_SIZE, dart->locked, dart->ias, dart->oas); return 0; +err_iommu_unregister: + iommu_device_unregister(&dart->iommu); err_sysfs_remove: iommu_device_sysfs_remove(&dart->iommu); err_free_irq: free_irq(dart->irq, dart); err_clk_disable: - pm_runtime_put(dev); + /* A diagnostic exit must not schedule runtime suspend during teardown. */ + if (debug_stop) + pm_runtime_put_noidle(dev); + else + pm_runtime_put(dev); clk_bulk_disable_unprepare(dart->num_clks, dart->clks); return ret; @@ -1587,10 +2021,12 @@ static __maybe_unused int apple_dart_suspend(struct device *dev) return 0; for (sid = 0; sid < dart->num_streams; sid++) { - dart->save_tcr[sid] = readl(dart->regs + DART_TCR(dart, sid)); + dart->save_tcr[sid] = apple_dart_readl(dart, + DART_TCR(dart, sid)); for (idx = 0; idx < dart->hw->ttbr_count; idx++) dart->save_ttbr[sid][idx] = - readl(dart->regs + DART_TTBR(dart, sid, idx)); + apple_dart_readl(dart, + DART_TTBR(dart, sid, idx)); } return 0; @@ -1614,9 +2050,10 @@ static __maybe_unused int apple_dart_resume(struct device *dev) for (sid = 0; sid < dart->num_streams; sid++) { for (idx = 0; idx < dart->hw->ttbr_count; idx++) - writel(dart->save_ttbr[sid][idx], - dart->regs + DART_TTBR(dart, sid, idx)); - writel(dart->save_tcr[sid], dart->regs + DART_TCR(dart, sid)); + apple_dart_writel(dart, dart->save_ttbr[sid][idx], + DART_TTBR(dart, sid, idx)); + apple_dart_writel(dart, dart->save_tcr[sid], + DART_TCR(dart, sid)); } return 0; From fa72d43fb7d709c89bf6472c2fa0671f923ccd9f Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Fri, 28 Aug 2026 00:06:24 -0400 Subject: [PATCH 25/51] nvme-pci: order tunneled non-posted queue accesses Signed-off-by: DjDeveloperr --- drivers/nvme/host/pci.c | 33 ++++++++++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/drivers/nvme/host/pci.c b/drivers/nvme/host/pci.c index 0de6844ca56c0c..758fa1d121abf7 100644 --- a/drivers/nvme/host/pci.c +++ b/drivers/nvme/host/pci.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -727,8 +728,12 @@ static inline void nvme_write_sq_db(struct nvme_queue *nvmeq, bool write_sq) } if (nvme_dbbuf_update_and_check_event(nvmeq->sq_tail, - nvmeq->dbbuf_sq_db, nvmeq->dbbuf_sq_ei)) + nvmeq->dbbuf_sq_db, nvmeq->dbbuf_sq_ei)) { writel(nvmeq->sq_tail, nvmeq->q_db); + if (pci_resource_flags(to_pci_dev(nvmeq->dev->dev), 0) & + IORESOURCE_MEM_NONPOSTED) + readl(nvmeq->dev->bar + NVME_REG_CSTS); + } nvmeq->last_sq_tail = nvmeq->sq_tail; } @@ -1548,8 +1553,12 @@ static inline void nvme_ring_cq_doorbell(struct nvme_queue *nvmeq) u16 head = nvmeq->cq_head; if (nvme_dbbuf_update_and_check_event(head, nvmeq->dbbuf_cq_db, - nvmeq->dbbuf_cq_ei)) + nvmeq->dbbuf_cq_ei)) { writel(head, nvmeq->q_db + nvmeq->dev->db_stride); + if (pci_resource_flags(to_pci_dev(nvmeq->dev->dev), 0) & + IORESOURCE_MEM_NONPOSTED) + readl(nvmeq->dev->bar + NVME_REG_CSTS); + } } static inline struct blk_mq_tags *nvme_queue_tagset(struct nvme_queue *nvmeq) @@ -1631,6 +1640,21 @@ static irqreturn_t nvme_irq(int irq, void *data) { struct nvme_queue *nvmeq = data; DEFINE_IO_COMP_BATCH(iob); + unsigned int retry; + + /* + * PCIe-C can deliver the tunneled MSI before the preceding completion + * write is visible to the CPU. Wait briefly for the CQ phase update on + * BARs marked non-posted by the Apple host controller. + */ + if (pci_resource_flags(to_pci_dev(nvmeq->dev->dev), 0) & + IORESOURCE_MEM_NONPOSTED) { + for (retry = 0; retry < 1000 && !nvme_cqe_pending(nvmeq); + retry++) { + udelay(1); + dma_rmb(); + } + } if (nvme_poll_cq(nvmeq, &iob)) { if (!rq_list_empty(&iob.req_list)) @@ -2318,7 +2342,10 @@ static int nvme_remap_bar(struct nvme_dev *dev, unsigned long size) return -ENOMEM; if (dev->bar) iounmap(dev->bar); - dev->bar = ioremap(pci_resource_start(pdev, 0), size); + if (pci_resource_flags(pdev, 0) & IORESOURCE_MEM_NONPOSTED) + dev->bar = ioremap_np(pci_resource_start(pdev, 0), size); + else + dev->bar = ioremap(pci_resource_start(pdev, 0), size); if (!dev->bar) { dev->bar_mapped_size = 0; return -ENOMEM; From 00c28cc11afe5a0b0864add250149b983d6a8915 Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Fri, 28 Aug 2026 00:07:39 -0400 Subject: [PATCH 26/51] thunderbolt: apple: complete PCIe-C build dependencies Signed-off-by: DjDeveloperr --- drivers/pci/controller/Kconfig | 1 + drivers/thunderbolt/Kconfig | 1 + drivers/thunderbolt/tb_regs.h | 1 + 3 files changed, 3 insertions(+) diff --git a/drivers/pci/controller/Kconfig b/drivers/pci/controller/Kconfig index 420db1255346f9..e3bb6ff1b6d714 100644 --- a/drivers/pci/controller/Kconfig +++ b/drivers/pci/controller/Kconfig @@ -46,6 +46,7 @@ config PCIE_APPLE depends on OF depends on PCI_MSI depends on PAGE_SIZE_16KB || COMPILE_TEST + select APPLE_TUNABLE select PCI_HOST_COMMON select IRQ_MSI_LIB help diff --git a/drivers/thunderbolt/Kconfig b/drivers/thunderbolt/Kconfig index 69f266371c3e47..d981f6bf2630d7 100644 --- a/drivers/thunderbolt/Kconfig +++ b/drivers/thunderbolt/Kconfig @@ -65,6 +65,7 @@ config USB4_APPLE_SOC depends on ARCH_APPLE || COMPILE_TEST depends on OF && APPLE_RTKIT depends on TYPEC + depends on PCIE_APPLE select APPLE_TUNABLE help Say Y here to add support for the USB4 and Thunderbolt host diff --git a/drivers/thunderbolt/tb_regs.h b/drivers/thunderbolt/tb_regs.h index c0bf136236e674..dc569acdfc6adf 100644 --- a/drivers/thunderbolt/tb_regs.h +++ b/drivers/thunderbolt/tb_regs.h @@ -473,6 +473,7 @@ struct tb_regs_port_header { /* PCIe adapter registers */ #define ADP_PCIE_CS_0 0x00 +#define ADP_PCIE_CS_0_LA BIT(16) #define ADP_PCIE_CS_0_PE BIT(31) #define ADP_PCIE_CS_1 0x01 #define ADP_PCIE_CS_1_EE BIT(0) From 8174ba3a32906cb659823210a32d876093bef11d Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Fri, 28 Aug 2026 22:48:43 -0400 Subject: [PATCH 27/51] iommu: apple-dart: support retained PCIe-C tunnel domains Signed-off-by: DjDeveloperr --- drivers/iommu/apple-dart.c | 240 ++++--------------------------------- 1 file changed, 23 insertions(+), 217 deletions(-) diff --git a/drivers/iommu/apple-dart.c b/drivers/iommu/apple-dart.c index ddc265ef74b342..bf3648125a78cf 100644 --- a/drivers/iommu/apple-dart.c +++ b/drivers/iommu/apple-dart.c @@ -45,20 +45,6 @@ #define DART_PCIEC_ADDR_WIDTH 42 #define DART_PCIEC_STREAMS 64 -/* - * Temporary bring-up gate for the cable-powered DART behind PCIe-C. The - * normal storage, display and USB4 DARTs never consult this parameter. - */ -static unsigned int apple_dart_debug_stage; -module_param_named(debug_stage, apple_dart_debug_stage, uint, 0644); -MODULE_PARM_DESC(debug_stage, - "Stop tunneled PCIe-C DART setup after the selected debug phase"); - -static unsigned int apple_dart_trace_delay_ms; -module_param_named(tunnel_trace_delay_ms, apple_dart_trace_delay_ms, uint, 0644); -MODULE_PARM_DESC(tunnel_trace_delay_ms, - "Delay after PCIe-C DART trace boundaries (0 disables tracing)"); - /* Common registers */ #define DART_PARAMS1 0x00 @@ -246,6 +232,7 @@ struct apple_dart { u32 four_level : 1; u32 locked : 1; u32 tunneled : 1; + u32 power_retained : 1; struct apple_tunable *tunables; dma_addr_t dma_min; @@ -310,17 +297,6 @@ static void apple_dart_apply_tunables(struct apple_dart *dart) } } -static void apple_dart_tunnel_trace(struct apple_dart *dart, - unsigned int step, const char *operation) -{ - if (!dart->tunneled || !READ_ONCE(apple_dart_trace_delay_ms)) - return; - - dev_info(dart->dev, "PCIe-C DART trace %u armed: %s\n", - step, operation); - msleep(READ_ONCE(apple_dart_trace_delay_ms)); -} - /* * Convenience struct to identify streams. * @@ -647,133 +623,24 @@ static int apple_dart_hw_reset(struct apple_dart *dart) bitmap_zero(stream_map.sidmap, DART_MAX_STREAMS); bitmap_set(stream_map.sidmap, 0, dart->num_streams); - apple_dart_tunnel_trace(dart, 20, "disable DMA for every stream"); apple_dart_hw_disable_dma(&stream_map); - apple_dart_tunnel_trace(dart, 21, "clear every stream TTBR"); apple_dart_hw_clear_all_ttbrs(&stream_map); /* enable all streams globally since TCR is used to control isolation */ - apple_dart_tunnel_trace(dart, 22, "enable all streams globally"); for (i = 0; i < BITS_TO_U32(dart->num_streams); i++) apple_dart_writel(dart, U32_MAX, dart->hw->enable_streams + 4 * i); /* clear any pending errors before the interrupt is unmasked */ - apple_dart_tunnel_trace(dart, 23, "read and clear DART error status"); apple_dart_writel(dart, apple_dart_readl(dart, dart->hw->error), dart->hw->error); - apple_dart_tunnel_trace(dart, 24, "clear T8110 error mask"); if (dart->hw->type == DART_T8110) apple_dart_writel(dart, 0, DART_T8110_ERROR_MASK); - apple_dart_tunnel_trace(dart, 25, "invalidate every stream TLB"); return dart->hw->invalidate_tlb(&stream_map); } -static int apple_dart_debug_reset(struct apple_dart *dart) -{ - struct apple_dart_stream_map stream_map; - u32 value; - int i, sid; - - if (dart->hw->type != DART_T8110 || apple_dart_debug_stage < 30) - return 0; - - stream_map.dart = dart; - bitmap_zero(stream_map.sidmap, DART_MAX_STREAMS); - set_bit(0, stream_map.sidmap); - - apple_dart_writel(dart, U32_MAX, dart->hw->enable_streams); - dev_info(dart->dev, - "PCIe-C DART reset stage 30: stream word 0 enabled\n"); - if (apple_dart_debug_stage == 30) - return -EAGAIN; - - for (i = 1; i < BITS_TO_U32(dart->num_streams); i++) - apple_dart_writel(dart, U32_MAX, - dart->hw->enable_streams + 4 * i); - dev_info(dart->dev, - "PCIe-C DART reset stage 31: all stream words enabled\n"); - if (apple_dart_debug_stage == 31) - return -EAGAIN; - - value = apple_dart_readl(dart, DART_TCR(dart, 0)); - dev_info(dart->dev, - "PCIe-C DART reset stage 32: SID0 TCR initial value=%#x\n", - value); - if (apple_dart_debug_stage == 32) - return -EAGAIN; - - apple_dart_writel(dart, dart->hw->tcr_disabled, - DART_TCR(dart, 0)); - dev_info(dart->dev, - "PCIe-C DART reset stage 33: SID0 TCR disabled\n"); - if (apple_dart_debug_stage == 33) - return -EAGAIN; - - value = apple_dart_readl(dart, DART_TCR(dart, 0)); - dev_info(dart->dev, - "PCIe-C DART reset stage 34: SID0 TCR readback=%#x\n", - value); - if (apple_dart_debug_stage == 34) - return -EAGAIN; - - for (sid = 1; sid < dart->num_streams; sid++) - apple_dart_writel(dart, dart->hw->tcr_disabled, - DART_TCR(dart, sid)); - dev_info(dart->dev, - "PCIe-C DART reset stage 35: all %u TCRs disabled\n", - dart->num_streams); - if (apple_dart_debug_stage == 35) - return -EAGAIN; - - apple_dart_writel(dart, 0, DART_TTBR(dart, 0, 0)); - dev_info(dart->dev, - "PCIe-C DART reset stage 36: SID0 TTBR0 cleared\n"); - if (apple_dart_debug_stage == 36) - return -EAGAIN; - - value = apple_dart_readl(dart, DART_TTBR(dart, 0, 0)); - dev_info(dart->dev, - "PCIe-C DART reset stage 37: SID0 TTBR0 readback=%#x\n", - value); - if (apple_dart_debug_stage == 37) - return -EAGAIN; - - for (sid = 1; sid < dart->num_streams; sid++) - for (i = 0; i < dart->hw->ttbr_count; i++) - apple_dart_writel(dart, 0, - DART_TTBR(dart, sid, i)); - dev_info(dart->dev, - "PCIe-C DART reset stage 38: all TTBRs cleared\n"); - if (apple_dart_debug_stage == 38) - return -EAGAIN; - - value = apple_dart_readl(dart, dart->hw->error); - dev_info(dart->dev, - "PCIe-C DART reset stage 39: error status=%#x\n", value); - if (apple_dart_debug_stage == 39) - return -EAGAIN; - - apple_dart_writel(dart, value, dart->hw->error); - dev_info(dart->dev, - "PCIe-C DART reset stage 40: error status cleared\n"); - if (apple_dart_debug_stage == 40) - return -EAGAIN; - - apple_dart_writel(dart, 0, DART_T8110_ERROR_MASK); - dev_info(dart->dev, - "PCIe-C DART reset stage 41: error mask cleared\n"); - if (apple_dart_debug_stage == 41) - return -EAGAIN; - - i = dart->hw->invalidate_tlb(&stream_map); - dev_info(dart->dev, - "PCIe-C DART reset stage 42: SID0 TLB flush returned %d\n", i); - return i ?: -EAGAIN; -} - static void apple_dart_domain_flush_tlb(struct apple_dart_domain *domain) { int i, j; @@ -1569,23 +1436,15 @@ static int apple_dart_probe(struct platform_device *pdev) { int ret; u32 dart_params[4]; + struct device_node *pd_np; struct resource *res; struct apple_dart *dart; struct device *dev = &pdev->dev; u64 dma_range[2]; - bool debug_stop = false; bool tunneled = dev->of_node->parent && of_node_name_prefix(dev->of_node->parent, "usb4-pcie-tunnel-"); - if (tunneled) { - dev_info(dev, "PCIe-C DART probe entered\n"); - if (apple_dart_debug_stage == 10) { - dev_info(dev, "PCIe-C DART debug stage 10: probe entry\n"); - return -EAGAIN; - } - } - dart = devm_kzalloc(dev, sizeof(*dart), GFP_KERNEL); if (!dart) return -ENOMEM; @@ -1593,6 +1452,14 @@ static int apple_dart_probe(struct platform_device *pdev) dart->dev = dev; dart->hw = of_device_get_match_data(dev); dart->tunneled = tunneled; + if (tunneled) { + pd_np = of_parse_phandle(dev->of_node, "power-domains", 0); + if (pd_np) { + dart->power_retained = + of_property_read_bool(pd_np, "apple,always-on"); + of_node_put(pd_np); + } + } spin_lock_init(&dart->lock); platform_set_drvdata(pdev, dart); @@ -1613,10 +1480,6 @@ static int apple_dart_probe(struct platform_device *pdev) &res); if (IS_ERR(dart->regs)) return PTR_ERR(dart->regs); - if (tunneled && apple_dart_debug_stage == 11) { - dev_info(dev, "PCIe-C DART debug stage 11: registers mapped\n"); - return -EAGAIN; - } if (resource_size(res) < 0x4000) { dev_err(dev, "MMIO region too small (%pr)\n", res); @@ -1639,7 +1502,6 @@ static int apple_dart_probe(struct platform_device *pdev) return ret; dart->num_clks = ret; - apple_dart_tunnel_trace(dart, 10, "enable PCIe-C DART clocks"); ret = clk_bulk_prepare_enable(dart->num_clks, dart->clks); if (ret) return ret; @@ -1651,26 +1513,11 @@ static int apple_dart_probe(struct platform_device *pdev) ret = devm_pm_runtime_enable(dev); if (ret) goto err_clk_disable; - if (tunneled && apple_dart_debug_stage == 12) { - dev_info(dev, - "PCIe-C DART debug stage 12: power and clocks enabled\n"); - ret = -EAGAIN; - debug_stop = true; - goto err_clk_disable; - } if (tunneled) { dev_info(dev, "applying %zu firmware PCIe-C DART tunables\n", dart->tunables->sz); - apple_dart_tunnel_trace(dart, 11, "apply firmware DART tunables"); apple_dart_apply_tunables(dart); dev_info(dev, "firmware PCIe-C DART tunables applied\n"); - if (apple_dart_debug_stage == 13) { - dev_info(dev, - "PCIe-C DART debug stage 13: firmware tunables applied\n"); - ret = -EAGAIN; - debug_stop = true; - goto err_clk_disable; - } } /* @@ -1796,75 +1643,28 @@ static int apple_dart_probe(struct platform_device *pdev) */ if (tunneled) { dart->locked = false; - dev_info(dev, "PCIe-C DART treating fresh tunnel context as unlocked\n"); } else { dart->locked = apple_dart_is_locked(dart); } - if (tunneled && apple_dart_debug_stage == 17) { - dev_info(dev, "PCIe-C DART debug stage 17: lock=%u\n", - dart->locked); - ret = -EAGAIN; - debug_stop = true; - goto err_clk_disable; - } - if (!dart->locked && tunneled && apple_dart_debug_stage >= 29) { - if (apple_dart_debug_stage == 29) { - dev_info(dev, - "PCIe-C DART debug stage 29: reset boundary reached\n"); - ret = -EAGAIN; - debug_stop = true; - goto err_clk_disable; - } - ret = apple_dart_debug_reset(dart); - debug_stop = ret == -EAGAIN; - goto err_clk_disable; - } if (!dart->locked) { ret = apple_dart_hw_reset(dart); if (ret) goto err_clk_disable; } - if (tunneled && apple_dart_debug_stage == 18) { - dev_info(dev, "PCIe-C DART debug stage 18: reset complete\n"); - ret = -EAGAIN; - debug_stop = true; - goto err_clk_disable; - } - apple_dart_tunnel_trace(dart, 30, "register DART fault IRQ"); ret = request_irq(dart->irq, apple_dart_irq, IRQF_SHARED, "apple-dart fault handler", dart); if (ret) goto err_clk_disable; - if (tunneled && apple_dart_debug_stage == 19) { - dev_info(dev, "PCIe-C DART debug stage 19: IRQ registered\n"); - ret = -EAGAIN; - debug_stop = true; - goto err_free_irq; - } - apple_dart_tunnel_trace(dart, 31, "register DART sysfs device"); ret = iommu_device_sysfs_add(&dart->iommu, dev, NULL, "apple-dart.%s", dev_name(&pdev->dev)); if (ret) goto err_free_irq; - if (tunneled && apple_dart_debug_stage == 20) { - dev_info(dev, "PCIe-C DART debug stage 20: sysfs registered\n"); - ret = -EAGAIN; - debug_stop = true; - goto err_sysfs_remove; - } - apple_dart_tunnel_trace(dart, 32, "register DART with IOMMU core"); ret = iommu_device_register(&dart->iommu, &apple_dart_iommu_ops, dev); if (ret) goto err_sysfs_remove; - if (tunneled && apple_dart_debug_stage == 21) { - dev_info(dev, "PCIe-C DART debug stage 21: IOMMU registered\n"); - ret = -EAGAIN; - debug_stop = true; - goto err_iommu_unregister; - } pm_runtime_put(dev); @@ -1875,18 +1675,12 @@ static int apple_dart_probe(struct platform_device *pdev) dart->pgsize > PAGE_SIZE, dart->locked, dart->ias, dart->oas); return 0; -err_iommu_unregister: - iommu_device_unregister(&dart->iommu); err_sysfs_remove: iommu_device_sysfs_remove(&dart->iommu); err_free_irq: free_irq(dart->irq, dart); err_clk_disable: - /* A diagnostic exit must not schedule runtime suspend during teardown. */ - if (debug_stop) - pm_runtime_put_noidle(dev); - else - pm_runtime_put(dev); + pm_runtime_put(dev); clk_bulk_disable_unprepare(dart->num_clks, dart->clks); return ret; @@ -2016,6 +1810,10 @@ static __maybe_unused int apple_dart_suspend(struct device *dev) struct apple_dart *dart = dev_get_drvdata(dev); unsigned int sid, idx; + /* The tunneled DART has no state to save when its domain stays on. */ + if (dart->power_retained) + return 0; + /* Locked DARTs can't be restored so skip saving their registers. */ if (dart->locked) return 0; @@ -2038,6 +1836,14 @@ static __maybe_unused int apple_dart_resume(struct device *dev) unsigned int sid, idx; int ret; + /* + * PCIe-C DARTs on an apple,always-on domain retain their translation + * state. Resetting one here is both unnecessary and earlier than Apple's + * force-active call at the end of PCIe-C port resume. + */ + if (dart->power_retained) + return 0; + /* Locked DARTs can't be restored, and they should not need it */ if (dart->locked) return 0; From 544b71a5d1449217bfbe4a87698096fc51de7af4 Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Fri, 28 Aug 2026 22:52:01 -0400 Subject: [PATCH 28/51] PCI: apple: quiesce and restore tunneled PCIe-C links Signed-off-by: DjDeveloperr --- drivers/pci/controller/pcie-apple.c | 652 +++++++++++++++++++++++++++- include/linux/pci-apple.h | 9 + 2 files changed, 653 insertions(+), 8 deletions(-) create mode 100644 include/linux/pci-apple.h diff --git a/drivers/pci/controller/pcie-apple.c b/drivers/pci/controller/pcie-apple.c index 642e0bbc098549..9ae26ca656a6d2 100644 --- a/drivers/pci/controller/pcie-apple.c +++ b/drivers/pci/controller/pcie-apple.c @@ -29,8 +29,12 @@ #include #include #include +#include +#include #include +#include +#include "../pci.h" #include "pci-host-common.h" static int link_up_timeout = 500; @@ -130,6 +134,11 @@ MODULE_PARM_DESC(link_up_timeout, "PCIe link training timeout in milliseconds"); #define PORT_TUNSTAT_PERST_ON BIT(0) #define PORT_TUNSTAT_PERST_ACK_PEND BIT(1) #define PORT_PREFMEM_ENABLE 0x00994 +#define PORT_COUNTER_CTRL 0x04020 +#define PORT_COUNTER_ENABLE 0x3 + +#define PCIEC_INTR2AXI_CTRL 0x00080 +#define PCIEC_INTR2AXI_ENABLE BIT(0) /* T602x (M2-pro and co) */ #define PORT_T602X_MSIADDR 0x016c @@ -197,6 +206,15 @@ struct apple_pcie { struct mutex lock; struct device *dev; void __iomem *base; + void __iomem *fabric_base; + void __iomem *debug_base; + void __iomem *intr2axi_base; + struct pci_config_window *cfg; + struct apple_tunable *rc_tunable; + struct apple_tunable *fabric_tunable; + struct apple_tunable *debug_tunable; + bool power_retained; + bool bus_stopped; const struct hw_info *hw; unsigned long *bitmap; struct list_head ports; @@ -215,8 +233,11 @@ struct apple_pcie_port { struct irq_domain *domain; struct list_head entry; unsigned long *sid_map; + u32 *saved_rid2sid; + struct apple_tunable *tunable; unsigned int irq; unsigned int link_irqs[2]; + u32 saved_intmask; int sid_map_sz; int idx; bool started; @@ -278,6 +299,40 @@ static void apple_pcie_port_rmw_clear(struct apple_pcie_port *port, u32 clear, offset); } +static inline u32 apple_pcie_tunnel_readl(void __iomem *base, u32 offset) +{ + u32 value = readl_relaxed(base + offset); + + mb(); + isb(); + return value; +} + +static inline void apple_pcie_tunnel_writel(void __iomem *base, u32 value, + u32 offset) +{ + writel_relaxed(value, base + offset); + mb(); + isb(); +} + +static void apple_pcie_tunnel_apply_tunable(void __iomem *base, + struct apple_tunable *tunable) +{ + size_t i; + + for (i = 0; i < tunable->sz; i++) { + u32 old, value; + + old = apple_pcie_tunnel_readl(base, tunable->values[i].offset); + value = (old & ~tunable->values[i].mask) | + tunable->values[i].value; + if (value != old) + apple_pcie_tunnel_writel(base, value, + tunable->values[i].offset); + } +} + static void apple_msi_compose_msg(struct irq_data *data, struct msi_msg *msg) { msg->address_hi = upper_32_bits(DOORBELL_ADDR); @@ -590,6 +645,9 @@ static void apple_pcie_port_unregister_irqs(struct apple_pcie_port *port) } } +static u32 apple_pcie_rid2sid_write(struct apple_pcie_port *port, + int idx, u32 val); + static int apple_pcie_tunnel_release_reset(struct apple_pcie_port *port) { u32 stat; @@ -611,11 +669,257 @@ static int apple_pcie_tunnel_release_reset(struct apple_pcie_port *port) return ret; } -static void apple_pcie_tunnel_stop(struct apple_pcie_port *port) +struct apple_pcie_reset_reg { + u16 offset; + u32 value; +}; + +/* Port hardware reset sequence, identical on T8103 and T600x PCIe-C. */ +static const struct apple_pcie_reset_reg apple_pcie_tunnel_reset_regs[] = { + { 0x08c, 0x00000110 }, + { 0x100, 0xffffffff }, + { 0x148, 0xffffffff }, + { 0x210, 0xffffffff }, + { 0x080, 0x00000000 }, + { 0x084, 0x00000000 }, + { 0x104, 0xffffffff }, + { 0x124, 0x00000000 }, + { 0x128, 0x00000000 }, + { 0x168, 0x00000000 }, + { 0x13c, 0x00000010 }, + { 0x800, 0x00100100 }, + { 0x808, 0x00100045 }, + { 0x810, 0x00000100 }, + { 0x814, 0x00000000 }, + { 0x130, 0x00000208 }, + { 0x140, 0x00000010 }, + { 0x144, 0x00253770 }, + { 0x21c, 0x00000000 }, + { 0x81c, 0x00000000 }, + { 0x824, 0x00000000 }, +}; + +static void apple_pcie_tunnel_reset_hardware(struct apple_pcie_port *port) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(apple_pcie_tunnel_reset_regs); i++) + apple_pcie_port_writel(port, + apple_pcie_tunnel_reset_regs[i].value, + apple_pcie_tunnel_reset_regs[i].offset); + + for (i = 0; i < port->pcie->hw->max_rid2sid; i++) + apple_pcie_rid2sid_write(port, i, 0); +} + +static int apple_pcie_tunnel_reinitialize(struct apple_pcie_port *port) { struct apple_pcie *pcie = port->pcie; u32 stat; - int ret; + int i, ret; + + /* + * A cable disconnect intentionally stops PCIe-C after destroying its + * host bridge. Its always-on power domain retains that stopped state, so + * a later platform reprobe cannot rely on the original m1n1 handoff. + * Replay the port enable sequence, which is the same on T8103 and + * T600x, while ACIO's tunnel and Intr2AXI aperture are live. + */ + apple_pcie_tunnel_apply_tunable(pcie->debug_base, + pcie->debug_tunable); + apple_pcie_tunnel_apply_tunable(pcie->fabric_base, + pcie->fabric_tunable); + apple_pcie_tunnel_reset_hardware(port); + apple_pcie_tunnel_apply_tunable(port->base, port->tunable); + + apple_pcie_port_rmw_set(port, PORT_PERST_OFF, + pcie->hw->port_perst); + apple_pcie_port_rmw_set(port, PORT_APPCLK_EN, PORT_APPCLK); + ret = read_poll_timeout_atomic(apple_pcie_port_readl, stat, + stat & PORT_STATUS_READY, + 10, 250000, false, port, PORT_STATUS); + if (ret) + return dev_err_probe(pcie->dev, ret, + "PCIe-C port did not enter RUN after hot reconnect\n"); + + ret = read_poll_timeout_atomic(apple_pcie_port_readl, stat, + !(stat & PORT_LINKSTS_BUSY), + 10, 250000, false, port, PORT_LINKSTS); + if (ret) + return dev_err_probe(pcie->dev, ret, + "PCIe-C port did not become idle after hot reconnect\n"); + + apple_pcie_port_rmw_clear(port, PORT_APPCLK_CGDIS, PORT_APPCLK); + apple_pcie_tunnel_apply_tunable(pcie->cfg->win, pcie->rc_tunable); + apple_pcie_port_writel(port, PORT_COUNTER_ENABLE, PORT_COUNTER_CTRL); + apple_pcie_tunnel_writel(pcie->intr2axi_base, PCIEC_INTR2AXI_ENABLE, + PCIEC_INTR2AXI_CTRL); + for (i = 0; i < pcie->hw->max_rid2sid; i++) + apple_pcie_rid2sid_write(port, i, 0); + + dev_info(pcie->dev, + "PCIe-C hardware reinitialized after cable reconnect\n"); + return 0; +} + +static void apple_pcie_tunnel_restore_irq_hw(struct apple_pcie_port *port) +{ + struct apple_pcie *pcie = port->pcie; + u32 value = 0; + int i; + + apple_pcie_port_writel(port, ~0, PORT_INTSTAT); + apple_pcie_port_writel(port, ~0, PORT_LINKCMDSTS); + apple_pcie_port_writel(port, lower_32_bits(DOORBELL_ADDR), + pcie->hw->port_msiaddr); + if (pcie->hw->port_msiaddr_hi) + apple_pcie_port_writel(port, 0, pcie->hw->port_msiaddr_hi); + + if (pcie->hw->port_msimap) { + for (i = 0; i < pcie->nvecs; i++) + apple_pcie_port_writel(port, + FIELD_PREP(PORT_MSIMAP_TARGET, i) | + PORT_MSIMAP_ENABLE, + pcie->hw->port_msimap + 4 * i); + } else { + apple_pcie_port_writel(port, 0, PORT_MSIBASE); + value = ilog2(pcie->nvecs) << PORT_MSICFG_L2MSINUM_SHIFT; + } + apple_pcie_port_writel(port, value | PORT_MSICFG_EN, PORT_MSICFG); + apple_pcie_port_writel(port, port->saved_intmask, PORT_INTMSK); +} + +static int apple_pcie_tunnel_start(struct apple_pcie_port *port) +{ + struct apple_pcie *pcie = port->pcie; + u32 stat; + int i, ret; + + if (pcie->power_retained) { + /* + * The ATC PCIe domain remains powered on these systems. Replaying + * resetPortHardware() against that live handoff is not a resume + * operation and raises an asynchronous external abort on T6020. + * Re-activating the ACIO PCIe tunnel closes the Intr2AXI aperture, + * just as it does during cold cable activation, so pulse it before + * releasing the retained port reset. + * Release the completed sleep handshake before re-enabling the + * retained port, which is the inverse of the disable sequence. + */ + apple_pcie_tunnel_writel(pcie->intr2axi_base, + PCIEC_INTR2AXI_ENABLE, + PCIEC_INTR2AXI_CTRL); + ret = apple_pcie_tunnel_release_reset(port); + if (ret) + return ret; + } else { + /* + * A genuinely power-gated PCIe-C controller lost its register + * state. Recreate the enablePortHardware() baseline before start. + */ + apple_pcie_tunnel_apply_tunable(pcie->debug_base, + pcie->debug_tunable); + apple_pcie_tunnel_apply_tunable(pcie->fabric_base, + pcie->fabric_tunable); + apple_pcie_tunnel_reset_hardware(port); + apple_pcie_tunnel_apply_tunable(port->base, port->tunable); + } + + apple_pcie_port_rmw_set(port, PORT_PERST_OFF, + pcie->hw->port_perst); + apple_pcie_port_rmw_set(port, PORT_APPCLK_EN, PORT_APPCLK); + + ret = read_poll_timeout_atomic(apple_pcie_port_readl, stat, + stat & PORT_STATUS_READY, + 10, 250000, false, port, PORT_STATUS); + if (ret) { + dev_err(pcie->dev, "port %pOF resume ready wait timed out\n", + port->np); + return ret; + } + + if (!pcie->power_retained) { + apple_pcie_tunnel_apply_tunable(pcie->cfg->win, + pcie->rc_tunable); + apple_pcie_port_rmw_clear(port, PORT_APPCLK_CGDIS, + PORT_APPCLK); + apple_pcie_port_writel(port, PORT_COUNTER_ENABLE, + PORT_COUNTER_CTRL); + apple_pcie_tunnel_writel(pcie->intr2axi_base, + PCIEC_INTR2AXI_ENABLE, + PCIEC_INTR2AXI_CTRL); + apple_pcie_tunnel_restore_irq_hw(port); + for_each_set_bit(i, port->sid_map, port->sid_map_sz) + apple_pcie_rid2sid_write(port, i, + port->saved_rid2sid[i]); + + ret = apple_pcie_tunnel_release_reset(port); + if (ret) + return ret; + } + + apple_pcie_port_writel(port, PORT_LTSSMCTL_START, PORT_LTSSMCTL); + ret = read_poll_timeout_atomic(apple_pcie_port_readl, stat, + stat & PORT_LINKSTS_UP, + 1000, 500000, false, port, PORT_LINKSTS); + if (ret) { + dev_err(pcie->dev, "port %pOF resume link training timed out\n", + port->np); + return ret; + } + if (pcie->power_retained) + apple_pcie_port_writel(port, port->saved_intmask, PORT_INTMSK); + + port->started = true; + dev_info(pcie->dev, "port %pOF tunnel link restored after suspend\n", + port->np); + return 0; +} + +static bool apple_pcie_tunnel_transactions_idle(struct apple_pcie_port *port) +{ + u32 value; + + value = apple_pcie_port_readl(port, PORT_OUTS_NPREQS); + if (value & (PORT_OUTS_NPREQS_REQ | PORT_OUTS_NPREQS_CPL)) + return false; + if (apple_pcie_port_readl(port, PORT_OUTS_PREQS_HDR) & + PORT_OUTS_PREQS_HDR_MASK) + return false; + if (apple_pcie_port_readl(port, PORT_OUTS_PREQS_DATA) & + PORT_OUTS_PREQS_DATA_MASK) + return false; + if (apple_pcie_port_readl(port, PORT_RXWR_FIFO) & + (PORT_RXWR_FIFO_HDR | PORT_RXWR_FIFO_DATA)) + return false; + if (apple_pcie_port_readl(port, PORT_RXRD_FIFO) & PORT_RXRD_FIFO_REQ) + return false; + if (apple_pcie_port_readl(port, PORT_OUTS_CPLS) & + (PORT_OUTS_CPLS_SHRD | PORT_OUTS_CPLS_WAIT)) + return false; + + return true; +} + +static int apple_pcie_tunnel_stop(struct apple_pcie_port *port) +{ + struct apple_pcie *pcie = port->pcie; + bool idle; + u32 stat; + int err = 0, ret; + + port->saved_intmask = apple_pcie_port_readl(port, PORT_INTMSK); + apple_pcie_port_writel(port, ~0, PORT_INTMSK); + apple_pcie_port_writel(port, ~0, PORT_INTSTAT); + apple_pcie_port_writel(port, ~0, PORT_LINKCMDSTS); + apple_pcie_port_rmw_clear(port, PORT_LTSSMCTL_START, PORT_LTSSMCTL); + ret = read_poll_timeout_atomic(apple_pcie_tunnel_transactions_idle, idle, + idle, 10, 20000, false, port); + if (ret) + dev_warn(pcie->dev, + "port %pOF transactions did not drain before suspend\n", + port->np); + err = ret; /* * PCIe-C has no external PERST# line. The USB4 router asserts the @@ -629,10 +933,11 @@ static void apple_pcie_tunnel_stop(struct apple_pcie_port *port) if (ret) dev_warn(pcie->dev, "port %pOF tunnel reset assertion timed out\n", port->np); + if (ret && !err) + err = ret; apple_pcie_port_rmw_clear(port, PORT_PERST_OFF, pcie->hw->port_perst); - apple_pcie_port_rmw_clear(port, PORT_LTSSMCTL_START, PORT_LTSSMCTL); apple_pcie_port_rmw_clear(port, PORT_APPCLK_EN, PORT_APPCLK); ret = read_poll_timeout_atomic(apple_pcie_port_readl, stat, @@ -640,6 +945,8 @@ static void apple_pcie_tunnel_stop(struct apple_pcie_port *port) 10, 100000, false, port, PORT_STATUS); if (ret) dev_warn(pcie->dev, "port %pOF disable timed out\n", port->np); + if (ret && !err) + err = ret; apple_pcie_port_rmw_set(port, PORT_TUNCTRL_PERST_ACK_REQ, PORT_TUNCTRL); ret = read_poll_timeout_atomic(apple_pcie_port_readl, stat, @@ -648,9 +955,13 @@ static void apple_pcie_tunnel_stop(struct apple_pcie_port *port) if (ret) dev_warn(pcie->dev, "port %pOF tunnel reset acknowledgment timed out\n", port->np); + if (ret && !err) + err = ret; apple_pcie_port_rmw_clear(port, PORT_TUNCTRL_PERST_ACK_REQ, PORT_TUNCTRL); port->started = false; + + return err; } static void apple_pcie_port_teardown(struct apple_pcie_port *port) @@ -840,6 +1151,10 @@ static int apple_pcie_setup_port(struct apple_pcie *pcie, port->sid_map = devm_bitmap_zalloc(pcie->dev, pcie->hw->max_rid2sid, GFP_KERNEL); if (!port->sid_map) return -ENOMEM; + port->saved_rid2sid = devm_kcalloc(pcie->dev, pcie->hw->max_rid2sid, + sizeof(*port->saved_rid2sid), GFP_KERNEL); + if (!port->saved_rid2sid) + return -ENOMEM; ret = of_property_read_u32_index(np, "reg", 0, &idx); if (ret) @@ -876,6 +1191,15 @@ static int apple_pcie_setup_port(struct apple_pcie *pcie, port->base = NULL; goto err_teardown; } + if (pcie->hw->tunneled) { + port->tunable = devm_apple_tunable_parse(pcie->dev, np, + "apple,tunable", res); + if (IS_ERR(port->tunable)) { + ret = dev_err_probe(pcie->dev, PTR_ERR(port->tunable), + "port %pOF tunables unavailable\n", np); + goto err_teardown; + } + } if (!pcie->hw->tunneled) { snprintf(name, sizeof(name), "phy%d", port->idx); @@ -912,9 +1236,12 @@ static int apple_pcie_setup_port(struct apple_pcie *pcie, 100, 250000, false, port, PORT_STATUS); if (ret < 0) { - dev_err(pcie->dev, - "port %pOF ready wait timeout\n", np); - goto err_teardown; + dev_info(pcie->dev, + "port %pOF stopped; replaying PCIe-C hardware initialization\n", + np); + ret = apple_pcie_tunnel_reinitialize(port); + if (ret) + goto err_teardown; } } else { /* U-Boot may already have brought up a conventional root port. */ @@ -979,6 +1306,8 @@ static int apple_pcie_setup_port(struct apple_pcie *pcie, (timeout - left) * 1000 / HZ); } + if (pcie->hw->tunneled) + port->started = true; return 0; @@ -1174,6 +1503,7 @@ static int apple_pcie_init(struct pci_config_window *cfg) pcie = apple_pcie_lookup(dev); if (WARN_ON(!pcie)) return -ENOENT; + pcie->cfg = cfg; for_each_available_child_of_node_scoped(dev->of_node, of_port) { ret = apple_pcie_setup_port(pcie, of_port); @@ -1221,6 +1551,191 @@ static int apple_pcie_probe_port(struct device_node *np) return 0; } +static int apple_pcie_tunnel_init_resources(struct platform_device *pdev, + struct apple_pcie *pcie) +{ + struct resource *config, *debug, *fabric, *intr2axi; + + config = platform_get_resource_byname(pdev, IORESOURCE_MEM, "config"); + debug = platform_get_resource_byname(pdev, IORESOURCE_MEM, "debug"); + fabric = platform_get_resource_byname(pdev, IORESOURCE_MEM, "fabric"); + intr2axi = platform_get_resource_byname(pdev, IORESOURCE_MEM, "intr2axi"); + if (!config || !debug || !fabric || !intr2axi) + return dev_err_probe(pcie->dev, -ENODEV, + "PCIe-C resume resources are incomplete\n"); + + debug->flags |= IORESOURCE_MEM_NONPOSTED; + pcie->debug_base = devm_ioremap_resource(pcie->dev, debug); + if (IS_ERR(pcie->debug_base)) + return PTR_ERR(pcie->debug_base); + + fabric->flags |= IORESOURCE_MEM_NONPOSTED; + pcie->fabric_base = devm_ioremap_resource(pcie->dev, fabric); + if (IS_ERR(pcie->fabric_base)) + return PTR_ERR(pcie->fabric_base); + + intr2axi->flags |= IORESOURCE_MEM_NONPOSTED; + pcie->intr2axi_base = devm_ioremap_resource(pcie->dev, intr2axi); + if (IS_ERR(pcie->intr2axi_base)) + return PTR_ERR(pcie->intr2axi_base); + + pcie->rc_tunable = devm_apple_tunable_parse(pcie->dev, + pcie->dev->of_node, + "apple,tunable-rc", config); + if (IS_ERR(pcie->rc_tunable)) + return dev_err_probe(pcie->dev, PTR_ERR(pcie->rc_tunable), + "PCIe-C root-complex tunables unavailable\n"); + + pcie->debug_tunable = devm_apple_tunable_parse(pcie->dev, + pcie->dev->of_node, + "apple,tunable-debug", debug); + if (IS_ERR(pcie->debug_tunable)) + return dev_err_probe(pcie->dev, PTR_ERR(pcie->debug_tunable), + "PCIe-C debug tunables unavailable\n"); + + pcie->fabric_tunable = devm_apple_tunable_parse(pcie->dev, + pcie->dev->of_node, + "apple,tunable-fabric", fabric); + if (IS_ERR(pcie->fabric_tunable)) + return dev_err_probe(pcie->dev, PTR_ERR(pcie->fabric_tunable), + "PCIe-C fabric tunables unavailable\n"); + + return 0; +} + +static bool apple_pcie_tunnel_power_is_retained(struct device_node *np) +{ + struct device_node *pd_np; + bool retained; + + pd_np = of_parse_phandle(np, "power-domains", 0); + if (!pd_np) + return false; + + retained = of_property_read_bool(pd_np, "apple,always-on"); + of_node_put(pd_np); + return retained; +} + +struct apple_pcie_tunnel_link { + struct device *consumer; + struct device *supplier; +}; + +static void apple_pcie_tunnel_delete_link(void *data) +{ + struct apple_pcie_tunnel_link *link = data; + + /* + * A hot-unplugged endpoint purges its device links during unregister, so + * the struct device_link returned by device_link_add() may already be + * gone by the time PCIe-C releases its devres. Resolve the link by its + * refcounted endpoints instead; device_link_remove() is a no-op after a + * purge and avoids dereferencing a stale link object. + */ + device_link_remove(link->consumer, link->supplier); + put_device(link->consumer); + put_device(link->supplier); +} + +static int apple_pcie_tunnel_add_link(struct apple_pcie *pcie, + struct device *consumer, + struct device *supplier) +{ + struct apple_pcie_tunnel_link *ref; + struct device_link *link; + int ret; + + link = device_link_add(consumer, supplier, DL_FLAG_STATELESS); + if (!link) + return -EINVAL; + + ref = devm_kzalloc(pcie->dev, sizeof(*ref), GFP_KERNEL); + if (!ref) { + device_link_del(link); + return -ENOMEM; + } + + ref->consumer = get_device(consumer); + ref->supplier = get_device(supplier); + ret = devm_add_action_or_reset(pcie->dev, + apple_pcie_tunnel_delete_link, ref); + return ret; +} + +static int apple_pcie_tunnel_keep_d0(struct pci_dev *pdev, void *data) +{ + /* + * The remote USB4 PCIe hierarchy becomes unreachable while its tunnel + * is asleep, so a device left in D3hot cannot receive the config write + * that would bring it back to D0. Quiesce drivers normally, but leave + * PCI power-state ownership to the tunnel across system sleep. + */ + pdev->dev_flags |= PCI_DEV_FLAGS_NO_D3; + return 0; +} + +static int apple_pcie_tunnel_add_links(struct apple_pcie *pcie) +{ + struct platform_device *dart_pdev = NULL; + struct platform_device *nhi_pdev = NULL; + int ret; + + /* + * Apple orders clientDidSleep after disabling the PCIe-C port, and + * clientWillWake before enabling it. NHI and PCIe-C are sibling devices + * under ACIO, so encode that missing edge explicitly: Linux must suspend + * PCIe-C before NHI tears down the router state and resume NHI before the + * host touches its tunneled aperture. + */ + for_each_available_child_of_node_scoped(pcie->dev->parent->of_node, + sibling) { + if (!of_node_name_prefix(sibling, "nhi")) + continue; + + nhi_pdev = of_find_device_by_node(sibling); + break; + } + if (!nhi_pdev) + return dev_err_probe(pcie->dev, -EPROBE_DEFER, + "Apple NHI device is not ready\n"); + + ret = apple_pcie_tunnel_add_link(pcie, pcie->dev, &nhi_pdev->dev); + put_device(&nhi_pdev->dev); + if (ret) + return dev_err_probe(pcie->dev, ret, + "failed to order PCIe-C after NHI resume\n"); + + /* + * The DART and PCIe-C host are synthesized as siblings. Apple restores + * port hardware and RID/SID forwarding before forcing the DART active; + * express the same dependency so Linux suspends DART first and resumes + * PCIe-C first. + */ + for_each_available_child_of_node_scoped(pcie->dev->of_node->parent, + sibling) { + if (!of_property_present(sibling, "#iommu-cells")) + continue; + + dart_pdev = of_find_device_by_node(sibling); + break; + } + if (!dart_pdev) + return dev_err_probe(pcie->dev, -EPROBE_DEFER, + "PCIe-C DART device is not ready\n"); + + ret = apple_pcie_tunnel_add_link(pcie, &dart_pdev->dev, pcie->dev); + put_device(&dart_pdev->dev); + if (ret) + return dev_err_probe(pcie->dev, ret, + "failed to order PCIe-C before DART resume\n"); + + dev_info(pcie->dev, + "PCIe-C ordered after NHI and before DART resume (%s power)\n", + pcie->power_retained ? "retained" : "cycled"); + return 0; +} + static int apple_pcie_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -1262,6 +1777,13 @@ static int apple_pcie_probe(struct platform_device *pdev) pcie->base = devm_platform_ioremap_resource(pdev, 1); if (IS_ERR(pcie->base)) return PTR_ERR(pcie->base); + if (pcie->hw->tunneled) { + ret = apple_pcie_tunnel_init_resources(pdev, pcie); + if (ret) + return ret; + pcie->power_retained = + apple_pcie_tunnel_power_is_retained(dev->of_node); + } mutex_init(&pcie->lock); INIT_LIST_HEAD(&pcie->ports); @@ -1280,14 +1802,127 @@ static int apple_pcie_probe(struct platform_device *pdev) * Port mappings are allocated by the ECAM init callback. Register cleanup * afterwards so devres runs it before unmapping those registers. */ - return devm_add_action_or_reset(dev, apple_pcie_cleanup, pcie); + ret = devm_add_action_or_reset(dev, apple_pcie_cleanup, pcie); + if (ret) + return ret; + + if (pcie->hw->tunneled) { + pci_walk_bus(bridge->bus, apple_pcie_tunnel_keep_d0, NULL); + return apple_pcie_tunnel_add_links(pcie); + } + + return 0; +} + +int apple_pcie_tunnel_quiesce(struct device *dev) +{ + struct pci_host_bridge *bridge = dev_get_drvdata(dev); + struct apple_pcie *pcie; + struct apple_pcie_port *port; + int ret = 0; + + if (!bridge || !bridge->bus) + return -ENODEV; + + pcie = pci_host_bridge_priv(bridge); + if (!pcie->hw->tunneled) + return -EINVAL; + + /* + * ACIO's NHI is the control plane for tunneled PERST. Stop PCI + * function drivers first, then complete the port reset handshake while + * the NHI and PCIe-C DART are still alive. The platform device remains + * bound so ACIO can remove the NHI before it finally destroys this host. + */ + pci_lock_rescan_remove(); + if (!pcie->bus_stopped) { + pci_walk_bus(bridge->bus, pci_dev_set_disconnected, NULL); + pci_stop_root_bus(bridge->bus); + pcie->bus_stopped = true; + } + + list_for_each_entry(port, &pcie->ports, entry) { + int err; + + if (!port->started) + continue; + err = apple_pcie_tunnel_stop(port); + if (err && !ret) + ret = err; + } + pci_unlock_rescan_remove(); + + if (!ret) + dev_info(dev, "PCIe-C hierarchy quiesced before NHI shutdown\n"); + + return ret; } +EXPORT_SYMBOL_GPL(apple_pcie_tunnel_quiesce); static void apple_pcie_remove(struct platform_device *pdev) { - pci_host_common_remove(pdev); + struct pci_host_bridge *bridge = platform_get_drvdata(pdev); + struct apple_pcie *pcie = pci_host_bridge_priv(bridge); + + /* + * A USB4 cable pull is a surprise removal: the remote hierarchy is + * already unreachable by the time ACIO depopulates PCIe-C. Mark it + * permanently disconnected before unbinding drivers, matching pciehp's + * surprise-removal path. In particular, this keeps NVMe teardown from + * issuing MMIO to the dead tunneled aperture and wedging the SoC. + */ + pci_lock_rescan_remove(); + if (!pcie->bus_stopped) { + if (pcie->hw->tunneled) + pci_walk_bus(bridge->bus, pci_dev_set_disconnected, NULL); + pci_stop_root_bus(bridge->bus); + } + pci_remove_root_bus(bridge->bus); + pci_unlock_rescan_remove(); +} + +static int apple_pcie_suspend_noirq(struct device *dev) +{ + struct apple_pcie *pcie = apple_pcie_lookup(dev); + struct apple_pcie_port *port; + int i; + + if (!pcie->hw->tunneled) + return 0; + list_for_each_entry(port, &pcie->ports, entry) { + if (port->started) { + for_each_set_bit(i, port->sid_map, port->sid_map_sz) + port->saved_rid2sid[i] = + apple_pcie_port_readl(port, + port_rid2sid_offset(port, i)); + apple_pcie_tunnel_stop(port); + } + } + return 0; +} + +static int apple_pcie_resume_noirq(struct device *dev) +{ + struct apple_pcie *pcie = apple_pcie_lookup(dev); + struct apple_pcie_port *port; + int ret; + + if (!pcie->hw->tunneled) + return 0; + list_for_each_entry(port, &pcie->ports, entry) { + ret = apple_pcie_tunnel_start(port); + if (ret) + return ret; + } + + return 0; } +static const struct dev_pm_ops apple_pcie_pm_ops = { + .suspend_noirq = apple_pcie_suspend_noirq, + .resume_noirq = apple_pcie_resume_noirq, +}; + static const struct of_device_id apple_pcie_of_match[] = { { .compatible = "apple,t8103-pciec", .data = &t8103_pciec_hw }, { .compatible = "apple,t6000-pciec", .data = &t8103_pciec_hw }, @@ -1304,6 +1939,7 @@ static struct platform_driver apple_pcie_driver = { .name = "pcie-apple", .of_match_table = apple_pcie_of_match, .suppress_bind_attrs = true, + .pm = &apple_pcie_pm_ops, }, }; module_platform_driver(apple_pcie_driver); diff --git a/include/linux/pci-apple.h b/include/linux/pci-apple.h new file mode 100644 index 00000000000000..29cff22f9cc0e2 --- /dev/null +++ b/include/linux/pci-apple.h @@ -0,0 +1,9 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _LINUX_PCI_APPLE_H +#define _LINUX_PCI_APPLE_H + +struct device; + +int apple_pcie_tunnel_quiesce(struct device *dev); + +#endif /* _LINUX_PCI_APPLE_H */ From f0c0ca02c8b3130ed9beea4af860dc264931ace9 Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Fri, 28 Aug 2026 23:21:32 -0400 Subject: [PATCH 29/51] thunderbolt: apple: coordinate tunneled PCIe lifecycle Signed-off-by: DjDeveloperr --- arch/arm64/boot/dts/apple/t602x-dieX.dtsi | 24 +- drivers/thunderbolt/apple.c | 350 +++++++++------------- drivers/thunderbolt/domain.c | 6 + 3 files changed, 156 insertions(+), 224 deletions(-) diff --git a/arch/arm64/boot/dts/apple/t602x-dieX.dtsi b/arch/arm64/boot/dts/apple/t602x-dieX.dtsi index 455052a9bd35bb..d4efc517d0b71c 100644 --- a/arch/arm64/boot/dts/apple/t602x-dieX.dtsi +++ b/arch/arm64/boot/dts/apple/t602x-dieX.dtsi @@ -594,9 +594,11 @@ <0x7 0x20000000 0x0 0x4000>, <0x7 0x21000000 0x0 0x8000>, <0x7 0x20004000 0x0 0x4000>, - <0x7 0x20200000 0x0 0x4000>; + <0x7 0x20200000 0x0 0x4000>, + <0x7 0x21100000 0x0 0x4000>, + <0x7 0x2100c000 0x0 0x4000>; reg-names = "config", "rc", "port0", "fabric", - "debug"; + "debug", "ltssm", "intr2axi"; interrupt-parent = <&aic>; interrupts = ; @@ -820,9 +822,11 @@ <0xb 0x20000000 0x0 0x4000>, <0xb 0x21000000 0x0 0x8000>, <0xb 0x20004000 0x0 0x4000>, - <0xb 0x20200000 0x0 0x4000>; + <0xb 0x20200000 0x0 0x4000>, + <0xb 0x21100000 0x0 0x4000>, + <0xb 0x2100c000 0x0 0x4000>; reg-names = "config", "rc", "port0", "fabric", - "debug"; + "debug", "ltssm", "intr2axi"; interrupt-parent = <&aic>; interrupts = ; @@ -1046,9 +1050,11 @@ <0xf 0x20000000 0x0 0x4000>, <0xf 0x21000000 0x0 0x8000>, <0xf 0x20004000 0x0 0x4000>, - <0xf 0x20200000 0x0 0x4000>; + <0xf 0x20200000 0x0 0x4000>, + <0xf 0x21100000 0x0 0x4000>, + <0xf 0x2100c000 0x0 0x4000>; reg-names = "config", "rc", "port0", "fabric", - "debug"; + "debug", "ltssm", "intr2axi"; interrupt-parent = <&aic>; interrupts = ; @@ -1270,9 +1276,11 @@ <0x13 0x20000000 0x0 0x4000>, <0x13 0x21000000 0x0 0x8000>, <0x13 0x20004000 0x0 0x4000>, - <0x13 0x20200000 0x0 0x4000>; + <0x13 0x20200000 0x0 0x4000>, + <0x13 0x21100000 0x0 0x4000>, + <0x13 0x2100c000 0x0 0x4000>; reg-names = "config", "rc", "port0", "fabric", - "debug"; + "debug", "ltssm", "intr2axi"; interrupt-parent = <&aic>; interrupts = ; diff --git a/drivers/thunderbolt/apple.c b/drivers/thunderbolt/apple.c index 8ebeae5f0c32e1..07a448b52dee73 100644 --- a/drivers/thunderbolt/apple.c +++ b/drivers/thunderbolt/apple.c @@ -62,6 +62,7 @@ #include #include #include +#include #include #include #include @@ -141,6 +142,7 @@ struct apple_cio { u32 target_cable_info; struct completion nhi_boot_completion; int nhi_boot_status; + struct platform_device *nhi_pdev; struct typec_thunderbolt_switch_dev *tbt_switch; /* Serializes PCIe-C population with cable teardown. */ @@ -150,37 +152,6 @@ struct apple_cio { bool pcie_tunnel_populated; }; -/* - * Temporary hardware bring-up gate. A non-zero value stops ACIO startup after - * the corresponding phase so each power/reset/MMIO transition can be tested - * independently on new SoCs without advancing into the next phase. - */ -static unsigned int apple_cio_debug_stage; -module_param_named(debug_stage, apple_cio_debug_stage, uint, 0644); -MODULE_PARM_DESC(debug_stage, "Stop ACIO startup after the selected debug phase"); - -static unsigned int apple_cio_pcie_tunnel_trace_delay_ms; -module_param_named(pcie_tunnel_trace_delay_ms, - apple_cio_pcie_tunnel_trace_delay_ms, uint, 0644); -MODULE_PARM_DESC(pcie_tunnel_trace_delay_ms, - "Delay before PCIe-C child population so its trace reaches persistent storage"); - -static bool apple_cio_pcie_intr2axi_prearmed; -module_param_named(pcie_intr2axi_prearmed, - apple_cio_pcie_intr2axi_prearmed, bool, 0644); -MODULE_PARM_DESC(pcie_intr2axi_prearmed, - "Diagnostic: consume a live userspace Intr2AXI pulse instead of pulsing again"); - -static bool apple_cio_defer_start; -module_param_named(defer_start, apple_cio_defer_start, bool, 0644); -MODULE_PARM_DESC(defer_start, - "Hold a detected USB4/TBT cable until ACIO is started through sysfs"); - -static int apple_cio_power_domain_limit = -1; -module_param_named(power_domain_limit, apple_cio_power_domain_limit, int, 0644); -MODULE_PARM_DESC(power_domain_limit, - "Enable this many traced ACIO power domains before returning (-1 = all)"); - static void apple_cio_of_node_put(void *data) { of_node_put(data); @@ -520,28 +491,14 @@ static int apple_cio_populate_pcie_tunnel(struct apple_cio *acio) * enabled both tunnel adapters and before either child can touch DART MMIO. * The bit self-clears after opening the bridge. */ - if (READ_ONCE(apple_cio_pcie_intr2axi_prearmed)) { - WRITE_ONCE(apple_cio_pcie_intr2axi_prearmed, false); - dev_info(acio->dev, - "PCIe-C Intr2AXI bridge accepted from live diagnostic handoff\n"); - } else { - writel(APPLE_CIO_PCIEC_INTR2AXI_ENABLE, - acio->pcie_intr2axi_base + APPLE_CIO_PCIEC_INTR2AXI_CTRL); - /* Complete the pulse without reading the transient register. */ - mb(); - dev_info(acio->dev, "PCIe-C Intr2AXI bridge enabled\n"); - } - if (apple_cio_debug_stage == 12) { - dev_info(acio->dev, - "ACIO debug stage 12: Intr2AXI pulsed; tunnel children held\n"); - return -EAGAIN; - } + writel(APPLE_CIO_PCIEC_INTR2AXI_ENABLE, + acio->pcie_intr2axi_base + APPLE_CIO_PCIEC_INTR2AXI_CTRL); + /* Complete the pulse without reading the transient register. */ + mb(); + dev_info(acio->dev, "PCIe-C Intr2AXI bridge enabled\n"); dev_info(acio->dev, "PCIe-C live handoff accepted; populating tunnel children\n"); - if (READ_ONCE(apple_cio_pcie_tunnel_trace_delay_ms)) - msleep(READ_ONCE(apple_cio_pcie_tunnel_trace_delay_ms)); - return of_platform_populate(acio->pcie_tunnel_np, NULL, NULL, acio->dev); } @@ -562,12 +519,6 @@ static int apple_cio_activate_pcie_tunnel_locked(struct apple_cio *acio) return dev_err_probe(acio->dev, -ENODEV, "pre-ACIO PCIe-C initialization is not active\n"); - if (apple_cio_debug_stage == 11) { - dev_info(acio->dev, - "ACIO debug stage 11: PCIe tunnel adapters held for manual population\n"); - return 0; - } - ret = apple_cio_populate_pcie_tunnel(acio); if (ret) return dev_err_probe(acio->dev, ret, @@ -598,73 +549,6 @@ static void apple_cio_pcie_tunnel_work(struct work_struct *work) dev_err(acio->dev, "deferred PCIe-C activation failed: %d\n", ret); } -static int apple_cio_start(struct apple_cio *acio); - -static ssize_t acio_start_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t count) -{ - struct apple_cio *acio = dev_get_drvdata(dev); - int ret; - - if (!sysfs_streq(buf, "1")) - return -EINVAL; - - ret = mutex_lock_interruptible(&acio->lock); - if (ret) - return ret; - - if (acio->current_cable_info) { - ret = -EALREADY; - goto out_unlock; - } - if (!acio->target_cable_info) { - ret = -ENODEV; - goto out_unlock; - } - - dev_info(acio->dev, - "starting deferred ACIO cable transition %#x\n", - acio->target_cable_info); - ret = apple_cio_start(acio); - -out_unlock: - mutex_unlock(&acio->lock); - return ret ?: count; -} -static DEVICE_ATTR_WO(acio_start); - -static ssize_t pcie_tunnel_populate_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t count) -{ - struct apple_cio *acio = dev_get_drvdata(dev); - int ret; - - if (!sysfs_streq(buf, "1")) - return -EINVAL; - - ret = mutex_lock_interruptible(&acio->pcie_tunnel_lock); - if (ret) - return ret; - - ret = apple_cio_activate_pcie_tunnel_locked(acio); - mutex_unlock(&acio->pcie_tunnel_lock); - - return ret ?: count; -} -static DEVICE_ATTR_WO(pcie_tunnel_populate); - -static struct attribute *apple_cio_attrs[] = { - &dev_attr_acio_start.attr, - &dev_attr_pcie_tunnel_populate.attr, - NULL, -}; - -static const struct attribute_group apple_cio_group = { - .attrs = apple_cio_attrs, -}; - static int apple_nhi_pci_tunnel_pre_activate(struct tb_nhi *nhi) { struct apple_nhi *anhi = nhi_to_anhi(nhi); @@ -682,21 +566,15 @@ static int apple_nhi_pci_tunnel_post_activate(struct tb_nhi *nhi) { struct apple_nhi *anhi = nhi_to_anhi(nhi); struct apple_cio *acio = anhi->acio; - int ret; /* - * This is the Linux equivalent of Apple's startUsingTunnel() boundary. - * Only now can releaseResetFlow() clear the tunneled PERST state before - * the root port enables LTSSM. + * This is the boundary at which the tunnel becomes usable. + * The USB4 configuration writes are asynchronous, so return to the + * Thunderbolt core before probing PCIe-C. The worker then waits on the + * hardware reset state without blocking completion of the tunnel paths. */ - mutex_lock(&acio->pcie_tunnel_lock); - ret = apple_cio_activate_pcie_tunnel_locked(acio); - mutex_unlock(&acio->pcie_tunnel_lock); - - /* Stage 12 intentionally leaves the active tunnel paths in place. */ - if (ret == -EAGAIN && apple_cio_debug_stage == 12) - return 0; - return ret; + mod_delayed_work(system_wq, &acio->pcie_tunnel_work, 0); + return 0; } static const struct tb_nhi_ops apple_nhi_ops = { @@ -852,6 +730,7 @@ static int apple_nhi_probe(struct platform_device *pdev) mutex_unlock(&anhi->tb->lock); + WRITE_ONCE(acio->nhi_pdev, pdev); acio->nhi_boot_status = 0; complete(&acio->nhi_boot_completion); return 0; @@ -869,10 +748,70 @@ static void apple_nhi_remove(struct platform_device *pdev) { struct apple_nhi *anhi = platform_get_drvdata(pdev); + WRITE_ONCE(anhi->acio->nhi_pdev, NULL); tb_domain_remove(anhi->tb); wait_for_completion(&anhi->nhi.domain_released); } +/* + * The Apple platform driver owns the NHI allocation and keeps an + * apple_nhi pointer in driver data. The generic NHI PM callbacks cannot be + * installed here because they expect driver data to contain struct tb. + */ +static int apple_nhi_suspend_noirq(struct device *dev) +{ + struct apple_nhi *anhi = dev_get_drvdata(dev); + + return tb_domain_suspend_noirq(anhi->tb); +} + +static int apple_nhi_resume_noirq(struct device *dev) +{ + struct apple_nhi *anhi = dev_get_drvdata(dev); + + return tb_domain_resume_noirq(anhi->tb); +} + +static int apple_nhi_freeze_noirq(struct device *dev) +{ + struct apple_nhi *anhi = dev_get_drvdata(dev); + + return tb_domain_freeze_noirq(anhi->tb); +} + +static int apple_nhi_thaw_noirq(struct device *dev) +{ + struct apple_nhi *anhi = dev_get_drvdata(dev); + + return tb_domain_thaw_noirq(anhi->tb); +} + +static int apple_nhi_suspend(struct device *dev) +{ + struct apple_nhi *anhi = dev_get_drvdata(dev); + + return tb_domain_suspend(anhi->tb); +} + +static void apple_nhi_complete(struct device *dev) +{ + struct apple_nhi *anhi = dev_get_drvdata(dev); + + tb_domain_complete(anhi->tb); +} + +static const struct dev_pm_ops apple_nhi_pm_ops = { + .suspend = apple_nhi_suspend, + .suspend_noirq = apple_nhi_suspend_noirq, + .resume_noirq = apple_nhi_resume_noirq, + .freeze_noirq = apple_nhi_freeze_noirq, + .thaw_noirq = apple_nhi_thaw_noirq, + .restore_noirq = apple_nhi_resume_noirq, + .poweroff = apple_nhi_suspend, + .poweroff_noirq = apple_nhi_suspend_noirq, + .complete = apple_nhi_complete, +}; + static const struct of_device_id apple_nhi_match[] = { { .compatible = "apple,t8103-usb4-nhi", @@ -885,13 +824,34 @@ static struct platform_driver apple_nhi_driver = { .driver = { .name = "thunderbolt-apple-nhi", .of_match_table = apple_nhi_match, + .pm = &apple_nhi_pm_ops, }, .probe = apple_nhi_probe, .remove = apple_nhi_remove, }; +static struct platform_device *apple_cio_find_pcie_tunnel(struct apple_cio *acio) +{ + struct platform_device *pdev = NULL; + + if (!acio->pcie_tunnel_np || !acio->pcie_tunnel_populated) + return NULL; + + for_each_available_child_of_node_scoped(acio->pcie_tunnel_np, child) { + if (!of_device_is_compatible(child, "apple,t8103-pciec") && + !of_device_is_compatible(child, "apple,t6000-pciec")) + continue; + + pdev = of_find_device_by_node(child); + break; + } + + return pdev; +} + static void apple_cio_stop(struct apple_cio *acio) { + struct platform_device *nhi_pdev, *pcie_pdev; int ret, i; lockdep_assert_held(&acio->lock); @@ -904,6 +864,35 @@ static void apple_cio_stop(struct apple_cio *acio) WRITE_ONCE(acio->pcie_tunnel_requested, false); cancel_delayed_work_sync(&acio->pcie_tunnel_work); mutex_lock(&acio->pcie_tunnel_lock); + + /* + * The PCIe-C tunnel reset handshake is carried by the NHI. Quiesce the + * remote PCI hierarchy and acknowledge tunneled PERST before removing + * that control plane. PCIe-C and its DART stay bound until the general + * child depopulation below. + */ + pcie_pdev = apple_cio_find_pcie_tunnel(acio); + if (pcie_pdev) { + ret = apple_pcie_tunnel_quiesce(&pcie_pdev->dev); + if (ret) + dev_warn(acio->dev, + "failed to quiesce PCIe-C before NHI shutdown: %d\n", + ret); + put_device(&pcie_pdev->dev); + } + + /* + * The NHI is the tunnel control plane. Stop it while the tunneled PCIe + * host and DART are still accessible, before depopulation tears down the + * data plane. Otherwise tb_domain_remove() walks the USB4 topology after + * PCIe-C has already reset its port and can wedge waiting on dead control + * traffic. The remaining children are still removed in reverse creation + * order below. + */ + nhi_pdev = READ_ONCE(acio->nhi_pdev); + if (nhi_pdev) + of_platform_device_destroy(&nhi_pdev->dev, NULL); + of_platform_depopulate(acio->dev); acio->pcie_tunnel_populated = false; mutex_unlock(&acio->pcie_tunnel_lock); @@ -920,6 +909,7 @@ static void apple_cio_stop(struct apple_cio *acio) if (acio->pd_list->pd_links[i]) device_link_del(acio->pd_list->pd_links[i]); acio->pd_list->pd_links[i] = NULL; + dev_pm_syscore_device(acio->pd_list->pd_devs[i], false); } acio->current_cable_info = 0; @@ -935,17 +925,6 @@ static int apple_cio_start(struct apple_cio *acio) /* Create device links to the power domains in order to power them on */ for (i = 0; i < acio->pd_list->num_pds; i++) { - if (READ_ONCE(apple_cio_power_domain_limit) >= 0) { - dev_emerg(acio->dev, - "ACIO power-domain access %d armed: enable %s\n", - i + 1, dev_name(acio->pd_list->pd_devs[i])); - if (i >= READ_ONCE(apple_cio_power_domain_limit)) { - ret = -EAGAIN; - goto remove_links; - } - dev_emerg(acio->dev, - "ACIO power-domain access %d executing\n", i + 1); - } link = device_link_add(acio->dev, acio->pd_list->pd_devs[i], DL_FLAG_STATELESS | DL_FLAG_PM_RUNTIME | DL_FLAG_RPM_ACTIVE); if (!link) { @@ -953,20 +932,14 @@ static int apple_cio_start(struct apple_cio *acio) goto remove_links; } acio->pd_list->pd_links[i] = link; - if (READ_ONCE(apple_cio_power_domain_limit) >= 0) - dev_emerg(acio->dev, - "ACIO power-domain access %d completed\n", i + 1); - dev_info(acio->dev, - "ACIO power domain %d (%s): runtime %s, usage %d\n", - i, dev_name(acio->pd_list->pd_devs[i]), - pm_runtime_active(acio->pd_list->pd_devs[i]) ? - "active" : "inactive", - atomic_read(&acio->pd_list->pd_devs[i]->power.usage_count)); - } - if (apple_cio_debug_stage == 1) { - dev_info(acio->dev, "ACIO debug stage 1: power domains enabled\n"); - ret = -EAGAIN; - goto remove_links; + /* + * ACIO owns these domains explicitly for the lifetime of the cable + * connection. The virtual genpd devices must not independently + * cycle them during system sleep: doing so destroys the live M3 and + * NHI state behind the still-bound child devices. Cable teardown + * removes the runtime-active links and gives system PM ownership back. + */ + dev_pm_syscore_device(acio->pd_list->pd_devs[i], true); } /* @@ -978,11 +951,6 @@ static int apple_cio_start(struct apple_cio *acio) dev_err(acio->dev, "ACIO block failed to start: %d\n", ret); goto remove_links; } - if (apple_cio_debug_stage == 2) { - dev_info(acio->dev, "ACIO debug stage 2: reset deasserted\n"); - ret = -EAGAIN; - goto remove_links; - } /* * Start and wait for the co-processor to boot. @@ -993,39 +961,19 @@ static int apple_cio_start(struct apple_cio *acio) * them with a bare store. */ val = readl(acio->rc_base + APPLE_CIO_M3_CTRL); - if (apple_cio_debug_stage == 3) { - dev_info(acio->dev, "ACIO debug stage 3: M3 control read as %#x\n", val); - ret = -EAGAIN; - goto remove_links; - } writel(val | APPLE_CIO_M3_CTRL_START, acio->rc_base + APPLE_CIO_M3_CTRL); - if (apple_cio_debug_stage == 4) { - dev_info(acio->dev, "ACIO debug stage 4: M3 start requested\n"); - ret = -EAGAIN; - goto remove_links; - } acio->rtk = apple_rtkit_init(acio->dev, acio, NULL, 0, &apple_cio_rtkit_ops); if (IS_ERR(acio->rtk)) { ret = PTR_ERR(acio->rtk); dev_err(acio->dev, "Failed to initialize RTKit: %d\n", ret); goto remove_links; } - if (apple_cio_debug_stage == 5) { - dev_info(acio->dev, "ACIO debug stage 5: RTKit initialized\n"); - ret = -EAGAIN; - goto err_free_rtkit; - } ret = apple_rtkit_boot(acio->rtk); if (ret) { dev_err(acio->dev, "M3 RTKit failed to boot: %d\n", ret); goto err_free_rtkit; } - if (apple_cio_debug_stage == 6) { - dev_info(acio->dev, "ACIO debug stage 6: RTKit booted\n"); - ret = -EAGAIN; - goto err_shutdown_rtkit; - } ret = readl_poll_timeout(acio->rc_base + APPLE_CIO_M3_STAT, state, state & APPLE_CIO_M3_STAT_STATE, 100, 500000); @@ -1033,18 +981,8 @@ static int apple_cio_start(struct apple_cio *acio) dev_err(acio->dev, "M3 firmware failed to get ready: %d\n", ret); goto err_shutdown_rtkit; } - if (apple_cio_debug_stage == 7) { - dev_info(acio->dev, "ACIO debug stage 7: M3 ready state %#x\n", state); - ret = -EAGAIN; - goto err_shutdown_rtkit; - } apple_tunable_apply(acio->rc_base, acio->rc_tunable); - if (apple_cio_debug_stage == 8) { - dev_info(acio->dev, "ACIO debug stage 8: RC tunables applied\n"); - ret = -EAGAIN; - goto err_shutdown_rtkit; - } /* * Bring up devices which are part of ACIO and are now accessible by the main SoC @@ -1056,11 +994,6 @@ static int apple_cio_start(struct apple_cio *acio) dev_err(acio->dev, "failed to populate children: %d\n", ret); goto err_depopulate; } - if (apple_cio_debug_stage == 9) { - dev_info(acio->dev, "ACIO debug stage 9: child devices populated\n"); - ret = -EAGAIN; - goto err_depopulate; - } if (!wait_for_completion_timeout(&acio->nhi_boot_completion, msecs_to_jiffies(APPLE_CIO_NHI_BOOT_TIMEOUT_MS))) { @@ -1072,11 +1005,6 @@ static int apple_cio_start(struct apple_cio *acio) ret = acio->nhi_boot_status; goto err_depopulate; } - if (apple_cio_debug_stage == 10) { - dev_info(acio->dev, "ACIO debug stage 10: NHI is ready\n"); - ret = -EAGAIN; - goto err_depopulate; - } acio->current_cable_info = acio->target_cable_info; return 0; @@ -1094,6 +1022,7 @@ static int apple_cio_start(struct apple_cio *acio) if (acio->pd_list->pd_links[i]) device_link_del(acio->pd_list->pd_links[i]); acio->pd_list->pd_links[i] = NULL; + dev_pm_syscore_device(acio->pd_list->pd_devs[i], false); } return ret; } @@ -1166,13 +1095,6 @@ static int apple_cio_tbt_switch_set(struct typec_thunderbolt_switch_dev *sw, * Bring up or power down the ACIO complex * current_cable_info will be updated in the start/stop functions */ - if (acio->target_cable_info && READ_ONCE(apple_cio_defer_start)) { - dev_info(acio->dev, - "USB4/TBT cable transition %#x held for controlled ACIO start\n", - acio->target_cable_info); - return 0; - } - if (acio->target_cable_info) return apple_cio_start(acio); @@ -1268,10 +1190,6 @@ static int apple_cio_probe(struct platform_device *pdev) else if (ret < 3) return dev_err_probe(dev, -EINVAL, "Not enough PM domains\n"); - ret = devm_device_add_group(dev, &apple_cio_group); - if (ret) - return ret; - /* And finally register the OOB notification for Thunderbolt/USB4 cables */ struct typec_thunderbolt_switch_desc desc = { .fwnode = pdev->dev.fwnode, diff --git a/drivers/thunderbolt/domain.c b/drivers/thunderbolt/domain.c index 162cbb0b22a1b1..acf9d126e3ca6f 100644 --- a/drivers/thunderbolt/domain.c +++ b/drivers/thunderbolt/domain.c @@ -545,6 +545,7 @@ int tb_domain_suspend_noirq(struct tb *tb) return ret; } +EXPORT_SYMBOL_NS_GPL(tb_domain_suspend_noirq, "USB4"); /** * tb_domain_resume_noirq() - Resume a domain @@ -567,11 +568,13 @@ int tb_domain_resume_noirq(struct tb *tb) return ret; } +EXPORT_SYMBOL_NS_GPL(tb_domain_resume_noirq, "USB4"); int tb_domain_suspend(struct tb *tb) { return tb->cm_ops->suspend ? tb->cm_ops->suspend(tb) : 0; } +EXPORT_SYMBOL_NS_GPL(tb_domain_suspend, "USB4"); int tb_domain_freeze_noirq(struct tb *tb) { @@ -586,6 +589,7 @@ int tb_domain_freeze_noirq(struct tb *tb) return ret; } +EXPORT_SYMBOL_NS_GPL(tb_domain_freeze_noirq, "USB4"); int tb_domain_thaw_noirq(struct tb *tb) { @@ -599,12 +603,14 @@ int tb_domain_thaw_noirq(struct tb *tb) return ret; } +EXPORT_SYMBOL_NS_GPL(tb_domain_thaw_noirq, "USB4"); void tb_domain_complete(struct tb *tb) { if (tb->cm_ops->complete) tb->cm_ops->complete(tb); } +EXPORT_SYMBOL_NS_GPL(tb_domain_complete, "USB4"); int tb_domain_runtime_suspend(struct tb *tb) { From b2bc356383c09490c0677fc5ea8499f08ff48693 Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Sat, 29 Aug 2026 04:05:17 -0400 Subject: [PATCH 30/51] PCI: apple: order PCIe-C hot-reconnect setup Signed-off-by: DjDeveloperr --- drivers/pci/controller/pcie-apple.c | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/drivers/pci/controller/pcie-apple.c b/drivers/pci/controller/pcie-apple.c index 9ae26ca656a6d2..780aedfe6078f6 100644 --- a/drivers/pci/controller/pcie-apple.c +++ b/drivers/pci/controller/pcie-apple.c @@ -732,6 +732,26 @@ static int apple_pcie_tunnel_reinitialize(struct apple_pcie_port *port) apple_pcie_tunnel_reset_hardware(port); apple_pcie_tunnel_apply_tunable(port->base, port->tunable); + /* + * Keep the port disabled and its downstream reset asserted while the + * root complex is configured. The enable sequence does this explicitly + * after applying the port tunables; releasing either + * one early occasionally leaves a hot-reconnected USB4 endpoint unable to + * train even though the tunnel itself is already live. + */ + apple_pcie_port_rmw_clear(port, PORT_APPCLK_EN, PORT_APPCLK); + apple_pcie_port_rmw_clear(port, PORT_PERST_OFF, + pcie->hw->port_perst); + apple_pcie_tunnel_apply_tunable(pcie->cfg->win, pcie->rc_tunable); + apple_pcie_port_rmw_clear(port, PORT_APPCLK_CGDIS, PORT_APPCLK); + apple_pcie_port_writel(port, PORT_COUNTER_ENABLE, PORT_COUNTER_CTRL); + apple_pcie_port_writel(port, ~0, PORT_INTSTAT); + apple_pcie_port_writel(port, ~0, PORT_LINKCMDSTS); + apple_pcie_tunnel_writel(pcie->intr2axi_base, + PCIEC_INTR2AXI_ENABLE, + PCIEC_INTR2AXI_CTRL); + + /* Release reset immediately before enabling the configured port. */ apple_pcie_port_rmw_set(port, PORT_PERST_OFF, pcie->hw->port_perst); apple_pcie_port_rmw_set(port, PORT_APPCLK_EN, PORT_APPCLK); @@ -749,11 +769,6 @@ static int apple_pcie_tunnel_reinitialize(struct apple_pcie_port *port) return dev_err_probe(pcie->dev, ret, "PCIe-C port did not become idle after hot reconnect\n"); - apple_pcie_port_rmw_clear(port, PORT_APPCLK_CGDIS, PORT_APPCLK); - apple_pcie_tunnel_apply_tunable(pcie->cfg->win, pcie->rc_tunable); - apple_pcie_port_writel(port, PORT_COUNTER_ENABLE, PORT_COUNTER_CTRL); - apple_pcie_tunnel_writel(pcie->intr2axi_base, PCIEC_INTR2AXI_ENABLE, - PCIEC_INTR2AXI_CTRL); for (i = 0; i < pcie->hw->max_rid2sid; i++) apple_pcie_rid2sid_write(port, i, 0); From 3b7f11e579c437beaf5cef11091427a342ed9bb3 Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Fri, 28 Aug 2026 23:22:16 -0400 Subject: [PATCH 31/51] usb: dwc3-apple: preserve live ports across system sleep Signed-off-by: DjDeveloperr --- drivers/usb/dwc3/dwc3-apple.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/drivers/usb/dwc3/dwc3-apple.c b/drivers/usb/dwc3/dwc3-apple.c index 40c3ccfddb6702..33d3b467f53804 100644 --- a/drivers/usb/dwc3/dwc3-apple.c +++ b/drivers/usb/dwc3/dwc3-apple.c @@ -301,6 +301,14 @@ static int dwc3_apple_init(struct dwc3_apple *appledwc, enum dwc3_apple_state st } appledwc->state = state; + /* + * A connected cable leaves the Apple DWC3 core and its PHY as one live, + * stateful unit. The glue driver tears that unit down explicitly on a + * role change or disconnect. Do not let generic system PM independently + * power-gate the DWC3 device in between: xHCI then observes a dead core on + * resume and cannot restore the root hubs without a full cable teardown. + */ + dev_pm_syscore_device(appledwc->dev, true); return 0; core_exit: @@ -325,9 +333,11 @@ static int dwc3_apple_exit(struct dwc3_apple *appledwc) /* Nothing to do if we're already off */ return 0; case DWC3_APPLE_DEVICE: + dev_pm_syscore_device(appledwc->dev, false); dwc3_gadget_exit(&appledwc->dwc); break; case DWC3_APPLE_HOST: + dev_pm_syscore_device(appledwc->dev, false); dwc3_host_exit(&appledwc->dwc); break; } From 161bfa5753ee84b401ac7354c3c4505fa728fd3e Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Fri, 28 Aug 2026 23:22:43 -0400 Subject: [PATCH 32/51] tty: samsung: avoid suspended Apple UART access Signed-off-by: DjDeveloperr --- drivers/tty/serial/samsung_tty.c | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/drivers/tty/serial/samsung_tty.c b/drivers/tty/serial/samsung_tty.c index 5c33ced062e0a9..83545ac152c1c0 100644 --- a/drivers/tty/serial/samsung_tty.c +++ b/drivers/tty/serial/samsung_tty.c @@ -132,6 +132,7 @@ struct s3c24xx_uart_dma { struct s3c24xx_uart_port { unsigned char rx_enabled; unsigned char tx_enabled; + bool system_sleep_skipped; unsigned int pm_level; unsigned long baudclk_rate; unsigned int min_dma_size; @@ -2091,6 +2092,16 @@ static void s3c24xx_serial_remove(struct platform_device *dev) static int __maybe_unused s3c24xx_serial_suspend(struct device *dev) { struct uart_port *port = s3c24xx_dev_to_port(dev); + struct s3c24xx_uart_port *ourport = to_ourport(port); + + /* + * Runtime PM has already quiesced the port and removed its clocks. Do + * not run the serial-core system-sleep path against an Apple S5L UART + * whose PMGR domain is therefore inaccessible. + */ + ourport->system_sleep_skipped = pm_runtime_suspended(dev); + if (ourport->system_sleep_skipped) + return 0; if (!console_suspend_enabled && uart_console(port)) device_set_wakeup_path(dev); @@ -2106,6 +2117,11 @@ static int __maybe_unused s3c24xx_serial_resume(struct device *dev) struct uart_port *port = s3c24xx_dev_to_port(dev); struct s3c24xx_uart_port *ourport = to_ourport(port); + if (ourport->system_sleep_skipped) { + ourport->system_sleep_skipped = false; + return 0; + } + if (port) { clk_prepare_enable(ourport->clk); if (!IS_ERR(ourport->baudclk)) @@ -2126,6 +2142,9 @@ static int __maybe_unused s3c24xx_serial_resume_noirq(struct device *dev) struct uart_port *port = s3c24xx_dev_to_port(dev); struct s3c24xx_uart_port *ourport = to_ourport(port); + if (ourport->system_sleep_skipped) + return 0; + if (port) { /* restore IRQ mask */ switch (ourport->info->type) { From 3f9346c8aed098e69bbee00ea97163d86edc3672 Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Fri, 28 Aug 2026 23:23:12 -0400 Subject: [PATCH 33/51] usb: typec: cd321x: isolate hotplug state by port Signed-off-by: DjDeveloperr --- drivers/usb/typec/tipd/core.c | 55 +++++++++++++++++++++++++++++++---- 1 file changed, 50 insertions(+), 5 deletions(-) diff --git a/drivers/usb/typec/tipd/core.c b/drivers/usb/typec/tipd/core.c index d4e2cd7e18a863..0ca0b8bcf66cd7 100644 --- a/drivers/usb/typec/tipd/core.c +++ b/drivers/usb/typec/tipd/core.c @@ -686,6 +686,11 @@ static void cd321x_update_work(struct work_struct *work) bool usb_connection = st.data_status & (TPS_DATA_STATUS_USB2_CONNECTION | TPS_DATA_STATUS_USB3_CONNECTION); + bool dp_mode_changed = st.data_status_changed & + (TPS_DATA_STATUS_DP_CONNECTION | + TPS_DATA_STATUS_DP_PIN_ASSIGNMENT_MASK); + bool dp_was_connected = cd321x->state.alt == cd321x->port_altmode_dp; + bool dp_connected = st.data_status & TPS_DATA_STATUS_DP_CONNECTION; bool dp_hpd = st.data_status & CD321X_DATA_STATUS_HPD_LEVEL; bool dp_hpd_changed = st.data_status_changed & CD321X_DATA_STATUS_HPD_LEVEL; @@ -714,11 +719,31 @@ static void cd321x_update_work(struct work_struct *work) (is_pd && memcmp(&st.partner_identity, &cd321x->cur_partner_identity, sizeof(struct usb_pd_identity)))); - /* If we are switching from an active role, transition to USB_ROLE_NONE first */ - if (old_role != USB_ROLE_NONE && (new_role != old_role || was_disconnected)) + /* + * ACIO carries the tunneled PCIe reset handshake over the still-live + * Type-C data path. Close a disappearing USB4/TBT session before dropping + * the USB role; otherwise the role transition tears down the ATC lanes and + * PCIe-C cannot quiesce its hierarchy before NHI removal. + */ + if ((!new_connected || was_disconnected) && + cd321x->state.mode != TYPEC_STATE_SAFE) + typec_thunderbolt_switch_set(cd321x->tbt_switch, + &tbt_switch_data); + + /* If we are switching from an active role, transition to USB_ROLE_NONE. */ + if (old_role != USB_ROLE_NONE && + (new_role != old_role || was_disconnected || dp_mode_changed)) usb_role_switch_set_role(tps->role_sw, USB_ROLE_NONE); - if (cd321x->connector_fwnode && (!dp_hpd || dp_hpd_changed)) { + /* + * Every Type-C port references the same external DCP connector. Do not + * report HPD-low for a port which neither had nor has a DP connection: + * USB4/TBT status changes on such a port would otherwise tear down a DP + * stream carried by a different Type-C port. + */ + if (cd321x->connector_fwnode && + (dp_was_connected || dp_connected) && + (!dp_hpd || dp_hpd_changed)) { drm_connector_oob_hotplug_event(cd321x->connector_fwnode, connector_status_disconnected); } @@ -731,7 +756,6 @@ static void cd321x_update_work(struct work_struct *work) /* If there was a disconnection, set PHY to off */ if (!new_connected || was_disconnected) { - typec_thunderbolt_switch_set(cd321x->tbt_switch, &tbt_switch_data); cd321x->state.alt = NULL; cd321x->state.mode = TYPEC_STATE_SAFE; cd321x->state.data = NULL; @@ -779,7 +803,7 @@ static void cd321x_update_work(struct work_struct *work) /* Launch the USB role switch */ usb_role_switch_set_role(tps->role_sw, new_role); - if (cd321x->connector_fwnode && dp_hpd) + if (cd321x->connector_fwnode && dp_connected && dp_hpd) drm_connector_oob_hotplug_event(cd321x->connector_fwnode, connector_status_connected); power_supply_changed(tps->psy); @@ -1698,8 +1722,29 @@ tps25750_register_port(struct tps6598x *tps, struct fwnode_handle *fwnode) static void cd321x_remove(struct tps6598x *tps) { struct cd321x *cd321x = container_of(tps, struct cd321x, tps); + struct typec_thunderbolt_switch_data tbt_switch_data = { + .state = TYPEC_THUNDERBOLT_SWITCH_OFF, + }; + int ret; cancel_delayed_work_sync(&cd321x->update_work); + + /* + * Driver teardown must close the same hardware sessions as a physical + * disconnect. Otherwise ACIO retains the old USB4 cable state while a + * reprobe enters a new session, which its firmware treats as an invalid + * live-to-live transition and resolves by resetting the SoC. + * + * Close ACIO first while the ATC lanes can still carry PCIe-C's tunneled + * reset handshake, then drop the USB role after NHI and PCIe-C teardown. + */ + ret = typec_thunderbolt_switch_set(cd321x->tbt_switch, + &tbt_switch_data); + if (ret) + dev_warn(tps->dev, + "failed to close Thunderbolt/USB4 session on remove: %d\n", + ret); + usb_role_switch_set_role(tps->role_sw, USB_ROLE_NONE); } int tipd_init(struct tps6598x *tps) From dac537864282bff8095b59e8f5d3931fbb30e711 Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Sat, 29 Aug 2026 03:48:04 -0400 Subject: [PATCH 34/51] usb: typec: cd321x: preserve events during debounce A reconnect IRQ can arrive after the update worker releases its lock but before the workqueue clears the running state. In that window cancel_delayed_work() does not cancel anything and schedule_delayed_work() refuses to queue another pass, leaving the accumulated cable state unprocessed.\n\nUse mod_delayed_work() so a later pass is guaranteed even when the previous update is still completing. Signed-off-by: DjDeveloperr --- drivers/usb/typec/tipd/core.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/drivers/usb/typec/tipd/core.c b/drivers/usb/typec/tipd/core.c index 0ca0b8bcf66cd7..b2076cf11b9681 100644 --- a/drivers/usb/typec/tipd/core.c +++ b/drivers/usb/typec/tipd/core.c @@ -832,12 +832,13 @@ static int cd321x_connect(struct tps6598x *tps, u32 status) cd321x_queue_status(cd321x); /* - * Cancel pending work if not already running, then requeue after CD321X_DEBOUNCE_DELAY_MS - * regardless since the work function will check for any plug or altmodes changes since - * its last run anyway. + * Requeue even when the previous update is already running. A separate + * cancel_delayed_work() and schedule_delayed_work() pair can lose a cable + * event in that window because the latter sees the running work as busy. + * The worker consumes every accumulated status change on its next pass. */ - cancel_delayed_work(&cd321x->update_work); - schedule_delayed_work(&cd321x->update_work, msecs_to_jiffies(CD321X_DEBOUNCE_DELAY_MS)); + mod_delayed_work(system_wq, &cd321x->update_work, + msecs_to_jiffies(CD321X_DEBOUNCE_DELAY_MS)); return 0; } From d2fbd5de2338df9687dabd915f856b922dce43fe Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Sat, 29 Aug 2026 04:55:14 -0400 Subject: [PATCH 35/51] usb: typec: cd321x: track the active display route Every CD321x port on these systems references the same external DCP connector. A second sink can negotiate DisplayPort Alt Mode while the DCP mux rejects its route with -EBUSY. The port nevertheless emitted shared-connector disconnect events, blanking the display already carried by another port. Track display-route ownership only after typec_mux_set() succeeds, propagate mux failures into that decision, and allow only the owner to emit disconnect events. This makes a rejected second display non-disruptive and keeps the software mux state retryable after an error. Fixes: 5a46b5cc44de ("usb: typec: cd321x: isolate hotplug state by port") Signed-off-by: DjDeveloperr --- drivers/usb/typec/tipd/core.c | 72 +++++++++++++++++++++---------- drivers/usb/typec/tipd/tps6598x.h | 1 + 2 files changed, 51 insertions(+), 22 deletions(-) diff --git a/drivers/usb/typec/tipd/core.c b/drivers/usb/typec/tipd/core.c index b2076cf11b9681..aae510d2f92345 100644 --- a/drivers/usb/typec/tipd/core.c +++ b/drivers/usb/typec/tipd/core.c @@ -543,21 +543,24 @@ static void tps6598x_handle_plug_event(struct tps6598x *tps, u32 status) } } -static void cd321x_typec_update_mode(struct tps6598x *tps, struct cd321x_status *st) +static int cd321x_typec_update_mode(struct tps6598x *tps, + struct cd321x_status *st) { struct cd321x *cd321x = container_of(tps, struct cd321x, tps); + struct typec_mux_state old_state = cd321x->state; struct typec_thunderbolt_switch_data tbt_switch_data = { .state = TYPEC_THUNDERBOLT_SWITCH_OFF, }; + int ret = 0; if (!(st->data_status & TPS_DATA_STATUS_DATA_CONNECTION)) { if (cd321x->state.mode == TYPEC_STATE_SAFE) - return; + return 0; cd321x->state.alt = NULL; cd321x->state.mode = TYPEC_STATE_SAFE; cd321x->state.data = NULL; typec_thunderbolt_switch_set(cd321x->tbt_switch, &tbt_switch_data); - typec_mux_set(cd321x->mux, &cd321x->state); + ret = typec_mux_set(cd321x->mux, &cd321x->state); } else if (st->data_status & TPS_DATA_STATUS_DP_CONNECTION) { struct typec_displayport_data dp_data; unsigned long mode; @@ -583,12 +586,12 @@ static void cd321x_typec_update_mode(struct tps6598x *tps, struct cd321x_status break; default: dev_err(tps->dev, "Invalid DP pin assignment\n"); - return; + return -EINVAL; } if (cd321x->state.alt == cd321x->port_altmode_dp && cd321x->state.mode == mode) { - return; + return 0; } dp_data.status = le32_to_cpu(st->dp_sid_status.status_rx); @@ -597,13 +600,13 @@ static void cd321x_typec_update_mode(struct tps6598x *tps, struct cd321x_status cd321x->state.data = &dp_data; cd321x->state.mode = mode; typec_thunderbolt_switch_set(cd321x->tbt_switch, &tbt_switch_data); - typec_mux_set(cd321x->mux, &cd321x->state); + ret = typec_mux_set(cd321x->mux, &cd321x->state); } else if (st->data_status & TPS_DATA_STATUS_TBT_CONNECTION) { struct typec_thunderbolt_data tbt_data; if (cd321x->state.alt == cd321x->port_altmode_tbt && cd321x->state.mode == TYPEC_TBT_MODE) - return; + return 0; tbt_data.cable_mode = TBT_MODE | TBT_SET_CABLE_SPEED(TPS_DATA_STATUS_TBT_CABLE_SPEED(st->data_status)) | @@ -621,7 +624,9 @@ static void cd321x_typec_update_mode(struct tps6598x *tps, struct cd321x_status cd321x->state.alt = cd321x->port_altmode_tbt; cd321x->state.mode = TYPEC_TBT_MODE; cd321x->state.data = &tbt_data; - typec_mux_set(cd321x->mux, &cd321x->state); + ret = typec_mux_set(cd321x->mux, &cd321x->state); + if (ret) + goto out; tbt_switch_data.state = TYPEC_THUNDERBOLT_SWITCH_TBT; tbt_switch_data.tbt = tbt_data; @@ -633,7 +638,7 @@ static void cd321x_typec_update_mode(struct tps6598x *tps, struct cd321x_status struct enter_usb_data eusb_data; if (cd321x->state.alt == NULL && cd321x->state.mode == TYPEC_MODE_USB4) - return; + return 0; eusb_data.eudo = le32_to_cpu(st->usb4_status.eudo); eusb_data.active_link_training = @@ -642,7 +647,9 @@ static void cd321x_typec_update_mode(struct tps6598x *tps, struct cd321x_status cd321x->state.alt = NULL; cd321x->state.data = &eusb_data; cd321x->state.mode = TYPEC_MODE_USB4; - typec_mux_set(cd321x->mux, &cd321x->state); + ret = typec_mux_set(cd321x->mux, &cd321x->state); + if (ret) + goto out; tbt_switch_data.state = TYPEC_THUNDERBOLT_SWITCH_USB4; tbt_switch_data.usb4 = eusb_data; @@ -652,16 +659,23 @@ static void cd321x_typec_update_mode(struct tps6598x *tps, struct cd321x_status typec_thunderbolt_switch_set(cd321x->tbt_switch, &tbt_switch_data); } else { if (cd321x->state.alt == NULL && cd321x->state.mode == TYPEC_STATE_USB) - return; + return 0; cd321x->state.alt = NULL; cd321x->state.mode = TYPEC_STATE_USB; cd321x->state.data = NULL; typec_thunderbolt_switch_set(cd321x->tbt_switch, &tbt_switch_data); - typec_mux_set(cd321x->mux, &cd321x->state); + ret = typec_mux_set(cd321x->mux, &cd321x->state); } +out: /* Clear data since it's no longer used after typec_mux_set and points to the stack */ cd321x->state.data = NULL; + if (ret) { + cd321x->state = old_state; + dev_warn(tps->dev, "failed to configure Type-C mux: %d\n", ret); + } + + return ret; } static void cd321x_update_work(struct work_struct *work) @@ -689,7 +703,7 @@ static void cd321x_update_work(struct work_struct *work) bool dp_mode_changed = st.data_status_changed & (TPS_DATA_STATUS_DP_CONNECTION | TPS_DATA_STATUS_DP_PIN_ASSIGNMENT_MASK); - bool dp_was_connected = cd321x->state.alt == cd321x->port_altmode_dp; + bool dp_route_was_active = cd321x->display_route_active; bool dp_connected = st.data_status & TPS_DATA_STATUS_DP_CONNECTION; bool dp_hpd = st.data_status & CD321X_DATA_STATUS_HPD_LEVEL; @@ -736,14 +750,16 @@ static void cd321x_update_work(struct work_struct *work) usb_role_switch_set_role(tps->role_sw, USB_ROLE_NONE); /* - * Every Type-C port references the same external DCP connector. Do not - * report HPD-low for a port which neither had nor has a DP connection: - * USB4/TBT status changes on such a port would otherwise tear down a DP - * stream carried by a different Type-C port. + * Every Type-C port references the same external DCP connector. Only the + * port which successfully acquired the shared display route may report a + * disconnect. A second DP sink can negotiate Alt Mode even though its mux + * request is rejected with -EBUSY; treating that sink as the connector + * owner would tear down the display carried by the first port. */ if (cd321x->connector_fwnode && - (dp_was_connected || dp_connected) && - (!dp_hpd || dp_hpd_changed)) { + dp_route_was_active && + (!new_connected || was_disconnected || !dp_connected || !dp_hpd || + dp_hpd_changed || dp_mode_changed)) { drm_connector_oob_hotplug_event(cd321x->connector_fwnode, connector_status_disconnected); } @@ -756,10 +772,21 @@ static void cd321x_update_work(struct work_struct *work) /* If there was a disconnection, set PHY to off */ if (!new_connected || was_disconnected) { + struct typec_mux_state old_state = cd321x->state; + int ret; + cd321x->state.alt = NULL; cd321x->state.mode = TYPEC_STATE_SAFE; cd321x->state.data = NULL; - typec_set_mode(tps->port, TYPEC_STATE_SAFE); + ret = typec_set_mode(tps->port, TYPEC_STATE_SAFE); + if (ret) { + dev_warn(tps->dev, + "failed to place Type-C mux in safe mode: %d\n", + ret); + cd321x->state = old_state; + } else { + cd321x->display_route_active = false; + } } /* Update Type-C properties */ @@ -798,12 +825,13 @@ static void cd321x_update_work(struct work_struct *work) } /* Update the TypeC MUX/PHY state */ - cd321x_typec_update_mode(tps, &st); + if (!cd321x_typec_update_mode(tps, &st)) + cd321x->display_route_active = dp_connected; /* Launch the USB role switch */ usb_role_switch_set_role(tps->role_sw, new_role); - if (cd321x->connector_fwnode && dp_connected && dp_hpd) + if (cd321x->connector_fwnode && cd321x->display_route_active && dp_hpd) drm_connector_oob_hotplug_event(cd321x->connector_fwnode, connector_status_connected); power_supply_changed(tps->psy); diff --git a/drivers/usb/typec/tipd/tps6598x.h b/drivers/usb/typec/tipd/tps6598x.h index 15a39f6ee1566d..c799edb6017564 100644 --- a/drivers/usb/typec/tipd/tps6598x.h +++ b/drivers/usb/typec/tipd/tps6598x.h @@ -370,6 +370,7 @@ struct cd321x { struct typec_mux *mux; struct typec_mux_state state; struct typec_thunderbolt_switch *tbt_switch; + bool display_route_active; struct cd321x_status update_status; struct delayed_work update_work; From 6dde6f1a06005799d345b660ac1d124e551a9d9f Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Sat, 29 Aug 2026 19:30:15 -0400 Subject: [PATCH 36/51] usb: typec: cd321x: cache and replay DP alt-mode status cd321x only forwarded a DisplayPort mux state when the alternate mode or the pin assignment changed. A partner that re-asserts HPD, or that changes its DP status while staying in the same mode, produced no mux notification at all, so a DisplayPort sink could sit powered and enumerated with no display route ever being established. Cache the DP status and configuration alongside the mux state and resend the notification whenever either changes. Take the HPD level from the CD321x DATA_STATUS register, which reports it reliably, rather than from the status_rx VDO. Clear the cache when the port leaves DP so the next entry always replays a fresh state. Signed-off-by: DjDeveloperr --- drivers/usb/typec/tipd/core.c | 21 +++++++++++++++++---- drivers/usb/typec/tipd/tps6598x.h | 2 ++ 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/drivers/usb/typec/tipd/core.c b/drivers/usb/typec/tipd/core.c index aae510d2f92345..c9afbf84d074b1 100644 --- a/drivers/usb/typec/tipd/core.c +++ b/drivers/usb/typec/tipd/core.c @@ -589,18 +589,28 @@ static int cd321x_typec_update_mode(struct tps6598x *tps, return -EINVAL; } + dp_data.status = le32_to_cpu(st->dp_sid_status.status_rx); + dp_data.conf = le32_to_cpu(st->dp_sid_status.configure); + if (st->data_status & CD321X_DATA_STATUS_HPD_LEVEL) + dp_data.status |= DP_STATUS_HPD_STATE; + else + dp_data.status &= ~DP_STATUS_HPD_STATE; + if (cd321x->state.alt == cd321x->port_altmode_dp && - cd321x->state.mode == mode) { + cd321x->state.mode == mode && + cd321x->dp_status == dp_data.status && + cd321x->dp_conf == dp_data.conf) return 0; - } - dp_data.status = le32_to_cpu(st->dp_sid_status.status_rx); - dp_data.conf = le32_to_cpu(st->dp_sid_status.configure); cd321x->state.alt = cd321x->port_altmode_dp; cd321x->state.data = &dp_data; cd321x->state.mode = mode; typec_thunderbolt_switch_set(cd321x->tbt_switch, &tbt_switch_data); ret = typec_mux_set(cd321x->mux, &cd321x->state); + if (!ret) { + cd321x->dp_status = dp_data.status; + cd321x->dp_conf = dp_data.conf; + } } else if (st->data_status & TPS_DATA_STATUS_TBT_CONNECTION) { struct typec_thunderbolt_data tbt_data; @@ -673,6 +683,9 @@ static int cd321x_typec_update_mode(struct tps6598x *tps, if (ret) { cd321x->state = old_state; dev_warn(tps->dev, "failed to configure Type-C mux: %d\n", ret); + } else if (cd321x->state.alt != cd321x->port_altmode_dp) { + cd321x->dp_status = 0; + cd321x->dp_conf = 0; } return ret; diff --git a/drivers/usb/typec/tipd/tps6598x.h b/drivers/usb/typec/tipd/tps6598x.h index c799edb6017564..c6c05da547f2ac 100644 --- a/drivers/usb/typec/tipd/tps6598x.h +++ b/drivers/usb/typec/tipd/tps6598x.h @@ -369,6 +369,8 @@ struct cd321x { struct typec_mux *mux; struct typec_mux_state state; + u32 dp_status; + u32 dp_conf; struct typec_thunderbolt_switch *tbt_switch; bool display_route_active; From 9feeb7fed0000e319825694b01a6bdae10820621 Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Tue, 25 Aug 2026 14:16:23 -0400 Subject: [PATCH 37/51] drm: apple: support shared Type-C display routes Signed-off-by: DjDeveloperr --- drivers/gpu/drm/apple/dcp-internal.h | 16 +++ drivers/gpu/drm/apple/dcp.c | 153 ++++++++++++++++++++++++++- drivers/gpu/drm/apple/dptxep.c | 10 +- 3 files changed, 173 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h index 57832487d9a4ab..7ee39f38c6be4d 100644 --- a/drivers/gpu/drm/apple/dcp-internal.h +++ b/drivers/gpu/drm/apple/dcp-internal.h @@ -21,10 +21,21 @@ #include "epic/dpavservep.h" #define DCP_MAX_PLANES 4 +#define DCP_MAX_TYPEC_ROUTES 4 struct apple_dcp; struct apple_dcp_afkep; +struct apple_dcp_typec_route { + struct apple_dcp *dcp; + struct phy *phy; + struct mux_control *xbar; + struct typec_mux_dev *typec_mux; + u32 dptx_phy; + u32 mux_index; + bool selected; +}; + struct dcpav_service_epic; enum dcp_firmware_version { @@ -256,6 +267,11 @@ struct apple_dcp { struct phy *phy; struct mux_control *xbar; struct typec_mux *typec_mux; + struct apple_dcp_typec_route typec_routes[DCP_MAX_TYPEC_ROUTES]; + struct apple_dcp_typec_route *active_typec_route; + struct mutex typec_route_lock; /* serializes Type-C route ownership */ + u32 nr_typec_routes; + bool phy_managed_by_typec; struct gpio_desc *hdmi_hpd; struct gpio_desc *hdmi_pwren; diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index ddd413fdeab245..fabc9028c04594 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -58,6 +58,150 @@ static bool unstable_edid = true; module_param(unstable_edid, bool, 0644); MODULE_PARM_DESC(unstable_edid, "Enable unstable EDID retrival support"); +static bool dcp_typec_route_is_dp(const struct typec_mux_state *state) +{ + return state->alt && state->alt->svid == USB_TYPEC_DP_SID && + (state->mode == TYPEC_DP_STATE_C || + state->mode == TYPEC_DP_STATE_D || + state->mode == TYPEC_DP_STATE_E); +} + +static int dcp_typec_route_set(struct typec_mux_dev *mux, + struct typec_mux_state *state) +{ + struct apple_dcp_typec_route *route = typec_mux_get_drvdata(mux); + struct apple_dcp *dcp = route->dcp; + int ret = 0; + + guard(mutex)(&dcp->typec_route_lock); + + if (!dcp_typec_route_is_dp(state)) { + if (!route->selected) + return 0; + + ret = mux_control_deselect(route->xbar); + route->selected = false; + if (dcp->active_typec_route == route) + dcp->active_typec_route = NULL; + return ret; + } + + if (route->selected) + return 0; + + if (dcp->active_typec_route) { + dev_warn(dcp->dev, "DP route is already in use by another Type-C port\n"); + return -EBUSY; + } + + ret = mux_control_select(route->xbar, route->mux_index); + if (ret) + return ret; + + dcp->phy = route->phy; + dcp->dptx_phy = route->dptx_phy; + dcp->active_typec_route = route; + route->selected = true; + + dev_info(dcp->dev, "Routed DCP output to Type-C DPTX PHY %u\n", + route->dptx_phy); + return 0; +} + +static void dcp_typec_route_unregister(void *data) +{ + struct apple_dcp_typec_route *route = data; + struct apple_dcp *dcp = route->dcp; + + guard(mutex)(&dcp->typec_route_lock); + if (route->selected) { + mux_control_deselect(route->xbar); + route->selected = false; + if (dcp->active_typec_route == route) + dcp->active_typec_route = NULL; + } + typec_mux_unregister(route->typec_mux); +} + +static int dcp_register_typec_routes(struct apple_dcp *dcp) +{ + struct device_node *routes __free(device_node) = + of_get_child_by_name(dcp->dev->of_node, "typec-routes"); + struct device *dev = dcp->dev; + u32 route_index; + int ret; + + if (!routes) + return 0; + + for_each_available_child_of_node_scoped(routes, route_np) { + struct apple_dcp_typec_route *route; + struct typec_mux_desc desc = {}; + const char *name; + + if (dcp->nr_typec_routes == DCP_MAX_TYPEC_ROUTES) + return dev_err_probe(dev, -E2BIG, "Too many Type-C display routes\n"); + + ret = of_property_read_u32(route_np, "reg", &route_index); + if (ret) + return dev_err_probe(dev, ret, "%pOF: missing route index\n", route_np); + if (route_index >= DCP_MAX_TYPEC_ROUTES) + return dev_err_probe(dev, -EINVAL, "%pOF: invalid route index %u\n", + route_np, route_index); + + name = devm_kasprintf(dev, GFP_KERNEL, "typec%u", route_index); + if (!name) + return -ENOMEM; + + route = &dcp->typec_routes[dcp->nr_typec_routes]; + route->dcp = dcp; + route->phy = devm_phy_get(dev, name); + if (IS_ERR(route->phy)) + return dev_err_probe(dev, PTR_ERR(route->phy), + "%pOF: failed to get DP PHY\n", route_np); + + route->xbar = devm_mux_control_get(dev, name); + if (IS_ERR(route->xbar)) + return dev_err_probe(dev, PTR_ERR(route->xbar), + "%pOF: failed to get display crossbar\n", route_np); + + ret = of_property_read_u32_index(dev->of_node, "apple,typec-mux-indices", + route_index, &route->mux_index); + if (ret) + return dev_err_probe(dev, ret, "%pOF: missing crossbar state\n", + route_np); + + ret = of_property_read_u32_index(dev->of_node, "apple,typec-dptx-phys", + route_index, &route->dptx_phy); + if (ret) + return dev_err_probe(dev, ret, "%pOF: missing DPTX PHY index\n", + route_np); + + desc.fwnode = of_fwnode_handle(route_np); + desc.set = dcp_typec_route_set; + desc.name = name; + desc.drvdata = route; + route->typec_mux = typec_mux_register(dev, &desc); + if (IS_ERR(route->typec_mux)) + return dev_err_probe(dev, PTR_ERR(route->typec_mux), + "%pOF: failed to register Type-C route\n", route_np); + + ret = devm_add_action_or_reset(dev, dcp_typec_route_unregister, route); + if (ret) + return ret; + + if (!dcp->phy) + dcp->phy = route->phy; + dcp->nr_typec_routes++; + } + + if (!dcp->nr_typec_routes) + return dev_err_probe(dev, -EINVAL, "Type-C route container is empty\n"); + + dcp->phy_managed_by_typec = true; + return 0; +} + /* copied and simplified from drm_vblank.c */ static void send_vblank_event(struct drm_device *dev, struct drm_pending_vblank_event *e, @@ -1156,7 +1300,7 @@ static int dcp_platform_probe(struct platform_device *pdev) enum dcp_firmware_version fw_compat; struct device *dev = &pdev->dev; struct apple_dcp *dcp; - int surf, num_surfs; + int ret, surf, num_surfs; u32 surf_en; u32 mux_index; @@ -1179,6 +1323,7 @@ static int dcp_platform_probe(struct platform_device *pdev) dcp->fw_compat = fw_compat; dcp->dev = dev; dcp->hw = *(struct apple_dcp_hw_data *)of_device_get_match_data(dev); + mutex_init(&dcp->typec_route_lock); platform_set_drvdata(pdev, dcp); @@ -1188,6 +1333,10 @@ static int dcp_platform_probe(struct platform_device *pdev) return PTR_ERR(dcp->phy); } + ret = dcp_register_typec_routes(dcp); + if (ret) + return ret; + bitmap_zero(dcp->iomfb_surfaces, DCP_MAX_PLANES); if (!of_property_present(dev->of_node, "apple,iomfb-surfaces")) num_surfs = 0; @@ -1283,6 +1432,8 @@ static int dcp_platform_probe(struct platform_device *pdev) }; int ret = typec_mux_set(dcp->typec_mux, &state); dev_info(dev, "typec_mux_set() returned: %d\n", ret); + if (!ret) + dcp->phy_managed_by_typec = true; } else { dev_info(dev, "fwnode_typec_mux_get() returned: %ld\n", IS_ERR(dcp->typec_mux) ? PTR_ERR(dcp->typec_mux) : 0); diff --git a/drivers/gpu/drm/apple/dptxep.c b/drivers/gpu/drm/apple/dptxep.c index 1ea00602d476d8..fa473743040220 100644 --- a/drivers/gpu/drm/apple/dptxep.c +++ b/drivers/gpu/drm/apple/dptxep.c @@ -478,9 +478,8 @@ dptxport_call_activate(struct apple_epic_service *service, struct dptx_port *dptx = service->cookie; const struct apple_dcp *dcp = service->ep->dcp; - // TODO: hack, use phy_set_mode to select the correct DCP(EXT) input - // for standalone phy (i.e. not atc phy). - if (!dcp->typec_mux) + /* Standalone PHYs need DCP input selection here. Type-C owns ATC PHY mode. */ + if (!dcp->phy_managed_by_typec) phy_set_mode_ext(dptx->atcphy, PHY_MODE_DP, dcp->index); memcpy(reply, data, min(reply_size, data_size)); @@ -496,9 +495,10 @@ dptxport_call_deactivate(struct apple_epic_service *service, void *reply, size_t reply_size) { struct dptx_port *dptx = service->cookie; + const struct apple_dcp *dcp = service->ep->dcp; - /* deactivate phy */ - phy_set_mode_ext(dptx->atcphy, PHY_MODE_INVALID, 0); + if (!dcp->phy_managed_by_typec) + phy_set_mode_ext(dptx->atcphy, PHY_MODE_INVALID, 0); memcpy(reply, data, min(reply_size, data_size)); if (reply_size >= 4) From 6152da6a47b867da71b4db136505f65837bb1a54 Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Wed, 26 Aug 2026 01:16:44 -0400 Subject: [PATCH 38/51] drm: apple: ignore legacy mux-index on Type-C DCPs Signed-off-by: DjDeveloperr --- drivers/gpu/drm/apple/dcp.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index fabc9028c04594..37bfcab0e82c6b 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -1406,7 +1406,16 @@ static int dcp_platform_probe(struct platform_device *pdev) if (IS_ERR(dcp->dp2hdmi_pwren)) return PTR_ERR(dcp->dp2hdmi_pwren); - ret = of_property_read_u32(dev->of_node, "mux-index", &mux_index); + /* + * Firmware or an inherited board DTS may leave the legacy mux-index + * property on external DCP nodes. Multi-route USB-C DCPs select their + * crossbar through the per-port routes registered above and do not + * have a single "dp-xbar" mux. Treat mux-index only as the legacy + * single-route binding; otherwise one stale property prevents every + * Type-C controller from probing while they wait for the DCP routes. + */ + ret = dcp->nr_typec_routes ? -ENODATA : + of_property_read_u32(dev->of_node, "mux-index", &mux_index); if (!ret) { dcp->xbar = devm_mux_control_get(dev, "dp-xbar"); if (IS_ERR(dcp->xbar)) { From 5c29016e370ba311059fe104234ed469cc466453 Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Sat, 29 Aug 2026 16:40:20 -0400 Subject: [PATCH 39/51] drm: apple: recover Type-C displays across link interruptions A Type-C display that briefly loses its route came back as a powered but blank sink. DCP discards its display mode on link loss while the DRM CRTC stays active, so userspace keeps submitting plane-only commits that never retrain the link, and the swap for such a commit is never completed. Track whether the cable is present, re-establish the DPTX session from a freezable worker when DCP tears its synthetic HPD down with the cable still attached, and re-apply the active CRTC state once the link-config callback has run. Bounce plane-only commits that arrive without a valid mode to the vblank worker so they cannot sit in front of the recovery commit with an unsignalled flip event. Tell DCP whether the routed output supports HPD when connecting the DPTX port, report the real HPD state back from set_hpd instead of assuming success, and answer the tiled-display hint so DCP stops retrying it. Signed-off-by: DjDeveloperr --- drivers/gpu/drm/apple/apple_drv.c | 5 +- drivers/gpu/drm/apple/dcp-internal.h | 3 + drivers/gpu/drm/apple/dcp.c | 154 +++++++++++++++++++++++++-- drivers/gpu/drm/apple/dptxep.c | 28 ++++- drivers/gpu/drm/apple/dptxep.h | 3 +- drivers/gpu/drm/apple/iomfb.c | 51 ++++++++- 6 files changed, 227 insertions(+), 17 deletions(-) diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index 17b59033f12dae..fc87a8fe06f480 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -89,8 +89,6 @@ static void apple_connector_oob_hotplug(struct drm_connector *connector, { struct apple_connector *apple_connector = to_apple_connector(connector); - printk("#### oob_hotplug status:0x%x ####\n", (u32)status); - if (status == connector_status_connected) dcp_dptx_connect_oob(apple_connector->dcp, 0); else if (status == connector_status_disconnected) @@ -328,6 +326,9 @@ static int apple_probe_per_dcp(struct device *dev, dcp_get_connector_type(dcp)); if (ret) return ret; + ret = drm_connector_attach_vrr_capable_property(&connector->base); + if (ret) + return ret; connector->base.polled = DRM_CONNECTOR_POLL_HPD; connector->connected = false; diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h index 7ee39f38c6be4d..511e3c465c534d 100644 --- a/drivers/gpu/drm/apple/dcp-internal.h +++ b/drivers/gpu/drm/apple/dcp-internal.h @@ -272,6 +272,9 @@ struct apple_dcp { struct mutex typec_route_lock; /* serializes Type-C route ownership */ u32 nr_typec_routes; bool phy_managed_by_typec; + bool typec_cable_connected; + struct delayed_work typec_reconnect_wq; + u32 typec_reconnect_tries; struct gpio_desc *hdmi_hpd; struct gpio_desc *hdmi_pwren; diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 37bfcab0e82c6b..fa1db3397354e3 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -500,6 +500,8 @@ int dcp_get_connector_type(struct platform_device *pdev) } #define DPTX_CONNECT_TIMEOUT msecs_to_jiffies(2000) +#define DPTX_RECONNECT_DELAY msecs_to_jiffies(1000) +#define DPTX_RECONNECT_RETRIES 5 static int dcp_dptx_connect(struct apple_dcp *dcp, u32 port) { @@ -523,19 +525,57 @@ static int dcp_dptx_connect(struct apple_dcp *dcp, u32 port) reinit_completion(&dcp->dptxport[port].linkcfg_completion); dcp->dptxport[port].atcphy = dcp->phy; - dptxport_connect(dcp->dptxport[port].service, 0, dcp->dptx_phy, dcp->dptx_die); - dptxport_request_display(dcp->dptxport[port].service); + ret = dptxport_validate_connection(dcp->dptxport[port].service, 0, + dcp->dptx_phy, dcp->dptx_die); + if (ret) { + dev_err(dcp->dev, + "dcp_dptx_connect: failed to validate DPTX target %u:%u: %d\n", + dcp->dptx_die, dcp->dptx_phy, ret); + goto out_unlock; + } + + ret = dptxport_connect(dcp->dptxport[port].service, 0, + dcp->dptx_phy, dcp->dptx_die, + dcp->connector_type == DRM_MODE_CONNECTOR_USB); + if (ret) { + dev_err(dcp->dev, + "dcp_dptx_connect: failed to connect DPTX target %u:%u: %d\n", + dcp->dptx_die, dcp->dptx_phy, ret); + goto out_unlock; + } + + ret = dptxport_request_display(dcp->dptxport[port].service); + if (ret) { + dev_err(dcp->dev, + "dcp_dptx_connect: failed to request display: %d\n", + ret); + goto out_release; + } dcp->dptxport[port].connected = true; + if (dcp->connector_type == DRM_MODE_CONNECTOR_USB) { + ret = dptxport_set_hpd(dcp->dptxport[port].service, true); + if (ret) { + dev_err(dcp->dev, + "dcp_dptx_connect: failed to assert Type-C HPD: %d\n", + ret); + dcp->dptxport[port].connected = false; + goto out_release; + } + } mutex_unlock(&dcp->hpd_mutex); ret = wait_for_completion_timeout(&dcp->dptxport[port].linkcfg_completion, DPTX_CONNECT_TIMEOUT); - if (ret < 0) - dev_warn(dcp->dev, "dcp_dptx_connect: port %d link complete failed:%d\n", - port, ret); - else - dev_dbg(dcp->dev, "dcp_dptx_connect: waited %d ms for link\n", - jiffies_to_msecs(DPTX_CONNECT_TIMEOUT - ret)); + if (!ret) { + dev_err(dcp->dev, + "dcp_dptx_connect: timed out waiting for port %u link configuration\n", + port); + ret = -ETIMEDOUT; + goto out_disconnect; + } + + dev_dbg(dcp->dev, "dcp_dptx_connect: waited %d ms for link\n", + jiffies_to_msecs(DPTX_CONNECT_TIMEOUT - ret)); usleep_range(5, 10); @@ -547,11 +587,43 @@ static int dcp_dptx_connect(struct apple_dcp *dcp, u32 port) return 0; +out_disconnect: + mutex_lock(&dcp->hpd_mutex); + dcp->dptxport[port].connected = false; +out_release: + dptxport_release_display(dcp->dptxport[port].service); + out_unlock: mutex_unlock(&dcp->hpd_mutex); return ret; } +static void dcp_typec_reconnect_work(struct work_struct *work) +{ + struct apple_dcp *dcp = + container_of(to_delayed_work(work), struct apple_dcp, + typec_reconnect_wq); + int ret; + + if (!READ_ONCE(dcp->typec_cable_connected)) + return; + + ret = dcp_dptx_connect(dcp, 0); + if (!ret) { + dcp->typec_reconnect_tries = 0; + return; + } + + if (++dcp->typec_reconnect_tries < DPTX_RECONNECT_RETRIES) { + mod_delayed_work(system_freezable_wq, &dcp->typec_reconnect_wq, + DPTX_RECONNECT_DELAY); + return; + } + + dev_err(dcp->dev, "Type-C DPTX reconnect failed after %u retries: %d\n", + dcp->typec_reconnect_tries, ret); +} + static void disconnected_hpd_event(struct apple_connector *con) { if (con && con->connected) { @@ -577,13 +649,31 @@ static int dcp_dptx_disconnect(struct apple_dcp *dcp, u32 port) int dcp_dptx_connect_oob(struct platform_device *pdev, u32 port) { struct apple_dcp *dcp = platform_get_drvdata(pdev); - return dcp_dptx_connect(dcp, port); + int ret; + + if (dcp->connector_type == DRM_MODE_CONNECTOR_USB) { + WRITE_ONCE(dcp->typec_cable_connected, true); + dcp->typec_reconnect_tries = 0; + cancel_delayed_work(&dcp->typec_reconnect_wq); + } + + ret = dcp_dptx_connect(dcp, port); + if (ret && dcp->connector_type == DRM_MODE_CONNECTOR_USB) + mod_delayed_work(system_freezable_wq, &dcp->typec_reconnect_wq, + DPTX_RECONNECT_DELAY); + + return ret; } int dcp_dptx_disconnect_oob(struct platform_device *pdev, u32 port) { struct apple_dcp *dcp = platform_get_drvdata(pdev); + if (dcp->connector_type == DRM_MODE_CONNECTOR_USB) { + WRITE_ONCE(dcp->typec_cable_connected, false); + cancel_delayed_work(&dcp->typec_reconnect_wq); + } + disconnected_hpd_event(dcp->connector); if (dcp->avep) @@ -793,6 +883,7 @@ static void __maybe_unused dcp_sleep(struct apple_dcp *dcp) void dcp_poweron(struct platform_device *pdev) { struct apple_dcp *dcp = platform_get_drvdata(pdev); + int ret; if (dcp->hdmi_hpd) { bool connected = gpiod_get_value_cansleep(dcp->hdmi_hpd); @@ -800,6 +891,20 @@ void dcp_poweron(struct platform_device *pdev) if (connected) dcp_dptx_connect(dcp, 0); + } else if (dcp->connector_type == DRM_MODE_CONNECTOR_USB && + READ_ONCE(dcp->typec_cable_connected)) { + /* + * A Type-C CRTC disable releases its DPTX session. Re-establish + * that session synchronously before powering IOMFB back on, so the + * following modeset cannot race the delayed reconnect worker. + */ + cancel_delayed_work(&dcp->typec_reconnect_wq); + dcp->typec_reconnect_tries = 0; + ret = dcp_dptx_connect(dcp, 0); + if (ret) + mod_delayed_work(system_freezable_wq, + &dcp->typec_reconnect_wq, + DPTX_RECONNECT_DELAY); } switch (dcp->fw_compat) { @@ -821,6 +926,7 @@ void dcp_poweron(struct platform_device *pdev) void dcp_poweroff(struct platform_device *pdev) { struct apple_dcp *dcp = platform_get_drvdata(pdev); + int ret; if (dcp->avep) av_service_disconnect(dcp); @@ -833,6 +939,33 @@ void dcp_poweroff(struct platform_device *pdev) disconnected_hpd_event(dcp->connector); dcp_dptx_disconnect(dcp, 0); } + } else if (dcp->connector_type == DRM_MODE_CONNECTOR_USB && + dcp->dptxport[0].enabled && dcp->dptxport[0].connected) { + /* + * Unlike a physical DP/HDMI HPD, the Type-C HPD exposed by the + * DPTX service belongs to this power-on session. Release it when + * the CRTC powers down so the next enable performs the same orderly + * connect/request/HPD sequence as a fresh cable connection. + */ + ret = dptxport_set_hpd(dcp->dptxport[0].service, false); + if (ret) + dev_warn(dcp->dev, + "failed to deassert Type-C DPTX HPD: %d\n", ret); + dcp_dptx_disconnect(dcp, 0); + + /* + * DCP tears down its synthetic HPD when the CRTC powers off, but + * the Type-C controller does not necessarily emit another cable + * connect edge. Recreate the DPTX session while the physical cable + * remains present. A freezable workqueue keeps this out of the + * suspend path; if suspend wins the race, reconnect after thaw. + */ + if (READ_ONCE(dcp->typec_cable_connected)) { + dcp->typec_reconnect_tries = 0; + mod_delayed_work(system_freezable_wq, + &dcp->typec_reconnect_wq, + DPTX_RECONNECT_DELAY); + } } } @@ -1284,6 +1417,7 @@ static void dcp_comp_unbind(struct device *dev, struct device *main, void *data) cancel_work_sync(&dcp->bl_register_wq); cancel_work_sync(&dcp->bl_update_wq); } + cancel_delayed_work_sync(&dcp->typec_reconnect_wq); cancel_work_sync(&dcp->vblank_wq); devm_clk_put(dev, dcp->clk); @@ -1324,6 +1458,8 @@ static int dcp_platform_probe(struct platform_device *pdev) dcp->dev = dev; dcp->hw = *(struct apple_dcp_hw_data *)of_device_get_match_data(dev); mutex_init(&dcp->typec_route_lock); + INIT_DELAYED_WORK(&dcp->typec_reconnect_wq, + dcp_typec_reconnect_work); platform_set_drvdata(pdev, dcp); diff --git a/drivers/gpu/drm/apple/dptxep.c b/drivers/gpu/drm/apple/dptxep.c index fa473743040220..3d286fc2c846ce 100644 --- a/drivers/gpu/drm/apple/dptxep.c +++ b/drivers/gpu/drm/apple/dptxep.c @@ -69,6 +69,10 @@ struct dptxport_apcall_drive_settings { __le32 unk7; }; +struct dptxport_apcall_set_tiled { + __le32 retcode; +}; + int dptxport_validate_connection(struct apple_epic_service *service, u8 core, u8 atc, u8 die) { @@ -98,11 +102,11 @@ int dptxport_validate_connection(struct apple_epic_service *service, u8 core, } int dptxport_connect(struct apple_epic_service *service, u8 core, u8 atc, - u8 die) + u8 die, bool supports_hpd) { struct dptx_port *dptx = service->cookie; struct dcpdptx_connection_cmd cmd, resp; - u32 unk_field = 0x0; // seen as 0x100 under some conditions + u32 unk_field = supports_hpd ? DCPDPTX_REMOTE_PORT_SUPPORTS_HPD : 0; int ret; u32 target = FIELD_PREP(DCPDPTX_REMOTE_PORT_CORE, core) | FIELD_PREP(DCPDPTX_REMOTE_PORT_ATC, atc) | @@ -151,7 +155,7 @@ int dptxport_set_hpd(struct apple_epic_service *service, bool hpd) sizeof(resp), 12); if (ret) return ret; - if (le32_to_cpu(resp.unk) != 1) + if (le32_to_cpu(resp.unk) != hpd) return -EINVAL; return 0; } @@ -446,13 +450,14 @@ static int dptxport_call_set_link_rate(struct apple_epic_service *service, static int dptxport_call_get_supports_hpd(struct apple_epic_service *service, void *reply_, size_t reply_size) { + struct apple_dcp *dcp = service->ep->dcp; struct dptxport_apcall_get_support *reply = reply_; if (reply_size < sizeof(*reply)) return -EINVAL; reply->retcode = cpu_to_le32(0); - reply->supported = cpu_to_le32(0); + reply->supported = cpu_to_le32(dcp->connector_type == DRM_MODE_CONNECTOR_USB); return 0; } @@ -470,6 +475,18 @@ dptxport_call_get_supports_downspread(struct apple_epic_service *service, return 0; } +static int dptxport_call_set_tiled_display_hint(void *reply_, + size_t reply_size) +{ + struct dptxport_apcall_set_tiled *reply = reply_; + + if (reply_size < sizeof(*reply)) + return -EINVAL; + + reply->retcode = cpu_to_le32(1); + return 0; +} + static int dptxport_call_activate(struct apple_epic_service *service, const void *data, size_t data_size, @@ -541,6 +558,9 @@ static int dptxport_call(struct apple_epic_service *service, u32 idx, case DPTX_APCALL_GET_MAX_DRIVE_SETTINGS: return dptxport_call_get_max_drive_settings(service, reply, reply_size); + case DPTX_APCALL_SET_TILED_DISPLAY_HINTS: + memcpy(reply, data, min(reply_size, data_size)); + return dptxport_call_set_tiled_display_hint(reply, reply_size); case DPTX_APCALL_GET_DRIVE_SETTINGS: return dptxport_call_get_drive_settings(service, data, data_size, reply, reply_size); diff --git a/drivers/gpu/drm/apple/dptxep.h b/drivers/gpu/drm/apple/dptxep.h index 0bf2534054fd7b..c8816a040f3214 100644 --- a/drivers/gpu/drm/apple/dptxep.h +++ b/drivers/gpu/drm/apple/dptxep.h @@ -36,6 +36,7 @@ enum dptx_apcall { #define DCPDPTX_REMOTE_PORT_ATC GENMASK(7, 4) #define DCPDPTX_REMOTE_PORT_DIE GENMASK(11, 8) #define DCPDPTX_REMOTE_PORT_CONNECTED BIT(15) +#define DCPDPTX_REMOTE_PORT_SUPPORTS_HPD BIT(8) enum dptx_link_rate { LINK_RATE_RBR = 0x06, @@ -63,7 +64,7 @@ struct dptx_port { int dptxport_validate_connection(struct apple_epic_service *service, u8 core, u8 atc, u8 die); int dptxport_connect(struct apple_epic_service *service, u8 core, u8 atc, - u8 die); + u8 die, bool supports_hpd); int dptxport_request_display(struct apple_epic_service *service); int dptxport_release_display(struct apple_epic_service *service); int dptxport_set_hpd(struct apple_epic_service *service, bool hpd); diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index 1d9448f0f4dc47..e80a9c0a292b36 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -16,11 +16,14 @@ #include #include +#include +#include #include #include #include #include #include +#include #include #include @@ -231,10 +234,31 @@ void dcp_ack(struct apple_dcp *dcp, enum dcp_context_id context) * waits for vblank (a DCP callback). That means we deadlock if we call from * the RTKit thread! Instead, move the call to another thread via a workqueue. */ +static int dcp_retrain_active_crtc(struct apple_connector *connector) +{ + struct drm_device *dev = connector->base.dev; + struct drm_modeset_acquire_ctx ctx; + struct drm_crtc *crtc; + int ret; + + DRM_MODESET_LOCK_ALL_BEGIN(dev, ctx, 0, ret); + + crtc = connector->base.state ? connector->base.state->crtc : NULL; + if (crtc && crtc->state && crtc->state->active) + ret = drm_atomic_helper_reset_crtc(crtc, &ctx); + else + ret = 0; + + DRM_MODESET_LOCK_ALL_END(dev, ctx, ret); + + return ret; +} + void dcp_hotplug(struct work_struct *work) { struct apple_connector *connector; struct apple_dcp *dcp; + int ret; connector = container_of(work, struct apple_connector, hotplug_wq); @@ -252,10 +276,24 @@ void dcp_hotplug(struct work_struct *work) * display modes from atomic_flush, so userspace needs to trigger a * flush, or the CRTC gets no signal. */ - if (connector->base.state && !dcp->valid_mode && connector->connected) + if (connector->base.state && !dcp->valid_mode && connector->connected) { drm_connector_set_link_status_property(&connector->base, DRM_MODE_LINK_STATUS_BAD); + /* + * A short Type-C route interruption can leave the DRM CRTC active + * while DCP has discarded its display mode. Userspace is then free + * to keep submitting plane-only commits, none of which retrains the + * link. Re-apply the active CRTC state once the DPTX link-config + * callback has completed. + */ + ret = dcp_retrain_active_crtc(connector); + if (ret) + dev_warn(dcp->dev, + "failed to retrain active CRTC after hotplug: %d\n", + ret); + } + drm_kms_helper_connector_hotplug_event(&connector->base); } @@ -463,6 +501,17 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) struct platform_device *pdev = to_apple_crtc(crtc)->dcp; struct apple_dcp *dcp = platform_get_drvdata(pdev); + /* + * DCP does not complete swaps after a link loss. A plane-only commit + * arriving between the reconnect callback and the required modeset + * would otherwise leave an unsignalled flip event in front of the + * recovery commit. Modesets set valid_mode before reaching flush. + */ + if (!dcp->valid_mode) { + schedule_work(&dcp->vblank_wq); + return; + } + if (dcp_channel_busy(&dcp->ch_cmd)) { if (!dcp->ch_cmd.warned_busy) { From ec22f66f71d4dbfe12711c9c74a88d090e4cfeb9 Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Sat, 29 Aug 2026 00:57:28 -0400 Subject: [PATCH 40/51] drm: apple: retrain Type-C display after USB4 route changes Signed-off-by: DjDeveloperr --- drivers/gpu/drm/apple/apple_drv.c | 2 ++ drivers/gpu/drm/apple/connector.h | 1 + drivers/gpu/drm/apple/iomfb.c | 17 +++++++++++++++++ drivers/gpu/drm/drm_connector.c | 4 ++++ drivers/usb/typec/tipd/core.c | 13 +++++++++++++ 5 files changed, 37 insertions(+) diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index fc87a8fe06f480..afe7bff783b991 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -93,6 +93,8 @@ static void apple_connector_oob_hotplug(struct drm_connector *connector, dcp_dptx_connect_oob(apple_connector->dcp, 0); else if (status == connector_status_disconnected) dcp_dptx_disconnect_oob(apple_connector->dcp, 0); + else if (status == connector_status_unknown) + dcp_retrain_oob(apple_connector); else dev_err(&apple_connector->dcp->dev, "unexpected connector status" ":0x%x in oob_hotplug event\n", (u32)status); diff --git a/drivers/gpu/drm/apple/connector.h b/drivers/gpu/drm/apple/connector.h index ef2c23737aac64..8c9d367eda94fc 100644 --- a/drivers/gpu/drm/apple/connector.h +++ b/drivers/gpu/drm/apple/connector.h @@ -15,6 +15,7 @@ struct apple_connector; #include "dcp-internal.h" void dcp_hotplug(struct work_struct *work); +void dcp_retrain_oob(struct apple_connector *connector); struct apple_connector { struct drm_connector base; diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index e80a9c0a292b36..3f24cb75c46ce1 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -254,6 +254,23 @@ static int dcp_retrain_active_crtc(struct apple_connector *connector) return ret; } +void dcp_retrain_oob(struct apple_connector *connector) +{ + struct apple_dcp *dcp = platform_get_drvdata(connector->dcp); + + if (!READ_ONCE(connector->connected) || !READ_ONCE(dcp->valid_mode)) + return; + + /* + * Bringing up another high-speed Type-C route can disturb an active DPTX + * stream without changing its HPD state. Invalidate the IOMFB mode and + * use the normal hotplug worker to replay the active CRTC from process + * context; sending a synthetic disconnect would tear down the connector. + */ + WRITE_ONCE(dcp->valid_mode, false); + schedule_work(&connector->hotplug_wq); +} + void dcp_hotplug(struct work_struct *work) { struct apple_connector *connector; diff --git a/drivers/gpu/drm/drm_connector.c b/drivers/gpu/drm/drm_connector.c index 29634757c06d19..12b2dc6155919f 100644 --- a/drivers/gpu/drm/drm_connector.c +++ b/drivers/gpu/drm/drm_connector.c @@ -3516,6 +3516,10 @@ struct drm_connector *drm_connector_find_by_fwnode(struct fwnode_handle *fwnode) * muxes the DisplayPort data and aux-lines but does not pass the altmode HPD * status bit to the GPU's DP HPD pin. * + * A caller may report &connector_status_unknown when a shared display route + * changed without a logical HPD transition and the connector should recheck or + * retrain its existing link. + * * This function can be used to report these out-of-band events after obtaining * a drm_connector reference through calling drm_connector_find_by_fwnode(). */ diff --git a/drivers/usb/typec/tipd/core.c b/drivers/usb/typec/tipd/core.c index c9afbf84d074b1..e11a832b47b0e6 100644 --- a/drivers/usb/typec/tipd/core.c +++ b/drivers/usb/typec/tipd/core.c @@ -718,6 +718,9 @@ static void cd321x_update_work(struct work_struct *work) TPS_DATA_STATUS_DP_PIN_ASSIGNMENT_MASK); bool dp_route_was_active = cd321x->display_route_active; bool dp_connected = st.data_status & TPS_DATA_STATUS_DP_CONNECTION; + bool usb4_started = + (st.data_status_changed & CD321X_DATA_STATUS_USB4_CONNECTION) && + (st.data_status & CD321X_DATA_STATUS_USB4_CONNECTION); bool dp_hpd = st.data_status & CD321X_DATA_STATUS_HPD_LEVEL; bool dp_hpd_changed = st.data_status_changed & CD321X_DATA_STATUS_HPD_LEVEL; @@ -847,6 +850,16 @@ static void cd321x_update_work(struct work_struct *work) if (cd321x->connector_fwnode && cd321x->display_route_active && dp_hpd) drm_connector_oob_hotplug_event(cd321x->connector_fwnode, connector_status_connected); + /* + * Apple routes all Type-C ports through shared high-speed display fabric. + * Starting USB4 on one port can interrupt a DP stream on another without + * changing that display's HPD state. Ask the display driver to retrain the + * still-connected route after ACIO and the USB role are live. + */ + if (cd321x->connector_fwnode && usb4_started && !dp_connected) + drm_connector_oob_hotplug_event(cd321x->connector_fwnode, + connector_status_unknown); + power_supply_changed(tps->psy); } From 0a580ce4855e3ec1c85eb7e05857710772aa51ed Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Sat, 29 Aug 2026 04:55:06 -0400 Subject: [PATCH 41/51] drm: apple: refresh EDID after Type-C disconnect The out-of-band Type-C disconnect path marks the connector offline and emits a hotplug event before the DCP callback arrives. Since that callback then observes no connector state transition, the normal worker never releases the cached EDID. A replacement display can therefore inherit the previous monitor name even though DCP has discovered its new modes. Release the cached EDID in the synchronous out-of-band disconnect path before notifying DRM. The next mode probe will fetch the replacement display EDID from DCPAV. Fixes: 85dd82bc5967 ("drm/apple: support shared Type-C display routes") Signed-off-by: DjDeveloperr --- drivers/gpu/drm/apple/dcp.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index fa1db3397354e3..502e4056163c82 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -628,6 +628,8 @@ static void disconnected_hpd_event(struct apple_connector *con) { if (con && con->connected) { con->connected = 0; + drm_edid_free(con->drm_edid); + con->drm_edid = NULL; drm_kms_helper_connector_hotplug_event(&con->base); } } From 1b6dd2985137ddf6cb6f7740c68e430d0edac044 Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Mon, 31 Aug 2026 01:09:14 -0400 Subject: [PATCH 42/51] drm: apple: route Type-C displays through a shared DCP fabric Every Type-C port was bound to one fixed external DCP, so a second DP-alt-mode connection collided with the first and was rejected with "DP route is already in use by another Type-C port" even when another external DCP sat idle. The hardware does not work that way: a display crossbar sits between the DCPs and the Type-C PHYs, and any pipeline it can reach can drive any port wired to it. Build a display fabric from the OF graph. Candidate pipelines are grouped by the remote USB-C connector and the first notification to arrive for a port does the work, the rest falling through. There is deliberately no nominated coordinator: fwnode_typec_mux_get() caps the providers one connector may have and drops the remainder silently, so a nominated route might never be called and the port would never be routed at all. The candidate count comes from the device tree, so machines with one, two or more dcpext instances work without a per-board constant. A Type-C output is a physical port, not a pipeline, so give each port its own DRM connector. Userspace keys its per-monitor configuration on the connector name, so ports are enumerated in device-tree order and a port keeps its name whichever pipeline drives it. A pipeline whose only output is Type-C has no connector, and no encoder, of its own. Give each port a single encoder whose possible_crtcs covers every pipeline that can drive it, and narrow that mask to the pipeline actually routed once the fabric picks one. Both halves are needed. Userspace takes the CRTCs a connector can use to be what all of its encoders have in common, so one encoder per pipeline would leave nothing in common and the port would have no usable CRTC at all. Leaving the mask wide instead lets userspace pair the port with a pipeline holding another monitor's mode list, which the CRTC's mode check then rejects. The hotplug that follows a route change makes userspace re-read the mask. While a port has no pipeline it reports disconnected, so nothing probes an output whose ->dcp is NULL. A hybrid pipeline saves its fixed PHY, crossbar and DPTX target, and is only borrowed while its fixed output is idle. On release it parks its DPTX target on a Type-C PHY rather than the fixed one: re-targeting an idle fixed PHY leaves DCP unable to train a link on a later Type-C target, answering DEVICE_NOT_STARTED until every DPTX call times out. Signed-off-by: DjDeveloperr --- drivers/gpu/drm/apple/apple_drv.c | 228 ++++++-- drivers/gpu/drm/apple/connector.h | 19 + drivers/gpu/drm/apple/dcp-internal.h | 20 +- drivers/gpu/drm/apple/dcp.c | 743 ++++++++++++++++++++++----- drivers/gpu/drm/apple/dcp.h | 11 + drivers/gpu/drm/apple/dptxep.c | 11 +- drivers/gpu/drm/apple/iomfb.c | 13 +- 7 files changed, 888 insertions(+), 157 deletions(-) diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index afe7bff783b991..514d54fe58296c 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -44,7 +45,7 @@ #define DRIVER_NAME "apple" #define DRIVER_DESC "Apple display controller DRM driver" -#define MAX_COPROCESSORS 3 +#define MAX_COPROCESSORS 8 struct apple_drm_private { struct drm_device drm; @@ -250,9 +251,35 @@ static const struct drm_connector_funcs apple_connector_funcs = { .oob_hotplug_event = apple_connector_oob_hotplug, }; +/* + * A Type-C connector is attached to every pipeline that can drive its port, so + * the atomic helper cannot pick one on its own. Follow the fabric's choice. + */ +static struct drm_encoder * +apple_connector_atomic_best_encoder(struct drm_connector *conn, + struct drm_atomic_state *state) +{ + struct apple_connector *apple_connector = to_apple_connector(conn); + struct drm_encoder *encoder; + + /* + * A Type-C port has one encoder, and its possible_crtcs already names + * the pipeline the fabric routed the port to, so it is the only answer + * there is. + */ + if (apple_connector->port_encoder) + return apple_connector->port_encoder; + + drm_connector_for_each_possible_encoder(conn, encoder) + return encoder; + + return NULL; +} + static const struct drm_connector_helper_funcs apple_connector_helper_funcs = { .get_modes = dcp_get_modes, .mode_valid = dcp_mode_valid, + .atomic_best_encoder = apple_connector_atomic_best_encoder, }; static const struct drm_crtc_helper_funcs apple_crtc_helper_funcs = { @@ -264,17 +291,68 @@ static const struct drm_crtc_helper_funcs apple_crtc_helper_funcs = { .mode_fixup = dcp_crtc_mode_fixup, }; +static int apple_connector_create(struct drm_device *drm, + struct platform_device *dcp, + struct apple_crtc *crtc, + struct apple_encoder *encoder, + int connector_type, bool attach_fwnode) +{ + struct apple_connector *connector; + int ret; + + connector = kzalloc_obj(*connector); + if (!connector) + return -ENOMEM; + + mutex_init(&connector->chunk_lock); + drm_connector_helper_add(&connector->base, + &apple_connector_helper_funcs); + if (attach_fwnode) + connector->base.fwnode = fwnode_handle_get(dcp->dev.fwnode); + + ret = drm_connector_init(drm, &connector->base, &apple_connector_funcs, + connector_type); + if (ret) + goto err_free; + ret = drm_connector_attach_vrr_capable_property(&connector->base); + if (ret) + goto err_cleanup; + + connector->base.polled = DRM_CONNECTOR_POLL_HPD; + connector->connected = false; + connector->dcp = dcp; + INIT_WORK(&connector->hotplug_wq, dcp_hotplug); + + ret = drm_connector_attach_encoder(&connector->base, &encoder->base); + if (ret) + goto err_cleanup; + dcp_link(dcp, crtc, connector); + + return 0; + +err_cleanup: + drm_connector_cleanup(&connector->base); + kfree(connector); + return ret; +err_free: + fwnode_handle_put(connector->base.fwnode); + kfree(connector); + return ret; +} + static int apple_probe_per_dcp(struct device *dev, struct drm_device *drm, struct platform_device *dcp, - int num, bool dcp_ext) + int num, bool dcp_ext, + struct drm_encoder **encoder, + u32 *crtc_mask) { struct apple_crtc *crtc; - struct apple_connector *connector; struct apple_encoder *enc; struct drm_plane *planes[DCP_MAX_PLANES]; unsigned long *iomfb_surfaces = dcp_get_iomfb_surfaces(dcp); int ret; + int connector_type; u32 surf; int zpos = 0; bool supports_l10r = !dcp_fw_compat_is_12_x(dcp); @@ -309,39 +387,113 @@ static int apple_probe_per_dcp(struct device *dev, drm_crtc_helper_add(&crtc->base, &apple_crtc_helper_funcs); drm_crtc_enable_color_mgmt(&crtc->base, 0, true, 0); + crtc->dcp = dcp; + *crtc_mask = drm_crtc_mask(&crtc->base); + connector_type = dcp_get_connector_type(dcp); + + /* + * Type-C outputs are physical ports, not pipelines: several DCPs may be + * able to drive one port, and which one does can change at runtime. + * Those connectors are created per port once every pipeline is known, + * so a pipeline whose only output is Type-C gets no connector here -- + * and no encoder either, because the port's encoder spans every + * pipeline that can drive it. + */ + if (connector_type == DRM_MODE_CONNECTOR_USB) { + dcp_link(dcp, crtc, NULL); + *encoder = NULL; + return 0; + } + enc = drmm_simple_encoder_alloc(drm, struct apple_encoder, base, DRM_MODE_ENCODER_TMDS); if (IS_ERR(enc)) - return PTR_ERR(enc); - enc->base.possible_crtcs = drm_crtc_mask(&crtc->base); + return PTR_ERR(enc); + enc->base.possible_crtcs = *crtc_mask; + *encoder = &enc->base; - connector = kzalloc(sizeof(*connector), GFP_KERNEL); - mutex_init(&connector->chunk_lock); - drm_connector_helper_add(&connector->base, - &apple_connector_helper_funcs); + return apple_connector_create(drm, dcp, crtc, enc, connector_type, + dcp_ext); +} - // HACK: - if (dcp_ext) - connector->base.fwnode = fwnode_handle_get(dcp->dev.fwnode); +/* + * Create one connector per physical Type-C port and attach it to every + * pipeline that can drive it. Keeping the connector tied to the port rather + * than to a pipeline is what gives userspace a stable name to hang its + * per-monitor configuration on. + */ +static int apple_probe_typec_ports(struct drm_device *drm, + struct platform_device **dcp, + u32 *crtc_mask, int num_dcp) +{ + unsigned int idx, nr_ports = dcp_typec_nr_ports(); + int i, ret; + + for (idx = 0; idx < nr_ports; idx++) { + struct apple_connector *connector; + struct apple_encoder *enc; + u32 mask = 0; + + connector = kzalloc_obj(*connector); + if (!connector) + return -ENOMEM; + + mutex_init(&connector->chunk_lock); + drm_connector_helper_add(&connector->base, + &apple_connector_helper_funcs); + + ret = drm_connector_init(drm, &connector->base, + &apple_connector_funcs, + DRM_MODE_CONNECTOR_USB); + if (ret) { + kfree(connector); + return ret; + } - ret = drm_connector_init(drm, &connector->base, &apple_connector_funcs, - dcp_get_connector_type(dcp)); - if (ret) - return ret; - ret = drm_connector_attach_vrr_capable_property(&connector->base); - if (ret) - return ret; + ret = drm_connector_attach_vrr_capable_property(&connector->base); + if (ret) + return ret; - connector->base.polled = DRM_CONNECTOR_POLL_HPD; - connector->connected = false; - connector->dcp = dcp; + connector->base.polled = DRM_CONNECTOR_POLL_HPD; + connector->connected = false; + connector->dcp = NULL; + INIT_WORK(&connector->hotplug_wq, dcp_hotplug); - INIT_WORK(&connector->hotplug_wq, dcp_hotplug); + for (i = 0; i < num_dcp; i++) { + if (dcp_typec_port_has_candidate(idx, dcp[i])) + mask |= crtc_mask[i]; + } - crtc->dcp = dcp; - dcp_link(dcp, crtc, connector); + if (!mask) { + drm_warn(drm, "Type-C port %u has no display pipeline\n", + idx); + return -ENODEV; + } - return drm_connector_attach_encoder(&connector->base, &enc->base); + /* + * One encoder for the port, spanning every pipeline that can + * drive it. Attaching one encoder per pipeline would describe + * the same hardware, but userspace takes the CRTCs a connector + * can use to be what all of its encoders have in common, and + * single-pipeline encoders have nothing in common -- the port + * ends up with no usable CRTC and the monitor is left dark. + */ + enc = drmm_simple_encoder_alloc(drm, struct apple_encoder, base, + DRM_MODE_ENCODER_TMDS); + if (IS_ERR(enc)) + return PTR_ERR(enc); + enc->base.possible_crtcs = mask; + + ret = drm_connector_attach_encoder(&connector->base, &enc->base); + if (ret) + return ret; + connector->port_encoder = &enc->base; + connector->candidate_crtcs = mask; + + dcp_typec_port_set_connector(idx, connector); + } + + return 0; } static int apple_get_fb_resource(struct device *dev, const char *name, @@ -386,6 +538,8 @@ static int apple_drm_init_dcp(struct device *dev) { struct apple_drm_private *apple = dev_get_drvdata(dev); struct platform_device *dcp[MAX_COPROCESSORS]; + struct drm_encoder *encoder[MAX_COPROCESSORS]; + u32 crtc_mask[MAX_COPROCESSORS] = {}; struct device_node *np; u64 timeout; int i, ret, num_dcp = 0; @@ -407,11 +561,8 @@ static int apple_drm_init_dcp(struct device *dev) device_link_add(dev, &dcp[num_dcp]->dev, DL_FLAG_AUTOREMOVE_SUPPLIER); ret = apple_probe_per_dcp(dev, &apple->drm, dcp[num_dcp], - num_dcp, dcp_ext); - if (ret) - continue; - - ret = dcp_start(dcp[num_dcp]); + num_dcp, dcp_ext, &encoder[num_dcp], + &crtc_mask[num_dcp]); if (ret) continue; @@ -421,6 +572,21 @@ static int apple_drm_init_dcp(struct device *dev) if (num_dcp < 1) return -ENODEV; + /* + * Build the Type-C port connectors before starting any pipeline: a + * pipeline can be handed a route as soon as the Type-C mux notifies, + * and it needs somewhere to report the display. + */ + ret = apple_probe_typec_ports(&apple->drm, dcp, crtc_mask, num_dcp); + if (ret) + return ret; + + for (i = 0; i < num_dcp; i++) { + ret = dcp_start(dcp[i]); + if (ret) + dev_warn(dev, "DCP[%d] failed to start: %d\n", i, ret); + } + /* * Starting DPTX might take some time. */ diff --git a/drivers/gpu/drm/apple/connector.h b/drivers/gpu/drm/apple/connector.h index 8c9d367eda94fc..4cb58502c1ef8c 100644 --- a/drivers/gpu/drm/apple/connector.h +++ b/drivers/gpu/drm/apple/connector.h @@ -17,12 +17,31 @@ struct apple_connector; void dcp_hotplug(struct work_struct *work); void dcp_retrain_oob(struct apple_connector *connector); +/* + * How many DCP pipelines may offer a route to one Type-C port. The Type-C mux + * class caps the mode-switch providers per connector (TYPEC_MUX_MAX_DEVS) and + * the ATC PHY already claims one of those slots, so this is generous. + */ struct apple_connector { struct drm_connector base; bool connected; + /* The pipeline currently driving this connector, NULL if unrouted. */ struct platform_device *dcp; + /* + * A Type-C connector is a physical port rather than a fixed pipeline, + * and it has one encoder covering every pipeline that can drive it. + * One encoder per pipeline would describe the same hardware, but + * userspace takes the CRTCs a connector can use to be what all of its + * encoders have in common, and single-pipeline encoders have nothing + * in common. + */ + struct drm_encoder *port_encoder; + + /* the CRTCs of every pipeline that can drive the port */ + u32 candidate_crtcs; + const struct drm_edid *drm_edid; /* Workqueue for sending hotplug events to the associated device */ diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h index 511e3c465c534d..3a7732dfbfd96b 100644 --- a/drivers/gpu/drm/apple/dcp-internal.h +++ b/drivers/gpu/drm/apple/dcp-internal.h @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -25,9 +26,12 @@ struct apple_dcp; struct apple_dcp_afkep; +struct apple_dcp_typec_port; struct apple_dcp_typec_route { struct apple_dcp *dcp; + struct apple_dcp_typec_port *port; + struct list_head port_link; struct phy *phy; struct mux_control *xbar; struct typec_mux_dev *typec_mux; @@ -36,6 +40,8 @@ struct apple_dcp_typec_route { bool selected; }; +bool dcp_is_typec_output(struct apple_dcp *dcp); + struct dcpav_service_epic; enum dcp_firmware_version { @@ -194,11 +200,16 @@ struct apple_dcp { /* swap id of the last completed swap */ u32 last_swap_id; ktime_t swap_start; + u64 swap_submit_timestamp; /* Current display mode */ bool during_modeset; + /* hotplug reported while a modeset was in flight, applied afterwards */ + bool pending_hotplug; + bool pending_hotplug_connected; bool valid_mode; bool use_timestamps; + bool vrr_enabled; struct dcp_set_digital_out_mode_req mode; /* completion for active turning true */ @@ -222,6 +233,7 @@ struct apple_dcp { /* Attributes of the connector */ int connector_type; + int fixed_connector_type; /* Attributes of the connected display */ int width_mm, height_mm; @@ -265,15 +277,16 @@ struct apple_dcp { /* these fields are output port specific */ struct phy *phy; + struct phy *fixed_phy; struct mux_control *xbar; struct typec_mux *typec_mux; struct apple_dcp_typec_route typec_routes[DCP_MAX_TYPEC_ROUTES]; struct apple_dcp_typec_route *active_typec_route; - struct mutex typec_route_lock; /* serializes Type-C route ownership */ u32 nr_typec_routes; bool phy_managed_by_typec; bool typec_cable_connected; struct delayed_work typec_reconnect_wq; + struct delayed_work typec_fabric_retrain_wq; u32 typec_reconnect_tries; struct gpio_desc *hdmi_hpd; @@ -284,6 +297,11 @@ struct apple_dcp { u32 dptx_phy; u32 dptx_die; + u32 fixed_dptx_phy; + u32 fixed_mux_index; + bool fixed_route_selected; + struct apple_connector *fixed_connector; + struct apple_connector *typec_connector; int hdmi_hpd_irq; }; diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 502e4056163c82..4fc018f4132392 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -58,69 +59,481 @@ static bool unstable_edid = true; module_param(unstable_edid, bool, 0644); MODULE_PARM_DESC(unstable_edid, "Enable unstable EDID retrival support"); +struct apple_dcp_typec_port { + struct list_head link; + struct list_head routes; + struct device_node *connector_np; + struct apple_dcp_typec_route *owner; + /* DRM connector for this physical port, driven by whichever DCP owns it */ + struct apple_connector *connector; + /* last mux state acted on, to collapse the per-candidate notifications */ + struct typec_altmode *applied_alt; + unsigned long applied_mode; + u32 applied_status; + u32 applied_conf; + bool applied_valid; + bool hpd; +}; + +static DEFINE_MUTEX(dcp_typec_fabric_lock); +static LIST_HEAD(dcp_typec_ports); + +static int dcp_dptx_connect(struct apple_dcp *dcp, u32 port); +static void disconnected_hpd_event(struct apple_connector *connector); + +bool dcp_is_typec_output(struct apple_dcp *dcp) +{ + return dcp->active_typec_route || + dcp->fixed_connector_type == DRM_MODE_CONNECTOR_USB; +} + static bool dcp_typec_route_is_dp(const struct typec_mux_state *state) { return state->alt && state->alt->svid == USB_TYPEC_DP_SID && - (state->mode == TYPEC_DP_STATE_C || - state->mode == TYPEC_DP_STATE_D || - state->mode == TYPEC_DP_STATE_E); + state->mode >= TYPEC_DP_STATE_A && + state->mode <= TYPEC_DP_STATE_F; } -static int dcp_typec_route_set(struct typec_mux_dev *mux, - struct typec_mux_state *state) +/* Keep a live fixed output on its own pipeline. */ +static bool dcp_typec_route_fixed_output_busy(struct apple_dcp_typec_route *route) { - struct apple_dcp_typec_route *route = typec_mux_get_drvdata(mux); struct apple_dcp *dcp = route->dcp; - int ret = 0; - guard(mutex)(&dcp->typec_route_lock); + if (dcp->fixed_connector_type == DRM_MODE_CONNECTOR_USB) + return false; + if (dcp->fixed_connector && dcp->fixed_connector->connected) + return true; + if (dcp->hdmi_hpd && gpiod_get_value_cansleep(dcp->hdmi_hpd)) + return true; - if (!dcp_typec_route_is_dp(state)) { - if (!route->selected) - return 0; + return false; +} - ret = mux_control_deselect(route->xbar); - route->selected = false; - if (dcp->active_typec_route == route) - dcp->active_typec_route = NULL; - return ret; - } +static bool dcp_typec_route_available(struct apple_dcp_typec_route *route) +{ + return !route->dcp->active_typec_route && + !dcp_typec_route_fixed_output_busy(route); +} - if (route->selected) - return 0; +static int dcp_typec_route_activate(struct apple_dcp_typec_route *route); +static int dcp_typec_route_deactivate(struct apple_dcp_typec_route *route); + +/* + * Pipelines are ranked by CRTC index so the fabric's choice is a pure function + * of the topology rather than of plug order. A pipeline whose fixed output is + * live is not a candidate at all, so a hybrid is only ever ranked here when it + * is genuinely free. + */ +static unsigned int dcp_typec_route_score(struct apple_dcp_typec_route *route) +{ + struct apple_dcp *dcp = route->dcp; - if (dcp->active_typec_route) { - dev_warn(dcp->dev, "DP route is already in use by another Type-C port\n"); - return -EBUSY; + if (!dcp->crtc) + return UINT_MAX - 1; + + return drm_crtc_index(&dcp->crtc->base); +} + +static int dcp_typec_route_activate(struct apple_dcp_typec_route *route) +{ + struct apple_dcp *dcp = route->dcp; + int ret; + + if (dcp->fixed_route_selected) { + ret = mux_control_deselect(dcp->xbar); + if (ret) + return ret; + dcp->fixed_route_selected = false; } ret = mux_control_select(route->xbar, route->mux_index); - if (ret) + if (ret) { + if (dcp->xbar) { + int restore_ret; + + restore_ret = mux_control_select(dcp->xbar, + dcp->fixed_mux_index); + if (!restore_ret) + dcp->fixed_route_selected = true; + else + dev_err(dcp->dev, + "failed to restore fixed display route: %d\n", + restore_ret); + } return ret; + } dcp->phy = route->phy; dcp->dptx_phy = route->dptx_phy; + dcp->connector_type = DRM_MODE_CONNECTOR_USB; + if (route->port->connector) { + route->port->connector->dcp = to_platform_device(dcp->dev); + dcp->typec_connector = route->port->connector; + dcp->connector = route->port->connector; + + /* + * Narrow the port to the pipeline now driving it. The encoder + * spans every pipeline that could, which is what lets userspace + * see the port as usable at all -- but only one of them is + * routed to the display, and userspace has no way to tell which. + * Offering it the choice makes it pair the port with a pipeline + * holding a different monitor's mode list, and the modeset is + * rejected with no way for it to recover. The hotplug that + * follows makes it re-read this. + */ + if (route->port->connector->port_encoder && dcp->crtc) + route->port->connector->port_encoder->possible_crtcs = + drm_crtc_mask(&dcp->crtc->base); + } dcp->active_typec_route = route; route->selected = true; - dev_info(dcp->dev, "Routed DCP output to Type-C DPTX PHY %u\n", - route->dptx_phy); + dev_info(dcp->dev, "allocated Type-C DPTX PHY %u\n", route->dptx_phy); return 0; } -static void dcp_typec_route_unregister(void *data) +static int dcp_typec_route_deactivate(struct apple_dcp_typec_route *route) { - struct apple_dcp_typec_route *route = data; struct apple_dcp *dcp = route->dcp; + int ret; + + ret = mux_control_deselect(route->xbar); + if (ret) + return ret; + + route->selected = false; + if (dcp->active_typec_route == route) + dcp->active_typec_route = NULL; + + if (route->port->connector && + route->port->connector->dcp == to_platform_device(dcp->dev)) { + /* + * Until the port is activated again it has no pipeline behind + * it, and nothing can read modes or EDID from it. Report it + * disconnected for that window: leaving a connected connector + * whose ->dcp is NULL lets anything probing it in between -- + * a compositor starting up while the fabric is still settling + * -- see an output it cannot get a mode for, and give up on + * it. The new pipeline marks it connected again once the + * display has come back up on it. + */ + WRITE_ONCE(route->port->connector->connected, false); + route->port->connector->dcp = NULL; + + /* Unrouted: the port could go to any of its pipelines again. */ + if (route->port->connector->port_encoder) + route->port->connector->port_encoder->possible_crtcs = + route->port->connector->candidate_crtcs; + } + dcp->typec_connector = NULL; + dcp->connector = dcp->fixed_connector; + + if (dcp->fixed_connector_type != DRM_MODE_CONNECTOR_USB) { + dcp->connector_type = dcp->fixed_connector_type; + + /* + * Only hand the pipeline back to its fixed output if that output + * is live. Re-targeting the DPTX endpoint at the fixed PHY while + * nothing is attached there leaves DCP unable to train a link on a + * later Type-C target: it answers DEVICE_NOT_STARTED and every + * following DPTX call times out. Park on a Type-C PHY instead, + * for the same reason the USB-C-only case does below. + */ + if (dcp->hdmi_hpd && gpiod_get_value_cansleep(dcp->hdmi_hpd)) { + dcp->phy = dcp->fixed_phy; + dcp->dptx_phy = dcp->fixed_dptx_phy; + + if (dcp->xbar) { + ret = mux_control_select(dcp->xbar, + dcp->fixed_mux_index); + if (ret) + return ret; + dcp->fixed_route_selected = true; + } + } else { + dcp->phy = dcp->typec_routes[0].phy; + dcp->dptx_phy = dcp->typec_routes[0].dptx_phy; + } + } else { + /* Keep DPTX endpoint discovery working before a cable is attached. */ + dcp->phy = dcp->typec_routes[0].phy; + dcp->dptx_phy = dcp->typec_routes[0].dptx_phy; + dcp->connector_type = DRM_MODE_CONNECTOR_USB; + } + + return 0; +} + +static void dcp_typec_retrain_work(struct work_struct *work) +{ + struct apple_dcp *dcp = + container_of(to_delayed_work(work), struct apple_dcp, + typec_fabric_retrain_wq); + + if (READ_ONCE(dcp->active_typec_route) && dcp->typec_connector) + dcp_retrain_oob(dcp->typec_connector); +} + +static void dcp_typec_retrain_active_routes(void) +{ + struct apple_dcp_typec_port *port; + + list_for_each_entry(port, &dcp_typec_ports, link) { + if (!port->owner) + continue; + mod_delayed_work(system_freezable_wq, + &port->owner->dcp->typec_fabric_retrain_wq, + msecs_to_jiffies(200)); + } +} + +static int dcp_typec_route_set(struct typec_mux_dev *mux, + struct typec_mux_state *state) +{ + struct apple_dcp_typec_route *route = typec_mux_get_drvdata(mux); + struct apple_dcp_typec_port *port = route->port; + struct apple_dcp_typec_route *candidate, *best = NULL; + bool is_dp = dcp_typec_route_is_dp(state); + struct typec_displayport_data *dp_data = is_dp ? state->data : NULL; + u32 dp_status = dp_data ? dp_data->status : 0; + u32 dp_conf = dp_data ? dp_data->conf : 0; + unsigned int best_score = UINT_MAX; + bool hpd; + int ret = 0; + + guard(mutex)(&dcp_typec_fabric_lock); + + /* + * Every candidate route for this port is notified with the same state, + * so only the first to arrive does the work; the others return early. + * + * Deliberately not a nominated coordinator: fwnode_typec_mux_get() + * caps the providers one connector may have and drops the remainder + * without a word, so a nominated route might never be called at all -- + * and the port would then never be routed. + */ + if (port->applied_valid && port->applied_alt == state->alt && + port->applied_mode == state->mode && + port->applied_status == dp_status && port->applied_conf == dp_conf) + return 0; + + port->applied_alt = state->alt; + port->applied_mode = state->mode; + port->applied_status = dp_status; + port->applied_conf = dp_conf; + port->applied_valid = true; + + if (!is_dp) { + if (port->owner) { + struct apple_dcp *dcp = port->owner->dcp; + + if (port->hpd || dcp->typec_cable_connected || + (dcp->typec_connector && + dcp->typec_connector->connected)) + dcp_dptx_disconnect_oob(to_platform_device(dcp->dev), 0); + port->hpd = false; + ret = dcp_typec_route_deactivate(port->owner); + if (ret) + return ret; + port->owner = NULL; + if (dcp->hdmi_hpd && dcp->active && + gpiod_get_value_cansleep(dcp->hdmi_hpd)) + dcp_dptx_connect(dcp, 0); + } + + if (state->mode == TYPEC_MODE_USB4) + dcp_typec_retrain_active_routes(); + return 0; + } + + if (!port->owner) { + list_for_each_entry(candidate, &port->routes, port_link) { + unsigned int score; + + if (!dcp_typec_route_available(candidate)) + continue; + score = dcp_typec_route_score(candidate); + if (score < best_score) { + best = candidate; + best_score = score; + } + } + + if (!best) + return -EBUSY; + + ret = dcp_typec_route_activate(best); + if (ret) + return ret; + port->owner = best; + } + + + hpd = dp_data && (dp_data->status & DP_STATUS_HPD_STATE); + if (!hpd && port->hpd) { + dcp_dptx_disconnect_oob(to_platform_device(port->owner->dcp->dev), 0); + } else if (hpd && !port->hpd) { + struct apple_dcp *dcp = port->owner->dcp; + + WRITE_ONCE(dcp->typec_cable_connected, true); + if (dcp->typec_connector) + dcp_dptx_connect_oob(to_platform_device(dcp->dev), 0); + } else if (hpd && dp_data && (dp_data->status & DP_STATUS_IRQ_HPD)) { + struct apple_dcp *dcp = port->owner->dcp; + + if (dcp->typec_connector) + dcp_retrain_oob(dcp->typec_connector); + } + port->hpd = hpd; + + return 0; +} + +static struct apple_dcp_typec_port * +dcp_typec_port_get(struct device_node *connector_np) +{ + struct apple_dcp_typec_port *port, *pos; + + lockdep_assert_held(&dcp_typec_fabric_lock); + + list_for_each_entry(port, &dcp_typec_ports, link) { + if (port->connector_np == connector_np) { + of_node_put(connector_np); + return port; + } + } + + port = kzalloc_obj(*port); + if (!port) { + of_node_put(connector_np); + return NULL; + } + + INIT_LIST_HEAD(&port->routes); + port->connector_np = connector_np; + + /* + * Insert in device-tree order rather than DCP probe order. The list + * index becomes the DRM connector index, and userspace keys its + * per-monitor configuration (scale, rotation, layout) on the connector + * name -- so it has to mean the same physical port on every boot. + */ + list_for_each_entry(pos, &dcp_typec_ports, link) + if (strcmp(of_node_full_name(connector_np), + of_node_full_name(pos->connector_np)) < 0) + break; + list_add_tail(&port->link, &pos->link); + return port; +} + +static struct apple_dcp_typec_port *dcp_typec_port_by_index(unsigned int idx) +{ + struct apple_dcp_typec_port *port; + unsigned int i = 0; + + lockdep_assert_held(&dcp_typec_fabric_lock); + + list_for_each_entry(port, &dcp_typec_ports, link) + if (i++ == idx) + return port; + + return NULL; +} + +unsigned int dcp_typec_nr_ports(void) +{ + struct apple_dcp_typec_port *port; + unsigned int n = 0; + + guard(mutex)(&dcp_typec_fabric_lock); + + list_for_each_entry(port, &dcp_typec_ports, link) + n++; + + return n; +} + +struct device_node *dcp_typec_port_of_node(unsigned int idx) +{ + struct apple_dcp_typec_port *port; + + guard(mutex)(&dcp_typec_fabric_lock); + + port = dcp_typec_port_by_index(idx); + + return port ? port->connector_np : NULL; +} + +bool dcp_typec_port_has_candidate(unsigned int idx, struct platform_device *pdev) +{ + struct apple_dcp_typec_port *port; + struct apple_dcp_typec_route *route; + + guard(mutex)(&dcp_typec_fabric_lock); + + port = dcp_typec_port_by_index(idx); + if (!port) + return false; + + list_for_each_entry(route, &port->routes, port_link) + if (route->dcp->dev == &pdev->dev) + return true; + + return false; +} + +void dcp_typec_port_set_connector(unsigned int idx, + struct apple_connector *connector) +{ + struct apple_dcp_typec_port *port; + + guard(mutex)(&dcp_typec_fabric_lock); - guard(mutex)(&dcp->typec_route_lock); - if (route->selected) { - mux_control_deselect(route->xbar); - route->selected = false; - if (dcp->active_typec_route == route) - dcp->active_typec_route = NULL; + port = dcp_typec_port_by_index(idx); + if (!port) + return; + + port->connector = connector; + + /* + * The port may already have been routed, either before DRM bound or + * while these connectors were being created. Adopt that owner now, + * otherwise its display would be reported on the pipeline's fixed + * connector instead of the port it is actually plugged into. + */ + if (port->owner) { + struct apple_dcp *dcp = port->owner->dcp; + + connector->dcp = to_platform_device(dcp->dev); + dcp->typec_connector = connector; + dcp->connector = connector; + dcp->connector_type = DRM_MODE_CONNECTOR_USB; } +} + +static void dcp_typec_route_unregister(void *data) +{ + struct apple_dcp_typec_route *route = data; + struct apple_dcp_typec_port *port = route->port; + typec_mux_unregister(route->typec_mux); + + guard(mutex)(&dcp_typec_fabric_lock); + if (port->owner == route) { + struct apple_dcp *dcp = route->dcp; + + if (port->hpd || dcp->typec_cable_connected) + dcp_dptx_disconnect_oob(to_platform_device(dcp->dev), 0); + port->hpd = false; + dcp_typec_route_deactivate(route); + port->owner = NULL; + } + list_del(&route->port_link); + if (list_empty(&port->routes)) { + list_del(&port->link); + of_node_put(port->connector_np); + kfree(port); + } } static int dcp_register_typec_routes(struct apple_dcp *dcp) @@ -136,8 +549,10 @@ static int dcp_register_typec_routes(struct apple_dcp *dcp) for_each_available_child_of_node_scoped(routes, route_np) { struct apple_dcp_typec_route *route; + struct device_node *endpoint __free(device_node) = NULL; + struct device_node *connector_np; struct typec_mux_desc desc = {}; - const char *name; + const char *name, *mux_name; if (dcp->nr_typec_routes == DCP_MAX_TYPEC_ROUTES) return dev_err_probe(dev, -E2BIG, "Too many Type-C display routes\n"); @@ -153,8 +568,19 @@ static int dcp_register_typec_routes(struct apple_dcp *dcp) if (!name) return -ENOMEM; + /* + * The DT lookups above are per-DCP, but the typec_mux class is + * global. Several DCPs can offer a route to the same Type-C port, + * so the registered mux needs a name unique across all of them. + */ + mux_name = devm_kasprintf(dev, GFP_KERNEL, "%s-typec%u", + dev_name(dev), route_index); + if (!mux_name) + return -ENOMEM; + route = &dcp->typec_routes[dcp->nr_typec_routes]; route->dcp = dcp; + INIT_LIST_HEAD(&route->port_link); route->phy = devm_phy_get(dev, name); if (IS_ERR(route->phy)) return dev_err_probe(dev, PTR_ERR(route->phy), @@ -177,14 +603,42 @@ static int dcp_register_typec_routes(struct apple_dcp *dcp) return dev_err_probe(dev, ret, "%pOF: missing DPTX PHY index\n", route_np); + endpoint = of_graph_get_next_endpoint(route_np, NULL); + if (!endpoint) + return dev_err_probe(dev, -EINVAL, + "%pOF: missing Type-C graph endpoint\n", + route_np); + connector_np = of_graph_get_remote_port_parent(endpoint); + if (!connector_np) + return dev_err_probe(dev, -EINVAL, + "%pOF: missing Type-C connector\n", + route_np); + + mutex_lock(&dcp_typec_fabric_lock); + route->port = dcp_typec_port_get(connector_np); + if (route->port) + list_add_tail(&route->port_link, &route->port->routes); + mutex_unlock(&dcp_typec_fabric_lock); + if (!route->port) + return -ENOMEM; + desc.fwnode = of_fwnode_handle(route_np); desc.set = dcp_typec_route_set; - desc.name = name; + desc.name = mux_name; desc.drvdata = route; route->typec_mux = typec_mux_register(dev, &desc); - if (IS_ERR(route->typec_mux)) + if (IS_ERR(route->typec_mux)) { + mutex_lock(&dcp_typec_fabric_lock); + list_del(&route->port_link); + if (list_empty(&route->port->routes)) { + list_del(&route->port->link); + of_node_put(route->port->connector_np); + kfree(route->port); + } + mutex_unlock(&dcp_typec_fabric_lock); return dev_err_probe(dev, PTR_ERR(route->typec_mux), "%pOF: failed to register Type-C route\n", route_np); + } ret = devm_add_action_or_reset(dev, dcp_typec_route_unregister, route); if (ret) @@ -484,9 +938,16 @@ int dcp_crtc_atomic_check(struct drm_crtc *crtc, struct drm_atomic_state *state) crtc_state = drm_atomic_get_new_crtc_state(state, crtc); needs_modeset = drm_atomic_crtc_needs_modeset(crtc_state) || !dcp->valid_mode; - if (!needs_modeset && !dcp->connector->connected) { - dev_err(dcp->dev, "crtc_atomic_check: disconnected but no modeset\n"); - return -EINVAL; + if (!needs_modeset && (!dcp->connector || !dcp->connector->connected)) { + /* + * Resume restores the mode before the firmware reports the + * display back, so a plane-only commit lands here while the + * connector is still marked disconnected. Rejecting it makes + * the compositor fail every flip and give up on the output; + * dcp_flush() defers the commit until the link returns. + */ + dev_dbg(dcp->dev, + "crtc_atomic_check: deferring commit, link still down\n"); } return 0; @@ -496,7 +957,14 @@ int dcp_get_connector_type(struct platform_device *pdev) { struct apple_dcp *dcp = platform_get_drvdata(pdev); - return (dcp->connector_type); + return dcp->fixed_connector_type; +} + +bool dcp_has_typec_routes(struct platform_device *pdev) +{ + struct apple_dcp *dcp = platform_get_drvdata(pdev); + + return dcp->nr_typec_routes; } #define DPTX_CONNECT_TIMEOUT msecs_to_jiffies(2000) @@ -511,7 +979,12 @@ static int dcp_dptx_connect(struct apple_dcp *dcp, u32 port) dev_warn(dcp->dev, "dcp_dptx_connect: missing phy\n"); return -ENODEV; } - dev_info(dcp->dev, "%s(port=%d)\n", __func__, port); + dev_info(dcp->dev, + "%s(port=%d) target=%u:%u typec=%d route=%s conn_type=%d connected=%d\n", + __func__, port, dcp->dptx_die, dcp->dptx_phy, + dcp_is_typec_output(dcp), + dcp->active_typec_route ? "borrowed" : "fixed", + dcp->connector_type, dcp->dptxport[port].connected); mutex_lock(&dcp->hpd_mutex); if (!dcp->dptxport[port].enabled) { @@ -536,7 +1009,7 @@ static int dcp_dptx_connect(struct apple_dcp *dcp, u32 port) ret = dptxport_connect(dcp->dptxport[port].service, 0, dcp->dptx_phy, dcp->dptx_die, - dcp->connector_type == DRM_MODE_CONNECTOR_USB); + dcp_is_typec_output(dcp)); if (ret) { dev_err(dcp->dev, "dcp_dptx_connect: failed to connect DPTX target %u:%u: %d\n", @@ -552,7 +1025,7 @@ static int dcp_dptx_connect(struct apple_dcp *dcp, u32 port) goto out_release; } dcp->dptxport[port].connected = true; - if (dcp->connector_type == DRM_MODE_CONNECTOR_USB) { + if (dcp_is_typec_output(dcp)) { ret = dptxport_set_hpd(dcp->dptxport[port].service, true); if (ret) { dev_err(dcp->dev, @@ -653,14 +1126,14 @@ int dcp_dptx_connect_oob(struct platform_device *pdev, u32 port) struct apple_dcp *dcp = platform_get_drvdata(pdev); int ret; - if (dcp->connector_type == DRM_MODE_CONNECTOR_USB) { + if (dcp_is_typec_output(dcp)) { WRITE_ONCE(dcp->typec_cable_connected, true); dcp->typec_reconnect_tries = 0; cancel_delayed_work(&dcp->typec_reconnect_wq); } ret = dcp_dptx_connect(dcp, port); - if (ret && dcp->connector_type == DRM_MODE_CONNECTOR_USB) + if (ret && dcp_is_typec_output(dcp)) mod_delayed_work(system_freezable_wq, &dcp->typec_reconnect_wq, DPTX_RECONNECT_DELAY); @@ -671,7 +1144,7 @@ int dcp_dptx_disconnect_oob(struct platform_device *pdev, u32 port) { struct apple_dcp *dcp = platform_get_drvdata(pdev); - if (dcp->connector_type == DRM_MODE_CONNECTOR_USB) { + if (dcp_is_typec_output(dcp)) { WRITE_ONCE(dcp->typec_cable_connected, false); cancel_delayed_work(&dcp->typec_reconnect_wq); } @@ -690,7 +1163,13 @@ int dcp_dptx_disconnect_oob(struct platform_device *pdev, u32 port) static irqreturn_t dcp_dp2hdmi_hpd(int irq, void *data) { struct apple_dcp *dcp = data; - bool connected = gpiod_get_value_cansleep(dcp->hdmi_hpd); + bool connected; + + guard(mutex)(&dcp_typec_fabric_lock); + + if (READ_ONCE(dcp->active_typec_route)) + return IRQ_HANDLED; + connected = gpiod_get_value_cansleep(dcp->hdmi_hpd); /* do nothing on disconnect and trust that dcp detects it itself. * Parallel disconnect HPDs result drm disabling the CRTC even when it @@ -718,7 +1197,20 @@ void dcp_link(struct platform_device *pdev, struct apple_crtc *crtc, struct apple_dcp *dcp = platform_get_drvdata(pdev); dcp->crtc = crtc; - dcp->connector = connector; + + /* + * Type-C connectors belong to physical ports and are bound by the + * display fabric when a pipeline takes a route, so a pipeline with no + * fixed output simply has no connector until then. + */ + if (!connector) + return; + + dcp->fixed_connector = connector; + if (!dcp->active_typec_route) { + dcp->connector = connector; + dcp->connector_type = dcp->fixed_connector_type; + } } @@ -825,8 +1317,11 @@ static void _dcp_poweroff(struct apple_dcp *dcp) static int dcp_enable_dp2hdmi_hpd(struct apple_dcp *dcp) { - // check HPD state before enabling the edge triggered IRQ - if (dcp->hdmi_hpd) { + if (dcp_is_typec_output(dcp)) { + if (READ_ONCE(dcp->typec_cable_connected)) + dcp_dptx_connect(dcp, 0); + } else if (dcp->hdmi_hpd) { + /* Check HPD before enabling the edge-triggered IRQ. */ bool connected = gpiod_get_value_cansleep(dcp->hdmi_hpd); dev_info(dcp->dev, "%s: DP2HDMI HPD connected:%d\n", __func__, connected); @@ -887,26 +1382,26 @@ void dcp_poweron(struct platform_device *pdev) struct apple_dcp *dcp = platform_get_drvdata(pdev); int ret; - if (dcp->hdmi_hpd) { + if (dcp_is_typec_output(dcp)) { + /* + * A Type-C CRTC disable releases its DPTX session. Re-establish it + * synchronously before IOMFB is powered back on. + */ + if (READ_ONCE(dcp->typec_cable_connected)) { + cancel_delayed_work(&dcp->typec_reconnect_wq); + dcp->typec_reconnect_tries = 0; + ret = dcp_dptx_connect(dcp, 0); + if (ret) + mod_delayed_work(system_freezable_wq, + &dcp->typec_reconnect_wq, + DPTX_RECONNECT_DELAY); + } + } else if (dcp->hdmi_hpd) { bool connected = gpiod_get_value_cansleep(dcp->hdmi_hpd); dev_info(dcp->dev, "%s: DP2HDMI HPD connected:%d\n", __func__, connected); if (connected) dcp_dptx_connect(dcp, 0); - } else if (dcp->connector_type == DRM_MODE_CONNECTOR_USB && - READ_ONCE(dcp->typec_cable_connected)) { - /* - * A Type-C CRTC disable releases its DPTX session. Re-establish - * that session synchronously before powering IOMFB back on, so the - * following modeset cannot race the delayed reconnect worker. - */ - cancel_delayed_work(&dcp->typec_reconnect_wq); - dcp->typec_reconnect_tries = 0; - ret = dcp_dptx_connect(dcp, 0); - if (ret) - mod_delayed_work(system_freezable_wq, - &dcp->typec_reconnect_wq, - DPTX_RECONNECT_DELAY); } switch (dcp->fw_compat) { @@ -935,39 +1430,31 @@ void dcp_poweroff(struct platform_device *pdev) _dcp_poweroff(dcp); - if (dcp->hdmi_hpd) { + if (dcp_is_typec_output(dcp)) { + /* + * DCP owns a synthetic HPD for Type-C. Release it with the CRTC, + * then recreate the session while the cable remains present. + */ + if (dcp->dptxport[0].enabled && dcp->dptxport[0].connected) { + ret = dptxport_set_hpd(dcp->dptxport[0].service, false); + if (ret) + dev_warn(dcp->dev, + "failed to deassert Type-C DPTX HPD: %d\n", ret); + dcp_dptx_disconnect(dcp, 0); + + if (READ_ONCE(dcp->typec_cable_connected)) { + dcp->typec_reconnect_tries = 0; + mod_delayed_work(system_freezable_wq, + &dcp->typec_reconnect_wq, + DPTX_RECONNECT_DELAY); + } + } + } else if (dcp->hdmi_hpd) { bool connected = gpiod_get_value_cansleep(dcp->hdmi_hpd); if (!connected) { disconnected_hpd_event(dcp->connector); dcp_dptx_disconnect(dcp, 0); } - } else if (dcp->connector_type == DRM_MODE_CONNECTOR_USB && - dcp->dptxport[0].enabled && dcp->dptxport[0].connected) { - /* - * Unlike a physical DP/HDMI HPD, the Type-C HPD exposed by the - * DPTX service belongs to this power-on session. Release it when - * the CRTC powers down so the next enable performs the same orderly - * connect/request/HPD sequence as a fresh cable connection. - */ - ret = dptxport_set_hpd(dcp->dptxport[0].service, false); - if (ret) - dev_warn(dcp->dev, - "failed to deassert Type-C DPTX HPD: %d\n", ret); - dcp_dptx_disconnect(dcp, 0); - - /* - * DCP tears down its synthetic HPD when the CRTC powers off, but - * the Type-C controller does not necessarily emit another cable - * connect edge. Recreate the DPTX session while the physical cable - * remains present. A freezable workqueue keeps this out of the - * suspend path; if suspend wins the race, reconnect after thaw. - */ - if (READ_ONCE(dcp->typec_cable_connected)) { - dcp->typec_reconnect_tries = 0; - mod_delayed_work(system_freezable_wq, - &dcp->typec_reconnect_wq, - DPTX_RECONNECT_DELAY); - } } } @@ -1250,6 +1737,18 @@ static enum dcp_firmware_version dcp_check_firmware_version(struct device *dev) return DCP_FIRMWARE_UNKNOWN; } +static int dcp_connector_type_from_dt(struct device_node *np) +{ + if (of_property_match_string(np, "apple,connector-type", "HDMI-A") >= 0) + return DRM_MODE_CONNECTOR_HDMIA; + if (of_property_match_string(np, "apple,connector-type", "DP") >= 0) + return DRM_MODE_CONNECTOR_DisplayPort; + if (of_property_match_string(np, "apple,connector-type", "USB-C") >= 0) + return DRM_MODE_CONNECTOR_USB; + + return DRM_MODE_CONNECTOR_Unknown; +} + static int dcp_comp_bind(struct device *dev, struct device *main, void *data) { struct device_node *panel_np; @@ -1265,12 +1764,6 @@ static int dcp_comp_bind(struct device *dev, struct device *main, void *data) if (IS_ERR(dcp->coproc_reg)) return PTR_ERR(dcp->coproc_reg); - of_property_read_u32(dev->of_node, "apple,dcp-index", - &dcp->index); - of_property_read_u32(dev->of_node, "apple,dptx-phy", - &dcp->dptx_phy); - of_property_read_u32(dev->of_node, "apple,dptx-die", - &dcp->dptx_die); if (dcp->index || dcp->dptx_phy || dcp->dptx_die) dev_info(dev, "DCP index:%u dptx target phy: %u dptx die: %u\n", dcp->index, dcp->dptx_phy, dcp->dptx_die); @@ -1309,18 +1802,12 @@ static int dcp_comp_bind(struct device *dev, struct device *main, void *data) &dcp->panel.height_mm); of_node_put(panel_np); + dcp->fixed_connector_type = DRM_MODE_CONNECTOR_eDP; dcp->connector_type = DRM_MODE_CONNECTOR_eDP; INIT_WORK(&dcp->bl_register_wq, dcp_work_register_backlight); mutex_init(&dcp->bl_register_mutex); INIT_WORK(&dcp->bl_update_wq, dcp_work_update_backlight); - } else if (of_property_match_string(dev->of_node, "apple,connector-type", "HDMI-A") >= 0) - dcp->connector_type = DRM_MODE_CONNECTOR_HDMIA; - else if (of_property_match_string(dev->of_node, "apple,connector-type", "DP") >= 0) - dcp->connector_type = DRM_MODE_CONNECTOR_DisplayPort; - else if (of_property_match_string(dev->of_node, "apple,connector-type", "USB-C") >= 0) - dcp->connector_type = DRM_MODE_CONNECTOR_USB; - else - dcp->connector_type = DRM_MODE_CONNECTOR_Unknown; + } ret = dcp_create_piodma_iommu_dev(dcp); if (ret || !dcp->iommu_dom) @@ -1420,6 +1907,7 @@ static void dcp_comp_unbind(struct device *dev, struct device *main, void *data) cancel_work_sync(&dcp->bl_update_wq); } cancel_delayed_work_sync(&dcp->typec_reconnect_wq); + cancel_delayed_work_sync(&dcp->typec_fabric_retrain_wq); cancel_work_sync(&dcp->vblank_wq); devm_clk_put(dev, dcp->clk); @@ -1459,9 +1947,16 @@ static int dcp_platform_probe(struct platform_device *pdev) dcp->fw_compat = fw_compat; dcp->dev = dev; dcp->hw = *(struct apple_dcp_hw_data *)of_device_get_match_data(dev); - mutex_init(&dcp->typec_route_lock); + dcp->fixed_connector_type = dcp_connector_type_from_dt(dev->of_node); + dcp->connector_type = dcp->fixed_connector_type; + of_property_read_u32(dev->of_node, "apple,dcp-index", &dcp->index); + of_property_read_u32(dev->of_node, "apple,dptx-phy", &dcp->dptx_phy); + of_property_read_u32(dev->of_node, "apple,dptx-die", &dcp->dptx_die); + dcp->fixed_dptx_phy = dcp->dptx_phy; INIT_DELAYED_WORK(&dcp->typec_reconnect_wq, dcp_typec_reconnect_work); + INIT_DELAYED_WORK(&dcp->typec_fabric_retrain_wq, + dcp_typec_retrain_work); platform_set_drvdata(pdev, dcp); @@ -1470,10 +1965,7 @@ static int dcp_platform_probe(struct platform_device *pdev) dev_err(dev, "Failed to get dp-phy: %ld\n", PTR_ERR(dcp->phy)); return PTR_ERR(dcp->phy); } - - ret = dcp_register_typec_routes(dcp); - if (ret) - return ret; + dcp->fixed_phy = dcp->phy; bitmap_zero(dcp->iomfb_surfaces, DCP_MAX_PLANES); if (!of_property_present(dev->of_node, "apple,iomfb-surfaces")) @@ -1545,16 +2037,15 @@ static int dcp_platform_probe(struct platform_device *pdev) return PTR_ERR(dcp->dp2hdmi_pwren); /* - * Firmware or an inherited board DTS may leave the legacy mux-index - * property on external DCP nodes. Multi-route USB-C DCPs select their - * crossbar through the per-port routes registered above and do not - * have a single "dp-xbar" mux. Treat mux-index only as the legacy - * single-route binding; otherwise one stale property prevents every - * Type-C controller from probing while they wait for the DCP routes. + * A DCP may have both a fixed HDMI/DP route and allocatable Type-C + * routes. Keep the fixed route selected until the allocator borrows + * this otherwise-idle pipeline for a Type-C display. */ - ret = dcp->nr_typec_routes ? -ENODATA : - of_property_read_u32(dev->of_node, "mux-index", &mux_index); + ret = dcp->fixed_phy ? + of_property_read_u32(dev->of_node, "mux-index", &mux_index) : + -ENODATA; if (!ret) { + dcp->fixed_mux_index = mux_index; dcp->xbar = devm_mux_control_get(dev, "dp-xbar"); if (IS_ERR(dcp->xbar)) { dev_err(dev, "Failed to get dp-xbar: %ld\n", PTR_ERR(dcp->xbar)); @@ -1563,6 +2054,8 @@ static int dcp_platform_probe(struct platform_device *pdev) ret = mux_control_select(dcp->xbar, mux_index); if (ret) dev_warn(dev, "mux_control_select failed: %d\n", ret); + else + dcp->fixed_route_selected = true; /* * Switch atcphy to DP-only. should move to a Macbook Pro @@ -1589,6 +2082,10 @@ static int dcp_platform_probe(struct platform_device *pdev) } } + ret = dcp_register_typec_routes(dcp); + if (ret) + return ret; + return component_add(&pdev->dev, &dcp_comp_ops); } @@ -1611,8 +2108,10 @@ static int dcp_platform_suspend(struct device *dev) if (dcp->hdmi_hpd_irq) { disable_irq(dcp->hdmi_hpd_irq); - disconnected_hpd_event(dcp->connector); - dcp_dptx_disconnect(dcp, 0); + if (!dcp->active_typec_route) { + disconnected_hpd_event(dcp->connector); + dcp_dptx_disconnect(dcp, 0); + } } /* * Set the device as a wakeup device, which forces its power diff --git a/drivers/gpu/drm/apple/dcp.h b/drivers/gpu/drm/apple/dcp.h index 2b421772bf5d57..c500c1b8a44347 100644 --- a/drivers/gpu/drm/apple/dcp.h +++ b/drivers/gpu/drm/apple/dcp.h @@ -34,6 +34,17 @@ void dcp_poweron(struct platform_device *pdev); int dcp_set_crc(struct drm_crtc *crtc, bool enabled); int dcp_crtc_atomic_check(struct drm_crtc *crtc, struct drm_atomic_state *state); int dcp_get_connector_type(struct platform_device *pdev); +bool dcp_has_typec_routes(struct platform_device *pdev); + +/* + * The Type-C display fabric. Ports are enumerated in device-tree order, not + * DCP probe order, so a given physical port keeps the same DRM connector index + * across boots -- userspace keys its per-monitor configuration on that name. + */ +unsigned int dcp_typec_nr_ports(void); +struct device_node *dcp_typec_port_of_node(unsigned int idx); +bool dcp_typec_port_has_candidate(unsigned int idx, struct platform_device *pdev); +void dcp_typec_port_set_connector(unsigned int idx, struct apple_connector *connector); bool dcp_fw_compat_is_12_x(struct platform_device *pdev); unsigned long* dcp_get_iomfb_surfaces(struct platform_device *pdev); void dcp_link(struct platform_device *pdev, struct apple_crtc *apple, diff --git a/drivers/gpu/drm/apple/dptxep.c b/drivers/gpu/drm/apple/dptxep.c index 3d286fc2c846ce..525ba637443705 100644 --- a/drivers/gpu/drm/apple/dptxep.c +++ b/drivers/gpu/drm/apple/dptxep.c @@ -457,7 +457,7 @@ static int dptxport_call_get_supports_hpd(struct apple_epic_service *service, return -EINVAL; reply->retcode = cpu_to_le32(0); - reply->supported = cpu_to_le32(dcp->connector_type == DRM_MODE_CONNECTOR_USB); + reply->supported = cpu_to_le32(dcp_is_typec_output(dcp)); return 0; } @@ -558,6 +558,15 @@ static int dptxport_call(struct apple_epic_service *service, u32 idx, case DPTX_APCALL_GET_MAX_DRIVE_SETTINGS: return dptxport_call_get_max_drive_settings(service, reply, reply_size); + case DPTX_APCALL_DEVICE_NOT_RESPONDING: + case DPTX_APCALL_DEVICE_BUSY_TIMEOUT: + case DPTX_APCALL_DEVICE_NOT_STARTED: + dev_warn(service->ep->dcp->dev, + "DPTXPort: firmware reports link fault %u on target %u:%u\n", + idx, service->ep->dcp->dptx_die, + service->ep->dcp->dptx_phy); + memcpy(reply, data, min(reply_size, data_size)); + return 0; case DPTX_APCALL_SET_TILED_DISPLAY_HINTS: memcpy(reply, data, min(reply_size, data_size)); return dptxport_call_set_tiled_display_hint(reply, reply_size); diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index 3f24cb75c46ce1..63040798e0bca0 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -402,12 +402,17 @@ int dcp_get_modes(struct drm_connector *connector) { struct apple_connector *apple_connector = to_apple_connector(connector); struct platform_device *pdev = apple_connector->dcp; - struct apple_dcp *dcp = platform_get_drvdata(pdev); + struct apple_dcp *dcp; struct drm_device *dev = connector->dev; struct drm_display_mode *mode; int i; + /* A Type-C port has no pipeline while the fabric is moving it. */ + if (!pdev) + return 0; + dcp = platform_get_drvdata(pdev); + for (i = 0; i < dcp->nr_modes; ++i) { mode = drm_mode_duplicate(dev, &dcp->modes[i].mode); @@ -457,7 +462,11 @@ enum drm_mode_status dcp_mode_valid(struct drm_connector *connector, { struct apple_connector *apple_connector = to_apple_connector(connector); struct platform_device *pdev = apple_connector->dcp; - struct apple_dcp *dcp = platform_get_drvdata(pdev); + struct apple_dcp *dcp; + + if (!pdev) + return MODE_ERROR; + dcp = platform_get_drvdata(pdev); return lookup_mode(dcp, mode) ? MODE_OK : MODE_BAD; } From 3d7d23cc78e819785bd8885b7c019d7579f32424 Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Sat, 29 Aug 2026 23:56:34 -0400 Subject: [PATCH 43/51] drm: apple: keep Type-C displays configured across suspend A display attached over Type-C came back blank after resume and stayed that way: the compositor reported every page flip as EINVAL, gave up on the output and tore it down. DCP reports the display back while the resume modeset is still in flight, and cb_hotplug() discarded any hotplug seen during a modeset. Nothing re-sends it, so the connector stayed marked disconnected for good. Latch that state instead and replay it once the modeset completes, taking the same path it would have taken had it arrived a moment later. The connector is briefly disconnected either way, because resume marks the mode valid again before the firmware reports the display back. A plane-only commit landing in that window used to be failed outright, which is what the compositor saw; defer it in dcp_flush() as is already done while a mode is invalid, and let the check pass. Signed-off-by: DjDeveloperr --- drivers/gpu/drm/apple/iomfb.c | 7 +++++-- drivers/gpu/drm/apple/iomfb_template.c | 28 +++++++++++++++++++++++++- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index 63040798e0bca0..452a35731f7cfd 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -423,7 +423,6 @@ int dcp_get_modes(struct drm_connector *connector) drm_mode_probed_add(connector, mode); } - if (dcp->nr_modes && dcp->dcpavserv.enabled && !apple_connector->drm_edid) { const struct drm_edid *edid; @@ -532,8 +531,12 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state) * arriving between the reconnect callback and the required modeset * would otherwise leave an unsignalled flip event in front of the * recovery commit. Modesets set valid_mode before reaching flush. + * + * The same applies while a connector is still disconnected: resume + * re-runs the modeset, which marks the mode valid again, before the + * firmware has reported the display back. */ - if (!dcp->valid_mode) { + if (!dcp->valid_mode || !dcp->connector || !dcp->connector->connected) { schedule_work(&dcp->vblank_wq); return; } diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c index cf40e273a2f43c..1b868a8e159ead 100644 --- a/drivers/gpu/drm/apple/iomfb_template.c +++ b/drivers/gpu/drm/apple/iomfb_template.c @@ -1021,9 +1021,17 @@ static void dcpep_cb_hotplug(struct apple_dcp *dcp, u64 *connected) return; if (dcp->during_modeset) { + /* + * Remember it rather than dropping it. Resume re-runs the + * modeset and the firmware reports the display back while that + * is still in flight; discarding it leaves the connector marked + * disconnected forever, with no further event to correct it. + */ dev_info(dcp->dev, - "cb_hotplug() ignored during modeset connected:%llu\n", + "cb_hotplug() deferred during modeset connected:%llu\n", *connected); + dcp->pending_hotplug = true; + dcp->pending_hotplug_connected = !!(*connected); return; } @@ -1257,6 +1265,24 @@ int DCP_FW_NAME(iomfb_modeset)(struct apple_dcp *dcp, kref_put(&cookie->refcount, release_wait_cookie); dcp->during_modeset = false; + + if (dcp->pending_hotplug) { + bool connected = dcp->pending_hotplug_connected; + struct apple_connector *connector = dcp->connector; + + dcp->pending_hotplug = false; + dev_info(dcp->dev, "replaying deferred hotplug connected:%d\n", + connected); + + if (!connected) + dcp->valid_mode = false; + + if (connector && connector->connected != connected) { + connector->connected = connected; + dcp->valid_mode = false; + schedule_work(&connector->hotplug_wq); + } + } dev_info(dcp->dev, "set_digital_out_mode finished:%d\n", ret); if (ret == 0) { From b8d9bddfd3e0bafd259379e18bc88e3a9845917f Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Sat, 29 Aug 2026 19:30:52 -0400 Subject: [PATCH 44/51] arm64: dts: apple: describe multi-DCP Type-C display topology With the DCP display fabric in place, describe the routes the hardware actually has instead of pinning every Type-C port to one external DCP. On the M2 Pro/Max laptops dcpext0 drives the fixed HDMI output but can also reach all three Type-C PHYs, so give it those PHYs, crossbar controls and typec-routes alongside its fixed dp-phy, and point each Type-C connector at both external DCPs. External DCP 0 uses crossbar state 0 and external DCP 1 state 2, matching the ADT. Keep this in the t602x file rather than the shared t600x one: on the M1 Pro/Max laptops dcpext0 is disabled, and a route node inherits no status from its parent, so describing routes there would leave typec_mux_match() deferring forever on a mux that never registers and take Type-C DP down on those machines entirely. Drop the stale mux-index from dcpext1, which selects its crossbar per route, and remove the static displayport phandles wherever a typec-routes provider now owns hotplug notification, so a connector cannot be notified twice or through the wrong pipeline. On the M2 Pro mini and M2 Max Studio that phandle is guarded by APPLE_T600X_NON_ULTRA, which only t6001.dtsi defines, so it survives into t602x and has to be deleted explicitly. Boards without such a provider keep their route. Signed-off-by: DjDeveloperr --- .../arm64/boot/dts/apple/t600x-j314-j316.dtsi | 25 +++-- arch/arm64/boot/dts/apple/t600x-j375.dtsi | 8 -- .../arm64/boot/dts/apple/t602x-j414-j416.dtsi | 91 ++++++++++++++++--- .../arm64/boot/dts/apple/t602x-j474-j475.dtsi | 13 ++- arch/arm64/boot/dts/apple/t8103-jxxx.dtsi | 2 - arch/arm64/boot/dts/apple/t8112-j473.dts | 4 - arch/arm64/boot/dts/apple/t8112-jxxx.dtsi | 8 -- 7 files changed, 100 insertions(+), 51 deletions(-) diff --git a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi index 1c46a3ea241a0c..464f621e682993 100644 --- a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi +++ b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi @@ -138,6 +138,7 @@ }; }; }; + }; #ifdef APPLE_T600X_NON_ULTRA @@ -581,12 +582,14 @@ #ifdef APPLE_T600X_NON_ULTRA &typec0 { - displayport = <&dcpext1>; - ports { port@3 { reg = <3>; - typec0_dcp: endpoint { + #address-cells = <1>; + #size-cells = <0>; + + typec0_dcp: endpoint@0 { + reg = <0>; remote-endpoint = <&dcpext1_typec0>; }; }; @@ -594,12 +597,14 @@ }; &typec1 { - displayport = <&dcpext1>; - ports { port@3 { reg = <3>; - typec1_dcp: endpoint { + #address-cells = <1>; + #size-cells = <0>; + + typec1_dcp: endpoint@0 { + reg = <0>; remote-endpoint = <&dcpext1_typec1>; }; }; @@ -607,12 +612,14 @@ }; &typec2 { - displayport = <&dcpext1>; - ports { port@3 { reg = <3>; - typec2_dcp: endpoint { + #address-cells = <1>; + #size-cells = <0>; + + typec2_dcp: endpoint@0 { + reg = <0>; remote-endpoint = <&dcpext1_typec2>; }; }; diff --git a/arch/arm64/boot/dts/apple/t600x-j375.dtsi b/arch/arm64/boot/dts/apple/t600x-j375.dtsi index b9bd2c6a5527b3..76713bc3cde229 100644 --- a/arch/arm64/boot/dts/apple/t600x-j375.dtsi +++ b/arch/arm64/boot/dts/apple/t600x-j375.dtsi @@ -401,8 +401,6 @@ #ifdef APPLE_T600X_NON_ULTRA &typec0 { - displayport = <&dcpext1>; - ports { port@3 { reg = <3>; @@ -414,8 +412,6 @@ }; &typec1 { - displayport = <&dcpext1>; - ports { port@3 { reg = <3>; @@ -427,8 +423,6 @@ }; &typec2 { - displayport = <&dcpext1>; - ports { port@3 { reg = <3>; @@ -440,8 +434,6 @@ }; &typec3 { - displayport = <&dcpext1>; - ports { port@3 { reg = <3>; diff --git a/arch/arm64/boot/dts/apple/t602x-j414-j416.dtsi b/arch/arm64/boot/dts/apple/t602x-j414-j416.dtsi index bff9abdfe9f9b2..3cff1b10057fe5 100644 --- a/arch/arm64/boot/dts/apple/t602x-j414-j416.dtsi +++ b/arch/arm64/boot/dts/apple/t602x-j414-j416.dtsi @@ -68,12 +68,16 @@ hdmi-hpd-gpios = <&pinctrl_aop 25 GPIO_ACTIVE_HIGH>; hdmi-pwren-gpios = <&smc_gpio 23 GPIO_ACTIVE_HIGH>; dp2hdmi-pwren-gpios = <&smc_gpio 6 GPIO_ACTIVE_HIGH>; - phys = <&atcphy3 PHY_TYPE_DP>; - phy-names = "dp-phy"; - mux-controls = <&atcphy3_xbar 0>; - mux-control-names = "dp-xbar"; + phys = <&atcphy3 PHY_TYPE_DP>, <&atcphy0 PHY_TYPE_DP>, + <&atcphy1 PHY_TYPE_DP>, <&atcphy2 PHY_TYPE_DP>; + phy-names = "dp-phy", "typec0", "typec1", "typec2"; + mux-controls = <&atcphy3_xbar 0>, <&atcphy0_xbar 0>, + <&atcphy1_xbar 0>, <&atcphy2_xbar 0>; + mux-control-names = "dp-xbar", "typec0", "typec1", "typec2"; mux-index = <0>; apple,dptx-phy = <3>; + apple,typec-mux-indices = <0 0 0>; + apple,typec-dptx-phys = <0 1 2>; ports { #address-cells = <1>; @@ -86,6 +90,44 @@ }; }; }; + + typec-routes { + #address-cells = <1>; + #size-cells = <0>; + + route@0 { + reg = <0>; + mode-switch; + + port { + dcpext0_typec0: endpoint { + remote-endpoint = <&typec0_dcpext0>; + }; + }; + }; + + route@1 { + reg = <1>; + mode-switch; + + port { + dcpext0_typec1: endpoint { + remote-endpoint = <&typec1_dcpext0>; + }; + }; + }; + + route@2 { + reg = <2>; + mode-switch; + + port { + dcpext0_typec2: endpoint { + remote-endpoint = <&typec2_dcpext0>; + }; + }; + }; + }; }; &dispext1_dart { @@ -101,6 +143,7 @@ }; &dcpext1 { + /delete-property/ mux-index; status = "okay"; apple,connector-type = "USB-C"; phys = <&atcphy0 PHY_TYPE_DP>, <&atcphy1 PHY_TYPE_DP>, @@ -152,40 +195,62 @@ }; &typec0 { - displayport = <&dcpext1>; - ports { port@3 { reg = <3>; - typec0_dcp: endpoint { + #address-cells = <1>; + #size-cells = <0>; + + typec0_dcp: endpoint@0 { + reg = <0>; remote-endpoint = <&dcpext1_typec0>; }; + + typec0_dcpext0: endpoint@1 { + reg = <1>; + remote-endpoint = <&dcpext0_typec0>; + }; }; }; }; &typec1 { - displayport = <&dcpext1>; - + /delete-property/ displayport; ports { port@3 { reg = <3>; - typec1_dcp: endpoint { + #address-cells = <1>; + #size-cells = <0>; + + typec1_dcp: endpoint@0 { + reg = <0>; remote-endpoint = <&dcpext1_typec1>; }; + + typec1_dcpext0: endpoint@1 { + reg = <1>; + remote-endpoint = <&dcpext0_typec1>; + }; }; }; }; &typec2 { - displayport = <&dcpext1>; - ports { port@3 { reg = <3>; - typec2_dcp: endpoint { + #address-cells = <1>; + #size-cells = <0>; + + typec2_dcp: endpoint@0 { + reg = <0>; remote-endpoint = <&dcpext1_typec2>; }; + + typec2_dcpext0: endpoint@1 { + reg = <1>; + remote-endpoint = <&dcpext0_typec2>; + }; }; }; }; diff --git a/arch/arm64/boot/dts/apple/t602x-j474-j475.dtsi b/arch/arm64/boot/dts/apple/t602x-j474-j475.dtsi index 7e4069b929a81c..ee1877acba41aa 100644 --- a/arch/arm64/boot/dts/apple/t602x-j474-j475.dtsi +++ b/arch/arm64/boot/dts/apple/t602x-j474-j475.dtsi @@ -107,8 +107,6 @@ }; &typec0 { - displayport = <&dcpext1>; - ports { port@3 { reg = <3>; @@ -120,7 +118,12 @@ }; &typec1 { - displayport = <&dcpext1>; + /* + * t600x-j375.dtsi guards this on APPLE_T600X_NON_ULTRA, which only + * t6001.dtsi defines, so the legacy phandle survives on t602x. The + * typec-routes provider owns hotplug notification here. + */ + /delete-property/ displayport; ports { port@3 { @@ -133,8 +136,6 @@ }; &typec2 { - displayport = <&dcpext1>; - ports { port@3 { reg = <3>; @@ -146,8 +147,6 @@ }; &typec3 { - displayport = <&dcpext1>; - ports { port@3 { reg = <3>; diff --git a/arch/arm64/boot/dts/apple/t8103-jxxx.dtsi b/arch/arm64/boot/dts/apple/t8103-jxxx.dtsi index 2a2e386e3e608c..81acd8ae491b0c 100644 --- a/arch/arm64/boot/dts/apple/t8103-jxxx.dtsi +++ b/arch/arm64/boot/dts/apple/t8103-jxxx.dtsi @@ -80,7 +80,6 @@ compatible = "usb-c-connector"; power-role = "dual"; data-role = "dual"; - displayport = <&dcpext>; ports { #address-cells = <1>; @@ -126,7 +125,6 @@ compatible = "usb-c-connector"; power-role = "dual"; data-role = "dual"; - displayport = <&dcpext>; ports { #address-cells = <1>; diff --git a/arch/arm64/boot/dts/apple/t8112-j473.dts b/arch/arm64/boot/dts/apple/t8112-j473.dts index bd90405e7cfd6b..1abeee2264bf08 100644 --- a/arch/arm64/boot/dts/apple/t8112-j473.dts +++ b/arch/arm64/boot/dts/apple/t8112-j473.dts @@ -118,8 +118,6 @@ }; &typec0 { - displayport = <&dcp>; - ports { port@3 { reg = <3>; @@ -131,8 +129,6 @@ }; &typec1 { - displayport = <&dcp>; - ports { port@3 { reg = <3>; diff --git a/arch/arm64/boot/dts/apple/t8112-jxxx.dtsi b/arch/arm64/boot/dts/apple/t8112-jxxx.dtsi index 208503f996c2fd..3d408c0095e320 100644 --- a/arch/arm64/boot/dts/apple/t8112-jxxx.dtsi +++ b/arch/arm64/boot/dts/apple/t8112-jxxx.dtsi @@ -74,10 +74,6 @@ compatible = "usb-c-connector"; power-role = "dual"; data-role = "dual"; -#ifndef T8112_TYPEC_DCP - displayport = <&dcpext>; -#endif - ports { #address-cells = <1>; #size-cells = <0>; @@ -125,10 +121,6 @@ power-role = "dual"; data-role = "dual"; -#ifndef T8112_TYPEC_DCP - displayport = <&dcpext>; -#endif - ports { #address-cells = <1>; #size-cells = <0>; From 3a65e83e94aaac522f4812c2fa50ab99942ddfdd Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Sun, 30 Aug 2026 18:38:37 -0400 Subject: [PATCH 45/51] drm: apple: don't use devres for DCP property chunks The DCP that receives a chunked transfer allocates the buffer and hands it to the connector, which frees the previous one when the next arrives. devres ties the buffer to the allocating DCP, but the free uses the connector's current DCP, and a Type-C port can move to another pipeline in between. devres_destroy() then fails to find the resource on the device it is given, so devm_kfree() warns and the buffer leaks, once per dictionary: WARNING: drivers/base/devres.c:1184 at devm_kfree+0x80/0x98 The lifetime is managed explicitly already. Use kzalloc() and kfree(), and release the dictionaries when the connector is destroyed. Signed-off-by: DjDeveloperr --- drivers/gpu/drm/apple/apple_drv.c | 8 +++++++- drivers/gpu/drm/apple/connector.c | 9 +++++++-- drivers/gpu/drm/apple/iomfb_template.c | 2 +- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index 514d54fe58296c..4c6fa62321a259 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -236,8 +236,14 @@ static const struct drm_mode_config_helper_funcs apple_mode_config_helpers = { static void appledrm_connector_cleanup(struct drm_connector *connector) { + struct apple_connector *apple_connector = to_apple_connector(connector); + drm_connector_cleanup(connector); - kfree(to_apple_connector(connector)); + kfree(apple_connector->color_elements.data); + kfree(apple_connector->timing_elements.data); + kfree(apple_connector->display_attributes.data); + kfree(apple_connector->transport.data); + kfree(apple_connector); } static const struct drm_connector_funcs apple_connector_funcs = { diff --git a/drivers/gpu/drm/apple/connector.c b/drivers/gpu/drm/apple/connector.c index 15b3664d85631e..c7a2d970874999 100644 --- a/drivers/gpu/drm/apple/connector.c +++ b/drivers/gpu/drm/apple/connector.c @@ -125,8 +125,13 @@ static void dcp_connector_set_dict(struct apple_connector *connector, struct dcp_chunks *dict, struct dcp_chunks *chunks) { - if (dict->data) - devm_kfree(&connector->dcp->dev, dict->data); + /* + * Not devm: the buffer is allocated by whichever DCP received the + * chunks, but a Type-C port can move to another pipeline before it is + * replaced, and freeing it against the connector's current DCP then + * fails devres_destroy() -- one WARN and one leak per dictionary. + */ + kfree(dict->data); *dict = *chunks; } diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c index 1b868a8e159ead..44902912c8a344 100644 --- a/drivers/gpu/drm/apple/iomfb_template.c +++ b/drivers/gpu/drm/apple/iomfb_template.c @@ -516,7 +516,7 @@ static u8 dcpep_cb_prop_start(struct apple_dcp *dcp, u32 *length) } dcp->chunks.length = *length; - dcp->chunks.data = devm_kzalloc(dcp->dev, *length, GFP_KERNEL); + dcp->chunks.data = kzalloc(*length, GFP_KERNEL); if (!dcp->chunks.data) { dev_warn(dcp->dev, "failed to allocate chunks\n"); From f6d4f47575f3b62b035c7eeaeca930a8f697e4a8 Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Mon, 31 Aug 2026 16:18:53 -0400 Subject: [PATCH 46/51] thunderbolt: Add a PCI tunnel deactivate callback A host controller can need to react to a PCI tunnel going away, not just to one being established. On Apple silicon the tunneled PCIe hierarchy is only reachable through the tunnel, so it has to be torn down before the paths are disabled rather than left behind with nothing underneath it. Add pci_tunnel_deactivate to tb_nhi_ops as the counterpart to the existing pre- and post-activate callbacks, and call it at the start of tunnel deactivation while the hierarchy can still be reached. Signed-off-by: DjDeveloperr --- drivers/thunderbolt/nhi.h | 1 + drivers/thunderbolt/tunnel.c | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/drivers/thunderbolt/nhi.h b/drivers/thunderbolt/nhi.h index da963354b2b7e8..f1c1a0237e6f7c 100644 --- a/drivers/thunderbolt/nhi.h +++ b/drivers/thunderbolt/nhi.h @@ -99,6 +99,7 @@ struct tb_nhi_ops { void (*ring_configure)(struct tb_ring *ring, u32 flags, u32 e2e_flags); int (*pci_tunnel_pre_activate)(struct tb_nhi *nhi); int (*pci_tunnel_post_activate)(struct tb_nhi *nhi); + int (*pci_tunnel_deactivate)(struct tb_nhi *nhi); bool (*is_present)(struct tb_nhi *nhi); int (*init_interrupts)(struct tb_nhi *nhi); }; diff --git a/drivers/thunderbolt/tunnel.c b/drivers/thunderbolt/tunnel.c index e000718aad8e78..ffe97ba6b4950d 100644 --- a/drivers/thunderbolt/tunnel.c +++ b/drivers/thunderbolt/tunnel.c @@ -342,10 +342,29 @@ static int tb_pci_post_activate(struct tb_tunnel *tunnel) return 0; } +static int tb_pci_deactivate(struct tb_tunnel *tunnel) +{ + const struct tb_nhi_ops *ops = tunnel->tb->nhi->ops; + + if (ops && ops->pci_tunnel_deactivate) + return ops->pci_tunnel_deactivate(tunnel->tb->nhi); + return 0; +} + static int tb_pci_activate(struct tb_tunnel *tunnel, bool activate) { int res; + /* + * Let the host drop its view of the tunnel before the paths go, while + * what is behind it can still be reached. + */ + if (!activate) { + res = tb_pci_deactivate(tunnel); + if (res) + return res; + } + if (activate) { res = tb_pci_set_ext_encapsulation(tunnel, activate); if (res) From d7f85f65ebc2544b8397fb30851728faa96f1550 Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Mon, 31 Aug 2026 16:18:53 -0400 Subject: [PATCH 47/51] thunderbolt: apple: tear down PCIe-C when its tunnel goes away Only an NHI shutdown quiesced the tunneled PCI hierarchy. A tunnel torn down while the router stays up left the endpoint enumerated with its data path gone, so every access to it blocked until the function driver's own timeout fired. For NVMe that is around a minute of hung I/O ending in nvme nvme1: controller is down; CSTS=0xffffffff, PCI_STATUS=0xffff nvme nvme1: Disabling device after reset failure: -19 and the device never came back when the tunnel did, because nothing had removed it for the host to enumerate again. Quiesce PCIe-C from the new deactivate callback, and restore it when the tunnel is activated again. Restoring restarts the ports the way resume does and rescans the bus. Quiescing now removes the hierarchy rather than only stopping it. Stopping unbinds the drivers but leaves the pci_dev objects behind, so a later rescan finds stale devices: the endpoint returns with an unbalanced runtime-PM count and its driver fails to probe. Signed-off-by: DjDeveloperr --- drivers/pci/controller/pcie-apple.c | 54 +++++++++++++++++++++++++- drivers/thunderbolt/apple.c | 59 ++++++++++++++++++++++++++++- include/linux/pci-apple.h | 1 + 3 files changed, 112 insertions(+), 2 deletions(-) diff --git a/drivers/pci/controller/pcie-apple.c b/drivers/pci/controller/pcie-apple.c index 780aedfe6078f6..67f3eeac2e0d9c 100644 --- a/drivers/pci/controller/pcie-apple.c +++ b/drivers/pci/controller/pcie-apple.c @@ -1851,8 +1851,21 @@ int apple_pcie_tunnel_quiesce(struct device *dev) */ pci_lock_rescan_remove(); if (!pcie->bus_stopped) { + struct pci_dev *pdev, *tmp; + pci_walk_bus(bridge->bus, pci_dev_set_disconnected, NULL); - pci_stop_root_bus(bridge->bus); + + /* + * Remove rather than merely stop. Stopping unbinds the drivers + * but leaves the pci_dev objects behind, and a later rescan + * then finds stale devices instead of enumerating fresh ones: + * the endpoint returns with an unbalanced runtime-PM count and + * its driver fails to probe. + */ + list_for_each_entry_safe(pdev, tmp, &bridge->bus->devices, + bus_list) + pci_stop_and_remove_bus_device(pdev); + pcie->bus_stopped = true; } @@ -1874,6 +1887,45 @@ int apple_pcie_tunnel_quiesce(struct device *dev) } EXPORT_SYMBOL_GPL(apple_pcie_tunnel_quiesce); +/* + * Bring the tunneled host back after apple_pcie_tunnel_quiesce(). The ports + * need the restart that resume performs, and the hierarchy below them has to be + * enumerated again because quiescing removed it. + */ +int apple_pcie_tunnel_restore(struct device *dev) +{ + struct pci_host_bridge *bridge = dev_get_drvdata(dev); + struct apple_pcie *pcie; + struct apple_pcie_port *port; + int ret = 0; + + if (!bridge || !bridge->bus) + return -ENODEV; + + pcie = pci_host_bridge_priv(bridge); + if (!pcie->hw->tunneled) + return -EINVAL; + if (!pcie->bus_stopped) + return 0; + + list_for_each_entry(port, &pcie->ports, entry) { + int err = apple_pcie_tunnel_start(port); + + if (err && !ret) + ret = err; + } + + pci_lock_rescan_remove(); + pci_rescan_bus(bridge->bus); + pcie->bus_stopped = false; + pci_unlock_rescan_remove(); + + dev_info(dev, "PCIe-C hierarchy restored after tunnel activation\n"); + + return ret; +} +EXPORT_SYMBOL_GPL(apple_pcie_tunnel_restore); + static void apple_pcie_remove(struct platform_device *pdev) { struct pci_host_bridge *bridge = platform_get_drvdata(pdev); diff --git a/drivers/thunderbolt/apple.c b/drivers/thunderbolt/apple.c index 07a448b52dee73..00c78bedc3185a 100644 --- a/drivers/thunderbolt/apple.c +++ b/drivers/thunderbolt/apple.c @@ -481,6 +481,8 @@ static void apple_nhi_ring_configure(struct tb_ring *ring, u32 flags, u32 e2e_fl writel(flags | e2e_flags, options); } +static struct platform_device *apple_cio_find_pcie_tunnel(struct apple_cio *acio); + static int apple_cio_populate_pcie_tunnel(struct apple_cio *acio) { /* @@ -509,8 +511,27 @@ static int apple_cio_activate_pcie_tunnel_locked(struct apple_cio *acio) lockdep_assert_held(&acio->pcie_tunnel_lock); - if (!acio->pcie_tunnel_np || acio->pcie_tunnel_populated) + if (!acio->pcie_tunnel_np) return 0; + + /* + * The host survives a tunnel teardown, quiesced with its hierarchy + * removed. Bring that one back rather than populating a second. + */ + if (acio->pcie_tunnel_populated) { + struct platform_device *pcie_pdev; + + pcie_pdev = apple_cio_find_pcie_tunnel(acio); + if (pcie_pdev) { + ret = apple_pcie_tunnel_restore(&pcie_pdev->dev); + if (ret) + dev_warn(acio->dev, + "failed to restore PCIe-C after tunnel activation: %d\n", + ret); + put_device(&pcie_pdev->dev); + } + return 0; + } if (!acio->pcie_tunnel_preinitialized) return dev_err_probe(acio->dev, -ENODEV, "PCIe-C requires a successful m1n1 preinit handoff\n"); @@ -549,6 +570,41 @@ static void apple_cio_pcie_tunnel_work(struct work_struct *work) dev_err(acio->dev, "deferred PCIe-C activation failed: %d\n", ret); } +static int apple_nhi_pci_tunnel_deactivate(struct tb_nhi *nhi) +{ + struct apple_nhi *anhi = nhi_to_anhi(nhi); + struct apple_cio *acio = anhi->acio; + struct platform_device *pcie_pdev; + int ret; + + /* + * The tunnel is going away while the router and NHI stay up, so nothing + * else tears the tunneled PCI hierarchy down. Left alone the endpoint + * keeps its driver bound with no data path underneath it: every access + * blocks until the function driver's own timeout fires, and the host has + * nothing to enumerate onto when the tunnel comes back. + */ + mutex_lock(&acio->pcie_tunnel_lock); + if (!acio->pcie_tunnel_populated) { + mutex_unlock(&acio->pcie_tunnel_lock); + return 0; + } + + pcie_pdev = apple_cio_find_pcie_tunnel(acio); + if (pcie_pdev) { + ret = apple_pcie_tunnel_quiesce(&pcie_pdev->dev); + if (ret) + dev_warn(acio->dev, + "failed to quiesce PCIe-C on tunnel teardown: %d\n", + ret); + put_device(&pcie_pdev->dev); + } + + mutex_unlock(&acio->pcie_tunnel_lock); + + return 0; +} + static int apple_nhi_pci_tunnel_pre_activate(struct tb_nhi *nhi) { struct apple_nhi *anhi = nhi_to_anhi(nhi); @@ -585,6 +641,7 @@ static const struct tb_nhi_ops apple_nhi_ops = { .ring_configure = apple_nhi_ring_configure, .pci_tunnel_pre_activate = apple_nhi_pci_tunnel_pre_activate, .pci_tunnel_post_activate = apple_nhi_pci_tunnel_post_activate, + .pci_tunnel_deactivate = apple_nhi_pci_tunnel_deactivate, }; static const struct tb_nhi_ring_layout apple_nhi_ring_layout = { diff --git a/include/linux/pci-apple.h b/include/linux/pci-apple.h index 29cff22f9cc0e2..ef664800801fe4 100644 --- a/include/linux/pci-apple.h +++ b/include/linux/pci-apple.h @@ -5,5 +5,6 @@ struct device; int apple_pcie_tunnel_quiesce(struct device *dev); +int apple_pcie_tunnel_restore(struct device *dev); #endif /* _LINUX_PCI_APPLE_H */ From 443321db6f7add02a5f97cb8a1492c482ef43196 Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Wed, 2 Sep 2026 19:13:33 -0400 Subject: [PATCH 48/51] thunderbolt: apple: accept cable speeds above Gen3 The cable information written to the switch only set the 20 Gbps bit when the Enter_USB4 data object reported exactly Gen3. That field is three bits wide and USB4 v2 defines Gen4 above Gen3, so a Thunderbolt 5 cable reports 4 and fails the equality test. The firmware is then told the cable cannot carry 20 Gbps and mistrains the link: thunderbolt-apple-acio 701ac0000.cio: RTKit: syslog message: high_speed_lane.c:289: Gen2/3 link error. lane=0, error=89 Those errors repeat until the port gives up. No router is ever found, so a dock on such a cable enumerates nothing at all, while a Gen3 cable on the same port works. A device that does link comes up on one lane and its PCIe tunnel fails to activate. The host supports nothing beyond Gen3, so any cable reporting Gen3 or faster can carry 20 Gbps. Compare accordingly. Signed-off-by: DjDeveloperr --- drivers/thunderbolt/apple.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/thunderbolt/apple.c b/drivers/thunderbolt/apple.c index 00c78bedc3185a..bd7aa242559375 100644 --- a/drivers/thunderbolt/apple.c +++ b/drivers/thunderbolt/apple.c @@ -1122,7 +1122,7 @@ static int apple_cio_tbt_switch_set(struct typec_thunderbolt_switch_dev *sw, acio->target_cable_info = TB_VSE_CAP_APPLE_CABLE_INFO_PRESENT; if (FIELD_GET(EUDO_CABLE_TYPE_MASK, data->usb4.eudo) != EUDO_CABLE_TYPE_PASSIVE) acio->target_cable_info |= TB_VSE_CAP_APPLE_CABLE_INFO_ACTIVE_CABLE; - if (FIELD_GET(EUDO_CABLE_SPEED_MASK, data->usb4.eudo) == EUDO_CABLE_SPEED_USB4_GEN3) + if (FIELD_GET(EUDO_CABLE_SPEED_MASK, data->usb4.eudo) >= EUDO_CABLE_SPEED_USB4_GEN3) acio->target_cable_info |= TB_VSE_CAP_APPLE_CABLE_INFO_20_GBPS; if (data->orientation == TYPEC_ORIENTATION_REVERSE) acio->target_cable_info |= TB_VSE_CAP_APPLE_CABLE_INFO_ORIENTATION_REVERSE; From c111a22db71d4f4f48e00d4c593556c676450515 Mon Sep 17 00:00:00 2001 From: Marcelo Date: Thu, 10 Sep 2026 09:37:44 +1000 Subject: [PATCH 49/51] usb: typec: tipd: fix partner identity use-after-free on CD321x cd321x_update_work() points desc.identity at st.partner_identity, where "st" is a stack local. typec_register_partner() stores that pointer in partner->identity rather than copying the structure, so once the work function returns the partner is left referencing a freed stack page. Any later read of the partner's PD identity then dereferences it. On a MacBook Pro (16-inch, M2 Max) reading /sys/class/typec/port0-partner/type as an unprivileged user is enough: Unable to handle kernel paging request at virtual address ffff800083a87d0c Internal error: Oops: 0000000096000007 [#1] SMP CPU: 10 UID: 1000 PID: 83017 Comm: cat pc : get_pd_product_type+0x54/0xa4 [typec] lr : type_show+0x14/0x3c [typec] get_pd_product_type() NULL-checks partner->identity and then reads id->id_header, which is the faulting access; the pointer is not NULL, just dangling. With VMAP_STACK the dead stack is unmapped, hence the vmalloc-range fault address. The driver already keeps a persistent copy in cd321x->cur_partner_identity, but only assigns it after registration. Fill it in first and register the partner against that instead. The generic tps6598x path already does the equivalent, pointing desc.identity at tps->partner_identity. Fixes: 82432bbfb9e8 ("usb: typec: tipd: Handle mode transitions for CD321x") Signed-off-by: Marcelo --- drivers/usb/typec/tipd/core.c | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/drivers/usb/typec/tipd/core.c b/drivers/usb/typec/tipd/core.c index e11a832b47b0e6..52f1f2a159336d 100644 --- a/drivers/usb/typec/tipd/core.c +++ b/drivers/usb/typec/tipd/core.c @@ -825,8 +825,15 @@ static void cd321x_update_work(struct work_struct *work) desc.accessory = TYPEC_ACCESSORY_NONE; /* XXX: handle accessories */ desc.identity = NULL; - if (desc.usb_pd) - desc.identity = &st.partner_identity; + /* + * typec_register_partner() stores this pointer rather than + * copying what it points at, and the partner outlives this + * function, so it must not point into "st" on the stack. + */ + if (desc.usb_pd) { + cd321x->cur_partner_identity = st.partner_identity; + desc.identity = &cd321x->cur_partner_identity; + } tps->partner = typec_register_partner(tps->port, &desc); if (IS_ERR(tps->partner)) { @@ -834,10 +841,8 @@ static void cd321x_update_work(struct work_struct *work) return; } - if (desc.identity) { + if (desc.identity) typec_partner_set_identity(tps->partner); - cd321x->cur_partner_identity = st.partner_identity; - } } /* Update the TypeC MUX/PHY state */ From 6a5489f24e8edfcfdea999ba03a7c2c500058989 Mon Sep 17 00:00:00 2001 From: Marcelo Date: Thu, 10 Sep 2026 10:03:40 +1000 Subject: [PATCH 50/51] usb: typec: fix sysfs_emit_at() offset underflow on an empty mode list usb_mode_show() and usb_capability_show() build a space-separated list and then overwrite the trailing space with a newline: sysfs_emit_at(buf, len - 1, "\n"); When the partner or port advertises none of USB2/USB3/USB4 the loop body never runs, len stays 0, and the call is made with at == -1: invalid sysfs_emit_at: buf:00000000ffaad2a3 at:-1 WARNING: fs/sysfs/file.c:785 at sysfs_emit_at+0x54/0xd4, CPU#1: cat/892 sysfs_emit_at() rejects the negative offset, so the attribute also reads back empty rather than as a newline like every other list attribute. Seen on a MacBook Pro (16-inch, M2 Max), where one of the Type-C partners registered by the CD321x has no USB capability bits set, by reading /sys/class/typec/port1-partner/usb_mode Return a bare newline when nothing was written. Fixes: 2140a952c4e9 ("usb: typec: Add attribute file showing the USB Modes of the partner") Signed-off-by: Marcelo --- drivers/usb/typec/class.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/usb/typec/class.c b/drivers/usb/typec/class.c index 0595e8cb83aa67..ed2c919e50287c 100644 --- a/drivers/usb/typec/class.c +++ b/drivers/usb/typec/class.c @@ -753,6 +753,10 @@ usb_mode_show(struct device *dev, struct device_attribute *attr, char *buf) len += sysfs_emit_at(buf, len, "%s ", usb_modes[i]); } + /* Nothing was written, so there is no trailing separator to replace. */ + if (!len) + return sysfs_emit(buf, "\n"); + sysfs_emit_at(buf, len - 1, "\n"); return len; @@ -1542,6 +1546,10 @@ usb_capability_show(struct device *dev, struct device_attribute *attr, char *buf len += sysfs_emit_at(buf, len, "%s ", usb_modes[i]); } + /* Nothing was written, so there is no trailing separator to replace. */ + if (!len) + return sysfs_emit(buf, "\n"); + sysfs_emit_at(buf, len - 1, "\n"); return len; From 38c14f6107dfdb3366dee3cdfc3226cf4cda9ca2 Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Mon, 31 Aug 2026 01:15:06 -0400 Subject: [PATCH 51/51] drm: apple: support ProMotion variable refresh rate DCP exposes an adaptive-sync parameter taking a minimum refresh rate, a media target rate and a fractional rate. Program it during modesets and report the panel's range through the standard vrr_capable connector property, so userspace drives VRR through the atomic VRR_ENABLED CRTC property rather than a driver-specific knob. The swap path previously wrote a constant 120 into the three timestamp fields to hold the internal panel at its maximum rate. Replace that with architectural counter timestamps taken at swap submit and at present, which is what the firmware needs in order to vary the rate at all. An internal ProMotion panel carries no EDID or DisplayID, so DCP reports no adaptive-sync range for it. Assume the ProMotion floor of 24 Hz up to the rate the mode itself advertises. Key that off the connector type from the device tree rather than the main-display flag, which is filled in by an asynchronous response and is not reliably set by the time the timing elements are parsed. External displays keep the range DCP reports, and a reported range is only believed when its maximum exceeds its minimum. Fill in display_info.monitor_range as well. Nothing else populates it for a panel with no EDID, so without it the range is missing from debugfs and from anything else that reads it. Signed-off-by: DjDeveloperr --- drivers/gpu/drm/apple/dcp.c | 1 + drivers/gpu/drm/apple/iomfb.c | 26 ++++++++ drivers/gpu/drm/apple/iomfb_template.c | 84 +++++++++++++++++++++----- drivers/gpu/drm/apple/iomfb_template.h | 10 +-- drivers/gpu/drm/apple/parser.c | 29 +++++---- drivers/gpu/drm/apple/parser.h | 3 +- drivers/gpu/drm/apple/trace.h | 21 +++++++ 7 files changed, 138 insertions(+), 36 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 4fc018f4132392..08156394b233f0 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -656,6 +656,7 @@ static int dcp_register_typec_routes(struct apple_dcp *dcp) return 0; } + /* copied and simplified from drm_vblank.c */ static void send_vblank_event(struct drm_device *dev, struct drm_pending_vblank_event *e, diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index 452a35731f7cfd..3ec33353d3abe0 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -406,6 +406,8 @@ int dcp_get_modes(struct drm_connector *connector) struct drm_device *dev = connector->dev; struct drm_display_mode *mode; + u16 min_vfreq = 0, max_vfreq = 0; + bool vrr_capable = false; int i; /* A Type-C port has no pipeline while the fabric is moving it. */ @@ -414,6 +416,16 @@ int dcp_get_modes(struct drm_connector *connector) dcp = platform_get_drvdata(pdev); for (i = 0; i < dcp->nr_modes; ++i) { + if (dcp->modes[i].vrr) { + u16 lo = dcp->modes[i].min_vrr >> 16; + u16 hi = dcp->modes[i].max_vrr >> 16; + + if (!min_vfreq || lo < min_vfreq) + min_vfreq = lo; + if (hi > max_vfreq) + max_vfreq = hi; + } + vrr_capable |= dcp->modes[i].vrr; mode = drm_mode_duplicate(dev, &dcp->modes[i].mode); if (!mode) { @@ -423,6 +435,8 @@ int dcp_get_modes(struct drm_connector *connector) drm_mode_probed_add(connector, mode); } + drm_connector_set_vrr_capable_property(connector, vrr_capable); + if (dcp->nr_modes && dcp->dcpavserv.enabled && !apple_connector->drm_edid) { const struct drm_edid *edid; @@ -437,6 +451,18 @@ int dcp_get_modes(struct drm_connector *connector) if (dcp->nr_modes && apple_connector->drm_edid) drm_edid_connector_update(connector, apple_connector->drm_edid); + /* + * An internal panel has no EDID, so nothing fills in the refresh range + * that userspace needs before it will drive VRR. Supply the range DCP + * reported for the mode. This has to follow the EDID update, which + * resets display_info. + */ + if (vrr_capable && max_vfreq && + !connector->display_info.monitor_range.max_vfreq) { + connector->display_info.monitor_range.min_vfreq = min_vfreq; + connector->display_info.monitor_range.max_vfreq = max_vfreq; + } + return dcp->nr_modes; } diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c index 44902912c8a344..c0b533c4f8a3c7 100644 --- a/drivers/gpu/drm/apple/iomfb_template.c +++ b/drivers/gpu/drm/apple/iomfb_template.c @@ -4,6 +4,7 @@ * Copyright The Asahi Linux Contributors */ +#include #include #include #include @@ -34,6 +35,9 @@ /* Register defines used in bandwidth setup structure */ #define REG_DOORBELL_BIT(idx) (2 + (idx)) +static_assert(offsetof(struct DCP_FW_NAME(dcp_swap), timestamp[6]) == 0x30); +static_assert(offsetof(struct DCP_FW_NAME(dcp_swap), flags1) == 0x40); + struct dcp_wait_cookie { struct kref refcount; struct completion done; @@ -567,7 +571,9 @@ static bool dcpep_process_chunks(struct apple_dcp *dcp, if (!strcmp(req->key, "TimingElements")) { dcp->modes = enumerate_modes(&ctx, &dcp->nr_modes, dcp->width_mm, dcp->height_mm, - dcp->notch_height); + dcp->notch_height, + dcp->fixed_connector_type == + DRM_MODE_CONNECTOR_eDP); if (IS_ERR(dcp->modes)) { dev_warn(dcp->dev, "failed to parse modes\n"); @@ -1152,6 +1158,7 @@ static void dcp_swapped(struct apple_dcp *dcp, void *data, void *cookie) return; } dcp->swap_start = ktime_get(); + dcp->swap_submit_timestamp = arch_timer_read_counter(); while (!list_empty(&dcp->swapped_out_fbs)) { struct dcp_fb_reference *entry; @@ -1198,6 +1205,35 @@ static void complete_set_digital_out_mode(struct apple_dcp *dcp, void *data, } } +/* DCP applies Adaptive Sync changes when the display mode is reselected. */ +static void dcp_on_set_adaptive_sync(struct apple_dcp *dcp, void *out, + void *cookie) +{ + dcp_set_digital_out_mode(dcp, false, &dcp->mode, + complete_set_digital_out_mode, cookie); +} + +static void dcp_set_adaptive_sync(struct apple_dcp *dcp, u32 min_vrr, + void *cookie) +{ + struct dcp_set_parameter_dcp param = { + .param = IOMFBPARAM_ADAPTIVE_SYNC, + .value = { + min_vrr, /* minRR, 16.16 fixed-point Hz */ + 0, /* mediaTargetRate */ + 0, /* fractional rate */ + }, +#if DCP_FW_VER >= DCP_FW_VERSION(13, 2, 0) + .count = 3, +#else + .count = 1, +#endif + }; + + dcp_set_parameter_dcp(dcp, false, ¶m, dcp_on_set_adaptive_sync, + cookie); +} + int DCP_FW_NAME(iomfb_modeset)(struct apple_dcp *dcp, struct drm_crtc_state *crtc_state) { @@ -1237,8 +1273,8 @@ int DCP_FW_NAME(iomfb_modeset)(struct apple_dcp *dcp, .timing_mode_id = mode->timing_mode_id }; - /* Keep track of suspected vrr modes */ - dcp->use_timestamps = mode->vrr; + /* Built-in ProMotion panels require timestamps even in fixed-120 mode. */ + dcp->use_timestamps = mode->vrr && dcp->main_display; cookie = kzalloc(sizeof(*cookie), GFP_KERNEL); if (!cookie) { @@ -1251,9 +1287,15 @@ int DCP_FW_NAME(iomfb_modeset)(struct apple_dcp *dcp, kref_get(&cookie->refcount); dcp->during_modeset = true; + dcp->swap_submit_timestamp = 0; - dcp_set_digital_out_mode(dcp, false, &dcp->mode, - complete_set_digital_out_mode, cookie); + if (mode->vrr) + dcp_set_adaptive_sync(dcp, + crtc_state->vrr_enabled ? mode->min_vrr : 0, + cookie); + else + dcp_set_digital_out_mode(dcp, false, &dcp->mode, + complete_set_digital_out_mode, cookie); /* * The DCP firmware has an internal timeout of ~8 seconds for @@ -1263,7 +1305,6 @@ int DCP_FW_NAME(iomfb_modeset)(struct apple_dcp *dcp, ret = wait_for_completion_timeout(&cookie->done, msecs_to_jiffies(8500)); - kref_put(&cookie->refcount, release_wait_cookie); dcp->during_modeset = false; if (dcp->pending_hotplug) { @@ -1287,18 +1328,21 @@ int DCP_FW_NAME(iomfb_modeset)(struct apple_dcp *dcp, if (ret == 0) { dev_info(dcp->dev, "set_digital_out_mode timed out\n"); + kref_put(&cookie->refcount, release_wait_cookie); return -EIO; } else if (ret < 0) { dev_info(dcp->dev, "waiting on set_digital_out_mode failed:%d\n", ret); + kref_put(&cookie->refcount, release_wait_cookie); return -EIO; - - } else if (ret > 0) { + } else { dev_dbg(dcp->dev, "set_digital_out_mode finished with %d to spare\n", jiffies_to_msecs(ret)); } + kref_put(&cookie->refcount, release_wait_cookie); dcp->valid_mode = true; + dcp->vrr_enabled = mode->vrr && crtc_state->vrr_enabled; return 0; } @@ -1405,14 +1449,26 @@ void DCP_FW_NAME(iomfb_flush)(struct apple_dcp *dcp, struct drm_crtc *crtc, stru req->clear = 1; } - if (has_surface && dcp->use_timestamps) { + if (has_surface && (dcp->use_timestamps || dcp->vrr_enabled)) { + u64 submit_timestamp = dcp->swap_submit_timestamp; + u64 timestamp = arch_timer_read_counter(); + /* - * Fake timstamps to get 120hz refresh rate. It looks - * like the actual value does not matter, as long as it is non zero. + * IOMobileFramebuffer uses Mach continuous-time values here. On + * Apple Silicon that is the ARM architectural counter. Empirical + * testing shows that using the current submission time together + * with the previous accepted swap makes DCP follow swap pacing. */ - req->swap.ts1 = 120; - req->swap.ts2 = 120; - req->swap.ts3 = 120; + if (!submit_timestamp) + submit_timestamp = timestamp; + + /* Firmware 12.x/13.x requires timestamp types 1, 2, and 7. */ + req->swap.timestamp[0] = timestamp; + req->swap.timestamp[1] = submit_timestamp; + req->swap.timestamp[6] = timestamp; + + trace_iomfb_vrr_timestamps(dcp, dcp->vrr_enabled, timestamp, + submit_timestamp); } /* These fields should be set together */ diff --git a/drivers/gpu/drm/apple/iomfb_template.h b/drivers/gpu/drm/apple/iomfb_template.h index 8efab49cc53d08..ce9fb92513cd1b 100644 --- a/drivers/gpu/drm/apple/iomfb_template.h +++ b/drivers/gpu/drm/apple/iomfb_template.h @@ -18,14 +18,8 @@ #include "version_utils.h" struct DCP_FW_NAME(dcp_swap) { - u64 ts1; - u64 ts2; - - u64 unk_10; - u64 unk_18; - u64 ts64_unk; - u64 unk_28; - u64 ts3; + /* IOMobileFramebuffer timestamp types 1 through 7, in wire order. */ + u64 timestamp[7]; u64 unk_38; u64 flags1; diff --git a/drivers/gpu/drm/apple/parser.c b/drivers/gpu/drm/apple/parser.c index 56960fa1fda265..3d520938a2900f 100644 --- a/drivers/gpu/drm/apple/parser.c +++ b/drivers/gpu/drm/apple/parser.c @@ -443,7 +443,7 @@ static u32 calculate_clock(struct dimension *horiz, struct dimension *vert) static int parse_mode(struct dcp_parse_ctx *handle, struct dcp_display_mode *out, s64 *score, int width_mm, - int height_mm, unsigned notch_height) + int height_mm, unsigned notch_height, bool internal) { int ret = 0; struct iterator it; @@ -515,20 +515,21 @@ static int parse_mode(struct dcp_parse_ctx *handle, return -EINVAL; /* - * HACK: - * Mark the 120 Hz mode on j314/j316 (identified by resolution) as vrr. - * We still do not know how to drive VRR but at least seetinng timestamps - * in the the swap_surface message to non-zero values drives the display - * at 120 fps. - */ - if (vert.precise_sync_rate >> 16 == 120 && - ((horiz.active == 3024 && vert.active == 1964) || - (horiz.active == 3456 && vert.active == 2234))) + * An internal ProMotion panel carries no EDID or DisplayID, so DCP + * reports no adaptive-sync range for it. Assume the ProMotion floor of + * 24 Hz up to whatever rate the mode itself advertises. + */ + if (internal && vert.precise_sync_rate >> 16 == 120) { + out->min_vrr = 24 << 16; + out->max_vrr = vert.precise_sync_rate; out->vrr = true; + } - if (min_vrr && max_vrr) { + /* Refresh rates are reported by DCP as 16.16 fixed-point Hz. */ + if (min_vrr && max_vrr > min_vrr) { out->min_vrr = min_vrr; out->max_vrr = max_vrr; + out->vrr = true; } vert.active -= notch_height; @@ -568,7 +569,8 @@ static int parse_mode(struct dcp_parse_ctx *handle, struct dcp_display_mode *enumerate_modes(struct dcp_parse_ctx *handle, unsigned int *count, int width_mm, - int height_mm, unsigned notch_height) + int height_mm, unsigned notch_height, + bool internal) { struct iterator it; int ret; @@ -590,7 +592,8 @@ struct dcp_display_mode *enumerate_modes(struct dcp_parse_ctx *handle, for (; it.idx < it.len; ++it.idx) { mode = &modes[*count]; - ret = parse_mode(it.handle, mode, &score, width_mm, height_mm, notch_height); + ret = parse_mode(it.handle, mode, &score, width_mm, height_mm, + notch_height, internal); /* Errors for a single mode are recoverable -- just skip it. */ if (ret) diff --git a/drivers/gpu/drm/apple/parser.h b/drivers/gpu/drm/apple/parser.h index 52c4de46d2f79f..35f4e81e762125 100644 --- a/drivers/gpu/drm/apple/parser.h +++ b/drivers/gpu/drm/apple/parser.h @@ -104,7 +104,8 @@ struct dimension { int parse(const void *blob, size_t size, struct dcp_parse_ctx *ctx); struct dcp_display_mode *enumerate_modes(struct dcp_parse_ctx *handle, unsigned int *count, int width_mm, - int height_mm, unsigned notch_height); + int height_mm, unsigned notch_height, + bool internal); int parse_display_attributes(struct dcp_parse_ctx *handle, int *width_mm, int *height_mm); int parse_epic_service_init(struct dcp_parse_ctx *handle, const char **name, diff --git a/drivers/gpu/drm/apple/trace.h b/drivers/gpu/drm/apple/trace.h index a13dd34fb7aab1..c0acac6ee690ab 100644 --- a/drivers/gpu/drm/apple/trace.h +++ b/drivers/gpu/drm/apple/trace.h @@ -263,6 +263,27 @@ TRACE_EVENT(iomfb_swap_submit, __entry->swap_id) ); +TRACE_EVENT(iomfb_vrr_timestamps, + TP_PROTO(struct apple_dcp *dcp, bool enabled, u64 timestamp, + u64 submit_timestamp), + TP_ARGS(dcp, enabled, timestamp, submit_timestamp), + TP_STRUCT__entry( + __field(u64, dcp) + __field(bool, enabled) + __field(u64, timestamp) + __field(u64, submit_timestamp) + ), + TP_fast_assign( + __entry->dcp = (u64)dcp; + __entry->enabled = enabled; + __entry->timestamp = timestamp; + __entry->submit_timestamp = submit_timestamp; + ), + TP_printk("dcp=%llx, enabled=%u, timestamp=%llu, previous=%llu", + __entry->dcp, __entry->enabled, __entry->timestamp, + __entry->submit_timestamp) +); + TRACE_EVENT(iomfb_swap_complete, TP_PROTO(struct apple_dcp *dcp, u32 swap_id), TP_ARGS(dcp, swap_id),