From 653f5375f4d33aba76c3636775060e49e5c3816c Mon Sep 17 00:00:00 2001 From: Jeff Queisser Date: Mon, 2 Nov 2026 02:24:07 -0800 Subject: [PATCH 01/43] dt-bindings: pci: apple,pcie: Add T6050 hosts adopted from the boot loader Add apple,t6050-pcie, a T6050 (M5 Ultra) PCIe host whose root ports the boot loader powered, trained and left running. Each such port is marked apple,link-trained-by-bootloader and named by its port, phy and intr2axi register ranges and its port interrupt; the host's local MSI map starts at apple,msi-vector-base. The OS adopts the trained state and never resets, retrains or changes the port's power: reset-gpios, pwren-gpios and power-domains are not allowed on it. Also name nonposted-mmio, which these hosts take. Signed-off-by: Jeff Queisser Developed with Claude Code. Signed-off-by: Erik Bourget --- .../devicetree/bindings/pci/apple,pcie.yaml | 55 +++++++++++++++++-- 1 file changed, 49 insertions(+), 6 deletions(-) diff --git a/Documentation/devicetree/bindings/pci/apple,pcie.yaml b/Documentation/devicetree/bindings/pci/apple,pcie.yaml index 245231ecfc3a9..5823cc0ff837a 100644 --- a/Documentation/devicetree/bindings/pci/apple,pcie.yaml +++ b/Documentation/devicetree/bindings/pci/apple,pcie.yaml @@ -98,11 +98,11 @@ properties: reg: minItems: 3 - maxItems: 11 + maxItems: 14 reg-names: minItems: 3 - maxItems: 11 + maxItems: 14 ranges: minItems: 2 @@ -128,8 +128,7 @@ properties: apple,msi-vector-base: $ref: /schemas/types.yaml#/definitions/uint32 description: - First vector of this PCIe-C controller's slice of the MSI window it - shares with the internal PCIe controller. + First global vector targeted by this controller's local MSI map. iommu-map: true iommu-map-mask: true @@ -234,6 +233,48 @@ allOf: - apple,msi-vector-base - $ref: /schemas/pci/pci-host-bridge.yaml# - $ref: /schemas/interrupt-controller/msi-controller.yaml# + - if: + properties: + compatible: + contains: + const: apple,t6050-pcie + then: + properties: + reg: + minItems: 5 + maxItems: 14 + reg-names: + description: + config and rc, then port, phy and intr2axi for each + bootloader-trained root port 00:.0. + minItems: 5 + maxItems: 14 + items: + pattern: "^(config|rc|(port|phy|intr2axi)[0-3])$" + interrupts: + maxItems: 4 + interrupt-names: + items: + pattern: "^port[0-3]$" + apple,msi-vector-base: + const: 64 + power-domains: false + required: + - interrupt-names + - apple,msi-vector-base + - iommu-map + patternProperties: + "^pci@": + properties: + reset-gpios: false + pwren-gpios: false + required: + - apple,link-trained-by-bootloader + else: + patternProperties: + "^pci@": + properties: + apple,link-trained-by-bootloader: false - if: properties: compatible: @@ -390,10 +431,10 @@ allOf: properties: reg: minItems: 5 - maxItems: 11 + maxItems: 14 reg-names: minItems: 5 - maxItems: 11 + maxItems: 14 items: enum: [config, rc, port0, phy0, intr2axi0, port1, phy1, intr2axi1, port2, phy2, intr2axi2, port3, phy3, @@ -420,6 +461,8 @@ allOf: maxItems: 3 else: properties: + reg: + maxItems: 10 reg-names: minItems: 3 items: From ca6cac821eb3605ae935f1277f66f4ce5d668d72 Mon Sep 17 00:00:00 2001 From: Jeff Queisser Date: Mon, 2 Nov 2026 02:25:06 -0800 Subject: [PATCH 02/43] PCI: apple: Adopt T6050 root ports the boot loader trained On the M5 Ultra (T6050, J775D) running Linux on the hardware, the boot loader brings up the internal PCIe host's ports itself: it powers the shared PHY, trains each port's link and leaves it up, with the endpoint idle. Port 2 carries the onboard RTL8127 Ethernet controller and port 3 the ASM3142 xHCI behind the rear USB-A ports. Repeating the reset and training sequence from Linux is not needed and not known to be safe on this SoC. Add apple,t6050-pcie, a host whose root ports marked apple,link-trained-by-bootloader are adopted as they are: no reset, no tunable replay, no clock or power change and no retraining. Every resource comes from the DT by the port's index (port, phy, intr2axi, interrupt port, the iommu-map entry for the endpoint's RID). Before the first PCIe register write the driver checks that the link is up and not retraining at Gen3 x1, that the boot loader numbered the root port's buses (primary 0, one secondary), that the endpoint on it is device 0 function 0 with DMA and MSI off and a well-formed capability list, and that the port's RID table holds no valid entry. A port that fails a check is left alone and the others are still adopted. Then the endpoint's RID is routed to its SID, the host's MSI map is set up from apple,msi-vector-base, and only the adopted root ports and the endpoint behind each are enumerated. The links cannot be recovered by a user reset or removal, since the boot loader owns them. No other SoC sets adopt_link. Testing: on J775D, every boot adopts port 2 (RTL8127, DHCP and SSH over it on every boot) and port 3 (ASM3142, a USB keyboard and mouse driving the desktop session). Signed-off-by: Jeff Queisser Developed with Claude Code. Signed-off-by: Erik Bourget --- drivers/pci/controller/pcie-apple.c | 283 ++++++++++++++++++++++++++-- 1 file changed, 270 insertions(+), 13 deletions(-) diff --git a/drivers/pci/controller/pcie-apple.c b/drivers/pci/controller/pcie-apple.c index 6ad3376dcf75e..b0a4a549cdd9e 100644 --- a/drivers/pci/controller/pcie-apple.c +++ b/drivers/pci/controller/pcie-apple.c @@ -207,6 +207,7 @@ struct hw_info { u32 pwren_on_ms; bool sid_indexed_rid2sid; bool retain_clocks; + bool adopt_link; bool root_bus_only; bool tunneled; bool tunnel_intr2axi; @@ -281,6 +282,24 @@ static const struct hw_info t8152_hw = { .pipe_rst = true, }; +/* + * Bare-metal T6050 adoption only. Firmware owns power, PHY, reset and training. + * The 64-entry route table is never probed with test writes. + */ +static const struct hw_info t6050_hw = { + .port_msiaddr = PORT_T602X_MSIADDR, + .port_msiaddr_hi = PORT_T602X_MSIADDR_HI, + .port_perst = PORT_T602X_PERST, + .port_rid2sid = PORT_T602X_RID2SID, + .port_msimap = PORT_T602X_MSIMAP, + .max_rid2sid = 64, + .retain_clocks = true, + .adopt_link = true, +}; + +#define T6050_ROUTE(rid, sid) (PORT_RID2SID_VALID | \ + ((sid) << PORT_RID2SID_SID_SHIFT) | (rid)) + static const struct hw_info t8103_pciec_hw = { .port_msiaddr = PORT_MSIADDR, .port_perst = PORT_PERST, @@ -456,6 +475,10 @@ struct apple_pcie_port { bool needs_stop; bool link_failed; bool irq_disabled; + bool adopted_route; + void __iomem *intr2axi; /* adopt_link: this port's */ + u16 adopted_rid; /* adopt_link: secondary bus << 8 */ + u32 adopted_sid; }; static int apple_pcie_managed_admit(struct apple_pcie_port *port); @@ -767,9 +790,20 @@ static int apple_pcie_port_setup_irq(struct apple_pcie_port *port) struct apple_pcie *pcie = port->pcie; u32 val = 0; - /* FIXME: consider moving each interrupt under each port */ - port->irq = irq_of_parse_and_map(to_of_node(dev_fwnode(port->pcie->dev)), - port->idx); + if (pcie->hw->adopt_link) { + char name[16]; + int irq; + + snprintf(name, sizeof(name), "port%d", port->idx); + irq = of_irq_get_byname(pcie->dev->of_node, name); + + if (irq <= 0) + return irq ?: -ENXIO; + port->irq = irq; + } else { + /* FIXME: consider moving each interrupt under each port */ + port->irq = irq_of_parse_and_map(pcie->dev->of_node, port->idx); + } if (!port->irq) return -ENXIO; @@ -782,6 +816,9 @@ static int apple_pcie_port_setup_irq(struct apple_pcie_port *port) return -ENOMEM; } + if (pcie->hw->adopt_link) + apple_pcie_port_writel(port, 0, PORT_MSICFG); + /* Disable all interrupts */ apple_pcie_port_writel(port, ~0, PORT_INTMSK); apple_pcie_port_writel(port, ~0, PORT_INTSTAT); @@ -815,7 +852,22 @@ static int apple_pcie_port_setup_irq(struct apple_pcie_port *port) val = ilog2(pcie->nvecs) << PORT_MSICFG_L2MSINUM_SHIFT; } + if (pcie->hw->adopt_link) { + if (apple_pcie_port_readl(port, pcie->hw->port_msiaddr) != DOORBELL_ADDR || + apple_pcie_port_readl(port, pcie->hw->port_msiaddr_hi)) + return -EIO; + for (int i = 0; i < pcie->nvecs; i++) + if (apple_pcie_port_readl(port, pcie->hw->port_msimap + 4 * i) != + (PORT_MSIMAP_ENABLE | (pcie->msi_vector_base + i))) + return -EIO; + /* Enable command is not readable; +0x84 is only a completion read. */ + writel(PCIEC_INTR2AXI_ENABLE, port->intr2axi + PCIEC_INTR2AXI_CTRL); + readl(port->intr2axi + 0x84); + } apple_pcie_port_writel(port, val | PORT_MSICFG_EN, PORT_MSICFG); + if (pcie->hw->adopt_link && + !(apple_pcie_port_readl(port, PORT_MSICFG) & PORT_MSICFG_EN)) + return -EIO; return 0; } @@ -831,6 +883,8 @@ static irqreturn_t apple_pcie_port_irq(int irq, void *data) complete_all(&port->pcie->event); break; case PORT_INT_LINK_DOWN: + if (port->pcie->hw->adopt_link) + WRITE_ONCE(port->link_failed, true); /* A later link-up alone does not prove downstream state survived. */ if (port->pcie->hw->tunneled && READ_ONCE(port->started)) { WRITE_ONCE(port->link_failed, true); @@ -1509,9 +1563,18 @@ static void apple_pcie_port_teardown(struct apple_pcie_port *port) apple_pcie_port_disable_irq(port); /* Busy managed admission has not acquired or programmed this IRQ block. */ - if (port->base && (!port->pcie->managed_sids || port->domain)) + if (port->base && + (port->domain || + (!port->pcie->managed_sids && !port->pcie->hw->adopt_link))) apple_pcie_port_writel(port, ~0, PORT_INTMSK); + if (port->pcie->hw->adopt_link && port->domain) + apple_pcie_port_writel(port, 0, PORT_MSICFG); + if (port->adopted_route) { + apple_pcie_rid2sid_write(port, port->adopted_sid, 0); + port->adopted_route = false; + } + if (port->pcie->hw->tunneled && (port->started || port->needs_stop)) apple_pcie_tunnel_stop(port); @@ -1595,6 +1658,134 @@ static u32 apple_pcie_rid2sid_write(struct apple_pcie_port *port, return apple_pcie_port_readl(port, offset); } +/* + * Qualified J775D handoff of one bootloader-trained port: its root port + * 00:.0 and the single endpoint on its secondary bus, with DMA and + * endpoint MSI disabled. Every resource comes from the DT by the port's index + * (port, phy, intr2axi, interrupt port, the iommu-map entry for the + * endpoint's RID). All checks precede the first PCIe register write. + */ +static int apple_pcie_adopt_link(struct apple_pcie_port *port) +{ + static const char *const names[] = { "port%d", "phy%d", "intr2axi%d" }; + struct apple_pcie *pcie = port->pcie; + struct platform_device *pdev = to_platform_device(pcie->dev); + struct of_phandle_args iommu = {}; + void __iomem *rp, *ep; + char name[16]; + u32 value, bus; + u8 cap; + bool msi = false; + int ret, i; + + if (pcie->cfg->busr.start != 0 || + !platform_get_resource_byname(pdev, IORESOURCE_MEM, "config") || + !platform_get_resource_byname(pdev, IORESOURCE_MEM, "rc")) + return -EINVAL; + for (i = 0; i < ARRAY_SIZE(names); i++) { + snprintf(name, sizeof(name), names[i], port->idx); + if (!platform_get_resource_byname(pdev, IORESOURCE_MEM, name)) + return -EINVAL; + } + + if (!(readl(pcie->base + CORE_RC_CTL) & CORE_RC_CTL_RUN) || + !(readl(pcie->base + CORE_RC_STAT) & CORE_RC_STAT_READY) || + !(apple_pcie_port_readl(port, PORT_STATUS) & PORT_STATUS_READY) || + (apple_pcie_port_readl(port, PORT_LINKSTS) & + (PORT_LINKSTS_UP | PORT_LINKSTS_BUSY)) != PORT_LINKSTS_UP || + !(apple_pcie_port_readl(port, PORT_LTSSMCTL) & PORT_LTSSMCTL_START) || + (apple_pcie_port_readl(port, PORT_APPCLK) & 0x101) != 0x101 || + (apple_pcie_port_readl(port, pcie->hw->port_perst) & 0x10001) != 1 || + (readl(port->phy + PHY_LANE_CFG) & 0x1000061f) != 0x60f) + return -ENOLINK; + + /* The bootloader numbered the root port's buses: primary 0, one secondary. */ + rp = pcie->cfg->win + (PCI_DEVFN(port->idx, 0) << 12); + value = readl(rp + PCI_PRIMARY_BUS); + bus = (value >> 8) & 0xff; + if (readl(rp) != 0x100c106b || (value & 0xff) != 0 || !bus || + ((value >> 16) & 0xff) != bus || bus > pcie->cfg->busr.end || + (readw(rp + PCI_BRIDGE_CONTROL) & PCI_BRIDGE_CTL_BUS_RESET)) + return -ENODEV; + ep = pcie->cfg->win + (bus << 20); + value = readl(ep); + if (value == 0xffffffff || !(value & 0xffff) || + (readb(ep + PCI_HEADER_TYPE) & 0x7f) != PCI_HEADER_TYPE_NORMAL || + (readw(ep + PCI_COMMAND) & (PCI_COMMAND_IO | PCI_COMMAND_MEMORY | + PCI_COMMAND_MASTER))) + return -ENODEV; + + port->adopted_rid = bus << 8; + ret = of_map_iommu_id(pcie->dev->of_node, port->adopted_rid, &iommu); + if (ret) + return ret; + ret = iommu.args_count == 1 && iommu.args[0] && + iommu.args[0] < pcie->hw->max_rid2sid && + of_device_is_compatible(iommu.np, "apple,t6050-dart") ? 0 : -EINVAL; + port->adopted_sid = iommu.args[0]; + of_node_put(iommu.np); + if (ret) + return ret; + + snprintf(name, sizeof(name), "intr2axi%d", port->idx); + port->intr2axi = devm_platform_ioremap_resource_byname(pdev, name); + if (IS_ERR(port->intr2axi)) + return PTR_ERR(port->intr2axi); + + /* The currently qualified handoff is Gen3 x1, with no retrain in progress. */ + cap = readb(rp + PCI_CAPABILITY_LIST); + for (i = 0; cap && i < 48; i++) { + if (cap < 0x40 || (cap & 3)) + return -EINVAL; + if (readb(rp + cap + PCI_CAP_LIST_ID) == PCI_CAP_ID_EXP) + break; + cap = readb(rp + cap + PCI_CAP_LIST_NEXT); + } + if (!cap || i == 48) + return -EINVAL; + value = readw(rp + cap + PCI_EXP_LNKSTA); + if ((value & (PCI_EXP_LNKSTA_CLS | PCI_EXP_LNKSTA_NLW | PCI_EXP_LNKSTA_LT)) != + (PCI_EXP_LNKSTA_CLS_8_0GB | (1 << PCI_EXP_LNKSTA_NLW_SHIFT))) + return -ENOLINK; + + /* Refuse an active endpoint or malformed capability chain. */ + cap = readb(ep + PCI_CAPABILITY_LIST); + for (i = 0; cap && i < 48; i++) { + if (cap < 0x40 || (cap & 3)) + return -EINVAL; + value = readw(ep + cap + PCI_MSI_FLAGS); + switch (readb(ep + cap + PCI_CAP_LIST_ID)) { + case PCI_CAP_ID_MSI: + if (value & PCI_MSI_FLAGS_ENABLE) + return -EBUSY; + msi = true; + break; + case PCI_CAP_ID_MSIX: + if (value & PCI_MSIX_FLAGS_ENABLE) + return -EBUSY; + msi = true; + break; + } + cap = readb(ep + cap + PCI_CAP_LIST_NEXT); + } + if (cap || !msi) + return -EINVAL; + + for (i = 0; i < pcie->hw->max_rid2sid; i++) + if (apple_pcie_port_readl(port, port_rid2sid_offset(port, i)) & + PORT_RID2SID_VALID) + return -EBUSY; + + /* The route goes in the slot whose index is the SID, as on J775D's adopted ports. */ + value = T6050_ROUTE(port->adopted_rid, port->adopted_sid); + port->adopted_route = true; + if (apple_pcie_rid2sid_write(port, port->adopted_sid, value) != value) + return -EIO; + dev_info(pcie->dev, "adopted port %d, RID %04x -> SID %u, slot %u\n", + port->idx, port->adopted_rid, port->adopted_sid, port->adopted_sid); + return 0; +} + static int apple_pcie_setup_link(struct apple_pcie *pcie, struct apple_pcie_port *port, struct device_node *np) @@ -1881,6 +2072,10 @@ static int apple_pcie_setup_port(struct apple_pcie *pcie, goto err_teardown; } } + } else if (pcie->hw->adopt_link) { + ret = apple_pcie_adopt_link(port); + if (ret) + goto err_teardown; } else { if (pcie->hw->retain_clocks) rmw_set(PORT_APPCLK_EN, port->base + PORT_APPCLK); @@ -1922,9 +2117,8 @@ static int apple_pcie_setup_port(struct apple_pcie *pcie, if (ret) goto err_teardown; - if (pcie->managed_sids) { + if (pcie->managed_sids || pcie->hw->adopt_link) goto rid2sid_ready; - } /* Reset all RID/SID mappings, and check for RAZ/WI registers */ for (i = 0; i < pcie->hw->max_rid2sid; i++) { @@ -1949,6 +2143,10 @@ static int apple_pcie_setup_port(struct apple_pcie *pcie, unsigned long timeout, left = 0; int attempt, attempts = 1; + if (pcie->hw->adopt_link) { + ret = -ENOLINK; + goto err_teardown; + } if (pcie->hw->tunneled) { if (pcie->kernel_init) { apple_pcie_tunnel_apply_tunable(port->base, @@ -2095,6 +2293,17 @@ static int apple_msi_init(struct apple_pcie *pcie) return ret; } + if (pcie->hw->adopt_link) { + ret = of_property_read_u32(to_of_node(fwnode), "apple,msi-vector-base", + &pcie->msi_vector_base); + if (ret || pcie->msi_vector_base != 64 || + (pcie->nvecs != 32 && pcie->nvecs != 64) || + args.args_count != 4 || args.args[0] != 0 || args.args[1] != 0 || + args.args[2] != 1888 || args.args[3] != IRQ_TYPE_EDGE_RISING || + DOORBELL_ADDR != 0xfffff000) + goto invalid_slice; + } + /* * The controller-wide ADT count (512) is not a per-host allocation. * Internal PCIe owns vectors 0..31. Each PCIe-C bridge owns a distinct @@ -2218,7 +2427,7 @@ static int apple_msi_init(struct apple_pcie *pcie) invalid_slice: of_node_put(args.np); return dev_err_probe(pcie->dev, -EINVAL, - "invalid or missing PCIe-C MSI slice\n"); + "invalid or missing MSI slice\n"); } static void apple_pcie_cleanup(void *data) @@ -2419,6 +2628,11 @@ static int apple_pcie_enable_device(struct pci_host_bridge *bridge, struct pci_d of_node_put(iommu_spec.np); sid = iommu_spec.args[0]; + if (pcie->hw->adopt_link) + return rid == port->adopted_rid && sid == port->adopted_sid && + !READ_ONCE(port->link_failed) && + apple_pcie_port_readl(port, port_rid2sid_offset(port, sid)) == + T6050_ROUTE(rid, sid) ? 0 : -ENODEV; mutex_lock(&port->pcie->lock); if (port->pcie->managed_sids || port->pcie->hw->sid_indexed_rid2sid) { @@ -2485,7 +2699,7 @@ static void apple_pcie_disable_device(struct pci_host_bridge *bridge, struct pci int idx; port = apple_pcie_get_port(pdev); - if (!port) + if (!port || port->pcie->hw->adopt_link) return; mutex_lock(&port->pcie->lock); @@ -2526,9 +2740,13 @@ static int apple_pcie_init(struct pci_config_window *cfg) ret = apple_pcie_setup_port(pcie, of_port); if (ret) { dev_err(dev, "Port %pOF setup fail: %d\n", of_port, ret); - return ret; + /* Adopted ports are independent: one refused leaves the others. */ + if (!pcie->hw->adopt_link) + return ret; } } + if (pcie->hw->adopt_link && list_empty(&pcie->ports)) + return -ENODEV; /* The bus scan follows this callback. */ apple_pcie_tunnel_wait_ready(pcie); @@ -2548,6 +2766,27 @@ static void __iomem *apple_pcie_map_bus(struct pci_bus *bus, struct apple_pcie *pcie = apple_pcie_lookup(cfg->parent); struct apple_pcie_port *port; + if (pcie->hw->adopt_link) { + /* Only adopted ports' root ports, and the one endpoint behind each. */ + list_for_each_entry(port, &pcie->ports, entry) { + if (bus->number == 0) { + if (devfn == PCI_DEVFN(port->idx, 0)) + return pci_ecam_map_bus(bus, devfn, where); + continue; + } + if (bus->number != port->adopted_rid >> 8) + continue; + if (devfn || READ_ONCE(port->link_failed)) + return NULL; + if ((apple_pcie_port_readl(port, PORT_LINKSTS) & + (PORT_LINKSTS_UP | PORT_LINKSTS_BUSY)) != PORT_LINKSTS_UP) { + WRITE_ONCE(port->link_failed, true); + return NULL; + } + return pci_ecam_map_bus(bus, devfn, where); + } + return NULL; + } if (!pcie->hw->root_bus_only) return pci_ecam_map_bus(bus, devfn, where); @@ -3121,7 +3360,22 @@ static int apple_pcie_probe(struct platform_device *pdev) * reset-gpios provider here rejects the valid synthesized port before * the tunneled setup path can consume that handoff. */ - if (!hw->tunneled) { + if (hw->adopt_link) { + if (!of_get_available_child_count(dev->of_node) || + of_property_present(dev->of_node, "power-domains")) + return -EINVAL; + for_each_available_child_of_node_scoped(dev->of_node, child) { + u32 reg; + + /* A root port 00:.0 on bus 0, n a GP port */ + if (of_property_read_u32(child, "reg", ®) || reg & ~0xf800 || + !(reg >> 11) || (reg >> 11) > 3 || + !of_property_read_bool(child, "apple,link-trained-by-bootloader") || + of_property_present(child, "reset-gpios") || + of_property_present(child, "pwren-gpios")) + return -EINVAL; + } + } else 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); @@ -3164,15 +3418,15 @@ static int apple_pcie_probe(struct platform_device *pdev) return ret; } pcie->piodma_supplier = piodma_supplier; - /* Only this retained-DMA radio hierarchy forbids user reset/removal. */ - if (piodma_supplier || pcie->managed_sids) { + /* Firmware-owned links cannot be recovered by user reset/removal. */ + if (piodma_supplier || pcie->managed_sids || hw->adopt_link) { bridge->no_user_remove = true; bridge->reset_sensitive = true; bridge->native_pcie_hotplug = false; bridge->native_shpc_hotplug = false; bridge->native_aer = false; bridge->native_dpc = false; - dev_dbg(dev, "radio host: automatic AER/DPC recovery disabled for retained DMA\n"); + dev_dbg(dev, "firmware-owned host: automatic AER/DPC recovery disabled\n"); } if (hw->root_bus_only && !piodma_supplier) dev_info(dev, "root-port probe only; downstream config is blocked\n"); @@ -3952,6 +4206,8 @@ static int apple_pcie_suspend_noirq(struct device *dev) */ if (pcie->managed_sids) return pm_suspend_target_state == PM_SUSPEND_TO_IDLE ? 0 : -EBUSY; + if (pcie->hw->adopt_link) + return -EBUSY; if (!pcie->hw->tunneled) return 0; pcie->reset_on_resume = false; @@ -4095,6 +4351,7 @@ static const struct of_device_id apple_pcie_of_match[] = { { .compatible = "apple,t8152-pcie", .data = &t8152_hw }, { .compatible = "apple,t6020-pcie", .data = &t602x_hw }, { .compatible = "apple,t8140-pcie", .data = &t8140_hw }, + { .compatible = "apple,t6050-pcie", .data = &t6050_hw }, { .compatible = "apple,pcie", .data = &t8103_hw }, { } }; From 3bb54de2de84878e43f60f4192e25ae5a702700e Mon Sep 17 00:00:00 2001 From: Dj Date: Mon, 26 Oct 2026 02:34:16 -0400 Subject: [PATCH 03/43] PCI: Add pci_trylock_rescan_remove() A driver's work item that rescans or resets part of a bus takes the rescan/remove lock. When the device is being removed, the remover holds that lock while it cancels the work, so a worker that blocks on the lock deadlocks against its own cancellation. Add pci_trylock_rescan_remove(), which takes the rescan/remove lock only if it is free. A worker that cannot get it returns and retries later, and the remover's cancellation completes. Signed-off-by: Dj Developed with Claude Code. Signed-off-by: Erik Bourget --- drivers/pci/probe.c | 7 +++++++ include/linux/pci.h | 1 + 2 files changed, 8 insertions(+) diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c index 27008e2ea5afc..d625dd5e778a8 100644 --- a/drivers/pci/probe.c +++ b/drivers/pci/probe.c @@ -3517,6 +3517,13 @@ void pci_lock_rescan_remove(void) } EXPORT_SYMBOL_GPL(pci_lock_rescan_remove); +/* Work cancelled by removal must not wait on the remover's own lock. */ +bool pci_trylock_rescan_remove(void) +{ + return mutex_trylock(&pci_rescan_remove_lock); +} +EXPORT_SYMBOL_GPL(pci_trylock_rescan_remove); + void pci_unlock_rescan_remove(void) { mutex_unlock(&pci_rescan_remove_lock); diff --git a/include/linux/pci.h b/include/linux/pci.h index 5cec6d838b3f7..c0d76d93a78f5 100644 --- a/include/linux/pci.h +++ b/include/linux/pci.h @@ -1559,6 +1559,7 @@ void set_pcie_hotplug_bridge(struct pci_dev *pdev); /* Functions for PCI Hotplug drivers to use */ unsigned int pci_rescan_bus(struct pci_bus *bus); void pci_lock_rescan_remove(void); +bool pci_trylock_rescan_remove(void); void pci_unlock_rescan_remove(void); /* Vital Product Data routines */ From d3ea57796c57270272dd218f2e5ba182158d438f Mon Sep 17 00:00:00 2001 From: Erik Bourget Date: Wed, 4 Nov 2026 20:45:53 -0500 Subject: [PATCH 04/43] PCI: apple: Add the T6050 port reset, enable, configure and train sequences An adopted T6050 port is left as the boot loader trained it, but an endpoint that has to come back on a retrained link (the N1 on port 0, after its ROM boot) needs the host to take the port down and bring it up again. Add the sequences for that, on the port's own register blocks (port, phy and intr2axi) and the root port's config space: - Disable: wait for the link to leave L1 and go idle, assert the port's PERST# through its handshake, then stop the lane clocks and the port's application clock. - Enable: the port reset table, which also clears the RID2SID and MSI map tables, the port's tunables, then the application clock and the PERST# release handshake, until the port is ready and its link idle. - Configure: with the root complex's read-only registers opened, its tunables and the per-rate shadow tunables, then a Gen3 x1 link (target speed, link width and capability) and a directed speed change; Intr2AXI enabled. - Train: the lane's reference clocks, the endpoint's PERST# released through its pad, the LTSSM started, then the 100 ms wait after link up. The tunables are the lists the boot loader copies onto the root port node, groups of (offset, 4, mask, value); a list it did not copy is skipped. The root-port reset that runs the sequences follows; until then they are marked __maybe_unused. Co-developed-by: Dj Signed-off-by: Dj Developed with Claude Code. Signed-off-by: Erik Bourget --- drivers/pci/controller/pcie-apple.c | 340 ++++++++++++++++++++++++++++ 1 file changed, 340 insertions(+) diff --git a/drivers/pci/controller/pcie-apple.c b/drivers/pci/controller/pcie-apple.c index b0a4a549cdd9e..521bc1738ae39 100644 --- a/drivers/pci/controller/pcie-apple.c +++ b/drivers/pci/controller/pcie-apple.c @@ -186,6 +186,20 @@ MODULE_PARM_DESC(link_up_timeout, "PCIe link training timeout in milliseconds"); #define PORT_MSIMAP_ENABLE BIT(31) #define PORT_MSIMAP_TARGET GENMASK(7, 0) +/* T6050: the root port's port logic, laid out as DesignWare's */ +#define RC_T6050_PORT_LINK_CTRL 0x710 +#define RC_T6050_LINK_MODE GENMASK(21, 16) +#define RC_T6050_PORT_DEBUG0 0x728 +#define RC_T6050_LTSSM_STATE GENMASK(5, 0) +#define RC_T6050_LTSSM_L1_IDLE 0x14 +#define RC_T6050_LINK_WIDTH_SPEED_CTRL 0x80c +#define RC_T6050_LINK_WIDTH GENMASK(12, 8) +#define RC_T6050_SPEED_CHANGE BIT(17) +#define RC_T6050_GEN3_RELATED 0x890 +#define RC_T6050_RATE_SHADOW_SEL GENMASK(25, 24) +#define RC_T6050_MISC_CONTROL_1 0x8bc +#define RC_T6050_DBI_RO_WR_EN BIT(0) + /* * The doorbell address must be in the bottom 4GB because the base register * is only 32 bits wide. Exclude it from the IOVA range and share the same @@ -479,6 +493,7 @@ struct apple_pcie_port { void __iomem *intr2axi; /* adopt_link: this port's */ u16 adopted_rid; /* adopt_link: secondary bus << 8 */ u32 adopted_sid; + void __iomem *perst; /* adopt_link: the PERST# pad, for a root-port reset */ }; static int apple_pcie_managed_admit(struct apple_pcie_port *port); @@ -1786,6 +1801,331 @@ static int apple_pcie_adopt_link(struct apple_pcie_port *port) return 0; } +/* + * T6050 port bring-up, for an adopted port whose endpoint must come back on a + * retrained link: port disable, the port reset table, port enable, root complex + * configuration for Gen3 x1, then the lane PHY, PERST# and the LTSSM. The port's + * registers are its port, phy and intr2axi blocks; the root complex's + * are the root port's config space. The boot loader copies the port's tunables + * onto its node. + */ +static const struct apple_pcie_reset_reg apple_pcie_t6050_reset_regs[] = { + { 0x088, 0x00000110 }, + { 0x100, 0xffffffff }, + { 0x148, 0xffffffff }, + { 0x210, 0xffffffff }, + { 0x080, 0x00000000 }, + { 0x084, 0x00000000 }, + { 0x104, 0xfffffff0 }, + { 0x124, 0x00000100 }, + { 0x16c, 0x00000000 }, + { 0x13c, 0x00000000 }, + { 0x800, 0x00100100 }, + { 0x808, 0x001001ff }, + { 0x82c, 0x00010000 }, +}; + +/* Written after the RID2SID and MSI map tables are cleared */ +static const struct apple_pcie_reset_reg apple_pcie_t6050_reset_regs_late[] = { + { 0x130, 0x03020000 }, + { 0x140, 0x00000010 }, + { 0x144, 0x00253770 }, + { 0x21c, 0x00000000 }, + { 0x834, 0x00000000 }, + { 0x83c, 0x00000000 }, +}; + +static void apple_pcie_t6050_rmw(void __iomem *addr, u32 clear, u32 set) +{ + writel((readl(addr) & ~clear) | set, addr); +} + +static int apple_pcie_t6050_rc_rmw(struct pci_dev *rp, int where, u32 clear, u32 set) +{ + u32 value; + int ret; + + ret = pci_read_config_dword(rp, where, &value); + if (!ret) + ret = pci_write_config_dword(rp, where, (value & ~clear) | set); + return pcibios_err_to_errno(ret); +} + +static int apple_pcie_t6050_wait(struct apple_pcie_port *port, void __iomem *base, + u32 offset, u32 mask, u32 value, unsigned int timeout_us) +{ + u32 stat; + int ret; + + ret = readl_poll_timeout(base + offset, stat, (stat & mask) == value, 1, timeout_us); + if (ret) + dev_err(port->pcie->dev, "port %d: %#x = %#x, waiting for %#x/%#x\n", + port->idx, offset, stat, value, mask); + return ret; +} + +/* + * A tunable list the boot loader copies onto the root port node, groups of + * (offset, 4, mask, value): for the port's registers, or with @rp for the root + * port's config space. A list it did not copy is skipped. + */ +static int apple_pcie_t6050_tune(struct apple_pcie_port *port, struct pci_dev *rp, + const char *name) +{ + u32 cells[64]; + int count, ret, i; + + if (!of_property_present(port->np, name)) + return 0; + count = of_property_count_u32_elems(port->np, name); + if (count <= 0 || count > ARRAY_SIZE(cells) || count % 4) + return -EINVAL; + ret = of_property_read_u32_array(port->np, name, cells, count); + if (ret) + return ret; + for (i = 0; i < count; i += 4) + if (cells[i + 1] != 4 || (cells[i] & 3) || + cells[i] >= (rp ? PCI_CFG_SPACE_EXP_SIZE : SZ_16K)) + return -EINVAL; + + for (i = 0; i < count; i += 4) { + if (!rp) { + apple_pcie_t6050_rmw(port->base + cells[i], cells[i + 2], cells[i + 3]); + continue; + } + ret = apple_pcie_t6050_rc_rmw(rp, cells[i], cells[i + 2], cells[i + 3]); + if (ret) + return ret; + } + return 0; +} + +/* PERST# through its pad: a GPIO output (function bits cleared) at the level asked for */ +static int apple_pcie_t6050_perst(struct apple_pcie_port *port, bool asserted) +{ + u32 old, value; + + old = readl(port->perst); + if (old & BIT(21)) + return -EIO; + value = (old & ~0x27f) | 0x202 | !asserted; + /* The input enable (bit 9) goes on after the rest */ + writel((value & ~0x200) | (old & 0x200), port->perst); + if (!(old & 0x200)) + writel(value, port->perst); + return (readl(port->perst) ^ value) & 0x27f ? -EIO : 0; +} + +/* Let the link go idle, hold the endpoint in reset and stop the port and its lane clocks */ +static int apple_pcie_t6050_port_disable(struct apple_pcie_port *port, struct pci_dev *rp) +{ + void __iomem *perst = port->base + PORT_T602X_PERST; + void __iomem *lane = port->phy + PHY_LANE_CFG; + u32 stat; + int ret, err; + + if (!(readl(port->base + PORT_STATUS) & BIT(2))) { + ret = apple_pcie_t6050_wait(port, port->base, 0x820, BIT(2), BIT(2), 10000); + if (ret) + return ret; + } + ret = readl_poll_timeout_atomic(port->base + 0x820, stat, + !(stat & GENMASK(8, 0)) || !(stat & GENMASK(23, 16)), + 1, 10000); + if (ret) + return ret; + apple_pcie_t6050_rmw(port->base + 0x13c, 0, BIT(8)); + + /* The link out of L1 */ + ret = read_poll_timeout_atomic(pci_read_config_dword, err, + err || FIELD_GET(RC_T6050_LTSSM_STATE, stat) != + RC_T6050_LTSSM_L1_IDLE, + 5, 5000, false, rp, RC_T6050_PORT_DEBUG0, &stat); + if (ret || err) + return ret ?: pcibios_err_to_errno(err); + + ret = apple_pcie_t6050_wait(port, port->base, 0xa8, BIT(0), BIT(0), 10000); + if (ret) + return ret; + apple_pcie_t6050_rmw(perst, 0, BIT(24)); + ret = apple_pcie_t6050_wait(port, port->base, 0xa8, BIT(16), BIT(16), 2000); + if (ret) + return ret; + apple_pcie_t6050_rmw(perst, 0, BIT(16)); + udelay(1); + apple_pcie_t6050_rmw(perst, PORT_PERST_OFF, 0); + udelay(1); + apple_pcie_t6050_rmw(perst, BIT(24), 0); + apple_pcie_t6050_rmw(port->base + 0x13c, BIT(8), 0); + + apple_pcie_t6050_rmw(lane, BIT(31), 0); + apple_pcie_t6050_rmw(lane, BIT(10), 0); + apple_pcie_t6050_rmw(lane, BIT(9), 0); + apple_pcie_t6050_rmw(lane, 0, BIT(4)); + apple_pcie_t6050_rmw(lane, PHY_LANE_CFG_REFCLK0REQ, 0); + apple_pcie_t6050_rmw(lane, PHY_LANE_CFG_REFCLK1REQ, 0); + + writel(~0, port->base + PORT_INTSTAT); + apple_pcie_t6050_rmw(port->base + PORT_APPCLK, PORT_APPCLK_EN, 0); + return apple_pcie_t6050_wait(port, port->base, PORT_STATUS, PORT_STATUS_READY, 0, + 250000); +} + +/* The port reset table, then the port clocked and out of reset, ready to train */ +static int apple_pcie_t6050_port_enable(struct apple_pcie_port *port) +{ + void __iomem *perst = port->base + PORT_T602X_PERST; + u32 stat; + int ret, i; + + for (i = 0; i < ARRAY_SIZE(apple_pcie_t6050_reset_regs); i++) + writel(apple_pcie_t6050_reset_regs[i].value, + port->base + apple_pcie_t6050_reset_regs[i].offset); + for (i = 0; i < port->pcie->hw->max_rid2sid; i++) + writel(0, port->base + port_rid2sid_offset(port, i)); + for (i = 0; i < 256; i++) + writel(0, port->base + PORT_T602X_MSIMAP + 4 * i); + for (i = 0; i < ARRAY_SIZE(apple_pcie_t6050_reset_regs_late); i++) + writel(apple_pcie_t6050_reset_regs_late[i].value, + port->base + apple_pcie_t6050_reset_regs_late[i].offset); + + if (!(readl(perst) & BIT(16))) + return -EIO; + ret = apple_pcie_t6050_tune(port, NULL, "apple,apcie-config-tunables"); + if (ret) + return ret; + apple_pcie_t6050_rmw(port->base + PORT_APPCLK, 0, PORT_APPCLK_EN); + + ret = readl_poll_timeout_atomic(port->base + 0xa8, stat, + (stat & (BIT(16) | BIT(0))) != (BIT(16) | BIT(0)), + 1, 10000); + if (ret) + return ret; + apple_pcie_t6050_rmw(perst, 0, PORT_PERST_OFF); + ret = apple_pcie_t6050_wait(port, port->base, 0xa8, BIT(0), BIT(0), 20000); + if (ret) + return ret; + apple_pcie_t6050_rmw(perst, BIT(16), 0); + ret = apple_pcie_t6050_wait(port, port->base, PORT_STATUS, PORT_STATUS_READY, + PORT_STATUS_READY, 250000); + if (ret) + return ret; + + apple_pcie_t6050_rmw(port->base + PORT_APPCLK, 0, PORT_APPCLK_CGDIS); + return apple_pcie_t6050_wait(port, port->base, PORT_LINKSTS, PORT_LINKSTS_BUSY, 0, + 250000); +} + +/* Root complex tunables and a Gen3 x1 link, with the read-only registers opened for it */ +static int apple_pcie_t6050_port_configure(struct apple_pcie_port *port, struct pci_dev *rp) +{ + static const char * const shadow[] = { + "apple,pcie-rc-gen3-shadow-tunables", + "apple,pcie-rc-gen4-shadow-tunables", + "apple,pcie-rc-gen5-shadow-tunables", + }; + static const u32 shadow_sel[] = { 0, BIT(24), 0 }; + int cap = pci_pcie_cap(rp); + u32 value; + u16 ctl2; + int ret, err, i; + + if (!cap) + return -EIO; + ret = apple_pcie_t6050_rc_rmw(rp, RC_T6050_MISC_CONTROL_1, 0, RC_T6050_DBI_RO_WR_EN); + if (ret) + return ret; + ret = apple_pcie_t6050_tune(port, rp, "apple,pcie-rc-tunables"); + if (ret) + goto lock; + for (i = 0; i < ARRAY_SIZE(shadow); i++) { + ret = apple_pcie_t6050_rc_rmw(rp, RC_T6050_GEN3_RELATED, + RC_T6050_RATE_SHADOW_SEL, shadow_sel[i]) ?: + apple_pcie_t6050_tune(port, rp, shadow[i]); + if (ret) + goto lock; + } + ret = apple_pcie_t6050_rc_rmw(rp, 0x194, 0, BIT(0)); + if (ret) + goto lock; + + ret = pcibios_err_to_errno(pci_read_config_dword(rp, cap + PCI_EXP_LNKCAP2, &value)); + if (ret) + goto lock; + if (!(value & PCI_EXP_LNKCAP2_SLS_8_0GB)) { + ret = -EIO; + goto lock; + } + ret = pcibios_err_to_errno(pci_read_config_word(rp, cap + PCI_EXP_LNKCTL2, &ctl2)) ?: + pcibios_err_to_errno(pci_write_config_word(rp, cap + PCI_EXP_LNKCTL2, + (ctl2 & ~PCI_EXP_LNKCTL2_TLS) | + PCI_EXP_LNKCTL2_TLS_8_0GT)) ?: + apple_pcie_t6050_rc_rmw(rp, RC_T6050_PORT_LINK_CTRL, RC_T6050_LINK_MODE, + FIELD_PREP(RC_T6050_LINK_MODE, 1)) ?: + apple_pcie_t6050_rc_rmw(rp, RC_T6050_LINK_WIDTH_SPEED_CTRL, RC_T6050_LINK_WIDTH, + FIELD_PREP(RC_T6050_LINK_WIDTH, 1)) ?: + apple_pcie_t6050_rc_rmw(rp, cap + PCI_EXP_LNKCAP, PCI_EXP_LNKCAP_MLW, + FIELD_PREP(PCI_EXP_LNKCAP_MLW, 1)); +lock: + err = apple_pcie_t6050_rc_rmw(rp, RC_T6050_MISC_CONTROL_1, RC_T6050_DBI_RO_WR_EN, 0); + ret = ret ?: err; + if (ret) + return ret; + + ret = apple_pcie_t6050_rc_rmw(rp, cap + PCI_EXP_LNKCTL, 0, PCI_EXP_LNKCTL_LBMIE) ?: + apple_pcie_t6050_rc_rmw(rp, RC_T6050_LINK_WIDTH_SPEED_CTRL, 0, RC_T6050_SPEED_CHANGE); + if (ret) + return ret; + writel(PCIEC_INTR2AXI_ENABLE, port->intr2axi + PCIEC_INTR2AXI_CTRL); + return 0; +} + +/* Disable, reset, enable and configure an adopted port whose endpoint is held in PERST# */ +static int __maybe_unused apple_pcie_t6050_port_restart(struct apple_pcie_port *port, + struct pci_dev *rp) +{ + return apple_pcie_t6050_port_disable(port, rp) ?: + apple_pcie_t6050_port_enable(port) ?: + apple_pcie_t6050_port_configure(port, rp); +} + +/* Lane clocks, PERST# released, then the LTSSM to link up */ +static int __maybe_unused apple_pcie_t6050_port_train(struct apple_pcie_port *port) +{ + void __iomem *lane = port->phy + PHY_LANE_CFG; + int ret; + + apple_pcie_t6050_rmw(lane, 0, PHY_LANE_CFG_REFCLK0REQ); + ret = apple_pcie_t6050_wait(port, port->phy, PHY_LANE_CFG, PHY_LANE_CFG_REFCLK0ACK, + PHY_LANE_CFG_REFCLK0ACK, 500000); + if (ret) + return ret; + apple_pcie_t6050_rmw(lane, 0, PHY_LANE_CFG_REFCLK1REQ); + ret = apple_pcie_t6050_wait(port, port->phy, PHY_LANE_CFG, PHY_LANE_CFG_REFCLK1ACK, + PHY_LANE_CFG_REFCLK1ACK, 500000); + if (ret) + return ret; + apple_pcie_t6050_rmw(lane, BIT(4), 0); + udelay(1); + apple_pcie_t6050_rmw(lane, 0, BIT(9)); + apple_pcie_t6050_rmw(lane, 0, BIT(10)); + + /* Tperst-clk is at least 100 us (PCIe CEM r5.0, 2.9.2) */ + usleep_range(100, 110); + ret = apple_pcie_t6050_perst(port, false); + if (ret) + return ret; + apple_pcie_t6050_rmw(port->base + PORT_LTSSMCTL, 0, PORT_LTSSMCTL_START); + ret = apple_pcie_t6050_wait(port, port->base, PORT_LINKSTS, PORT_LINKSTS_UP, + PORT_LINKSTS_UP, 500000); + if (ret) + return ret; + + /* Wait for 100ms after the link trains (PCIe r5.0, 6.6.1) */ + msleep(100); + return 0; +} + static int apple_pcie_setup_link(struct apple_pcie *pcie, struct apple_pcie_port *port, struct device_node *np) From 554bacbb3baa639642aef86ae09c2adfe4e07f24 Mon Sep 17 00:00:00 2001 From: Jeff Queisser Date: Wed, 4 Nov 2026 11:59:40 -0800 Subject: [PATCH 05/43] PCI: apple: Adopt T6050 port 0's multi-function N1 and reset its root port The N1 Wi-Fi/Bluetooth sits on apcie0 port 0, which the bootloader now trains like ports 2 and 3. Two things the adoption assumed do not hold for it: - It is multi-function. In its ROM personality only function 0 answers; after its firmware starts, Control, Alpha and Beta (0-2) do. Each function's RID is routed to its own DART stream from the iommu-map (slot == SID) when it is enabled, instead of the one route set at adoption; config accesses on the adopted bus reach every function of device 0; teardown clears every slot it routed. Port 0 is accepted. - Its ROM links at Gen1. Adoption takes an x1 link at Gen1 to Gen3 (the bootloader's target), not only Gen3; the endpoint picks the speed. - After the ROM boot it must come back on a retrained link. The host provides reset_root_port for an adopted port that names its PERST# pad (perst): PME_Turn_Off to L2, PERST#, then the T6050 port disable, reset, enable, configure and train sequences, with the routes and the MSI map rewritten in between. Tunables the boot loader copied onto the root port node are applied; absent ones are skipped. Signed-off-by: Jeff Queisser Developed with Claude Code. Signed-off-by: Erik Bourget --- .../devicetree/bindings/pci/apple,pcie.yaml | 9 +- drivers/pci/controller/pcie-apple.c | 218 ++++++++++++++---- 2 files changed, 184 insertions(+), 43 deletions(-) diff --git a/Documentation/devicetree/bindings/pci/apple,pcie.yaml b/Documentation/devicetree/bindings/pci/apple,pcie.yaml index 5823cc0ff837a..2446facb91d3a 100644 --- a/Documentation/devicetree/bindings/pci/apple,pcie.yaml +++ b/Documentation/devicetree/bindings/pci/apple,pcie.yaml @@ -242,15 +242,16 @@ allOf: properties: reg: minItems: 5 - maxItems: 14 + maxItems: 18 reg-names: description: config and rc, then port, phy and intr2axi for each - bootloader-trained root port 00:.0. + bootloader-trained root port 00:.0, and perst (its PERST# + pad) for a port whose endpoint needs a root-port reset. minItems: 5 - maxItems: 14 + maxItems: 18 items: - pattern: "^(config|rc|(port|phy|intr2axi)[0-3])$" + pattern: "^(config|rc|(port|phy|intr2axi|perst)[0-3])$" interrupts: maxItems: 4 interrupt-names: diff --git a/drivers/pci/controller/pcie-apple.c b/drivers/pci/controller/pcie-apple.c index 521bc1738ae39..9a99347476bc4 100644 --- a/drivers/pci/controller/pcie-apple.c +++ b/drivers/pci/controller/pcie-apple.c @@ -489,14 +489,14 @@ struct apple_pcie_port { bool needs_stop; bool link_failed; bool irq_disabled; - bool adopted_route; void __iomem *intr2axi; /* adopt_link: this port's */ - u16 adopted_rid; /* adopt_link: secondary bus << 8 */ - u32 adopted_sid; + u8 adopted_bus; /* adopt_link: the secondary bus */ + unsigned long adopted_slots; /* adopt_link: RID2SID slots routed (slot == SID) */ void __iomem *perst; /* adopt_link: the PERST# pad, for a root-port reset */ }; static int apple_pcie_managed_admit(struct apple_pcie_port *port); +static struct apple_pcie *apple_pcie_lookup(struct device *dev); static int apple_pcie_radio_check_cold(struct apple_pcie_port *port); static void rmw_set(u32 set, void __iomem *addr) @@ -1585,9 +1585,12 @@ static void apple_pcie_port_teardown(struct apple_pcie_port *port) if (port->pcie->hw->adopt_link && port->domain) apple_pcie_port_writel(port, 0, PORT_MSICFG); - if (port->adopted_route) { - apple_pcie_rid2sid_write(port, port->adopted_sid, 0); - port->adopted_route = false; + if (port->adopted_slots) { + int slot; + + for_each_set_bit(slot, &port->adopted_slots, BITS_PER_LONG) + apple_pcie_rid2sid_write(port, slot, 0); + port->adopted_slots = 0; } if (port->pcie->hw->tunneled && (port->started || port->needs_stop)) @@ -1673,9 +1676,60 @@ static u32 apple_pcie_rid2sid_write(struct apple_pcie_port *port, return apple_pcie_port_readl(port, offset); } +/* + * The DART stream an adopted port's function `rid` maps to: one T6050 DART stream, nonzero + * and below the RID2SID table size (slot == SID), from the host's iommu-map. + */ +static int apple_pcie_adopted_sid(struct apple_pcie_port *port, u32 rid, u32 *sid) +{ + struct apple_pcie *pcie = port->pcie; + struct of_phandle_args iommu = {}; + int ret; + + if (rid >> 8 != port->adopted_bus) { + /* The PCI core numbers root ports' buses in devfn order: the bootloader must too */ + dev_err(pcie->dev, "port %d: RID %04x is off bus %02x, which the bootloader numbered\n", + port->idx, rid, port->adopted_bus); + return -ENODEV; + } + ret = of_map_iommu_id(pcie->dev->of_node, rid, &iommu); + if (ret) + return ret; + ret = iommu.args_count == 1 && iommu.args[0] && + iommu.args[0] < pcie->hw->max_rid2sid && + iommu.args[0] < BITS_PER_LONG && + of_device_is_compatible(iommu.np, "apple,t6050-dart") ? 0 : -EINVAL; + *sid = iommu.args[0]; + of_node_put(iommu.np); + return ret; +} + +/* Route an adopted port's function `rid` to its stream, or confirm the route is in place. */ +static int apple_pcie_adopted_route(struct apple_pcie_port *port, u32 rid) +{ + u32 sid, value, cur; + int ret = apple_pcie_adopted_sid(port, rid, &sid); + + if (ret) + return ret; + value = T6050_ROUTE(rid, sid); + cur = apple_pcie_port_readl(port, port_rid2sid_offset(port, sid)); + if (cur == value && test_bit(sid, &port->adopted_slots)) + return 0; + if (cur & PORT_RID2SID_VALID) + return -EBUSY; + set_bit(sid, &port->adopted_slots); + if (apple_pcie_rid2sid_write(port, sid, value) != value) + return -EIO; + dev_info(port->pcie->dev, "port %d: RID %04x -> SID %u, slot %u\n", + port->idx, rid, sid, sid); + return 0; +} + /* * Qualified J775D handoff of one bootloader-trained port: its root port - * 00:.0 and the single endpoint on its secondary bus, with DMA and + * 00:.0 and the endpoint on its secondary bus (function 0 routed here; a + * multi-function endpoint's other functions when they are enabled), with DMA and * endpoint MSI disabled. Every resource comes from the DT by the port's index * (port, phy, intr2axi, interrupt port, the iommu-map entry for the * endpoint's RID). All checks precede the first PCIe register write. @@ -1685,7 +1739,6 @@ static int apple_pcie_adopt_link(struct apple_pcie_port *port) static const char *const names[] = { "port%d", "phy%d", "intr2axi%d" }; struct apple_pcie *pcie = port->pcie; struct platform_device *pdev = to_platform_device(pcie->dev); - struct of_phandle_args iommu = {}; void __iomem *rp, *ep; char name[16]; u32 value, bus; @@ -1730,15 +1783,8 @@ static int apple_pcie_adopt_link(struct apple_pcie_port *port) PCI_COMMAND_MASTER))) return -ENODEV; - port->adopted_rid = bus << 8; - ret = of_map_iommu_id(pcie->dev->of_node, port->adopted_rid, &iommu); - if (ret) - return ret; - ret = iommu.args_count == 1 && iommu.args[0] && - iommu.args[0] < pcie->hw->max_rid2sid && - of_device_is_compatible(iommu.np, "apple,t6050-dart") ? 0 : -EINVAL; - port->adopted_sid = iommu.args[0]; - of_node_put(iommu.np); + port->adopted_bus = bus; + ret = apple_pcie_adopted_sid(port, bus << 8, &value); if (ret) return ret; @@ -1746,6 +1792,13 @@ static int apple_pcie_adopt_link(struct apple_pcie_port *port) port->intr2axi = devm_platform_ioremap_resource_byname(pdev, name); if (IS_ERR(port->intr2axi)) return PTR_ERR(port->intr2axi); + /* Only a port whose endpoint needs a root-port reset names its PERST# pad */ + snprintf(name, sizeof(name), "perst%d", port->idx); + if (platform_get_resource_byname(pdev, IORESOURCE_MEM, name)) { + port->perst = devm_platform_ioremap_resource_byname(pdev, name); + if (IS_ERR(port->perst)) + return PTR_ERR(port->perst); + } /* The currently qualified handoff is Gen3 x1, with no retrain in progress. */ cap = readb(rp + PCI_CAPABILITY_LIST); @@ -1758,9 +1811,14 @@ static int apple_pcie_adopt_link(struct apple_pcie_port *port) } if (!cap || i == 48) return -EINVAL; + /* + * x1 at up to the Gen3 target the bootloader set. The endpoint picks the speed: the N1's + * ROM personality links at Gen1 and comes back at Gen3 after its root-port reset. + */ value = readw(rp + cap + PCI_EXP_LNKSTA); - if ((value & (PCI_EXP_LNKSTA_CLS | PCI_EXP_LNKSTA_NLW | PCI_EXP_LNKSTA_LT)) != - (PCI_EXP_LNKSTA_CLS_8_0GB | (1 << PCI_EXP_LNKSTA_NLW_SHIFT))) + if ((value & (PCI_EXP_LNKSTA_NLW | PCI_EXP_LNKSTA_LT)) != (1 << PCI_EXP_LNKSTA_NLW_SHIFT) || + (value & PCI_EXP_LNKSTA_CLS) < PCI_EXP_LNKSTA_CLS_2_5GB || + (value & PCI_EXP_LNKSTA_CLS) > PCI_EXP_LNKSTA_CLS_8_0GB) return -ENOLINK; /* Refuse an active endpoint or malformed capability chain. */ @@ -1792,13 +1850,10 @@ static int apple_pcie_adopt_link(struct apple_pcie_port *port) return -EBUSY; /* The route goes in the slot whose index is the SID, as on J775D's adopted ports. */ - value = T6050_ROUTE(port->adopted_rid, port->adopted_sid); - port->adopted_route = true; - if (apple_pcie_rid2sid_write(port, port->adopted_sid, value) != value) - return -EIO; - dev_info(pcie->dev, "adopted port %d, RID %04x -> SID %u, slot %u\n", - port->idx, port->adopted_rid, port->adopted_sid, port->adopted_sid); - return 0; + ret = apple_pcie_adopted_route(port, bus << 8); + if (!ret) + dev_info(pcie->dev, "adopted port %d, endpoint %02x:00\n", port->idx, bus); + return ret; } /* @@ -2081,8 +2136,7 @@ static int apple_pcie_t6050_port_configure(struct apple_pcie_port *port, struct } /* Disable, reset, enable and configure an adopted port whose endpoint is held in PERST# */ -static int __maybe_unused apple_pcie_t6050_port_restart(struct apple_pcie_port *port, - struct pci_dev *rp) +static int apple_pcie_t6050_port_restart(struct apple_pcie_port *port, struct pci_dev *rp) { return apple_pcie_t6050_port_disable(port, rp) ?: apple_pcie_t6050_port_enable(port) ?: @@ -2090,7 +2144,7 @@ static int __maybe_unused apple_pcie_t6050_port_restart(struct apple_pcie_port * } /* Lane clocks, PERST# released, then the LTSSM to link up */ -static int __maybe_unused apple_pcie_t6050_port_train(struct apple_pcie_port *port) +static int apple_pcie_t6050_port_train(struct apple_pcie_port *port) { void __iomem *lane = port->phy + PHY_LANE_CFG; int ret; @@ -2126,6 +2180,87 @@ static int __maybe_unused apple_pcie_t6050_port_train(struct apple_pcie_port *po return 0; } +/* + * Root-port reset of an adopted T6050 port, for an endpoint that must come back on a + * retrained link (the N1 after its ROM boot): PME_Turn_Off to L2, PERST# asserted, the + * port disabled, reset, enabled and configured, its routes and MSI map rewritten, then the + * lane PHY, PERST# release and LTSSM to a Gen3 x1 link. The caller has quiesced the + * endpoint and saved the root port's state; the PCI core restores it. + */ +static int apple_pcie_t6050_reset_root_port(struct pci_host_bridge *bridge, + struct pci_dev *rp) +{ + struct pci_config_window *cfg = rp->sysdata; + struct apple_pcie *pcie = apple_pcie_lookup(cfg->parent); + struct apple_pcie_port *port = NULL, *p; + u32 routes[BITS_PER_LONG], value; + int ret, slot, i; + + if (!pcie || !pcie->hw->adopt_link || !pci_is_root_bus(rp->bus)) + return -EOPNOTSUPP; + list_for_each_entry(p, &pcie->ports, entry) + if (p->idx == PCI_SLOT(rp->devfn)) + port = p; + if (!port || !port->perst || !port->intr2axi) + return -EOPNOTSUPP; + + mutex_lock(&pcie->lock); + for_each_set_bit(slot, &port->adopted_slots, BITS_PER_LONG) + routes[slot] = apple_pcie_port_readl(port, port_rid2sid_offset(port, slot)); + + /* Bridge decoding off and the secondary bus unnumbered while the link is down */ + ret = pcibios_err_to_errno(pci_write_config_word(rp, PCI_COMMAND, 0)) ?: + pcibios_err_to_errno(pci_read_config_dword(rp, PCI_PRIMARY_BUS, &value)) ?: + pcibios_err_to_errno(pci_write_config_dword(rp, PCI_PRIMARY_BUS, + value & 0xff0000ff)); + if (ret) + goto out; + /* PME_Turn_Off, then L2 */ + apple_pcie_port_writel(port, apple_pcie_port_readl(port, PORT_APPCLK) | 0x100, PORT_APPCLK); + apple_pcie_port_writel(port, 0x11, 0x88); + ret = apple_pcie_t6050_wait(port, port->base, 0x88, 1, 0, 500000) ?: + apple_pcie_t6050_wait(port, port->base, PORT_LINKSTS, BIT(6), BIT(6), 500000); + if (ret) + goto out; + WRITE_ONCE(port->link_failed, true); + ret = apple_pcie_t6050_perst(port, true); + if (ret) + goto out; + writel(readl(port->phy) & ~BIT(31), port->phy); + ret = apple_pcie_t6050_port_restart(port, rp); + if (ret) + goto out; + + /* The reset table cleared the routes and the MSI setup: put back what Linux set */ + for_each_set_bit(slot, &port->adopted_slots, BITS_PER_LONG) + if (apple_pcie_rid2sid_write(port, slot, routes[slot]) != routes[slot]) + ret = -EIO; + 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); + for (i = 0; i < pcie->nvecs; i++) + apple_pcie_port_writel(port, FIELD_PREP(PORT_MSIMAP_TARGET, pcie->msi_vector_base + i) | + PORT_MSIMAP_ENABLE, pcie->hw->port_msimap + 4 * i); + writel(PCIEC_INTR2AXI_ENABLE, port->intr2axi + PCIEC_INTR2AXI_CTRL); + readl(port->intr2axi + 0x84); + apple_pcie_port_writel(port, PORT_MSICFG_EN, PORT_MSICFG); + if (ret) + goto out; + + ret = apple_pcie_t6050_port_train(port); + if (ret) + goto out; + /* Link interrupts stay masked after the reset table: the link is checked on access */ + apple_pcie_port_writel(port, ~0, PORT_INTSTAT); + WRITE_ONCE(port->link_failed, false); + dev_info(pcie->dev, "port %d: link retrained after a root-port reset\n", port->idx); +out: + mutex_unlock(&pcie->lock); + if (ret) + dev_err(pcie->dev, "port %d: root-port reset stopped: %d\n", port->idx, ret); + return ret; +} + static int apple_pcie_setup_link(struct apple_pcie *pcie, struct apple_pcie_port *port, struct device_node *np) @@ -2968,11 +3103,14 @@ static int apple_pcie_enable_device(struct pci_host_bridge *bridge, struct pci_d of_node_put(iommu_spec.np); sid = iommu_spec.args[0]; - if (pcie->hw->adopt_link) - return rid == port->adopted_rid && sid == port->adopted_sid && - !READ_ONCE(port->link_failed) && - apple_pcie_port_readl(port, port_rid2sid_offset(port, sid)) == - T6050_ROUTE(rid, sid) ? 0 : -ENODEV; + if (pcie->hw->adopt_link) { + if (READ_ONCE(port->link_failed)) + return -ENODEV; + mutex_lock(&pcie->lock); + err = apple_pcie_adopted_route(port, rid); + mutex_unlock(&pcie->lock); + return err; + } mutex_lock(&port->pcie->lock); if (port->pcie->managed_sids || port->pcie->hw->sid_indexed_rid2sid) { @@ -3107,16 +3245,16 @@ static void __iomem *apple_pcie_map_bus(struct pci_bus *bus, struct apple_pcie_port *port; if (pcie->hw->adopt_link) { - /* Only adopted ports' root ports, and the one endpoint behind each. */ + /* Only adopted ports' root ports, and the endpoint behind each. */ list_for_each_entry(port, &pcie->ports, entry) { if (bus->number == 0) { if (devfn == PCI_DEVFN(port->idx, 0)) return pci_ecam_map_bus(bus, devfn, where); continue; } - if (bus->number != port->adopted_rid >> 8) + if (bus->number != port->adopted_bus) continue; - if (devfn || READ_ONCE(port->link_failed)) + if (PCI_SLOT(devfn) || READ_ONCE(port->link_failed)) return NULL; if ((apple_pcie_port_readl(port, PORT_LINKSTS) & (PORT_LINKSTS_UP | PORT_LINKSTS_BUSY)) != PORT_LINKSTS_UP) { @@ -3707,9 +3845,9 @@ static int apple_pcie_probe(struct platform_device *pdev) for_each_available_child_of_node_scoped(dev->of_node, child) { u32 reg; - /* A root port 00:.0 on bus 0, n a GP port */ + /* A root port 00:.0 on bus 0, n a GP port (0-3) */ if (of_property_read_u32(child, "reg", ®) || reg & ~0xf800 || - !(reg >> 11) || (reg >> 11) > 3 || + (reg >> 11) > 3 || !of_property_read_bool(child, "apple,link-trained-by-bootloader") || of_property_present(child, "reset-gpios") || of_property_present(child, "pwren-gpios")) @@ -3832,6 +3970,8 @@ static int apple_pcie_probe(struct platform_device *pdev) } } + if (hw->adopt_link) + bridge->reset_root_port = apple_pcie_t6050_reset_root_port; ret = pci_host_common_init(pdev, bridge, &apple_pcie_cfg_ecam_ops); if (ret) { apple_pcie_cleanup(pcie); From 5843113c59c0cb0dad812414dedfaaa51888cae4 Mon Sep 17 00:00:00 2001 From: Jeff Queisser Date: Mon, 2 Nov 2026 23:53:26 -0800 Subject: [PATCH 06/43] PCI: apple: Drive T6050 port 0's PERST# through the pin controller The adopted port 0 named its PERST# pad as a 4-byte reg of the host (perst0, one word inside the pin controller's block) and wrote it raw. With the pin controller in the device tree the two nodes claim the same MMIO word, and whichever probes second fails: when pinctrl wins, the host's port setup stops with -EBUSY and no port is adopted, so the Ethernet and USB behind ports 2 and 3 are gone with it. One owner for the pad: the pin controller. The port node carries reset-gpios, as every other Apple host's port nodes do, and the host takes the line as the bootloader left it (GPIOD_ASIS: probe never touches a trained link's reset) and drives it only in the root-port reset, as a GPIO output at the logical level the reset asks for. The raw pad write set the same things (function bits cleared, mode output, the data bit) plus the pad's input enable, which the pin controller leaves as the bootloader set it. Probe asks for the line before any port is set up, so a host whose pin controller is not up yet defers instead of adopting nothing; an adopted port without reset-gpios has no reset, as before (reset_root_port answers -EOPNOTSUPP). The perst reg, its binding entry and the raw pad write go; the binding allows reset-gpios on an adopted port again. Probe names the port whose line it cannot have, as the probe of the other hosts' ports does: a line the pin controller reserves (gpio-reserved-ranges) is refused with -EINVAL, which the host's probe otherwise returned with no word of which port or property. Signed-off-by: Jeff Queisser Developed with Claude Code. Signed-off-by: Erik Bourget --- .../devicetree/bindings/pci/apple,pcie.yaml | 15 +++--- drivers/pci/controller/pcie-apple.c | 49 +++++++++++-------- 2 files changed, 38 insertions(+), 26 deletions(-) diff --git a/Documentation/devicetree/bindings/pci/apple,pcie.yaml b/Documentation/devicetree/bindings/pci/apple,pcie.yaml index 2446facb91d3a..64f0a2d43e9aa 100644 --- a/Documentation/devicetree/bindings/pci/apple,pcie.yaml +++ b/Documentation/devicetree/bindings/pci/apple,pcie.yaml @@ -242,16 +242,15 @@ allOf: properties: reg: minItems: 5 - maxItems: 18 + maxItems: 14 reg-names: description: config and rc, then port, phy and intr2axi for each - bootloader-trained root port 00:.0, and perst (its PERST# - pad) for a port whose endpoint needs a root-port reset. + bootloader-trained root port 00:.0. minItems: 5 - maxItems: 18 + maxItems: 14 items: - pattern: "^(config|rc|(port|phy|intr2axi|perst)[0-3])$" + pattern: "^(config|rc|(port|phy|intr2axi)[0-3])$" interrupts: maxItems: 4 interrupt-names: @@ -267,7 +266,11 @@ allOf: patternProperties: "^pci@": properties: - reset-gpios: false + reset-gpios: + description: + The PERST# line of a port whose endpoint needs a root-port + reset after the bootloader trained the link. Left as the + bootloader set it until that reset. pwren-gpios: false required: - apple,link-trained-by-bootloader diff --git a/drivers/pci/controller/pcie-apple.c b/drivers/pci/controller/pcie-apple.c index 9a99347476bc4..a28781a22ed0d 100644 --- a/drivers/pci/controller/pcie-apple.c +++ b/drivers/pci/controller/pcie-apple.c @@ -473,7 +473,7 @@ struct apple_pcie_port { struct device_node *np; void __iomem *base; void __iomem *phy; - /* PERST# of a conventional port with a single reset line. */ + /* PERST# of a conventional or adopted port with a single reset line. */ struct gpio_desc *perst; struct irq_domain *domain; struct list_head entry; @@ -492,7 +492,6 @@ struct apple_pcie_port { void __iomem *intr2axi; /* adopt_link: this port's */ u8 adopted_bus; /* adopt_link: the secondary bus */ unsigned long adopted_slots; /* adopt_link: RID2SID slots routed (slot == SID) */ - void __iomem *perst; /* adopt_link: the PERST# pad, for a root-port reset */ }; static int apple_pcie_managed_admit(struct apple_pcie_port *port); @@ -1792,12 +1791,18 @@ static int apple_pcie_adopt_link(struct apple_pcie_port *port) port->intr2axi = devm_platform_ioremap_resource_byname(pdev, name); if (IS_ERR(port->intr2axi)) return PTR_ERR(port->intr2axi); - /* Only a port whose endpoint needs a root-port reset names its PERST# pad */ - snprintf(name, sizeof(name), "perst%d", port->idx); - if (platform_get_resource_byname(pdev, IORESOURCE_MEM, name)) { - port->perst = devm_platform_ioremap_resource_byname(pdev, name); - if (IS_ERR(port->perst)) + /* + * Only a port whose endpoint needs a root-port reset names its PERST# line + * (reset-gpios, on the port node as on every other Apple host). The pin + * controller owns the pad; the line is taken as the bootloader left it, + * released, and driven only by the reset. + */ + port->perst = devm_fwnode_gpiod_get(pcie->dev, of_fwnode_handle(port->np), "reset", + GPIOD_ASIS, "PERST#"); + if (IS_ERR(port->perst)) { + if (PTR_ERR(port->perst) != -ENOENT) return PTR_ERR(port->perst); + port->perst = NULL; } /* The currently qualified handoff is Gen3 x1, with no retrain in progress. */ @@ -1955,20 +1960,14 @@ static int apple_pcie_t6050_tune(struct apple_pcie_port *port, struct pci_dev *r return 0; } -/* PERST# through its pad: a GPIO output (function bits cleared) at the level asked for */ +/* + * PERST# through the pin controller: the pad as a GPIO output (its function bits + * cleared, mode output) at the line's logical level, which the DT's + * GPIO_ACTIVE_LOW turns into the pad level. + */ static int apple_pcie_t6050_perst(struct apple_pcie_port *port, bool asserted) { - u32 old, value; - - old = readl(port->perst); - if (old & BIT(21)) - return -EIO; - value = (old & ~0x27f) | 0x202 | !asserted; - /* The input enable (bit 9) goes on after the rest */ - writel((value & ~0x200) | (old & 0x200), port->perst); - if (!(old & 0x200)) - writel(value, port->perst); - return (readl(port->perst) ^ value) & 0x27f ? -EIO : 0; + return gpiod_direction_output(port->perst, asserted); } /* Let the link go idle, hold the endpoint in reset and stop the port and its lane clocks */ @@ -3849,9 +3848,19 @@ static int apple_pcie_probe(struct platform_device *pdev) if (of_property_read_u32(child, "reg", ®) || reg & ~0xf800 || (reg >> 11) > 3 || !of_property_read_bool(child, "apple,link-trained-by-bootloader") || - of_property_present(child, "reset-gpios") || of_property_present(child, "pwren-gpios")) return -EINVAL; + /* + * A port whose endpoint needs a root-port reset names its PERST# + * line: probe waits for the line's controller before any port is + * touched (a failed adoption would otherwise swallow the deferral). + */ + if (of_property_present(child, "reset-gpios")) { + ret = apple_pcie_probe_port(child); + if (ret) + return dev_err_probe(dev, ret, "Port %pOF probe fail\n", + child); + } } } else if (!hw->tunneled) { /* Check for probe dependencies for all ports first */ From 704b66f70f2ad14fd61f7ebbf4c3a7943f220fad Mon Sep 17 00:00:00 2001 From: Erik Bourget Date: Sun, 8 Nov 2026 01:06:09 -0500 Subject: [PATCH 07/43] selftests: apple: Preserve multiline PCIe macros in policy tests The PCIe-C policy test extracts driver macro definitions to compile its production-code fixture. Taking only the first physical line truncates T6050_ROUTE and joins the following directive to its trailing backslash, so the fixture fails to compile after the T6050 host support is added. Copy continuation lines along with their define. The production fixture and both negative controls now pass. Developed with Claude Code. Signed-off-by: Erik Bourget --- tools/asahi/pciec-policy-tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/asahi/pciec-policy-tests.py b/tools/asahi/pciec-policy-tests.py index a399c7ef6e023..a5a8fb96e143d 100644 --- a/tools/asahi/pciec-policy-tests.py +++ b/tools/asahi/pciec-policy-tests.py @@ -144,7 +144,7 @@ def main(): args = parser.parse_args() pci = (ROOT / 'drivers/pci/controller/pcie-apple.c').read_text() acio = (ROOT / 'drivers/thunderbolt/apple.c').read_text() - defines = '\n'.join(re.findall(r'^#define .*$', pci, re.M)) + defines = '\n'.join(re.findall(r'^#define (?:[^\n]*\\\n)*[^\n]*$', pci, re.M)) info = pci[pci.index('struct hw_info {'):pci.index('static const struct of_device_id apple_pcie_of_match[];')] matches = re.search(r'static const struct of_device_id apple_pcie_of_match\[\] = \{.*?\n\};', pci, re.S)[0] production = '\n'.join((defines, info, matches, From d1ccc586855d3a5bfc356ea20aa04c7402f73b7d Mon Sep 17 00:00:00 2001 From: Dj Date: Mon, 26 Oct 2026 02:34:18 -0400 Subject: [PATCH 08/43] phy: apple: Add T6050 DisplayPort and USB combo PHY Add the T6050 DisplayPort/USB combo PHY and four-clock display crossbar profile. Carry the current Type-C, USB2, USB3 PIPE sequencing, and teardown behavior from the published T6050 source. Source snapshot: t6050-gpu ee68abb105200e99636a5edfc647a5cfc1d89954 Integration base: 9cbdd0734d9e186c5f5f5949a21b909d10c2571b Signed-off-by: Dj Signed-off-by: Erik Bourget --- .../bindings/mux/apple,display-crossbar.yaml | 6 +- .../bindings/phy/apple,t6050-dp-phy.yaml | 224 +++++ drivers/mux/apple-display-crossbar.c | 301 ++++++- drivers/phy/apple/Kconfig | 13 + drivers/phy/apple/Makefile | 2 + drivers/phy/apple/atc-t6050-combo.h | 394 +++++++++ drivers/phy/apple/atc-t6050-dp.c | 806 ++++++++++++++++++ drivers/phy/apple/atc-t6050-typec-data.h | 700 +++++++++++++++ .../apple/usb-t6050/apple_t6050_usb2_reset.h | 23 + .../apple/usb-t6050/apple_t6050_usb3_bist.h | 59 ++ .../apple/usb-t6050/apple_t6050_usb3_pipe.h | 136 +++ .../phy/apple/usb-t6050/apple_t6050_usb_phy.h | 286 +++++++ 12 files changed, 2942 insertions(+), 8 deletions(-) create mode 100644 Documentation/devicetree/bindings/phy/apple,t6050-dp-phy.yaml create mode 100644 drivers/phy/apple/atc-t6050-combo.h create mode 100644 drivers/phy/apple/atc-t6050-dp.c create mode 100644 drivers/phy/apple/atc-t6050-typec-data.h create mode 100644 drivers/phy/apple/usb-t6050/apple_t6050_usb2_reset.h create mode 100644 drivers/phy/apple/usb-t6050/apple_t6050_usb3_bist.h create mode 100644 drivers/phy/apple/usb-t6050/apple_t6050_usb3_pipe.h create mode 100644 drivers/phy/apple/usb-t6050/apple_t6050_usb_phy.h diff --git a/Documentation/devicetree/bindings/mux/apple,display-crossbar.yaml b/Documentation/devicetree/bindings/mux/apple,display-crossbar.yaml index 245f520044e64..5597c9c1489ea 100644 --- a/Documentation/devicetree/bindings/mux/apple,display-crossbar.yaml +++ b/Documentation/devicetree/bindings/mux/apple,display-crossbar.yaml @@ -1,4 +1,4 @@ -# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) AND GPL-2.0-only %YAML 1.2 --- $id: http://devicetree.org/schemas/mux/apple,display-crossbar.yaml# @@ -11,8 +11,8 @@ maintainers: description: Routes display pipelines to the DisplayPort transmitters and inputs - of an Apple Type-C PHY. Each crossbar exposes three mux controls, - selected by the consumer's mux specifier. + of an Apple Type-C PHY. Older layouts expose three mux controls; the T8142/T6050 layout + exposes five. The consumer selects an output using the mux specifier. allOf: - $ref: mux-controller.yaml# diff --git a/Documentation/devicetree/bindings/phy/apple,t6050-dp-phy.yaml b/Documentation/devicetree/bindings/phy/apple,t6050-dp-phy.yaml new file mode 100644 index 0000000000000..52cb96f4a965b --- /dev/null +++ b/Documentation/devicetree/bindings/phy/apple,t6050-dp-phy.yaml @@ -0,0 +1,224 @@ +# SPDX-License-Identifier: GPL-2.0-only +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/phy/apple,t6050-dp-phy.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Apple T6050 DisplayPort and Type-C combo PHY + +maintainers: + - Dj + +description: | + Fixed four-lane DisplayPort PHY for HDMI bridges, or one shared owner of + Type-C USB2, USB3 and DisplayPort alternate mode. DisplayPort supports RBR + through HBR3 without spread-spectrum clocking. The combo provider serializes + controller shutdown, PIPE handoff, orientation and alternate-mode changes. + Register resources, power domains and board tunables come from the producer. + +properties: + compatible: + enum: + - apple,t6050-dp-phy + - apple,t6050-combo-phy + + reg: + minItems: 6 + maxItems: 11 + + reg-names: + minItems: 6 + maxItems: 11 + + '#phy-cells': + enum: [0, 1] + + '#reset-cells': + const: 1 + + apple,port-index: + enum: [1, 2] + description: Native Type-C port; the debug/proxy port is excluded + + apple,guest-port-reserved: + type: boolean + description: The boot producer has excluded this port from host ownership + + mode-switch: true + orientation-switch: true + + mboxes: + maxItems: 1 + + port: + $ref: /schemas/graph.yaml#/properties/port + + apple,tunable-aus40cmn-shm: + $ref: /schemas/types.yaml#/definitions/uint32-array + description: Offset, mask and value tuples for the common analog bank + + apple,tunable-cio4pll: + $ref: /schemas/types.yaml#/definitions/uint32-array + description: Offset, mask and value tuples for the CIO PLL bank + + apple,tunable-usb2-default: + $ref: /schemas/types.yaml#/definitions/uint32-array + description: Offset, mask and value tuples for the USB2 PHY bank + + apple,tunable-usb2-host: + $ref: /schemas/types.yaml#/definitions/uint32-array + description: Offset, mask and value tuples for the USB2 host bank + + apple,tunable-pipe-default: + $ref: /schemas/types.yaml#/definitions/uint32-array + description: Offset, mask and value tuples for the PIPE handler + + power-domains: + minItems: 3 + maxItems: 3 + + power-domain-names: + items: + - const: usb-aon + - const: common + - const: wrapper + + apple,tunable-axi2af: + $ref: /schemas/types.yaml#/definitions/uint32-array + description: Offset, mask and value tuples within the named bank + + apple,tunable-fabric: + $ref: /schemas/types.yaml#/definitions/uint32-array + description: Offset, mask and value tuples within the named bank + + apple,tunable-common-cfg: + $ref: /schemas/types.yaml#/definitions/uint32-array + description: Offset, mask and value tuples within the named bank + + apple,tunable-lioa: + $ref: /schemas/types.yaml#/definitions/uint32-array + description: Offset, mask and value tuples within the named bank + + apple,tunable-lane0-rx-clock: + $ref: /schemas/types.yaml#/definitions/uint32-array + description: Offset, mask and value tuples within the named bank + + apple,tunable-lane1-rx-clock: + $ref: /schemas/types.yaml#/definitions/uint32-array + description: Offset, mask and value tuples within the named bank + + apple,tunable-lane0-rx-eq: + $ref: /schemas/types.yaml#/definitions/uint32-array + description: Offset, mask and value tuples within the named bank + + apple,tunable-lane1-rx-eq: + $ref: /schemas/types.yaml#/definitions/uint32-array + description: Offset, mask and value tuples within the named bank + + apple,tunable-auspll: + $ref: /schemas/types.yaml#/definitions/uint32-array + description: Offset, mask and value tuples within the named bank + +patternProperties: + '^apple,dp-training-table(-rbr|-hbr|-hbr2|-hbr3)?$': + $ref: /schemas/types.yaml#/definitions/uint32-array + minItems: 16 + maxItems: 16 + description: Drive presets indexed by voltage swing times four plus pre-emphasis + +required: + - compatible + - reg + - reg-names + - '#phy-cells' + - power-domains + - power-domain-names + - apple,tunable-axi2af + - apple,tunable-fabric + - apple,tunable-common-cfg + - apple,tunable-lioa + - apple,tunable-lane0-rx-clock + - apple,tunable-lane1-rx-clock + - apple,tunable-lane0-rx-eq + - apple,tunable-lane1-rx-eq + - apple,tunable-auspll + +allOf: + - if: + properties: + compatible: + const: apple,t6050-combo-phy + then: + properties: + '#phy-cells': + const: 1 + reg: + minItems: 11 + maxItems: 11 + reg-names: + items: + - const: core + - const: usb2phy + - const: usb2-digital + - const: reset + - const: fabric + - const: axi2af + - const: lioa + - const: common-cfg + - const: usb-control + - const: firmware-data + - const: pipehandler + required: + - '#reset-cells' + - apple,port-index + - apple,guest-port-reserved + - mode-switch + - orientation-switch + - mboxes + - apple,tunable-aus40cmn-shm + - apple,tunable-cio4pll + - apple,tunable-usb2-default + - apple,tunable-usb2-host + - apple,tunable-pipe-default + else: + properties: + '#phy-cells': + const: 0 + if: + required: + - mode-switch + then: + properties: + reg: + minItems: 8 + maxItems: 8 + reg-names: + items: + - const: core + - const: reset + - const: fabric + - const: axi2af + - const: lioa + - const: common-cfg + - const: usb-control + - const: firmware-data + required: + - orientation-switch + - mboxes + - apple,tunable-aus40cmn-shm + - apple,tunable-cio4pll + else: + properties: + reg: + minItems: 6 + maxItems: 6 + reg-names: + items: + - const: core + - const: reset + - const: fabric + - const: axi2af + - const: lioa + - const: common-cfg + +additionalProperties: false diff --git a/drivers/mux/apple-display-crossbar.c b/drivers/mux/apple-display-crossbar.c index 65107c7de3800..223d7b813dc15 100644 --- a/drivers/mux/apple-display-crossbar.c +++ b/drivers/mux/apple-display-crossbar.c @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-2.0-only OR MIT +// SPDX-License-Identifier: (GPL-2.0-only OR MIT) AND GPL-2.0-only /* * Apple Silicon Display Crossbar multiplexer driver * @@ -14,6 +14,9 @@ #include #include #include +#include +#include +#include #include #include #include @@ -90,7 +93,10 @@ enum { MUX_DPPHY = 0, MUX_DPIN0 = 1, MUX_DPIN1 = 2, MUX_MAX = 3 }; static const char *apple_dpxbar_names[MUX_MAX] = { "dpphy", "dpin0", "dpin1" }; +enum { T8142_MUX_MAX = 5 }; + struct apple_dpxbar_hw { + unsigned int n_mux; unsigned int n_ufp; u32 tunable; const struct mux_control_ops *ops; @@ -108,12 +114,16 @@ struct apple_dpxbar { struct device *dev; const struct apple_dpxbar_hw *hw; void __iomem *regs; - int selected_dispext[MUX_MAX]; + int selected_dispext[T8142_MUX_MAX]; /* T6030 programs all its outputs itself, see apple_dpxbar_t6030_link() */ bool t6030_dpin; /* outputs holding a runtime PM reference (runtime_pm crossbars) */ unsigned long rpm_held; bool tunnel_link_up[MUX_MAX]; + u8 pclk[T8142_MUX_MAX]; + bool uhbr[T8142_MUX_MAX]; + bool enabled[T8142_MUX_MAX]; + bool faulted; spinlock_t lock; }; @@ -880,6 +890,225 @@ int apple_dpxbar_tunnel_select_source(struct mux_control *mux, int state) } EXPORT_SYMBOL_GPL(apple_dpxbar_tunnel_select_source); +/* + * T8142 (also used by T6050) has four pixel clocks and five outputs. + * Keep selection separate from clock activation: the PHY/DP IN owner supplies + * its actual clock and coding before link_up(), after configuring that clock. + * Only the four two-port external display sources are described here; AUX + * sources use a different selector-to-clock-bit mapping. + */ +static const u8 t8142_dest_shift[T8142_MUX_MAX] = { 8, 0, 4, 12, 16 }; +static const u8 t8142_select_shift[T8142_MUX_MAX] = { 8, 0, 4, 24, 0 }; +static const u8 t8142_select_shift2[T8142_MUX_MAX] = { 20, 12, 16, 28, 4 }; + +static int t8142_link_down(struct apple_dpxbar *xbar, unsigned int index) +{ + u32 src, dst, clock, value; + int ret; + + if (!xbar->enabled[index]) + return 0; + src = BIT(xbar->selected_dispext[index]); + dst = BIT(t8142_dest_shift[index]); + clock = xbar->pclk[index] - 1; + + dpxbar_clear32(xbar, 0x3c, src); + dpxbar_clear32(xbar, 0x00, src); + dpxbar_clear32(xbar, 0x0c + 4 * clock, src); + dpxbar_clear32(xbar, 0x24 + 4 * clock, dst); + udelay(1); + ret = readl_poll_timeout_atomic(xbar->regs + 0x800, value, + !(value & src), 1, 1000); + if (ret) { + xbar->faulted = true; + return ret; + } + ret = readl_poll_timeout_atomic(xbar->regs + 0x808 + 4 * clock, value, + !(value & src), 1, 1000); + if (ret) { + xbar->faulted = true; + return ret; + } + ret = readl_poll_timeout_atomic(xbar->regs + 0x81c + 4 * clock, value, + !(value & dst), 1, 1000); + if (ret) { + xbar->faulted = true; + return ret; + } + dpxbar_clear32(xbar, 0x08, src); + dpxbar_clear32(xbar, 0x20, 7 << (3 * xbar->selected_dispext[index])); + dpxbar_clear32(xbar, 0x38, 7 << t8142_dest_shift[index]); + dpxbar_set32(xbar, 0x04, src); + dpxbar_set32(xbar, 0x1c, src); + dpxbar_set32(xbar, 0x34, dst); + xbar->enabled[index] = false; + return 0; +} + +static int t8142_link_up(struct apple_dpxbar *xbar, unsigned int index) +{ + int state = xbar->selected_dispext[index]; + u32 clock, select, src, dst, value; + int ret; + + if (state < 0) + return -ENODEV; + if (!xbar->pclk[index]) + return -EINVAL; + if (xbar->enabled[index]) + return 0; + + /* The coding bit is shared by every output using this pixel clock. */ + for (unsigned int i = 0; i < T8142_MUX_MAX; i++) + if (i != index && xbar->enabled[i] && + xbar->pclk[i] == xbar->pclk[index] && + xbar->uhbr[i] != xbar->uhbr[index]) + return -EBUSY; + + clock = xbar->pclk[index] - 1; + select = 2 * clock + 1; + src = BIT(state); + dst = BIT(t8142_dest_shift[index]); + dpxbar_clear32(xbar, 0x04, src); + dpxbar_clear32(xbar, 0x1c, src); + dpxbar_clear32(xbar, 0x34, dst); + udelay(1); + ret = readl_poll_timeout_atomic(xbar->regs + 0x804, value, + !(value & src), 1, 1000); + if (ret) { + xbar->faulted = true; + return ret; + } + ret = readl_poll_timeout_atomic(xbar->regs + 0x818, value, + !(value & src), 1, 1000); + if (ret) { + xbar->faulted = true; + return ret; + } + ret = readl_poll_timeout_atomic(xbar->regs + 0x82c, value, + !(value & dst), 1, 1000); + if (ret) { + xbar->faulted = true; + return ret; + } + dpxbar_set32(xbar, 0x08, src); + dpxbar_mask32(xbar, 0x20, 7 << (3 * state), select << (3 * state)); + dpxbar_mask32(xbar, 0x38, 7 << t8142_dest_shift[index], + select << t8142_dest_shift[index]); + dpxbar_mask32(xbar, 0x48, BIT(clock), xbar->uhbr[index] ? BIT(clock) : 0); + dpxbar_set32(xbar, 0x00, src); + dpxbar_set32(xbar, 0x0c + 4 * clock, src); + dpxbar_set32(xbar, 0x24 + 4 * clock, dst); + dpxbar_set32(xbar, 0x4c, dst); + dpxbar_set32(xbar, 0x3c, src); + xbar->enabled[index] = true; + return 0; +} + +static int apple_dpxbar_set_t8142(struct mux_control *mux, int state) +{ + struct apple_dpxbar *xbar = mux_chip_priv(mux->chip); + unsigned int index = mux_control_get_index(mux); + unsigned long flags; + u32 mask, value; + int ret = 0; + + if (state != MUX_IDLE_DISCONNECT && + (state < 0 || state >= 8 || (index == MUX_DPPHY && (state & 1)))) + return -EINVAL; + + spin_lock_irqsave(&xbar->lock, flags); + if (xbar->faulted) { + ret = -EIO; + goto out; + } + if (state >= 0) { + for (unsigned int i = 0; i < T8142_MUX_MAX; i++) { + if (i != index && xbar->selected_dispext[i] == state) { + spin_unlock_irqrestore(&xbar->lock, flags); + return -EBUSY; + } + } + } + + if (xbar->selected_dispext[index] == state) + goto out; + if (xbar->selected_dispext[index] >= 0) { + ret = t8142_link_down(xbar, index); + if (ret) + goto out; + } + + /* No implicit clock choice or activation when selecting a new source. */ + xbar->pclk[index] = 0; + xbar->selected_dispext[index] = state; + if (state >= 0) { + mask = (0xfU << t8142_select_shift[index]) | + (0xfU << t8142_select_shift2[index]); + value = ((u32)state << t8142_select_shift[index]) | + ((u32)state << t8142_select_shift2[index]); + dpxbar_mask32(xbar, index == 4 ? 0x40 : 0x44, mask, value); + } +out: + spin_unlock_irqrestore(&xbar->lock, flags); + return ret; +} + +static const struct mux_control_ops apple_dpxbar_t8142_ops = { + .set = apple_dpxbar_set_t8142, +}; + +/* Caller holds the mux selection throughout configuration and activation. */ +int apple_dpxbar_link_configure(struct mux_control *mux, unsigned int pclk, bool uhbr) +{ + struct apple_dpxbar *xbar; + unsigned int index; + unsigned long flags; + int ret = 0; + + if (!mux || mux->chip->ops != &apple_dpxbar_t8142_ops) + return -EOPNOTSUPP; + if (pclk < 1 || pclk > 4) + return -EINVAL; + xbar = mux_chip_priv(mux->chip); + index = mux_control_get_index(mux); + spin_lock_irqsave(&xbar->lock, flags); + if (xbar->faulted) + ret = -EIO; + else if (xbar->selected_dispext[index] < 0) + ret = -ENODEV; + else if (xbar->enabled[index]) + ret = -EBUSY; + else { + xbar->pclk[index] = pclk; + xbar->uhbr[index] = uhbr; + } + spin_unlock_irqrestore(&xbar->lock, flags); + return ret; +} +EXPORT_SYMBOL_GPL(apple_dpxbar_link_configure); + +static int apple_dpxbar_t8142_link(struct mux_control *mux, bool up) +{ + struct apple_dpxbar *xbar = mux_chip_priv(mux->chip); + unsigned int index = mux_control_get_index(mux); + unsigned long flags; + int ret = 0; + + spin_lock_irqsave(&xbar->lock, flags); + if (xbar->faulted) + ret = -EIO; + else if (xbar->selected_dispext[index] < 0) + ret = -ENODEV; + else if (up) + ret = t8142_link_up(xbar, index); + else + ret = t8142_link_down(xbar, index); + spin_unlock_irqrestore(&xbar->lock, flags); + return ret; +} + + static int apple_dpxbar_t602x_link_up(struct mux_control *mux) { struct apple_dpxbar *xbar; @@ -961,6 +1190,9 @@ static int apple_dpxbar_t6030_link_set(struct mux_control *mux, bool up) int apple_dpxbar_link_up(struct mux_control *mux) { + if (mux && mux->chip->ops == &apple_dpxbar_t8142_ops) + return apple_dpxbar_t8142_link(mux, true); + if (!mux) return -EINVAL; if (mux->chip->ops == &apple_dpxbar_t602x_ops) { @@ -1009,6 +1241,9 @@ static int apple_dpxbar_t602x_link_down(struct mux_control *mux) int apple_dpxbar_link_down(struct mux_control *mux) { + if (mux && mux->chip->ops == &apple_dpxbar_t8142_ops) + return apple_dpxbar_t8142_link(mux, false); + if (!mux) return -EINVAL; if (mux->chip->ops == &apple_dpxbar_t602x_ops) { @@ -1022,6 +1257,39 @@ int apple_dpxbar_link_down(struct mux_control *mux) } EXPORT_SYMBOL_GPL(apple_dpxbar_link_down); +static void apple_dpxbar_domain_release(void *data) +{ + struct device *pd = data; + + pm_runtime_put_sync_suspend(pd); + dev_pm_domain_detach(pd, true); +} + +static int apple_dpxbar_domains(struct device *dev) +{ + static const char * const names[] = { "usb-aon", "common" }; + int ret; + + /* Older handoff-only trees have no Linux-managed domains. */ + if (!of_property_present(dev->of_node, "power-domain-names")) + return 0; + for (unsigned int i = 0; i < ARRAY_SIZE(names); i++) { + struct device *pd = dev_pm_domain_attach_by_name(dev, names[i]); + + if (IS_ERR_OR_NULL(pd)) + return pd ? PTR_ERR(pd) : -ENODEV; + ret = pm_runtime_resume_and_get(pd); + if (ret < 0) { + dev_pm_domain_detach(pd, true); + return ret; + } + ret = devm_add_action_or_reset(dev, apple_dpxbar_domain_release, pd); + if (ret) + return ret; + } + return 0; +} + static int apple_dpxbar_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -1031,7 +1299,16 @@ static int apple_dpxbar_probe(struct platform_device *pdev) int ret; hw = of_device_get_match_data(dev); - mux_chip = devm_mux_chip_alloc(dev, MUX_MAX, sizeof(*dpxbar)); + if (hw->ops == &apple_dpxbar_t8142_ops) { + struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + + if (!res || resource_size(res) < 0x830) + return -EINVAL; + ret = apple_dpxbar_domains(dev); + if (ret) + return dev_err_probe(dev, ret, "cannot power crossbar controls\n"); + } + mux_chip = devm_mux_chip_alloc(dev, hw->n_mux, sizeof(*dpxbar)); if (IS_ERR(mux_chip)) return PTR_ERR(mux_chip); @@ -1052,14 +1329,14 @@ static int apple_dpxbar_probe(struct platform_device *pdev) return ret; } - if (!hw->no_tunable && + if (hw->ops == &apple_dpxbar_ops && !hw->no_tunable && !of_device_is_compatible(dev->of_node, "apple,t6020-display-crossbar")) { readl(dpxbar->regs + UNK_TUNABLE); writel(hw->tunable, dpxbar->regs + UNK_TUNABLE); readl(dpxbar->regs + UNK_TUNABLE); } - for (unsigned int i = 0; i < MUX_MAX; ++i) { + for (unsigned int i = 0; i < hw->n_mux; ++i) { mux_chip->mux[i].states = hw->n_ufp; mux_chip->mux[i].idle_state = MUX_IDLE_DISCONNECT; dpxbar->selected_dispext[i] = -1; @@ -1124,18 +1401,21 @@ static int apple_dpxbar_resume(struct device *dev) static DEFINE_SIMPLE_DEV_PM_OPS(apple_dpxbar_pm_ops, NULL, apple_dpxbar_resume); static const struct apple_dpxbar_hw apple_dpxbar_hw_t8103 = { + .n_mux = MUX_MAX, .n_ufp = 2, .tunable = 0, .ops = &apple_dpxbar_ops, }; static const struct apple_dpxbar_hw apple_dpxbar_hw_t8112 = { + .n_mux = MUX_MAX, .n_ufp = 4, .tunable = 4278196325, .ops = &apple_dpxbar_ops, }; static const struct apple_dpxbar_hw apple_dpxbar_hw_t6000 = { + .n_mux = MUX_MAX, .n_ufp = 9, .tunable = 5, .ops = &apple_dpxbar_ops, @@ -1143,6 +1423,7 @@ static const struct apple_dpxbar_hw apple_dpxbar_hw_t6000 = { }; static const struct apple_dpxbar_hw apple_dpxbar_hw_t6020 = { + .n_mux = MUX_MAX, .n_ufp = 9, .ops = &apple_dpxbar_t602x_ops, }; @@ -1155,6 +1436,7 @@ static const struct apple_dpxbar_hw apple_dpxbar_hw_t6020 = { * The ADT lists nine UFP endpoints (dispext0-3, scodec0 and the DP IN ports). */ static const struct apple_dpxbar_hw apple_dpxbar_hw_t6041 = { + .n_mux = MUX_MAX, .n_ufp = 9, .ops = &apple_dpxbar_t602x_ops, .t6030_flow = true, @@ -1170,6 +1452,7 @@ static const struct apple_dpxbar_hw apple_dpxbar_hw_t6041 = { * output is selected; probe touches no register. */ static const struct apple_dpxbar_hw apple_dpxbar_hw_t8132 = { + .n_mux = MUX_MAX, .n_ufp = 9, .ops = &apple_dpxbar_t602x_ops, .t6030_flow = true, @@ -1177,7 +1460,15 @@ static const struct apple_dpxbar_hw apple_dpxbar_hw_t8132 = { .runtime_pm = true, }; +static const struct apple_dpxbar_hw apple_dpxbar_hw_t8142 = { + .n_mux = T8142_MUX_MAX, + .n_ufp = 8, + .ops = &apple_dpxbar_t8142_ops, +}; + static const struct of_device_id apple_dpxbar_ids[] = { + { .compatible = "apple,t6050-display-crossbar", .data = &apple_dpxbar_hw_t8142 }, + { .compatible = "apple,t8142-display-crossbar", .data = &apple_dpxbar_hw_t8142 }, { .compatible = "apple,t8103-display-crossbar", .data = &apple_dpxbar_hw_t8103, diff --git a/drivers/phy/apple/Kconfig b/drivers/phy/apple/Kconfig index 5348987cf1dc8..36ed85b7935f2 100644 --- a/drivers/phy/apple/Kconfig +++ b/drivers/phy/apple/Kconfig @@ -62,3 +62,16 @@ config PHY_APPLE_DPTX such as the M2. This driver provides support for DisplayPort and is used on the Mac mini (M2 and M2 Pro, 2023). + +config PHY_APPLE_T6050_DP + tristate "Apple T6050 DisplayPort and Type-C combo PHY" + depends on ARCH_APPLE || COMPILE_TEST + depends on OF && PM && TYPEC + select GENERIC_PHY + select APPLE_TUNABLE + select APPLE_RTKIT + select RESET_CONTROLLER + help + Fixed HDMI bridges and Type-C USB host/DisplayPort alternate mode. + One owner coordinates the shared PHY, firmware, PIPE and USB2 state. + Requires named register banks, power domains and firmware tunables. diff --git a/drivers/phy/apple/Makefile b/drivers/phy/apple/Makefile index e7156408e792f..e0b94bb1dcaa5 100644 --- a/drivers/phy/apple/Makefile +++ b/drivers/phy/apple/Makefile @@ -8,3 +8,5 @@ phy-apple-t6040-usb2-y := t6040-usb2.o obj-$(CONFIG_PHY_APPLE_DPTX) += phy-apple-dptx.o phy-apple-dptx-y += dptx.o + +obj-$(CONFIG_PHY_APPLE_T6050_DP) += atc-t6050-dp.o diff --git a/drivers/phy/apple/atc-t6050-combo.h b/drivers/phy/apple/atc-t6050-combo.h new file mode 100644 index 0000000000000..1066748dc1afe --- /dev/null +++ b/drivers/phy/apple/atc-t6050-combo.h @@ -0,0 +1,394 @@ +/* SPDX-License-Identifier: GPL-2.0-only OR MIT */ +/* One Type-C owner reuses the established DP and USB backend sequences. + * All calls below hold dp->lock; only PHY power_off authorizes PIPE teardown + * while the controller reset is deasserted. Lane changes require reset/off. + */ +static u32 combo_read(void *ctx, unsigned int bank, unsigned int offset) +{ + struct t6050_dp *dp = ctx; + return readl(dp->banks[bank] + offset); +} +static void combo_write(void *ctx, unsigned int bank, unsigned int offset, u32 value) +{ + struct t6050_dp *dp = ctx; + writel(value, dp->banks[bank] + offset); +} +static int combo_poll(void *ctx, unsigned int bank, unsigned int offset, + unsigned int mask, unsigned int expected, unsigned int usec) +{ + struct t6050_dp *dp = ctx; + u32 value; + return readl_poll_timeout(dp->banks[bank] + offset, value, + (value & mask) == expected, + (bank == 44 || bank == 35 || usec <= 1000) ? 1 : 1000, usec); +} +static void combo_delay(void *ctx, unsigned int usec) { udelay(usec); } +static void combo_pause(void *ctx, unsigned int nsec) { ndelay(nsec); } +static void combo_sleep(void *ctx, unsigned int msec) { msleep(msec); } +static int combo_wrapper(void *ctx, unsigned int active) +{ + struct t6050_dp *dp = ctx; + return atc_domain_set(&dp->domain[2], active); +} +static int combo_firmware(void *ctx, unsigned int active) +{ + return atc_firmware(ctx, active); +} +static int combo_tunable(void *ctx, unsigned int bank, const char *name) +{ + static const struct { u8 bank, index; const char *name; } common[] = { + {41, 0, "tunable_ATC0AXI2AF"}, {40, 1, "tunable_ATC_FABRIC"}, + {43, 2, "tunable_ATC_COMMON_CFG"}, {42, 3, "tunable_ATC0AXI2AF_LIOA"}, + {20, 4, "tunable_LN0_RX_CFG_TX_OF_RXCLK"}, {29, 5, "tunable_LN1_RX_CFG_TX_OF_RXCLK"}, + {18, 6, "tunable_LN0_RX_EQ_DFLT"}, {27, 7, "tunable_LN1_RX_EQ_DFLT"}, + {8, 8, "tunable_AUSPLL_CORE"}, {5, 9, "tunable_AUS40CMN_SHM"}, + {10, 10, "tunable_CIO4PLL_CORE"}, + }; + struct t6050_dp *dp = ctx; + + if (bank == 0 && !strcmp(name, "tunable_USB2PHY_DFLT")) { + apple_tunable_apply(dp->banks[0], dp->usb2_default); + return 0; + } + if (bank == 0 && !strcmp(name, "tunable_USB2PHY_HOST")) { + apple_tunable_apply(dp->banks[0], dp->usb2_host); + return 0; + } + for (unsigned int i = 0; i < ARRAY_SIZE(common); i++) + if (bank == common[i].bank && !strcmp(name, common[i].name)) { + apple_tunable_apply(dp->banks[bank], dp->tunables[common[i].index]); + return 0; + } + return -EINVAL; +} + +static int combo_quiesce(void *ctx) +{ + struct t6050_dp *dp = ctx; + int ret = 0; + + if (dp->usb_prepared) { + ret = APPLE_T6050_USB_RUN(&dp->usb_platform, apple_t6050_usb3_shutdown); + if (!ret) + dp->usb_prepared = false; + } else if (dp->enabled) { + /* Multifunction has a distinct native shutdown; never run the + * USB-only analog sequence over live DisplayPort lane state. */ + ret = atc_stop(dp); + } + return ret; +} + +static int combo_stop(struct t6050_dp *dp) +{ + int ret; + + lockdep_assert_held(&dp->lock); + if (dp->faulted) + return -EIO; + if (dp->controller_active || dp->pipe_active) + return -EBUSY; + ret = combo_quiesce(dp); + if (!ret && dp->usb2_active) { + ret = apple_t6050_usb2_stop(&dp->usb_platform); + if (!ret) + dp->usb2_active = false; + } + if (!ret) + ret = atc_domain_set(&dp->domain[1], false); + if (!ret) + ret = atc_domain_set(&dp->domain[0], false); + if (ret) { + dp->faulted = true; + return ret < 0 ? ret : -EIO; + } + dp->combo_mode = COMBO_SAFE; + return 0; +} + +static int combo_start(struct t6050_dp *dp, enum combo_mode mode) +{ + int ret; + + lockdep_assert_held(&dp->lock); + if (dp->faulted) + return -EIO; + if (dp->orientation == TYPEC_ORIENTATION_NONE) + return -ENOLINK; + ret = atc_domain_set(&dp->domain[0], true); + if (!ret) + ret = atc_domain_set(&dp->domain[1], true); + if (!ret) + ret = APPLE_T6050_USB_RUN(&dp->usb_platform, apple_t6050_usb2_host); + if (!ret) + dp->usb2_active = true; + if (!ret && mode == COMBO_USB) { + ret = dp->reverse ? + APPLE_T6050_USB_RUN(&dp->usb_platform, apple_t6050_usb3_init_reverse) : + APPLE_T6050_USB_RUN(&dp->usb_platform, apple_t6050_usb3_init_normal); + if (!ret) + dp->usb_prepared = true; + } else if (!ret) { + dp->multifunction = mode == COMBO_DP2; + ret = atc_start(dp); + } + if (ret) { + /* A partial shared transition has no cold-start ownership proof. */ + dp->faulted = true; + return ret < 0 ? ret : -EIO; + } + dp->combo_mode = mode; + return 0; +} + +static int combo_orientation(struct t6050_dp *dp, enum typec_orientation orientation) +{ + int ret; + + guard(mutex)(&dp->lock); + if (dp->faulted) + return -EIO; + if (orientation != TYPEC_ORIENTATION_NONE && orientation != TYPEC_ORIENTATION_NORMAL && + orientation != TYPEC_ORIENTATION_REVERSE) + return -EINVAL; + if (dp->orientation == orientation) + return 0; + ret = combo_stop(dp); + if (!ret) { + dp->orientation = orientation; + dp->reverse = orientation == TYPEC_ORIENTATION_REVERSE; + } + return ret; +} + +static int combo_mux(struct t6050_dp *dp, struct typec_mux_state *state) +{ + enum combo_mode mode; + int ret; + + if (state->mode == TYPEC_STATE_SAFE) + mode = COMBO_SAFE; + else if (state->mode == TYPEC_STATE_USB && !state->alt) + mode = COMBO_USB; + else if (state->alt && state->alt->svid == USB_TYPEC_DP_SID) { + switch (state->mode) { + case TYPEC_DP_STATE_C: + case TYPEC_DP_STATE_E: mode = COMBO_DP4; break; + case TYPEC_DP_STATE_D: mode = COMBO_DP2; break; + default: return -EOPNOTSUPP; + } + } else { + return -EOPNOTSUPP; + } + guard(mutex)(&dp->lock); + if (dp->faulted) + return -EIO; + if (mode == dp->combo_mode && + (mode == COMBO_SAFE || dp->usb_prepared || dp->enabled)) + return 0; + if (mode != COMBO_SAFE && dp->orientation == TYPEC_ORIENTATION_NONE) + return -ENOLINK; + ret = combo_stop(dp); + if (ret || mode == COMBO_SAFE) + return ret; + return combo_start(dp, mode); +} + +static int combo_usb2_mode(struct phy *phy, enum phy_mode mode, int submode) +{ + struct t6050_dp *dp = phy_get_drvdata(phy); + int ret; + + guard(mutex)(&dp->lock); + if (dp->faulted) + return -EIO; + if (mode != PHY_MODE_USB_HOST) + return -EOPNOTSUPP; + if (!dp->usb_prepared && !dp->enabled) + return -ENOLINK; + if (dp->usb2_active) + return 0; + ret = APPLE_T6050_USB_RUN(&dp->usb_platform, apple_t6050_usb2_host); + if (ret) + dp->faulted = true; + else + dp->usb2_active = true; + return ret; +} + +static int combo_usb2_reset(struct phy *phy) +{ + struct t6050_dp *dp = phy_get_drvdata(phy); + int ret; + + guard(mutex)(&dp->lock); + if (dp->faulted || !dp->usb2_active) + return -ENOLINK; + ret = apple_t6050_usb2_port_reset(&dp->usb_io, dp, combo_sleep); + if (ret) + dp->faulted = true; + return ret ? -EIO : 0; +} + +static int combo_usb3_mode(struct phy *phy, enum phy_mode mode, int submode) +{ + struct t6050_dp *dp = phy_get_drvdata(phy); + int ret; + + guard(mutex)(&dp->lock); + if (dp->faulted) + return -EIO; + if (mode != PHY_MODE_USB_HOST) + return -EOPNOTSUPP; + if (!dp->controller_active || !dp->usb2_active) + return -ENOLINK; + /* Four DP lanes leave DWC3 on its established dummy PIPE; USB2 still + * uses its independent D+/D- PHY. Do not switch USB3 onto DP lanes. */ + if (dp->combo_mode == COMBO_DP4 && dp->enabled) + return 0; + if (!dp->usb_prepared && !(dp->combo_mode == COMBO_DP2 && dp->enabled)) + return -ENOLINK; + if (dp->pipe_active) + return (readl(dp->banks[44] + 0xc) & 0x73) == 0x10 ? 0 : -EIO; + apple_tunable_apply(dp->banks[44], dp->pipe_default); + ret = apple_t6050_usb3_pipe_switch(&dp->usb_io); + if (ret) + dp->faulted = true; + else + dp->pipe_active = true; + return ret ? -EIO : 0; +} + +static int combo_usb3_poweroff(struct phy *phy) +{ + struct t6050_dp *dp = phy_get_drvdata(phy); + int ret = 0; + + guard(mutex)(&dp->lock); + if (dp->faulted) + return -EIO; + /* DWC3 calls power_off only after host/gadget shutdown and SUSPHY. + * PIPE must select its dummy producer before the controller is reset. */ + if (dp->pipe_active) { + ret = apple_t6050_usb3_pipe_stop(&dp->usb_io, combo_quiesce, dp); + if (!ret) + dp->pipe_active = false; + } + if (!ret && dp->usb2_active) { + ret = apple_t6050_usb2_stop(&dp->usb_platform); + if (!ret) + dp->usb2_active = false; + } + if (ret) + dp->faulted = true; + return ret ? -EIO : 0; +} +static const struct phy_ops combo_usb2_ops = { + .set_mode = combo_usb2_mode, .reset = combo_usb2_reset, .owner = THIS_MODULE, +}; +static const struct phy_ops combo_usb3_ops = { + .set_mode = combo_usb3_mode, .power_off = combo_usb3_poweroff, .owner = THIS_MODULE, +}; + +static int combo_reset_assert(struct reset_controller_dev *reset, unsigned long id) +{ + struct t6050_dp *dp = container_of(reset, struct t6050_dp, reset); + int ret; + + guard(mutex)(&dp->lock); + if (id || dp->pipe_active) + return -EBUSY; + if (dp->faulted) + return -EIO; + apple_t6050_usb_mask(&dp->usb_io, 44, 0x1c, 1, 0); + apple_t6050_usb_mask(&dp->usb_io, 44, 0x1c, 0x10, 0x10); + ret = apple_t6050_usb3_pipe_cold_dummy(&dp->usb_io); + if (ret) + dp->faulted = true; + else + dp->controller_active = false; + return ret ? -EIO : 0; +} +static int combo_reset_deassert(struct reset_controller_dev *reset, unsigned long id) +{ + struct t6050_dp *dp = container_of(reset, struct t6050_dp, reset); + + guard(mutex)(&dp->lock); + if (id) + return -EINVAL; + if (dp->faulted || !dp->usb2_active) + return -ENOLINK; + apple_t6050_usb_mask(&dp->usb_io, 44, 0x1c, 0x10, 0); + apple_t6050_usb_mask(&dp->usb_io, 44, 0x1c, 1, 1); + dp->controller_active = true; + return 0; +} +static const struct reset_control_ops combo_reset_ops = { + .assert = combo_reset_assert, .deassert = combo_reset_deassert, +}; + +static struct phy *combo_xlate(struct device *dev, const struct of_phandle_args *args) +{ + struct t6050_dp *dp = dev_get_drvdata(dev); + if (args->args_count != 1) + return ERR_PTR(-EINVAL); + switch (args->args[0]) { + case PHY_TYPE_USB2: return dp->combo_phy[0]; + case PHY_TYPE_USB3: return dp->combo_phy[1]; + case PHY_TYPE_DP: return dp->combo_phy[2]; + default: return ERR_PTR(-EINVAL); + } +} + +static int combo_setup(struct platform_device *pdev, struct t6050_dp *dp) +{ + static const struct { u8 bank; u32 minimum; const char *name; } extra[] = { + {0, 0x20, "usb2phy"}, {1, 0x24, "usb2-digital"}, {44, 0x48, "pipehandler"}, + }; + struct device *dev = dp->dev; + int ret; + + for (unsigned int i = 0; i < ARRAY_SIZE(extra); i++) { + struct resource *res = platform_get_resource_byname(pdev, IORESOURCE_MEM, extra[i].name); + if (!res || resource_size(res) < extra[i].minimum) + return -EINVAL; + dp->banks[extra[i].bank] = devm_ioremap_resource(dev, res); + if (IS_ERR(dp->banks[extra[i].bank])) + return PTR_ERR(dp->banks[extra[i].bank]); + dp->resources[extra[i].bank] = *res; + } + dp->usb2_default = devm_apple_tunable_parse(dev, dev->of_node, + "apple,tunable-usb2-default", &dp->resources[0]); + if (IS_ERR(dp->usb2_default)) + return PTR_ERR(dp->usb2_default); + dp->usb2_host = devm_apple_tunable_parse(dev, dev->of_node, + "apple,tunable-usb2-host", &dp->resources[0]); + if (IS_ERR(dp->usb2_host)) + return PTR_ERR(dp->usb2_host); + dp->pipe_default = devm_apple_tunable_parse(dev, dev->of_node, + "apple,tunable-pipe-default", &dp->resources[44]); + if (IS_ERR(dp->pipe_default)) + return PTR_ERR(dp->pipe_default); + dp->usb_io = (struct apple_t6050_usb_io) { + .context = dp, .read = combo_read, .write = combo_write, + .poll = combo_poll, .delay = combo_delay, + }; + dp->usb_platform = (struct apple_t6050_usb_platform) { + .context = dp, .io = &dp->usb_io, .wrapper_power = combo_wrapper, + .firmware = combo_firmware, .tunable = combo_tunable, .pause_ns = combo_pause, + }; + dp->reset = (struct reset_controller_dev) { + .owner = THIS_MODULE, .ops = &combo_reset_ops, .dev = dev, + .of_node = dev->of_node, .nr_resets = 1, + }; + ret = devm_reset_controller_register(dev, &dp->reset); + if (ret) + return ret; + for (unsigned int i = 0; i < 2; i++) { + dp->combo_phy[i] = devm_phy_create(dev, NULL, i ? &combo_usb3_ops : &combo_usb2_ops); + if (IS_ERR(dp->combo_phy[i])) + return PTR_ERR(dp->combo_phy[i]); + phy_set_drvdata(dp->combo_phy[i], dp); + } + return 0; +} diff --git a/drivers/phy/apple/atc-t6050-dp.c b/drivers/phy/apple/atc-t6050-dp.c new file mode 100644 index 0000000000000..cafee096a6250 --- /dev/null +++ b/drivers/phy/apple/atc-t6050-dp.c @@ -0,0 +1,806 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* T6050 DisplayPort PHY for fixed bridges and Type-C DP alternate mode. */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "usb-t6050/apple_t6050_usb_phy.h" +#include "usb-t6050/apple_t6050_usb3_pipe.h" +#include "usb-t6050/apple_t6050_usb2_reset.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +enum atc_operation { ATC_MASK, ATC_TUNABLE, ATC_DELAY, ATC_POLL, ATC_POWER, ATC_FIRMWARE }; +struct atc_op { + u8 op, bank; + u16 offset; + u32 mask, value, timeout; +}; +struct atc_rate_op { + struct atc_op op; + u8 rates; + u32 values[4]; +}; +#include "atc-t6050-dp-data.h" +#include "atc-t6050-typec-data.h" + +static const struct { u8 bank; u32 offset; } core_banks[] = { + {2, 0}, {3, 0x400}, {4, 0x800}, {5, 0xa00}, {6, 0x1000}, + {7, 0x3000}, {8, 0x3200}, {9, 0x3800}, {10, 0x3a00}, + {14, 0x7000}, {16, 0x9000}, {18, 0xb000}, {19, 0xc000}, + {20, 0xd000}, {21, 0xe000}, {22, 0xf000}, {25, 0x19000}, + {27, 0x1b000}, {28, 0x1c000}, {29, 0x1d000}, {30, 0x1e000}, + {31, 0x1f000}, {33, 0x28000}, {34, 0x28400}, {35, 0x29000}, +}; +static const struct { u8 bank; const char *name; } extra_banks[] = { + {39, "reset"}, {40, "fabric"}, {41, "axi2af"}, + {42, "lioa"}, {43, "common-cfg"}, + {38, "usb-control"}, +}; +static const struct { u8 bank; const char *name; } tunable_names[] = { + {41, "apple,tunable-axi2af"}, {40, "apple,tunable-fabric"}, + {43, "apple,tunable-common-cfg"}, {42, "apple,tunable-lioa"}, + {20, "apple,tunable-lane0-rx-clock"}, {29, "apple,tunable-lane1-rx-clock"}, + {18, "apple,tunable-lane0-rx-eq"}, {27, "apple,tunable-lane1-rx-eq"}, + {8, "apple,tunable-auspll"}, + {5, "apple,tunable-aus40cmn-shm"}, {10, "apple,tunable-cio4pll"}, +}; +static const unsigned int link_rates[] = {1620, 2700, 5400, 8100}; +static const char * const training_names[] = { + "apple,dp-training-table-rbr", "apple,dp-training-table-hbr", + "apple,dp-training-table-hbr2", "apple,dp-training-table-hbr3", +}; +struct atc_domain { + struct device *dev; + bool active; +}; +enum combo_mode { COMBO_SAFE, COMBO_USB, COMBO_DP4, COMBO_DP2 }; +struct t6050_dp { + bool combo, usb_prepared, usb2_active, pipe_active, controller_active; + enum combo_mode combo_mode; + enum typec_orientation orientation; + struct phy *combo_phy[3]; + struct reset_controller_dev reset; + struct apple_t6050_usb_io usb_io; + struct apple_t6050_usb_platform usb_platform; + struct apple_tunable *usb2_default, *usb2_host, *pipe_default; + struct device *dev; + struct mutex lock; + void __iomem *banks[45]; + struct resource resources[45]; + struct apple_tunable *tunables[ARRAY_SIZE(tunable_names)]; + struct atc_domain domain[3]; /* USB AON, common, wrapper: native order. */ + u32 training[4][16]; + u32 voltage[4], pre[4]; + int rate; + bool enabled, faulted; + bool typec, reverse, multifunction; + struct apple_rtkit *rtkit; + void __iomem *fw_data; + bool fw_running; +}; + +static int atc_domain_set(struct atc_domain *pd, bool active) +{ + int ret; + + if (pd->active == active) + return 0; + if (active) { + ret = pm_runtime_resume_and_get(pd->dev); + if (ret < 0) + return ret; + } else { + ret = pm_runtime_put_sync_suspend(pd->dev); + if (ret < 0) { + /* put decremented usage even when suspend failed. */ + pm_runtime_get_noresume(pd->dev); + return ret; + } + } + pd->active = active; + return 0; +} + +static void atc_domain_release(void *data) +{ + struct atc_domain *pd = data; + + if (pd->active) + pm_runtime_put_sync_suspend(pd->dev); + dev_pm_domain_detach(pd->dev, true); +} + +/* The bootloader preloads CIO80 firmware into local SRAM. The firmware's + * data segment is 0x10000000..0x10010000, not guest system RAM. */ +static int atc_fw_shmem_setup(void *cookie, struct apple_rtkit_shmem *bfr) +{ + struct t6050_dp *dp = cookie; + + if (bfr->iova < 0x10000000 || + bfr->size > 0x10000 || bfr->iova - 0x10000000 > 0x10000 - bfr->size) { + dev_err(dp->dev, "unsupported PHY firmware buffer %llx size %zx\n", + (unsigned long long)bfr->iova, bfr->size); + return -EINVAL; + } + bfr->iomem = dp->fw_data + (bfr->iova - 0x10000000); + bfr->is_mapped = true; + return 0; +} + +static void atc_fw_shmem_destroy(void *cookie, struct apple_rtkit_shmem *bfr) +{ + /* SRAM is owned by the PHY and mapped for the device lifetime. */ +} + +static void atc_fw_message(void *cookie, u8 endpoint, u64 message) +{ + struct t6050_dp *dp = cookie; + + dev_dbg(dp->dev, "PHY firmware ep%x: %016llx\n", endpoint, message); +} + +static void atc_fw_crashed(void *cookie, const void *log, size_t size) +{ + struct t6050_dp *dp = cookie; + + WRITE_ONCE(dp->faulted, true); + dev_err(dp->dev, "PHY firmware crashed\n"); +} + +static const struct apple_rtkit_ops atc_fw_ops = { + .shmem_setup = atc_fw_shmem_setup, + .shmem_destroy = atc_fw_shmem_destroy, + .recv_message = atc_fw_message, + .crashed = atc_fw_crashed, +}; + +static int atc_firmware(struct t6050_dp *dp, bool start) +{ + int ret; + + if (!dp->rtkit) + return -ENODEV; + if (dp->fw_running == start) + return 0; + if (start) { + /* Native coprocessor run, after its +0x40 firmware enable. */ + writel(BIT(4), dp->banks[38] + 0x28); + ret = apple_rtkit_wake(dp->rtkit); + if (!ret) + ret = apple_rtkit_start_ep(dp->rtkit, 0x20); + } else { + /* CIO80PhyMBI::sendPowerDown, then the RTBuddy power transition. */ + ret = apple_rtkit_send_message(dp->rtkit, 0x20, BIT_ULL(56), NULL, false); + if (!ret) + ret = apple_rtkit_shutdown(dp->rtkit); + if (!ret) + writel((u32)~BIT(4), dp->banks[38] + 0x28); + } + if (ret) + dev_err(dp->dev, "PHY firmware %s failed: %d\n", start ? "start" : "stop", ret); + else + dp->fw_running = start; + return ret; +} + +static int atc_validate_step(struct t6050_dp *dp, const struct atc_op *s) +{ + if (s->op == ATC_DELAY) + return s->value <= 10000 ? 0 : -EINVAL; + if (s->op == ATC_POWER) + return s->value <= 1 ? 0 : -EINVAL; + if (s->op == ATC_FIRMWARE) + return dp->typec && s->value <= 1 ? 0 : -EINVAL; + if (s->bank >= ARRAY_SIZE(dp->banks) || !dp->banks[s->bank] || + s->offset % 4 || s->offset + 4 > resource_size(&dp->resources[s->bank])) + return -EINVAL; + if (s->op == ATC_TUNABLE) + return s->value < ARRAY_SIZE(tunable_names) && + tunable_names[s->value].bank == s->bank ? 0 : -EINVAL; + if (s->op == ATC_MASK || s->op == ATC_POLL) + return s->value & ~s->mask ? -EINVAL : 0; + return -EINVAL; +} + +static int atc_step(struct t6050_dp *dp, const struct atc_op *s) +{ + void __iomem *reg; + u32 value; + int ret = atc_validate_step(dp, s); + + if (ret) + return ret; + if (s->op == ATC_DELAY) { + udelay(s->value); + return 0; + } + if (s->op == ATC_FIRMWARE) + return atc_firmware(dp, s->value); + if (s->op == ATC_POWER) + return atc_domain_set(&dp->domain[2], s->value); + if (s->bank >= ARRAY_SIZE(dp->banks) || !dp->banks[s->bank] || + s->offset + 4 > resource_size(&dp->resources[s->bank])) + return -EINVAL; + reg = dp->banks[s->bank] + s->offset; + switch (s->op) { + case ATC_MASK: + value = readl(reg); + writel((value & ~s->mask) | s->value, reg); + return 0; + case ATC_TUNABLE: + if (s->value >= ARRAY_SIZE(dp->tunables)) + return -EINVAL; + apple_tunable_apply(dp->banks[s->bank], dp->tunables[s->value]); + return 0; + case ATC_POLL: + ret = readl_poll_timeout(reg, value, (value & s->mask) == s->value, + s->timeout > 1000 ? 1000 : 1, s->timeout); + if (ret) + dev_err(dp->dev, "bank%u+%x timeout: %x & %x != %x\n", + s->bank, s->offset, value, s->mask, s->value); + return ret; + default: + return -EINVAL; + } +} + +static int atc_sequence(struct t6050_dp *dp, const struct atc_op *s, size_t count) +{ + for (size_t i = 0; i < count; i++) { + int ret = atc_step(dp, &s[i]); + + if (ret) { + dp->faulted = true; + return ret; + } + } + return 0; +} +#define ATC_SEQUENCE(dp, seq) atc_sequence(dp, seq, ARRAY_SIZE(seq)) + +static int atc_link_rate(struct t6050_dp *dp, int rate) +{ + const struct atc_rate_op *on = dp_link_on; + size_t nr_on = ARRAY_SIZE(dp_link_on); + int ret; + + if (dp->multifunction) { + on = dp->reverse ? dp_group0_on : dp_group1_on; + nr_on = dp->reverse ? ARRAY_SIZE(dp_group0_on) : ARRAY_SIZE(dp_group1_on); + } + if (dp->rate == rate) + return 0; + if (dp->rate >= 0) { + if (dp->multifunction) + ret = dp->reverse ? ATC_SEQUENCE(dp, dp_group0_off) : + ATC_SEQUENCE(dp, dp_group1_off); + else + ret = ATC_SEQUENCE(dp, dp_link_off); + if (ret) + return ret; + dp->rate = -1; + } + if (rate < 0) + return 0; + for (size_t i = 0; i < nr_on; i++) { + struct atc_op op = on[i].op; + + if (!(on[i].rates & BIT(rate))) + continue; + op.value = on[i].values[rate]; + ret = atc_step(dp, &op); + if (ret) { + dp->faulted = true; + return ret; + } + } + dp->rate = rate; + memset(dp->voltage, 0, sizeof(dp->voltage)); + memset(dp->pre, 0, sizeof(dp->pre)); + return 0; +} + +static int atc_start(struct t6050_dp *dp) +{ + int ret; + + if (dp->faulted) + return -EIO; + if (dp->enabled) + return 0; + ret = atc_domain_set(&dp->domain[0], true); + if (ret) + return ret; + ret = atc_domain_set(&dp->domain[1], true); + if (ret) { + atc_domain_set(&dp->domain[0], false); + return ret; + } + if (dp->multifunction) + ret = dp->reverse ? ATC_SEQUENCE(dp, mf_reverse_init) : + ATC_SEQUENCE(dp, mf_normal_init); + else + ret = dp->reverse ? ATC_SEQUENCE(dp, dp_reverse_init) : + ATC_SEQUENCE(dp, dp_init); + if (!ret) + ret = ATC_SEQUENCE(dp, aux_on); + if (ret) + return ret; + dp->enabled = true; + return 0; +} + +static int atc_stop(struct t6050_dp *dp) +{ + int ret; + + if (dp->faulted) + return -EIO; + if (!dp->enabled) + return 0; + ret = atc_link_rate(dp, -1); + if (!ret) + ret = ATC_SEQUENCE(dp, aux_off); + if (!ret) + ret = dp->multifunction ? ATC_SEQUENCE(dp, mf_poweroff) : + ATC_SEQUENCE(dp, dp_poweroff); + if (ret) + return ret; + dp->enabled = false; + /* Combo owns domain shutdown after USB2 and PIPE have also quiesced. */ + if (dp->combo) + return 0; + ret = atc_domain_set(&dp->domain[1], false); + if (!ret) + ret = atc_domain_set(&dp->domain[0], false); + return ret; +} + +static void atc_mask(void __iomem *reg, u32 mask, u32 value) +{ + writel((readl(reg) & ~mask) | value, reg); +} + +static void atc_drive(struct t6050_dp *dp, unsigned int lane, u32 preset) +{ + /* Native DP lane order is RX/TX in each orientation-selected group. */ + unsigned int group = (lane < 2 ? 1 : 0) ^ dp->reverse; + bool tx = lane & 1; + void __iomem *base = dp->banks[(tx ? 21 : 20) + 9 * group]; + u32 ctrl = ((preset >> 2) & 0xf) | ((preset & 3) << 14); + u32 eq = ((preset >> 5) & 0x7fffe) | 1; + + if (tx) + atc_mask(base + 0x100, BIT(20), BIT(20)); + atc_mask(base + (tx ? 0x58 : 0x68), 0xc00f, ctrl); + atc_mask(base + (tx ? 0x104 : 0xac), BIT(29), BIT(29)); + atc_mask(base + (tx ? 0x100 : 0xa8), 0x7ffff, eq); +} + +#include "atc-t6050-combo.h" + +static int t6050_dp_set_mode(struct phy *phy, enum phy_mode mode, int submode) +{ + struct t6050_dp *dp = phy_get_drvdata(phy); + + guard(mutex)(&dp->lock); + if (mode == PHY_MODE_DP) + return dp->typec ? (dp->enabled ? 0 : -ENOLINK) : atc_start(dp); + if (mode == PHY_MODE_INVALID) + return dp->combo ? atc_link_rate(dp, -1) : atc_stop(dp); + return -EINVAL; +} + +static int t6050_dp_validate(struct phy *phy, enum phy_mode mode, int submode, + union phy_configure_opts *opts) +{ + struct t6050_dp *dp = phy_get_drvdata(phy); + + guard(mutex)(&dp->lock); + if (mode != PHY_MODE_DP) + return -EINVAL; + memset(opts, 0, sizeof(*opts)); + opts->dp.lanes = dp->multifunction ? 2 : 4; + opts->dp.link_rate = 8100; + opts->dp.ssc = 0; + return 0; +} + +static int t6050_dp_configure(struct phy *phy, union phy_configure_opts *opts) +{ + struct t6050_dp *dp = phy_get_drvdata(phy); + struct phy_configure_opts_dp *cfg = &opts->dp; + int rate = -1, ret; + + /* Validate the whole request before altering a running transmitter. */ + if (cfg->set_rate && cfg->link_rate) { + for (unsigned int i = 0; i < ARRAY_SIZE(link_rates); i++) + if (cfg->link_rate == link_rates[i]) + rate = i; + if (rate < 0 || cfg->ssc) + return -EINVAL; + } + if ((cfg->set_lanes || cfg->set_voltages) && + cfg->lanes != 0 && cfg->lanes != 1 && cfg->lanes != 2 && cfg->lanes != 4) + return -EINVAL; + if (cfg->set_voltages) + for (unsigned int i = 0; i < cfg->lanes; i++) + if (cfg->voltage[i] > 3 || cfg->pre[i] > 3 || + cfg->voltage[i] + cfg->pre[i] > 3) + return -EINVAL; + if (cfg->set_voltages && cfg->set_rate && rate < 0 && cfg->lanes) + return -EINVAL; + guard(mutex)(&dp->lock); + if (dp->faulted) + return -EIO; + if (!dp->enabled) + return -EPIPE; + if ((cfg->set_lanes || cfg->set_voltages) && dp->multifunction && cfg->lanes > 2) + return -EINVAL; + if (cfg->set_rate) { + ret = atc_link_rate(dp, rate); + if (ret) + return ret; + } + /* Native setActiveLaneCount is a no-op; DCP controls the active count. */ + if (cfg->set_voltages) { + if (dp->rate < 0) + return -EPIPE; + for (unsigned int i = 0; i < cfg->lanes; i++) { + u32 preset = dp->training[dp->rate][4 * cfg->voltage[i] + cfg->pre[i]]; + + atc_drive(dp, i, preset); + dp->voltage[i] = cfg->voltage[i]; + dp->pre[i] = cfg->pre[i]; + } + } + return 0; +} + +static const struct phy_ops t6050_dp_ops = { + .set_mode = t6050_dp_set_mode, + .validate = t6050_dp_validate, + .configure = t6050_dp_configure, + .owner = THIS_MODULE, +}; + +int apple_dp_phy_link_config(struct phy *phy, unsigned int *pclk, bool *uhbr) +{ + struct t6050_dp *dp; + + if (!phy || phy->ops != &t6050_dp_ops) + return -EOPNOTSUPP; + dp = phy_get_drvdata(phy); + guard(mutex)(&dp->lock); + if (dp->faulted) + return -EIO; + if (!dp->enabled || dp->rate < 0) + return -ENOLINK; + /* Native direct DPPHY uses logical clock0, mapped to xbar PCLK1. + * The other four logical clocks are separately managed tunnel clocks. */ + *pclk = 1; + *uhbr = false; + return 0; +} +EXPORT_SYMBOL_GPL(apple_dp_phy_link_config); + +/* The connector sets orientation before changing the alternate-mode mux. */ +static int t6050_set_orientation(struct t6050_dp *dp, + enum typec_orientation orientation) +{ + bool reverse = orientation == TYPEC_ORIENTATION_REVERSE; + int ret; + + if (dp->combo) + return combo_orientation(dp, orientation); + guard(mutex)(&dp->lock); + if (orientation == TYPEC_ORIENTATION_NONE) + return atc_stop(dp); + if (dp->reverse == reverse) + return 0; + ret = atc_stop(dp); + if (!ret) + dp->reverse = reverse; + return ret; +} + +static int t6050_set_typec_mode(struct t6050_dp *dp, struct typec_mux_state *state) +{ + bool multifunction; + int ret; + + if (dp->combo) + return combo_mux(dp, state); + guard(mutex)(&dp->lock); + if (state->mode == TYPEC_STATE_SAFE) + return atc_stop(dp); + /* USB-only and USB4 require their controller's separate PHY interface. */ + if (!state->alt || state->alt->svid != USB_TYPEC_DP_SID) + return -EOPNOTSUPP; + switch (state->mode) { + case TYPEC_DP_STATE_C: + case TYPEC_DP_STATE_E: + multifunction = false; + break; + case TYPEC_DP_STATE_D: + multifunction = true; + break; + default: + return -EOPNOTSUPP; + } + if (dp->enabled && dp->multifunction == multifunction) + return 0; + ret = atc_stop(dp); + if (ret) + return ret; + dp->multifunction = multifunction; + ret = atc_start(dp); + if (!ret) + dev_info(dp->dev, "Type-C DP: %u lanes, %s orientation\n", + multifunction ? 2 : 4, dp->reverse ? "reverse" : "normal"); + return ret; +} + +static int t6050_typec_orientation(struct typec_switch_dev *sw, + enum typec_orientation orientation) +{ + return t6050_set_orientation(typec_switch_get_drvdata(sw), orientation); +} + +static int t6050_typec_mode(struct typec_mux_dev *mux, struct typec_mux_state *state) +{ + return t6050_set_typec_mode(typec_mux_get_drvdata(mux), state); +} + +static void t6050_typec_switch_release(void *data) +{ + typec_switch_unregister(data); +} + +static void t6050_typec_mux_release(void *data) +{ + typec_mux_unregister(data); +} + +static int t6050_typec_register(struct t6050_dp *dp) +{ + struct typec_switch_desc sw_desc = { + .fwnode = dev_fwnode(dp->dev), .drvdata = dp, + .set = t6050_typec_orientation, + }; + struct typec_mux_desc mux_desc = { + .fwnode = dev_fwnode(dp->dev), .drvdata = dp, + .set = t6050_typec_mode, + }; + struct typec_switch_dev *sw; + struct typec_mux_dev *mux; + int ret; + + sw = typec_switch_register(dp->dev, &sw_desc); + if (IS_ERR(sw)) + return PTR_ERR(sw); + ret = devm_add_action_or_reset(dp->dev, t6050_typec_switch_release, sw); + if (ret) + return ret; + mux = typec_mux_register(dp->dev, &mux_desc); + if (IS_ERR(mux)) + return PTR_ERR(mux); + return devm_add_action_or_reset(dp->dev, t6050_typec_mux_release, mux); +} + +static int t6050_dp_probe(struct platform_device *pdev) +{ + static const char * const domains[] = {"usb-aon", "common", "wrapper"}; + struct device *dev = &pdev->dev; + struct t6050_dp *dp; + struct phy_provider *provider; + struct phy *phy; + struct resource *core; + void __iomem *base; + int ret; + + dp = devm_kzalloc(dev, sizeof(*dp), GFP_KERNEL); + if (!dp) + return -ENOMEM; + dp->dev = dev; + dp->combo = of_device_is_compatible(dev->of_node, "apple,t6050-combo-phy"); + if (dp->combo) { + u32 port; + if (of_property_read_u32(dev->of_node, "apple,port-index", &port) || + (port != 1 && port != 2) || + !of_property_present(dev->of_node, "apple,guest-port-reserved") || + !of_property_present(dev->of_node, "mode-switch")) + return -EINVAL; + } + dp->typec = of_property_present(dev->of_node, "mode-switch"); + dp->rate = -1; + mutex_init(&dp->lock); + platform_set_drvdata(pdev, dp); + core = platform_get_resource_byname(pdev, IORESOURCE_MEM, "core"); + if (!core || resource_size(core) < (dp->combo ? 0x2c000 : 0x2c400)) + return -EINVAL; + base = devm_ioremap_resource(dev, core); + if (IS_ERR(base)) + return PTR_ERR(base); + for (unsigned int i = 0; i < ARRAY_SIZE(core_banks); i++) { + u8 bank = core_banks[i].bank; + + dp->banks[bank] = base + core_banks[i].offset; + dp->resources[bank] = *core; + dp->resources[bank].start += core_banks[i].offset; + dp->resources[bank].end = core->end; + } + for (unsigned int i = 0; i < ARRAY_SIZE(extra_banks); i++) { + u8 bank = extra_banks[i].bank; + struct resource *res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + extra_banks[i].name); + + if (bank == 38 && !dp->typec) + continue; + if (!res || resource_size(res) < 4) + return -EINVAL; + dp->banks[bank] = devm_ioremap_resource(dev, res); + if (IS_ERR(dp->banks[bank])) + return PTR_ERR(dp->banks[bank]); + dp->resources[bank] = *res; + } + /* Check every register step before any domain is enabled. */ + { + static const struct { const struct atc_op *ops; size_t count; } sequences[] = { + { dp_init, ARRAY_SIZE(dp_init) }, + { aux_on, ARRAY_SIZE(aux_on) }, + { aux_off, ARRAY_SIZE(aux_off) }, + { dp_link_off, ARRAY_SIZE(dp_link_off) }, + { dp_poweroff, ARRAY_SIZE(dp_poweroff) }, + }; + + for (unsigned int i = 0; i < ARRAY_SIZE(sequences); i++) + for (size_t j = 0; j < sequences[i].count; j++) { + ret = atc_validate_step(dp, &sequences[i].ops[j]); + if (ret) + return ret; + } + for (unsigned int i = 0; i < ARRAY_SIZE(dp_link_on); i++) + for (unsigned int rate = 0; rate < ARRAY_SIZE(link_rates); rate++) { + struct atc_op op = dp_link_on[i].op; + + if (!(dp_link_on[i].rates & BIT(rate))) + continue; + op.value = dp_link_on[i].values[rate]; + ret = atc_validate_step(dp, &op); + if (ret) + return ret; + } + } + if (dp->typec) { + static const struct { const struct atc_op *ops; size_t count; } sequences[] = { + { dp_reverse_init, ARRAY_SIZE(dp_reverse_init) }, + { mf_normal_init, ARRAY_SIZE(mf_normal_init) }, + { mf_reverse_init, ARRAY_SIZE(mf_reverse_init) }, + { mf_poweroff, ARRAY_SIZE(mf_poweroff) }, + { dp_group0_off, ARRAY_SIZE(dp_group0_off) }, + { dp_group1_off, ARRAY_SIZE(dp_group1_off) }, + }; + const struct atc_rate_op *rates[] = { dp_group0_on, dp_group1_on }; + const size_t counts[] = { ARRAY_SIZE(dp_group0_on), ARRAY_SIZE(dp_group1_on) }; + + for (unsigned int i = 0; i < ARRAY_SIZE(sequences); i++) + for (size_t j = 0; j < sequences[i].count; j++) { + ret = atc_validate_step(dp, &sequences[i].ops[j]); + if (ret) + return ret; + } + for (unsigned int group = 0; group < ARRAY_SIZE(rates); group++) + for (size_t i = 0; i < counts[group]; i++) + for (unsigned int rate = 0; rate < ARRAY_SIZE(link_rates); rate++) { + struct atc_op op = rates[group][i].op; + if (!(rates[group][i].rates & BIT(rate))) + continue; + op.value = rates[group][i].values[rate]; + ret = atc_validate_step(dp, &op); + if (ret) + return ret; + } + } + for (unsigned int i = 0; i < ARRAY_SIZE(tunable_names); i++) { + if (i >= 9 && !dp->typec) + continue; + dp->tunables[i] = devm_apple_tunable_parse(dev, dev->of_node, + tunable_names[i].name, &dp->resources[tunable_names[i].bank]); + if (IS_ERR(dp->tunables[i])) + return dev_err_probe(dev, PTR_ERR(dp->tunables[i]), + "missing or invalid %s\n", tunable_names[i].name); + } + for (unsigned int i = 0; i < ARRAY_SIZE(training_names); i++) { + const char *name = training_names[i]; + + if (!of_property_present(dev->of_node, name)) + name = "apple,dp-training-table"; + if (!of_property_present(dev->of_node, name)) { + memcpy(dp->training[i], dp_training_default, sizeof(dp_training_default)); + continue; + } + if (of_property_count_u32_elems(dev->of_node, name) != 16) + return -EINVAL; + ret = of_property_read_u32_array(dev->of_node, name, dp->training[i], 16); + if (ret) + return ret; + } + /* Three named domains prevent automatic wrapper enable at probe time. */ + if (of_count_phandle_with_args(dev->of_node, "power-domains", "#power-domain-cells") != 3) + return -EINVAL; + for (unsigned int i = 0; i < ARRAY_SIZE(domains); i++) { + dp->domain[i].dev = dev_pm_domain_attach_by_name(dev, domains[i]); + if (IS_ERR_OR_NULL(dp->domain[i].dev)) + return dev_err_probe(dev, dp->domain[i].dev ? PTR_ERR(dp->domain[i].dev) : -ENODEV, + "cannot attach %s domain\n", domains[i]); + ret = devm_add_action_or_reset(dev, atc_domain_release, &dp->domain[i]); + if (ret) + return ret; + } + if (dp->typec) { + dp->fw_data = devm_platform_ioremap_resource_byname(pdev, "firmware-data"); + if (IS_ERR(dp->fw_data)) + return PTR_ERR(dp->fw_data); + dp->rtkit = devm_apple_rtkit_init(dev, dp, NULL, 0, &atc_fw_ops); + if (IS_ERR(dp->rtkit)) + return dev_err_probe(dev, PTR_ERR(dp->rtkit), "cannot initialize PHY firmware IPC\n"); + } + if (dp->combo) { + ret = combo_setup(pdev, dp); + if (ret) + return ret; + } + phy = devm_phy_create(dev, NULL, &t6050_dp_ops); + if (IS_ERR(phy)) + return PTR_ERR(phy); + phy_set_drvdata(phy, dp); + dp->combo_phy[2] = phy; + provider = devm_of_phy_provider_register(dev, dp->combo ? combo_xlate : of_phy_simple_xlate); + if (IS_ERR(provider)) + return PTR_ERR(provider); + return dp->typec ? t6050_typec_register(dp) : 0; +} + +static void t6050_dp_shutdown(struct platform_device *pdev) +{ + struct t6050_dp *dp = platform_get_drvdata(pdev); + + guard(mutex)(&dp->lock); + if (dp->combo ? combo_stop(dp) : atc_stop(dp)) + dev_warn(dp->dev, "PHY shutdown incomplete after an earlier error\n"); +} + +static const struct of_device_id t6050_dp_match[] = { + { .compatible = "apple,t6050-dp-phy" }, + { .compatible = "apple,t6050-combo-phy" }, + {}, +}; +MODULE_DEVICE_TABLE(of, t6050_dp_match); +static struct platform_driver t6050_dp_driver = { + .probe = t6050_dp_probe, + .remove = t6050_dp_shutdown, + .shutdown = t6050_dp_shutdown, + .driver = { + .name = "apple-t6050-dp-phy", + .of_match_table = t6050_dp_match, + }, +}; +module_platform_driver(t6050_dp_driver); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Apple T6050 DisplayPort and Type-C USB combo PHY"); diff --git a/drivers/phy/apple/atc-t6050-typec-data.h b/drivers/phy/apple/atc-t6050-typec-data.h new file mode 100644 index 0000000000000..096307b657c08 --- /dev/null +++ b/drivers/phy/apple/atc-t6050-typec-data.h @@ -0,0 +1,700 @@ +/* SPDX-License-Identifier: GPL-2.0-only OR MIT */ +/* Native firmware27 Type-C DP recipes; board tunables remain DT data. */ +static const struct atc_op dp_reverse_init[] = { + { ATC_MASK, 39, 0x0, 0x1, 0x1, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_POLL, 39, 0x4, 0x1, 0x1, 1000 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 39, 0x0, 0x2, 0x2, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_POLL, 39, 0x4, 0x2, 0x2, 1000 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 39, 0x0, 0x4, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_POWER, 2, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 39, 0x2c, 0x1, 0x0, 0 }, + { ATC_POLL, 39, 0x4, 0x100, 0x0, 1000 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 39, 0x0, 0x8, 0x8, 0 }, + { ATC_TUNABLE, 41, 0x0, 0x0, 0x0, 0 }, + { ATC_TUNABLE, 40, 0x0, 0x0, 0x1, 0 }, + { ATC_TUNABLE, 43, 0x0, 0x0, 0x2, 0 }, + { ATC_TUNABLE, 42, 0x0, 0x0, 0x3, 0 }, + { ATC_TUNABLE, 20, 0x0, 0x0, 0x4, 0 }, + { ATC_TUNABLE, 29, 0x0, 0x0, 0x5, 0 }, + { ATC_TUNABLE, 18, 0x0, 0x0, 0x6, 0 }, + { ATC_TUNABLE, 27, 0x0, 0x0, 0x7, 0 }, + { ATC_MASK, 4, 0x0, 0x3f0, 0x1c0, 0 }, + { ATC_MASK, 4, 0x18, 0xf000, 0x3000, 0 }, + { ATC_MASK, 9, 0x88, 0xff, 0x27, 0 }, + { ATC_MASK, 33, 0x4, 0x38, 0x8, 0 }, + { ATC_MASK, 10, 0xdc, 0x3ff0, 0x1760, 0 }, + { ATC_MASK, 21, 0xcc, 0x1f80000, 0x380000, 0 }, + { ATC_MASK, 30, 0xcc, 0x1f80000, 0x380000, 0 }, + { ATC_MASK, 20, 0x90, 0x1f80000, 0x380000, 0 }, + { ATC_MASK, 29, 0x90, 0x1f80000, 0x380000, 0 }, + { ATC_TUNABLE, 8, 0x0, 0x0, 0x8, 0 }, + { ATC_MASK, 2, 0x6c, 0x3c000fc, 0x48, 0 }, + { ATC_MASK, 2, 0x78, 0x1f, 0x14, 0 }, + { ATC_MASK, 2, 0x78, 0x3ffe0, 0x100, 0 }, + { ATC_MASK, 2, 0x6c, 0x3, 0x2, 0 }, + { ATC_MASK, 2, 0xc, 0x2a, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0xa, 0 }, + { ATC_MASK, 39, 0x0, 0x10, 0x10, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x64, 0 }, +}; + +static const struct atc_op mf_normal_init[] = { + { ATC_MASK, 39, 0x0, 0x1, 0x1, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_POLL, 39, 0x4, 0x1, 0x1, 1000 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 39, 0x0, 0x2, 0x2, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_POLL, 39, 0x4, 0x2, 0x2, 1000 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 39, 0x0, 0x4, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_POWER, 2, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 39, 0x2c, 0x1, 0x0, 0 }, + { ATC_POLL, 39, 0x4, 0x100, 0x0, 1000 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 39, 0x0, 0x8, 0x8, 0 }, + { ATC_TUNABLE, 41, 0x0, 0x0, 0x0, 0 }, + { ATC_TUNABLE, 40, 0x0, 0x0, 0x1, 0 }, + { ATC_TUNABLE, 43, 0x0, 0x0, 0x2, 0 }, + { ATC_TUNABLE, 42, 0x0, 0x0, 0x3, 0 }, + { ATC_TUNABLE, 20, 0x0, 0x0, 0x4, 0 }, + { ATC_TUNABLE, 29, 0x0, 0x0, 0x5, 0 }, + { ATC_TUNABLE, 18, 0x0, 0x0, 0x6, 0 }, + { ATC_TUNABLE, 27, 0x0, 0x0, 0x7, 0 }, + { ATC_TUNABLE, 5, 0x0, 0x0, 0x9, 0 }, + { ATC_TUNABLE, 8, 0x0, 0x0, 0x8, 0 }, + { ATC_TUNABLE, 10, 0x0, 0x0, 0xa, 0 }, + { ATC_MASK, 4, 0x0, 0x3f0, 0x1c0, 0 }, + { ATC_MASK, 4, 0x18, 0xf000, 0x3000, 0 }, + { ATC_MASK, 9, 0x88, 0xff, 0x27, 0 }, + { ATC_MASK, 33, 0x4, 0x38, 0x8, 0 }, + { ATC_MASK, 10, 0xdc, 0x3ff0, 0x1760, 0 }, + { ATC_MASK, 21, 0xcc, 0x1f80000, 0x380000, 0 }, + { ATC_MASK, 30, 0xcc, 0x1f80000, 0x380000, 0 }, + { ATC_MASK, 20, 0x90, 0x1f80000, 0x380000, 0 }, + { ATC_MASK, 29, 0x90, 0x1f80000, 0x380000, 0 }, + { ATC_TUNABLE, 8, 0x0, 0x0, 0x8, 0 }, + { ATC_MASK, 2, 0x6c, 0x28000fc, 0x44, 0 }, + { ATC_MASK, 2, 0x78, 0x1ffff, 0x110, 0 }, + { ATC_MASK, 2, 0x6c, 0x3, 0x2, 0 }, + { ATC_MASK, 2, 0xc, 0x2a, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0x10, 0x4, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0x10, 0x40, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0xc, 0x10000, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0xc, 0x100, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0xc, 0x1000, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0x120, 0x40, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0x120, 0x4, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0x120, 0x400, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 39, 0x8, 0x2, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0xa, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0xa, 0 }, + { ATC_MASK, 38, 0x40, 0x1, 0x1, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0xa, 0 }, + { ATC_FIRMWARE, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0xa, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1388, 0 }, + { ATC_POLL, 2, 0x260, 0x400, 0x400, 1000000 }, + { ATC_MASK, 39, 0x0, 0x10, 0x10, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x64, 0 }, +}; + +static const struct atc_op mf_reverse_init[] = { + { ATC_MASK, 39, 0x0, 0x1, 0x1, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_POLL, 39, 0x4, 0x1, 0x1, 1000 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 39, 0x0, 0x2, 0x2, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_POLL, 39, 0x4, 0x2, 0x2, 1000 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 39, 0x0, 0x4, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_POWER, 2, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 39, 0x2c, 0x1, 0x0, 0 }, + { ATC_POLL, 39, 0x4, 0x100, 0x0, 1000 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 39, 0x0, 0x8, 0x8, 0 }, + { ATC_TUNABLE, 41, 0x0, 0x0, 0x0, 0 }, + { ATC_TUNABLE, 40, 0x0, 0x0, 0x1, 0 }, + { ATC_TUNABLE, 43, 0x0, 0x0, 0x2, 0 }, + { ATC_TUNABLE, 42, 0x0, 0x0, 0x3, 0 }, + { ATC_TUNABLE, 20, 0x0, 0x0, 0x4, 0 }, + { ATC_TUNABLE, 29, 0x0, 0x0, 0x5, 0 }, + { ATC_TUNABLE, 18, 0x0, 0x0, 0x6, 0 }, + { ATC_TUNABLE, 27, 0x0, 0x0, 0x7, 0 }, + { ATC_TUNABLE, 5, 0x0, 0x0, 0x9, 0 }, + { ATC_TUNABLE, 8, 0x0, 0x0, 0x8, 0 }, + { ATC_TUNABLE, 10, 0x0, 0x0, 0xa, 0 }, + { ATC_MASK, 4, 0x0, 0x3f0, 0x1c0, 0 }, + { ATC_MASK, 4, 0x18, 0xf000, 0x3000, 0 }, + { ATC_MASK, 9, 0x88, 0xff, 0x27, 0 }, + { ATC_MASK, 33, 0x4, 0x38, 0x8, 0 }, + { ATC_MASK, 10, 0xdc, 0x3ff0, 0x1760, 0 }, + { ATC_MASK, 21, 0xcc, 0x1f80000, 0x380000, 0 }, + { ATC_MASK, 30, 0xcc, 0x1f80000, 0x380000, 0 }, + { ATC_MASK, 20, 0x90, 0x1f80000, 0x380000, 0 }, + { ATC_MASK, 29, 0x90, 0x1f80000, 0x380000, 0 }, + { ATC_TUNABLE, 8, 0x0, 0x0, 0x8, 0 }, + { ATC_MASK, 2, 0x6c, 0x14000fc, 0x28, 0 }, + { ATC_MASK, 2, 0x78, 0x1ffff, 0x111, 0 }, + { ATC_MASK, 2, 0x6c, 0x3, 0x2, 0 }, + { ATC_MASK, 2, 0xc, 0x2a, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0x10, 0x8, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0x10, 0x80, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0xc, 0x20000, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0xc, 0x200, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0xc, 0x2000, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0x120, 0x80, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0x120, 0x8, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0x120, 0x800, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 39, 0x8, 0x2, 0x2, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0xa, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0xa, 0 }, + { ATC_MASK, 38, 0x40, 0x1, 0x1, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0xa, 0 }, + { ATC_FIRMWARE, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0xa, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1388, 0 }, + { ATC_POLL, 2, 0x260, 0x400, 0x400, 1000000 }, + { ATC_MASK, 39, 0x0, 0x10, 0x10, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x64, 0 }, +}; + +static const struct atc_op mf_poweroff[] = { + { ATC_MASK, 35, 0x4, 0x1, 0x1, 0 }, + { ATC_MASK, 35, 0x10c, 0x100000, 0x0, 0 }, + { ATC_MASK, 35, 0x110, 0x60, 0x20, 0 }, + { ATC_MASK, 35, 0x4, 0x8, 0x8, 0 }, + { ATC_MASK, 35, 0x4, 0x8, 0x0, 0 }, + { ATC_POLL, 35, 0x104, 0x100, 0x100, 1000000 }, + { ATC_MASK, 35, 0x10c, 0x1e000000, 0x4000000, 0 }, + { ATC_MASK, 35, 0x110, 0x600, 0x200, 0 }, + { ATC_MASK, 35, 0x4, 0x8, 0x8, 0 }, + { ATC_MASK, 35, 0x4, 0x8, 0x0, 0 }, + { ATC_POLL, 35, 0x104, 0x100, 0x100, 1000000 }, + { ATC_MASK, 35, 0x4, 0x1, 0x0, 0 }, + { ATC_MASK, 2, 0x120, 0x100, 0x100, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0x120, 0x1, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0x120, 0x10, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0x120, 0x200, 0x200, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0x120, 0x2, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0x120, 0x20, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 5, 0x0, 0x2, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0xc, 0x10, 0x10, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0xc, 0x1, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0xc, 0x4, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0xc8, 0 }, + { ATC_FIRMWARE, 0, 0x0, 0x0, 0x0, 0 }, + { ATC_POWER, 2, 0x0, 0x0, 0x0, 0 }, + { ATC_MASK, 39, 0x2c, 0x1, 0x1, 0 }, + { ATC_POLL, 39, 0x4, 0x100, 0x100, 1000 }, + { ATC_MASK, 39, 0x0, 0x10, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 39, 0x0, 0x8, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 39, 0x0, 0x4, 0x4, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 39, 0x0, 0x2, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_POLL, 39, 0x4, 0x2, 0x0, 1000 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 39, 0x0, 0x1, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_POLL, 39, 0x4, 0x1, 0x0, 1000 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 39, 0x8, 0x1, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, +}; + +static const struct atc_op dp_group0_off[] = { + { ATC_MASK, 39, 0x14, 0x1007, 0x7, 0 }, + { ATC_MASK, 39, 0x18, 0x1, 0x0, 0 }, + { ATC_POLL, 39, 0x1c, 0x1, 0x0, 5000 }, + { ATC_MASK, 14, 0x0, 0x1, 0x1, 0 }, + { ATC_MASK, 22, 0x1c, 0xc00000, 0xc00000, 0 }, + { ATC_MASK, 22, 0x10, 0x600, 0x400, 0 }, + { ATC_MASK, 22, 0x4, 0x30000000, 0x20000000, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 22, 0x44, 0x100000, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 22, 0x44, 0x400000, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 22, 0x44, 0x4000000, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 22, 0x44, 0x1000000, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 22, 0x44, 0x1000, 0x0, 0 }, + { ATC_MASK, 2, 0x120, 0x100, 0x100, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0x120, 0x1, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0x120, 0x10, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 19, 0x44, 0x6000000, 0x4000000, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 19, 0x44, 0x18000000, 0x10000000, 0 }, + { ATC_MASK, 19, 0x24, 0xc0000000, 0x80000000, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 19, 0x24, 0x30000000, 0x20000000, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 19, 0x28, 0x10, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 19, 0x40, 0xc000, 0xc000, 0 }, + { ATC_MASK, 16, 0x1d0, 0x3, 0x1, 0 }, + { ATC_MASK, 19, 0x40, 0x10000, 0x0, 0 }, + { ATC_MASK, 2, 0xc, 0x4000, 0x4000, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0xc, 0x40, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0xc, 0x400, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0x10, 0x1, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0x10, 0x10, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 14, 0x0, 0xc, 0x8, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0x160, 0x40000, 0x40000, 0 }, + { ATC_MASK, 3, 0x8, 0x1, 0x1, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0x160, 0x80000, 0x0, 0 }, + { ATC_MASK, 3, 0x8, 0x2, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0x160, 0x80000, 0x80000, 0 }, + { ATC_MASK, 3, 0x8, 0x2, 0x2, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0x160, 0x40000, 0x0, 0 }, + { ATC_MASK, 3, 0x8, 0x1, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 6, 0x1c, 0x7, 0x1, 0 }, + { ATC_MASK, 6, 0x1c, 0x8, 0x8, 0 }, + { ATC_POLL, 6, 0x2c, 0xff00, 0x100, 500000 }, + { ATC_MASK, 6, 0x1c, 0x8, 0x0, 0 }, + { ATC_POLL, 6, 0x2c, 0xff00, 0x0, 500000 }, + { ATC_POLL, 14, 0x34, 0x8, 0x0, 500000 }, + { ATC_DELAY, 0, 0x0, 0x0, 0xa, 0 }, + { ATC_MASK, 6, 0x34, 0xf, 0x1, 0 }, + { ATC_MASK, 6, 0x1c, 0x7, 0x0, 0 }, + { ATC_MASK, 6, 0x1c, 0x8, 0x8, 0 }, + { ATC_POLL, 6, 0x2c, 0xff00, 0x100, 500000 }, + { ATC_MASK, 6, 0x1c, 0x8, 0x0, 0 }, + { ATC_POLL, 6, 0x2c, 0xff00, 0x0, 500000 }, + { ATC_MASK, 8, 0x0, 0xaaa, 0x0, 0 }, +}; + +static const struct atc_rate_op dp_group0_on[] = { + { { ATC_MASK, 39, 0x14, 0x7, 0, 0 }, 0xf, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 39, 0x18, 0x1, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 2, 0x6c, 0x1c, 0, 0 }, 0xf, { 0x8, 0x8, 0x8, 0x8 } }, + { { ATC_MASK, 14, 0x0, 0x40fff0, 0, 0 }, 0xf, { 0x1110, 0x1110, 0x1110, 0x1110 } }, + { { ATC_MASK, 8, 0x34, 0x3, 0, 0 }, 0x1, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 7, 0x80, 0xffffffff, 0, 0 }, 0x1, { 0x1e0e021c, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 7, 0x84, 0xfffffff, 0, 0 }, 0x1, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 7, 0x88, 0x7fffff, 0, 0 }, 0x1, { 0x156600, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 8, 0x8, 0x1f0000, 0, 0 }, 0x1, { 0x70000, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 8, 0x18, 0x1, 0, 0 }, 0x1, { 0x1, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 8, 0x34, 0x3, 0, 0 }, 0x2, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 7, 0x80, 0xffffffff, 0, 0 }, 0x2, { 0x0, 0x1e0e01c2, 0x0, 0x0 } }, + { { ATC_MASK, 7, 0x84, 0xfffffff, 0, 0 }, 0x2, { 0x0, 0x7fffffe, 0x0, 0x0 } }, + { { ATC_MASK, 7, 0x88, 0x7fffff, 0, 0 }, 0x2, { 0x0, 0x155200, 0x0, 0x0 } }, + { { ATC_MASK, 8, 0x8, 0x1f0000, 0, 0 }, 0x2, { 0x0, 0x70000, 0x0, 0x0 } }, + { { ATC_MASK, 8, 0x18, 0x1, 0, 0 }, 0x2, { 0x0, 0x1, 0x0, 0x0 } }, + { { ATC_MASK, 8, 0x34, 0x3, 0, 0 }, 0x4, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 7, 0x80, 0xffffffff, 0, 0 }, 0x4, { 0x0, 0x0, 0x1e0e01c2, 0x0 } }, + { { ATC_MASK, 7, 0x84, 0xfffffff, 0, 0 }, 0x4, { 0x0, 0x0, 0x7fffffe, 0x0 } }, + { { ATC_MASK, 7, 0x88, 0x7fffff, 0, 0 }, 0x4, { 0x0, 0x0, 0x554800, 0x0 } }, + { { ATC_MASK, 8, 0x8, 0x1f0000, 0, 0 }, 0x4, { 0x0, 0x0, 0x70000, 0x0 } }, + { { ATC_MASK, 8, 0x18, 0x1, 0, 0 }, 0x4, { 0x0, 0x0, 0x1, 0x0 } }, + { { ATC_MASK, 8, 0x34, 0x3, 0, 0 }, 0x8, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 7, 0x80, 0xffffffff, 0, 0 }, 0x8, { 0x0, 0x0, 0x0, 0x1e0e02a3 } }, + { { ATC_MASK, 7, 0x84, 0xfffffff, 0, 0 }, 0x8, { 0x0, 0x0, 0x0, 0xbff7ffc } }, + { { ATC_MASK, 7, 0x88, 0x7fffff, 0, 0 }, 0x8, { 0x0, 0x0, 0x0, 0x564800 } }, + { { ATC_MASK, 8, 0x8, 0x1f0000, 0, 0 }, 0x8, { 0x0, 0x0, 0x0, 0x70000 } }, + { { ATC_MASK, 8, 0x18, 0x1, 0, 0 }, 0x8, { 0x0, 0x0, 0x0, 0x1 } }, + { { ATC_MASK, 8, 0x0, 0xaaa, 0, 0 }, 0xf, { 0xaaa, 0xaaa, 0xaaa, 0xaaa } }, + { { ATC_MASK, 6, 0x30, 0x4000, 0, 0 }, 0xf, { 0x4000, 0x4000, 0x4000, 0x4000 } }, + { { ATC_MASK, 6, 0x34, 0xf, 0, 0 }, 0xf, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 6, 0x18, 0x1fe000, 0, 0 }, 0xf, { 0x2000, 0x2000, 0x2000, 0x2000 } }, + { { ATC_MASK, 6, 0x20, 0x1, 0, 0 }, 0xf, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 6, 0x1c, 0x7, 0, 0 }, 0xf, { 0x7, 0x7, 0x7, 0x7 } }, + { { ATC_MASK, 6, 0x1c, 0x8, 0, 0 }, 0xf, { 0x8, 0x8, 0x8, 0x8 } }, + { { ATC_POLL, 6, 0x2c, 0xff00, 0, 500000 }, 0xf, { 0x100, 0x100, 0x100, 0x100 } }, + { { ATC_MASK, 6, 0x1c, 0x8, 0, 0 }, 0xf, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_POLL, 6, 0x2c, 0xff00, 0, 500000 }, 0xf, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_POLL, 14, 0x34, 0x8, 0, 500000 }, 0xf, { 0x8, 0x8, 0x8, 0x8 } }, + { { ATC_MASK, 19, 0x3c, 0x18000, 0, 0 }, 0xf, { 0x10000, 0x10000, 0x10000, 0x10000 } }, + { { ATC_MASK, 14, 0x0, 0xc, 0, 0 }, 0xf, { 0xc, 0xc, 0xc, 0xc } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 2, 0x120, 0x10, 0, 0 }, 0xf, { 0x10, 0x10, 0x10, 0x10 } }, + { { ATC_MASK, 2, 0x120, 0x40, 0, 0 }, 0xf, { 0x40, 0x40, 0x40, 0x40 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 2, 0x120, 0x1, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 2, 0x120, 0x4, 0, 0 }, 0xf, { 0x4, 0x4, 0x4, 0x4 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 2, 0x120, 0x100, 0, 0 }, 0xf, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 2, 0x120, 0x400, 0, 0 }, 0xf, { 0x400, 0x400, 0x400, 0x400 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 22, 0x44, 0xfffffc0, 0, 0 }, 0xf, { 0xaafb800, 0xaafb800, 0xaafb800, 0xaafb800 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 22, 0x44, 0x1000000, 0, 0 }, 0xf, { 0x1000000, 0x1000000, 0x1000000, 0x1000000 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 22, 0x44, 0x4000000, 0, 0 }, 0xf, { 0x4000000, 0x4000000, 0x4000000, 0x4000000 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 22, 0x44, 0x7c0, 0, 0 }, 0xf, { 0x300, 0x300, 0x300, 0x300 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 22, 0x44, 0x6c000, 0, 0 }, 0xf, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 21, 0x100, 0x100000, 0, 0 }, 0xf, { 0x100000, 0x100000, 0x100000, 0x100000 } }, + { { ATC_MASK, 21, 0x58, 0xc00f, 0, 0 }, 0xf, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 21, 0x104, 0x20000000, 0, 0 }, 0xf, { 0x20000000, 0x20000000, 0x20000000, 0x20000000 } }, + { { ATC_MASK, 21, 0x100, 0x7ffff, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 22, 0x44, 0x100000, 0, 0 }, 0x7, { 0x100000, 0x100000, 0x100000, 0x0 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0x7, { 0x1, 0x1, 0x1, 0x0 } }, + { { ATC_MASK, 22, 0x44, 0x400000, 0, 0 }, 0x7, { 0x400000, 0x400000, 0x400000, 0x0 } }, + { { ATC_MASK, 22, 0x4, 0x3000000, 0, 0 }, 0xf, { 0x2000000, 0x2000000, 0x2000000, 0x2000000 } }, + { { ATC_MASK, 22, 0x4, 0x30000000, 0, 0 }, 0xf, { 0x30000000, 0x30000000, 0x30000000, 0x30000000 } }, + { { ATC_MASK, 22, 0xc, 0x7000000, 0, 0 }, 0x1, { 0x5000000, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 22, 0xc, 0x7000000, 0, 0 }, 0xe, { 0x0, 0x4000000, 0x4000000, 0x4000000 } }, + { { ATC_MASK, 22, 0x8, 0x3, 0, 0 }, 0xf, { 0x2, 0x2, 0x2, 0x2 } }, + { { ATC_MASK, 22, 0x10, 0x600, 0, 0 }, 0xf, { 0x600, 0x600, 0x600, 0x600 } }, + { { ATC_MASK, 22, 0x1c, 0xc00000, 0, 0 }, 0xf, { 0x800000, 0x800000, 0x800000, 0x800000 } }, + { { ATC_MASK, 19, 0xf4, 0x3000, 0, 0 }, 0xf, { 0x3000, 0x3000, 0x3000, 0x3000 } }, + { { ATC_MASK, 16, 0x188, 0x1, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 16, 0x1d4, 0x700, 0, 0 }, 0xf, { 0x300, 0x300, 0x300, 0x300 } }, + { { ATC_MASK, 19, 0x24, 0x3f0000, 0, 0 }, 0xf, { 0x2a0000, 0x2a0000, 0x2a0000, 0x2a0000 } }, + { { ATC_MASK, 19, 0x3c, 0x18000, 0, 0 }, 0xf, { 0x10000, 0x10000, 0x10000, 0x10000 } }, + { { ATC_MASK, 19, 0x78, 0xc0000, 0, 0 }, 0xf, { 0x80000, 0x80000, 0x80000, 0x80000 } }, + { { ATC_MASK, 19, 0x7c, 0xc0, 0, 0 }, 0xf, { 0x80, 0x80, 0x80, 0x80 } }, + { { ATC_MASK, 19, 0x84, 0xc000, 0, 0 }, 0xf, { 0x8000, 0x8000, 0x8000, 0x8000 } }, + { { ATC_MASK, 19, 0xd4, 0x600000, 0, 0 }, 0xf, { 0x400000, 0x400000, 0x400000, 0x400000 } }, + { { ATC_MASK, 19, 0xd8, 0x300, 0, 0 }, 0xf, { 0x200, 0x200, 0x200, 0x200 } }, + { { ATC_MASK, 19, 0x40, 0xc00, 0, 0 }, 0xf, { 0x800, 0x800, 0x800, 0x800 } }, + { { ATC_MASK, 19, 0x48, 0x600, 0, 0 }, 0xf, { 0x400, 0x400, 0x400, 0x400 } }, + { { ATC_MASK, 19, 0x28, 0x1ff030, 0, 0 }, 0xf, { 0x11c020, 0x11c020, 0x11c020, 0x11c020 } }, + { { ATC_MASK, 19, 0x2c, 0xfc, 0, 0 }, 0xf, { 0xb0, 0xb0, 0xb0, 0xb0 } }, + { { ATC_MASK, 19, 0x28, 0xe00, 0, 0 }, 0xf, { 0x800, 0x800, 0x800, 0x800 } }, + { { ATC_MASK, 19, 0x28, 0x1c0, 0, 0 }, 0xf, { 0x180, 0x180, 0x180, 0x180 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 2, 0x10, 0x10, 0, 0 }, 0xf, { 0x10, 0x10, 0x10, 0x10 } }, + { { ATC_MASK, 2, 0x10, 0x40, 0, 0 }, 0xf, { 0x40, 0x40, 0x40, 0x40 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 2, 0x10, 0x1, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 2, 0x10, 0x4, 0, 0 }, 0xf, { 0x4, 0x4, 0x4, 0x4 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 2, 0xc, 0x400, 0, 0 }, 0xf, { 0x400, 0x400, 0x400, 0x400 } }, + { { ATC_MASK, 2, 0xc, 0x1000, 0, 0 }, 0xf, { 0x1000, 0x1000, 0x1000, 0x1000 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 2, 0xc, 0x40, 0, 0 }, 0xf, { 0x40, 0x40, 0x40, 0x40 } }, + { { ATC_MASK, 2, 0xc, 0x100, 0, 0 }, 0xf, { 0x100, 0x100, 0x100, 0x100 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 2, 0xc, 0x4000, 0, 0 }, 0xf, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 2, 0xc, 0x10000, 0, 0 }, 0xf, { 0x10000, 0x10000, 0x10000, 0x10000 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 16, 0x1d0, 0x3, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 16, 0x1d0, 0x2, 0, 0 }, 0xf, { 0x2, 0x2, 0x2, 0x2 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 19, 0x40, 0x30000, 0, 0 }, 0xf, { 0x30000, 0x30000, 0x30000, 0x30000 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 19, 0x40, 0xc000, 0, 0 }, 0xf, { 0xc000, 0xc000, 0xc000, 0xc000 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 19, 0x28, 0x3010, 0, 0 }, 0xf, { 0x1010, 0x1010, 0x1010, 0x1010 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 19, 0x28, 0x600, 0, 0 }, 0xf, { 0x200, 0x200, 0x200, 0x200 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 19, 0x28, 0x3000, 0, 0 }, 0xf, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 19, 0x24, 0x30000000, 0, 0 }, 0xf, { 0x30000000, 0x30000000, 0x30000000, 0x30000000 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 19, 0x24, 0xc0000000, 0, 0 }, 0xf, { 0xc0000000, 0xc0000000, 0xc0000000, 0xc0000000 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 19, 0x28, 0x600, 0, 0 }, 0xf, { 0x600, 0x600, 0x600, 0x600 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 19, 0x28, 0x600, 0, 0 }, 0xf, { 0x400, 0x400, 0x400, 0x400 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 19, 0x28, 0x600, 0, 0 }, 0xf, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x2, 0x2, 0x2, 0x2 } }, + { { ATC_MASK, 19, 0x44, 0x18000000, 0, 0 }, 0xf, { 0x18000000, 0x18000000, 0x18000000, 0x18000000 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x2, 0x2, 0x2, 0x2 } }, + { { ATC_MASK, 19, 0x40, 0x38, 0, 0 }, 0x1, { 0x28, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 19, 0x40, 0x38, 0, 0 }, 0xe, { 0x0, 0x20, 0x20, 0x20 } }, + { { ATC_MASK, 19, 0x44, 0x60000000, 0, 0 }, 0xf, { 0x40000000, 0x40000000, 0x40000000, 0x40000000 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 19, 0x44, 0x6000000, 0, 0 }, 0xf, { 0x6000000, 0x6000000, 0x6000000, 0x6000000 } }, + { { ATC_MASK, 19, 0x40, 0xc000, 0, 0 }, 0xf, { 0x8000, 0x8000, 0x8000, 0x8000 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 20, 0x68, 0xc00f, 0, 0 }, 0xf, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 20, 0xac, 0x20000000, 0, 0 }, 0xf, { 0x20000000, 0x20000000, 0x20000000, 0x20000000 } }, + { { ATC_MASK, 20, 0xa8, 0x7ffff, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 20, 0x18, 0x38, 0, 0 }, 0xf, { 0x20, 0x20, 0x20, 0x20 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 14, 0x0, 0x1, 0, 0 }, 0xf, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_POLL, 39, 0x1c, 0x1, 0, 5000 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, +}; + +static const struct atc_op dp_group1_off[] = { + { ATC_MASK, 39, 0x14, 0x1007, 0x7, 0 }, + { ATC_MASK, 39, 0x18, 0x1, 0x0, 0 }, + { ATC_POLL, 39, 0x1c, 0x1, 0x0, 5000 }, + { ATC_MASK, 14, 0x0, 0x1, 0x1, 0 }, + { ATC_MASK, 31, 0x1c, 0xc00000, 0xc00000, 0 }, + { ATC_MASK, 31, 0x10, 0x600, 0x400, 0 }, + { ATC_MASK, 31, 0x4, 0x30000000, 0x20000000, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 31, 0x44, 0x100000, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 31, 0x44, 0x400000, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 31, 0x44, 0x4000000, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 31, 0x44, 0x1000000, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 31, 0x44, 0x1000, 0x0, 0 }, + { ATC_MASK, 2, 0x120, 0x200, 0x200, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0x120, 0x2, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0x120, 0x20, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 28, 0x44, 0x6000000, 0x4000000, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 28, 0x44, 0x18000000, 0x10000000, 0 }, + { ATC_MASK, 28, 0x24, 0xc0000000, 0x80000000, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 28, 0x24, 0x30000000, 0x20000000, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 28, 0x28, 0x10, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 28, 0x40, 0xc000, 0xc000, 0 }, + { ATC_MASK, 25, 0x1d0, 0x3, 0x1, 0 }, + { ATC_MASK, 28, 0x40, 0x10000, 0x0, 0 }, + { ATC_MASK, 2, 0xc, 0x8000, 0x8000, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0xc, 0x80, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0xc, 0x800, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0x10, 0x2, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0x10, 0x20, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 14, 0x0, 0xc, 0x8, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0x160, 0x400000, 0x400000, 0 }, + { ATC_MASK, 3, 0x8, 0x4, 0x4, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0x160, 0x800000, 0x0, 0 }, + { ATC_MASK, 3, 0x8, 0x8, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0x160, 0x800000, 0x800000, 0 }, + { ATC_MASK, 3, 0x8, 0x8, 0x8, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 2, 0x160, 0x400000, 0x0, 0 }, + { ATC_MASK, 3, 0x8, 0x4, 0x0, 0 }, + { ATC_DELAY, 0, 0x0, 0x0, 0x1, 0 }, + { ATC_MASK, 6, 0x1c, 0x7, 0x1, 0 }, + { ATC_MASK, 6, 0x1c, 0x8, 0x8, 0 }, + { ATC_POLL, 6, 0x2c, 0xff00, 0x100, 500000 }, + { ATC_MASK, 6, 0x1c, 0x8, 0x0, 0 }, + { ATC_POLL, 6, 0x2c, 0xff00, 0x0, 500000 }, + { ATC_POLL, 14, 0x34, 0x8, 0x0, 500000 }, + { ATC_DELAY, 0, 0x0, 0x0, 0xa, 0 }, + { ATC_MASK, 6, 0x34, 0xf, 0x1, 0 }, + { ATC_MASK, 6, 0x1c, 0x7, 0x0, 0 }, + { ATC_MASK, 6, 0x1c, 0x8, 0x8, 0 }, + { ATC_POLL, 6, 0x2c, 0xff00, 0x100, 500000 }, + { ATC_MASK, 6, 0x1c, 0x8, 0x0, 0 }, + { ATC_POLL, 6, 0x2c, 0xff00, 0x0, 500000 }, + { ATC_MASK, 8, 0x0, 0xaaa, 0x0, 0 }, +}; + +static const struct atc_rate_op dp_group1_on[] = { + { { ATC_MASK, 39, 0x14, 0x7, 0, 0 }, 0xf, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 39, 0x18, 0x1, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 2, 0x6c, 0xe0, 0, 0 }, 0xf, { 0x40, 0x40, 0x40, 0x40 } }, + { { ATC_MASK, 14, 0x0, 0x40fff0, 0, 0 }, 0xf, { 0x1110, 0x1110, 0x1110, 0x1110 } }, + { { ATC_MASK, 8, 0x34, 0x3, 0, 0 }, 0x1, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 7, 0x80, 0xffffffff, 0, 0 }, 0x1, { 0x1e0e021c, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 7, 0x84, 0xfffffff, 0, 0 }, 0x1, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 7, 0x88, 0x7fffff, 0, 0 }, 0x1, { 0x156600, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 8, 0x8, 0x1f0000, 0, 0 }, 0x1, { 0x70000, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 8, 0x18, 0x1, 0, 0 }, 0x1, { 0x1, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 8, 0x34, 0x3, 0, 0 }, 0x2, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 7, 0x80, 0xffffffff, 0, 0 }, 0x2, { 0x0, 0x1e0e01c2, 0x0, 0x0 } }, + { { ATC_MASK, 7, 0x84, 0xfffffff, 0, 0 }, 0x2, { 0x0, 0x7fffffe, 0x0, 0x0 } }, + { { ATC_MASK, 7, 0x88, 0x7fffff, 0, 0 }, 0x2, { 0x0, 0x155200, 0x0, 0x0 } }, + { { ATC_MASK, 8, 0x8, 0x1f0000, 0, 0 }, 0x2, { 0x0, 0x70000, 0x0, 0x0 } }, + { { ATC_MASK, 8, 0x18, 0x1, 0, 0 }, 0x2, { 0x0, 0x1, 0x0, 0x0 } }, + { { ATC_MASK, 8, 0x34, 0x3, 0, 0 }, 0x4, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 7, 0x80, 0xffffffff, 0, 0 }, 0x4, { 0x0, 0x0, 0x1e0e01c2, 0x0 } }, + { { ATC_MASK, 7, 0x84, 0xfffffff, 0, 0 }, 0x4, { 0x0, 0x0, 0x7fffffe, 0x0 } }, + { { ATC_MASK, 7, 0x88, 0x7fffff, 0, 0 }, 0x4, { 0x0, 0x0, 0x554800, 0x0 } }, + { { ATC_MASK, 8, 0x8, 0x1f0000, 0, 0 }, 0x4, { 0x0, 0x0, 0x70000, 0x0 } }, + { { ATC_MASK, 8, 0x18, 0x1, 0, 0 }, 0x4, { 0x0, 0x0, 0x1, 0x0 } }, + { { ATC_MASK, 8, 0x34, 0x3, 0, 0 }, 0x8, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 7, 0x80, 0xffffffff, 0, 0 }, 0x8, { 0x0, 0x0, 0x0, 0x1e0e02a3 } }, + { { ATC_MASK, 7, 0x84, 0xfffffff, 0, 0 }, 0x8, { 0x0, 0x0, 0x0, 0xbff7ffc } }, + { { ATC_MASK, 7, 0x88, 0x7fffff, 0, 0 }, 0x8, { 0x0, 0x0, 0x0, 0x564800 } }, + { { ATC_MASK, 8, 0x8, 0x1f0000, 0, 0 }, 0x8, { 0x0, 0x0, 0x0, 0x70000 } }, + { { ATC_MASK, 8, 0x18, 0x1, 0, 0 }, 0x8, { 0x0, 0x0, 0x0, 0x1 } }, + { { ATC_MASK, 8, 0x0, 0xaaa, 0, 0 }, 0xf, { 0xaaa, 0xaaa, 0xaaa, 0xaaa } }, + { { ATC_MASK, 6, 0x30, 0x4000, 0, 0 }, 0xf, { 0x4000, 0x4000, 0x4000, 0x4000 } }, + { { ATC_MASK, 6, 0x34, 0xf, 0, 0 }, 0xf, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 6, 0x18, 0x1fe000, 0, 0 }, 0xf, { 0x2000, 0x2000, 0x2000, 0x2000 } }, + { { ATC_MASK, 6, 0x20, 0x1, 0, 0 }, 0xf, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 6, 0x1c, 0x7, 0, 0 }, 0xf, { 0x7, 0x7, 0x7, 0x7 } }, + { { ATC_MASK, 6, 0x1c, 0x8, 0, 0 }, 0xf, { 0x8, 0x8, 0x8, 0x8 } }, + { { ATC_POLL, 6, 0x2c, 0xff00, 0, 500000 }, 0xf, { 0x100, 0x100, 0x100, 0x100 } }, + { { ATC_MASK, 6, 0x1c, 0x8, 0, 0 }, 0xf, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_POLL, 6, 0x2c, 0xff00, 0, 500000 }, 0xf, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_POLL, 14, 0x34, 0x8, 0, 500000 }, 0xf, { 0x8, 0x8, 0x8, 0x8 } }, + { { ATC_MASK, 28, 0x3c, 0x18000, 0, 0 }, 0xf, { 0x10000, 0x10000, 0x10000, 0x10000 } }, + { { ATC_MASK, 14, 0x0, 0xc, 0, 0 }, 0xf, { 0xc, 0xc, 0xc, 0xc } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 2, 0x120, 0x20, 0, 0 }, 0xf, { 0x20, 0x20, 0x20, 0x20 } }, + { { ATC_MASK, 2, 0x120, 0x80, 0, 0 }, 0xf, { 0x80, 0x80, 0x80, 0x80 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 2, 0x120, 0x2, 0, 0 }, 0xf, { 0x2, 0x2, 0x2, 0x2 } }, + { { ATC_MASK, 2, 0x120, 0x8, 0, 0 }, 0xf, { 0x8, 0x8, 0x8, 0x8 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 2, 0x120, 0x200, 0, 0 }, 0xf, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 2, 0x120, 0x800, 0, 0 }, 0xf, { 0x800, 0x800, 0x800, 0x800 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 31, 0x44, 0xfffffc0, 0, 0 }, 0xf, { 0xaafb800, 0xaafb800, 0xaafb800, 0xaafb800 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 31, 0x44, 0x1000000, 0, 0 }, 0xf, { 0x1000000, 0x1000000, 0x1000000, 0x1000000 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 31, 0x44, 0x4000000, 0, 0 }, 0xf, { 0x4000000, 0x4000000, 0x4000000, 0x4000000 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 31, 0x44, 0x7c0, 0, 0 }, 0xf, { 0x300, 0x300, 0x300, 0x300 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 31, 0x44, 0x6c000, 0, 0 }, 0xf, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 30, 0x100, 0x100000, 0, 0 }, 0xf, { 0x100000, 0x100000, 0x100000, 0x100000 } }, + { { ATC_MASK, 30, 0x58, 0xc00f, 0, 0 }, 0xf, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 30, 0x104, 0x20000000, 0, 0 }, 0xf, { 0x20000000, 0x20000000, 0x20000000, 0x20000000 } }, + { { ATC_MASK, 30, 0x100, 0x7ffff, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 31, 0x44, 0x100000, 0, 0 }, 0x7, { 0x100000, 0x100000, 0x100000, 0x0 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0x7, { 0x1, 0x1, 0x1, 0x0 } }, + { { ATC_MASK, 31, 0x44, 0x400000, 0, 0 }, 0x7, { 0x400000, 0x400000, 0x400000, 0x0 } }, + { { ATC_MASK, 31, 0x4, 0x3000000, 0, 0 }, 0xf, { 0x2000000, 0x2000000, 0x2000000, 0x2000000 } }, + { { ATC_MASK, 31, 0x4, 0x30000000, 0, 0 }, 0xf, { 0x30000000, 0x30000000, 0x30000000, 0x30000000 } }, + { { ATC_MASK, 31, 0xc, 0x7000000, 0, 0 }, 0x1, { 0x5000000, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 31, 0xc, 0x7000000, 0, 0 }, 0xe, { 0x0, 0x4000000, 0x4000000, 0x4000000 } }, + { { ATC_MASK, 31, 0x8, 0x3, 0, 0 }, 0xf, { 0x2, 0x2, 0x2, 0x2 } }, + { { ATC_MASK, 31, 0x10, 0x600, 0, 0 }, 0xf, { 0x600, 0x600, 0x600, 0x600 } }, + { { ATC_MASK, 31, 0x1c, 0xc00000, 0, 0 }, 0xf, { 0x800000, 0x800000, 0x800000, 0x800000 } }, + { { ATC_MASK, 28, 0xf4, 0x3000, 0, 0 }, 0xf, { 0x3000, 0x3000, 0x3000, 0x3000 } }, + { { ATC_MASK, 25, 0x188, 0x1, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 25, 0x1d4, 0x700, 0, 0 }, 0xf, { 0x300, 0x300, 0x300, 0x300 } }, + { { ATC_MASK, 28, 0x24, 0x3f0000, 0, 0 }, 0xf, { 0x2a0000, 0x2a0000, 0x2a0000, 0x2a0000 } }, + { { ATC_MASK, 28, 0x3c, 0x18000, 0, 0 }, 0xf, { 0x10000, 0x10000, 0x10000, 0x10000 } }, + { { ATC_MASK, 28, 0x78, 0xc0000, 0, 0 }, 0xf, { 0x80000, 0x80000, 0x80000, 0x80000 } }, + { { ATC_MASK, 28, 0x7c, 0xc0, 0, 0 }, 0xf, { 0x80, 0x80, 0x80, 0x80 } }, + { { ATC_MASK, 28, 0x84, 0xc000, 0, 0 }, 0xf, { 0x8000, 0x8000, 0x8000, 0x8000 } }, + { { ATC_MASK, 28, 0xd4, 0x600000, 0, 0 }, 0xf, { 0x400000, 0x400000, 0x400000, 0x400000 } }, + { { ATC_MASK, 28, 0xd8, 0x300, 0, 0 }, 0xf, { 0x200, 0x200, 0x200, 0x200 } }, + { { ATC_MASK, 28, 0x40, 0xc00, 0, 0 }, 0xf, { 0x800, 0x800, 0x800, 0x800 } }, + { { ATC_MASK, 28, 0x48, 0x600, 0, 0 }, 0xf, { 0x400, 0x400, 0x400, 0x400 } }, + { { ATC_MASK, 28, 0x28, 0x1ff030, 0, 0 }, 0xf, { 0x11c020, 0x11c020, 0x11c020, 0x11c020 } }, + { { ATC_MASK, 28, 0x2c, 0xfc, 0, 0 }, 0xf, { 0xb0, 0xb0, 0xb0, 0xb0 } }, + { { ATC_MASK, 28, 0x28, 0xe00, 0, 0 }, 0xf, { 0x800, 0x800, 0x800, 0x800 } }, + { { ATC_MASK, 28, 0x28, 0x1c0, 0, 0 }, 0xf, { 0x180, 0x180, 0x180, 0x180 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 2, 0x10, 0x20, 0, 0 }, 0xf, { 0x20, 0x20, 0x20, 0x20 } }, + { { ATC_MASK, 2, 0x10, 0x80, 0, 0 }, 0xf, { 0x80, 0x80, 0x80, 0x80 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 2, 0x10, 0x2, 0, 0 }, 0xf, { 0x2, 0x2, 0x2, 0x2 } }, + { { ATC_MASK, 2, 0x10, 0x8, 0, 0 }, 0xf, { 0x8, 0x8, 0x8, 0x8 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 2, 0xc, 0x800, 0, 0 }, 0xf, { 0x800, 0x800, 0x800, 0x800 } }, + { { ATC_MASK, 2, 0xc, 0x2000, 0, 0 }, 0xf, { 0x2000, 0x2000, 0x2000, 0x2000 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 2, 0xc, 0x80, 0, 0 }, 0xf, { 0x80, 0x80, 0x80, 0x80 } }, + { { ATC_MASK, 2, 0xc, 0x200, 0, 0 }, 0xf, { 0x200, 0x200, 0x200, 0x200 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 2, 0xc, 0x8000, 0, 0 }, 0xf, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 2, 0xc, 0x20000, 0, 0 }, 0xf, { 0x20000, 0x20000, 0x20000, 0x20000 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 25, 0x1d0, 0x3, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 25, 0x1d0, 0x2, 0, 0 }, 0xf, { 0x2, 0x2, 0x2, 0x2 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 28, 0x40, 0x30000, 0, 0 }, 0xf, { 0x30000, 0x30000, 0x30000, 0x30000 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 28, 0x40, 0xc000, 0, 0 }, 0xf, { 0xc000, 0xc000, 0xc000, 0xc000 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 28, 0x28, 0x3010, 0, 0 }, 0xf, { 0x1010, 0x1010, 0x1010, 0x1010 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 28, 0x28, 0x600, 0, 0 }, 0xf, { 0x200, 0x200, 0x200, 0x200 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 28, 0x28, 0x3000, 0, 0 }, 0xf, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 28, 0x24, 0x30000000, 0, 0 }, 0xf, { 0x30000000, 0x30000000, 0x30000000, 0x30000000 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 28, 0x24, 0xc0000000, 0, 0 }, 0xf, { 0xc0000000, 0xc0000000, 0xc0000000, 0xc0000000 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 28, 0x28, 0x600, 0, 0 }, 0xf, { 0x600, 0x600, 0x600, 0x600 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 28, 0x28, 0x600, 0, 0 }, 0xf, { 0x400, 0x400, 0x400, 0x400 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 28, 0x28, 0x600, 0, 0 }, 0xf, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x2, 0x2, 0x2, 0x2 } }, + { { ATC_MASK, 28, 0x44, 0x18000000, 0, 0 }, 0xf, { 0x18000000, 0x18000000, 0x18000000, 0x18000000 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x2, 0x2, 0x2, 0x2 } }, + { { ATC_MASK, 28, 0x40, 0x38, 0, 0 }, 0x1, { 0x28, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 28, 0x40, 0x38, 0, 0 }, 0xe, { 0x0, 0x20, 0x20, 0x20 } }, + { { ATC_MASK, 28, 0x44, 0x60000000, 0, 0 }, 0xf, { 0x40000000, 0x40000000, 0x40000000, 0x40000000 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 28, 0x44, 0x6000000, 0, 0 }, 0xf, { 0x6000000, 0x6000000, 0x6000000, 0x6000000 } }, + { { ATC_MASK, 28, 0x40, 0xc000, 0, 0 }, 0xf, { 0x8000, 0x8000, 0x8000, 0x8000 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 29, 0x68, 0xc00f, 0, 0 }, 0xf, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_MASK, 29, 0xac, 0x20000000, 0, 0 }, 0xf, { 0x20000000, 0x20000000, 0x20000000, 0x20000000 } }, + { { ATC_MASK, 29, 0xa8, 0x7ffff, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 29, 0x18, 0x38, 0, 0 }, 0xf, { 0x20, 0x20, 0x20, 0x20 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_DELAY, 0, 0x0, 0x0, 0, 0 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, + { { ATC_MASK, 14, 0x0, 0x1, 0, 0 }, 0xf, { 0x0, 0x0, 0x0, 0x0 } }, + { { ATC_POLL, 39, 0x1c, 0x1, 0, 5000 }, 0xf, { 0x1, 0x1, 0x1, 0x1 } }, +}; diff --git a/drivers/phy/apple/usb-t6050/apple_t6050_usb2_reset.h b/drivers/phy/apple/usb-t6050/apple_t6050_usb2_reset.h new file mode 100644 index 0000000000000..503cbb537f865 --- /dev/null +++ b/drivers/phy/apple/usb-t6050/apple_t6050_usb2_reset.h @@ -0,0 +1,23 @@ +/* SPDX-License-Identifier: MIT */ +#ifndef APPLE_T6050_USB2_RESET_H +#define APPLE_T6050_USB2_RESET_H +#include "apple_t6050_usb3_bist.h" + +/* Native T6050 Type-C PHY USB2 port reset block, native27: + * bank0+4 bit1 assert, sleep 5 ms, release. + * Caller holds exclusive PHY ownership with its USB power domains active. + * sleep_ms is supplied by the OS frontend; there is no packet-path polling. */ +static inline int apple_t6050_usb2_port_reset( + const struct apple_t6050_usb_io *io, void *sleep_context, + void (*sleep_ms)(void *, unsigned int)) +{ + if (!io || !io->read || !io->write || !sleep_ms) + return -1; + apple_t6050_usb_mask(io, 0, 4, 2U, 2U); + if (!(io->read(io->context, 0, 4) & 2U)) + return -1; + sleep_ms(sleep_context, 5); + apple_t6050_usb_mask(io, 0, 4, 2U, 0); + return io->read(io->context, 0, 4) & 2U ? -1 : 0; +} +#endif diff --git a/drivers/phy/apple/usb-t6050/apple_t6050_usb3_bist.h b/drivers/phy/apple/usb-t6050/apple_t6050_usb3_bist.h new file mode 100644 index 0000000000000..3af8fcf2a3a13 --- /dev/null +++ b/drivers/phy/apple/usb-t6050/apple_t6050_usb3_bist.h @@ -0,0 +1,59 @@ +/* SPDX-License-Identifier: MIT */ +/* T6050-specific native PHY recipe; no Windows/Linux frontend policy. */ +#ifndef APPLE_T6050_USB3_BIST_H +#define APPLE_T6050_USB3_BIST_H +struct apple_t6050_usb_io { + void *context; + unsigned int (*read)(void *, unsigned int bank, unsigned int offset); + void (*write)(void *, unsigned int bank, unsigned int offset, unsigned int value); + int (*poll)(void *, unsigned int bank, unsigned int offset, unsigned int mask, unsigned int value, unsigned int usec); + void (*delay)(void *, unsigned int usec); +}; +static inline void apple_t6050_usb_mask(const struct apple_t6050_usb_io *io, unsigned int bank, unsigned int offset, unsigned int mask, unsigned int value) +{ io->write(io->context, bank, offset, (io->read(io->context, bank, offset) & ~mask) | value); } +static inline int apple_t6050_usb_bist_off(const struct apple_t6050_usb_io *io) +{ + /* native sequence step */ + apple_t6050_usb_mask(io, 35, 0x110, 0x660, 0x0); + /* native sequence step */ + apple_t6050_usb_mask(io, 35, 0x4, 0x8, 0x8); + /* native sequence step */ + apple_t6050_usb_mask(io, 35, 0x4, 0x8, 0x0); + /* native sequence step */ + apple_t6050_usb_mask(io, 35, 0x4, 0x1, 0x0); + return 0; +} +static inline int apple_t6050_usb_bist_on(const struct apple_t6050_usb_io *io) +{ + /* native sequence step */ + apple_t6050_usb_mask(io, 35, 0x4, 0x1, 0x1); + /* native sequence step */ + apple_t6050_usb_mask(io, 35, 0x10c, 0x1e100000, 0x4100000); + /* native sequence step */ + apple_t6050_usb_mask(io, 35, 0x110, 0x660, 0x220); + /* native sequence step */ + apple_t6050_usb_mask(io, 35, 0x4, 0x8, 0x8); + /* native sequence step */ + apple_t6050_usb_mask(io, 35, 0x4, 0x8, 0x0); + if (io->poll(io->context, 35, 0x104, 0x100, 0x0, 20000)) return -1; + /* native sequence step */ + apple_t6050_usb_mask(io, 35, 0x8, 0x20, 0x20); + /* native sequence step */ + apple_t6050_usb_mask(io, 35, 0x8, 0x20, 0x0); + /* native sequence step */ + apple_t6050_usb_mask(io, 35, 0x10c, 0x1e000000, 0x6000000); + /* native sequence step */ + apple_t6050_usb_mask(io, 35, 0x110, 0x600, 0x200); + /* native sequence step */ + apple_t6050_usb_mask(io, 35, 0x4, 0x8, 0x8); + /* native sequence step */ + apple_t6050_usb_mask(io, 35, 0x4, 0x8, 0x0); + if (io->poll(io->context, 35, 0x104, 0x40, 0x40, 130000)) return -1; + if (io->poll(io->context, 35, 0x104, 0x100, 0x0, 1000)) return -1; + /* native sequence step */ + apple_t6050_usb_mask(io, 35, 0x8, 0x20, 0x20); + /* native sequence step */ + apple_t6050_usb_mask(io, 35, 0x8, 0x20, 0x0); + return 0; +} +#endif diff --git a/drivers/phy/apple/usb-t6050/apple_t6050_usb3_pipe.h b/drivers/phy/apple/usb-t6050/apple_t6050_usb3_pipe.h new file mode 100644 index 0000000000000..953a42dbc7aab --- /dev/null +++ b/drivers/phy/apple/usb-t6050/apple_t6050_usb3_pipe.h @@ -0,0 +1,136 @@ +/* SPDX-License-Identifier: MIT */ +/* Platform backend for native direct USB3, not a Type-C policy engine. + * The caller owns the port, has powered the PHY/firmware for native USB3, + * completed DWC3 core init and enabled SUSPHY, and has valid DART mappings. + * Bank 35 is T6050's ADT PHY reg[35]. Bank 44 is usb-drdN reg[3]. + * The first left/debug port must never reach this backend. + */ +#ifndef APPLE_T6050_USB3_PIPE_H +#define APPLE_T6050_USB3_PIPE_H +#include "apple_t6050_usb3_bist.h" + +/* Cold controller setup while RESET_N is clear. Linux's existing ATC + * frontend supplies a dummy PIPE clock before DWC3 reads its core registers. + * T6050 uses the native-RE 6:4 clock field and dummy encoding 0x42. + * This is not an active producer handoff; never use it for USB4 teardown. + */ +static inline int +apple_t6050_usb3_pipe_cold_dummy(const struct apple_t6050_usb_io *io) +{ + unsigned int mux; + if (!io || !io->read || !io->write || !io->delay) + return -1; + if (io->read(io->context, 44, 0x1c) & 1) + return -1; + mux = io->read(io->context, 44, 0x0c) & 0x73; + if (mux != 0 && mux != 2 && mux != 0x42) + return -1; + if (mux == 0x42) + return 0; + apple_t6050_usb_mask(io, 44, 0x0c, 0x70, 0); + io->delay(io->context, 10); + apple_t6050_usb_mask(io, 44, 0x0c, 3, 2); + io->delay(io->context, 10); + apple_t6050_usb_mask(io, 44, 0x0c, 0x70, 0x40); + io->delay(io->context, 10); + return (io->read(io->context, 44, 0x0c) & 0x73) == 0x42 ? 0 : -1; +} + +static inline int +apple_t6050_usb3_pipe_switch(const struct apple_t6050_usb_io *io) +{ + unsigned int aon, mux; + int result = -1, locked = 0, bist = 0; + + if (!io || !io->read || !io->write || !io->poll || !io->delay) + return -1; + aon = io->read(io->context, 44, 0x1c); + mux = io->read(io->context, 44, 0x0c) & 0x73; + /* Never reroute an active USB4 producer or guess an unknown state. */ + if (!(aon & 1) || (mux != 0x42 && mux != 0x10)) + return -1; + + /* Native T8142 xHCI USB3 mode switch, native27 T6050-compatible path. */ + apple_t6050_usb_mask(io, 44, 0x04, 0x0e, 0); + apple_t6050_usb_mask(io, 44, 0x00, 1, 1); + apple_t6050_usb_mask(io, 44, 0x10, 1, 1); + locked = 1; + if (io->poll(io->context, 44, 0x14, 1, 1, 6000)) + goto out; + + bist = 1; + if (apple_t6050_usb_bist_on(io)) + goto out; + apple_t6050_usb_mask(io, 44, 0x20, 0x100f, 3); + if (io->poll(io->context, 44, 0x20, 0xf, 3, 1000)) + goto out; + if (apple_t6050_usb_bist_off(io)) + goto out; + bist = 0; + + /* T6050 clock bits are 6:4, not the older 5:3. Native USB3 is 0x10. */ + apple_t6050_usb_mask(io, 44, 0x0c, 0x70, 0); + io->delay(io->context, 5); + apple_t6050_usb_mask(io, 44, 0x0c, 0x73, 0); + apple_t6050_usb_mask(io, 44, 0x0c, 0x73, 0x10); + result = (io->read(io->context, 44, 0x0c) & 0x73) == 0x10 ? 0 : -1; + +out: + if (bist && apple_t6050_usb_bist_off(io)) + result = -1; + apple_t6050_usb_mask(io, 44, 0x00, 5, 0); + if (locked) { + apple_t6050_usb_mask(io, 44, 0x10, 1, 0); + if (io->poll(io->context, 44, 0x14, 1, 0, 6000)) + result = -1; + } + return result; +} + +/* The native close() call quiesces the ACIO PHY while PIPE is locked, + * before selecting the dummy producer. The caller supplies that lifecycle + * operation; merely changing MUX is not a complete disconnect sequence. + */ +static inline int +apple_t6050_usb3_pipe_stop(const struct apple_t6050_usb_io *io, + int (*quiesce)(void *), void *context) +{ + unsigned int mux; + int result = -1; + + if (!io || !io->read || !io->write || !io->poll || !io->delay || !quiesce) + return -1; + mux = io->read(io->context, 44, 0x0c) & 0x73; + if (mux != 0x10 && mux != 0x42) + return -1; + apple_t6050_usb_mask(io, 44, 0x04, 0x0e, 0); + apple_t6050_usb_mask(io, 44, 0x00, 1, 1); + apple_t6050_usb_mask(io, 44, 0x00, 4, 4); + apple_t6050_usb_mask(io, 44, 0x10, 1, 1); + /* Native mode0-from2 waits 150 ms, logs a missing lock ACK, and + * continues teardown, as the saved native driver does. + * The caller has already stopped the controller's DMA/PIPE consumer; + * this is not the active-producer handoff used by pipe_switch(). + * The nonselected override, final mux readback and unlock checks below + * remain mandatory, as does successful PHY quiescence. + */ + io->poll(io->context, 44, 0x14, 1, 1, 150000); + if (quiesce(context)) + goto out; + apple_t6050_usb_mask(io, 44, 0x20, 0x100f, 0x1002); + if (io->poll(io->context, 44, 0x20, 0xf, 2, 1000)) + goto out; + apple_t6050_usb_mask(io, 44, 0x0c, 0x70, 0); + io->delay(io->context, 5); + apple_t6050_usb_mask(io, 44, 0x0c, 3, 2); + apple_t6050_usb_mask(io, 44, 0x0c, 0x70, 0x40); + result = (io->read(io->context, 44, 0x0c) & 0x73) == 0x42 ? 0 : -1; +out: + apple_t6050_usb_mask(io, 44, 0x00, 1, 0); + apple_t6050_usb_mask(io, 44, 0x00, 4, 0); + apple_t6050_usb_mask(io, 44, 0x10, 1, 0); + if (io->poll(io->context, 44, 0x14, 1, 0, 6000)) + result = -1; + return result; +} +#endif diff --git a/drivers/phy/apple/usb-t6050/apple_t6050_usb_phy.h b/drivers/phy/apple/usb-t6050/apple_t6050_usb_phy.h new file mode 100644 index 0000000000000..5c96da7d82c2d --- /dev/null +++ b/drivers/phy/apple/usb-t6050/apple_t6050_usb_phy.h @@ -0,0 +1,286 @@ +/* SPDX-License-Identifier: MIT */ +/* Generated by generate-phy.py; modeled ACKs are not hardware proof. */ +#ifndef APPLE_T6050_USB_PHY_H +#define APPLE_T6050_USB_PHY_H +#include "apple_t6050_usb3_bist.h" +struct apple_t6050_usb_platform { + const struct apple_t6050_usb_io *io; + void *context; + int (*wrapper_power)(void *, unsigned int); + int (*firmware)(void *, unsigned int); + int (*tunable)(void *, unsigned int, const char *); + void (*pause_ns)(void *, unsigned int); +}; +enum apple_t6050_usb_op { T6050_MASK, T6050_POLL, T6050_DELAY, + T6050_POWER, T6050_FIRMWARE, T6050_TUNABLE }; +struct apple_t6050_usb_step { + unsigned int op, bank, offset, mask, value, timeout; + const char *name; +}; +static const struct apple_t6050_usb_step apple_t6050_usb2_host[] = { + { T6050_TUNABLE, 0, 0x0, 0x0, 0x0, 0, "tunable_USB2PHY_DFLT" }, + { T6050_TUNABLE, 0, 0x0, 0x0, 0x0, 0, "tunable_USB2PHY_HOST" }, + { T6050_DELAY, 0, 0x0, 0x0, 0x2710, 0, 0 }, + { T6050_MASK, 0, 0x4, 0x8, 0x0, 0, 0 }, + { T6050_DELAY, 0, 0x0, 0x0, 0xa, 0, 0 }, + { T6050_MASK, 0, 0x4, 0x1, 0x0, 0, 0 }, + { T6050_MASK, 0, 0x4, 0x2, 0x0, 0, 0 }, + { T6050_MASK, 1, 0x0, 0x9, 0x9, 0, 0 }, + { T6050_MASK, 0, 0x4, 0x4, 0x4, 0, 0 }, + { T6050_MASK, 0, 0x1c, 0x20000000, 0x0, 0, 0 }, + { T6050_MASK, 0, 0x1c, 0x40000000, 0x0, 0, 0 }, + { T6050_DELAY, 0, 0x0, 0x0, 0x1e, 0, 0 }, + { T6050_DELAY, 0, 0x0, 0x0, 0x1388, 0, 0 }, + { T6050_MASK, 0, 0x0, 0x7, 0x2, 0, 0 }, +}; +static const struct apple_t6050_usb_step apple_t6050_usb3_init_normal[] = { + { T6050_MASK, 39, 0x0, 0x1, 0x1, 0, 0 }, + { T6050_DELAY, 0, 0x0, 0x0, 0x1, 0, 0 }, + { T6050_POLL, 39, 0x4, 0x1, 0x1, 1000, 0 }, + { T6050_DELAY, 0, 0x0, 0x0, 0x1, 0, 0 }, + { T6050_MASK, 39, 0x0, 0x2, 0x2, 0, 0 }, + { T6050_DELAY, 0, 0x0, 0x0, 0x1, 0, 0 }, + { T6050_POLL, 39, 0x4, 0x2, 0x2, 1000, 0 }, + { T6050_DELAY, 0, 0x0, 0x0, 0x1, 0, 0 }, + { T6050_MASK, 39, 0x0, 0x4, 0x0, 0, 0 }, + { T6050_DELAY, 0, 0x0, 0x0, 0x1, 0, 0 }, + { T6050_POWER, 0, 0x0, 0x0, 0x1, 0, 0 }, + { T6050_MASK, 39, 0x2c, 0x1, 0x0, 0, 0 }, + { T6050_POLL, 39, 0x4, 0x100, 0x0, 1000, 0 }, + { T6050_DELAY, 0, 0x0, 0x0, 0x1, 0, 0 }, + { T6050_MASK, 39, 0x0, 0x8, 0x8, 0, 0 }, + { T6050_TUNABLE, 41, 0x0, 0x0, 0x0, 0, "tunable_ATC0AXI2AF" }, + { T6050_TUNABLE, 40, 0x0, 0x0, 0x0, 0, "tunable_ATC_FABRIC" }, + { T6050_TUNABLE, 43, 0x0, 0x0, 0x0, 0, "tunable_ATC_COMMON_CFG" }, + { T6050_TUNABLE, 42, 0x0, 0x0, 0x0, 0, "tunable_ATC0AXI2AF_LIOA" }, + { T6050_TUNABLE, 20, 0x0, 0x0, 0x0, 0, "tunable_LN0_RX_CFG_TX_OF_RXCLK" }, + { T6050_TUNABLE, 29, 0x0, 0x0, 0x0, 0, "tunable_LN1_RX_CFG_TX_OF_RXCLK" }, + { T6050_TUNABLE, 18, 0x0, 0x0, 0x0, 0, "tunable_LN0_RX_EQ_DFLT" }, + { T6050_TUNABLE, 27, 0x0, 0x0, 0x0, 0, "tunable_LN1_RX_EQ_DFLT" }, + { T6050_TUNABLE, 5, 0x0, 0x0, 0x0, 0, "tunable_AUS40CMN_SHM" }, + { T6050_TUNABLE, 8, 0x0, 0x0, 0x0, 0, "tunable_AUSPLL_CORE" }, + { T6050_TUNABLE, 10, 0x0, 0x0, 0x0, 0, "tunable_CIO4PLL_CORE" }, + { T6050_MASK, 4, 0x0, 0x3f0, 0x1c0, 0, 0 }, + { T6050_MASK, 4, 0x18, 0xf000, 0x3000, 0, 0 }, + { T6050_MASK, 9, 0x88, 0xff, 0x27, 0, 0 }, + { T6050_MASK, 33, 0x4, 0x38, 0x8, 0, 0 }, + { T6050_MASK, 10, 0xdc, 0x3ff0, 0x1760, 0, 0 }, + { T6050_MASK, 21, 0xcc, 0x1f80000, 0x380000, 0, 0 }, + { T6050_MASK, 30, 0xcc, 0x1f80000, 0x380000, 0, 0 }, + { T6050_MASK, 20, 0x90, 0x1f80000, 0x380000, 0, 0 }, + { T6050_MASK, 29, 0x90, 0x1f80000, 0x380000, 0, 0 }, + { T6050_TUNABLE, 8, 0x0, 0x0, 0x0, 0, "tunable_AUSPLL_CORE" }, + { T6050_MASK, 2, 0x6c, 0x28000fc, 0x44, 0, 0 }, + { T6050_MASK, 2, 0x78, 0x1ffff, 0x110, 0, 0 }, + { T6050_MASK, 2, 0x6c, 0x3, 0x2, 0, 0 }, + { T6050_MASK, 2, 0xc, 0x2a, 0x0, 0, 0 }, + { T6050_DELAY, 0, 0x0, 0x0, 0x1, 0, 0 }, + { T6050_MASK, 2, 0x10, 0x4, 0x0, 0, 0 }, + { T6050_DELAY, 0, 0x0, 0x0, 0x1, 0, 0 }, + { T6050_MASK, 2, 0x10, 0x40, 0x0, 0, 0 }, + { T6050_DELAY, 0, 0x0, 0x0, 0x1, 0, 0 }, + { T6050_MASK, 2, 0xc, 0x10000, 0x0, 0, 0 }, + { T6050_DELAY, 0, 0x0, 0x0, 0x1, 0, 0 }, + { T6050_MASK, 2, 0xc, 0x100, 0x0, 0, 0 }, + { T6050_DELAY, 0, 0x0, 0x0, 0x1, 0, 0 }, + { T6050_MASK, 2, 0xc, 0x1000, 0x0, 0, 0 }, + { T6050_DELAY, 0, 0x0, 0x0, 0x1, 0, 0 }, + { T6050_MASK, 2, 0x120, 0x40, 0x0, 0, 0 }, + { T6050_DELAY, 0, 0x0, 0x0, 0x1, 0, 0 }, + { T6050_MASK, 2, 0x120, 0x4, 0x0, 0, 0 }, + { T6050_DELAY, 0, 0x0, 0x0, 0x1, 0, 0 }, + { T6050_MASK, 2, 0x120, 0x400, 0x0, 0, 0 }, + { T6050_DELAY, 0, 0x0, 0x0, 0x1, 0, 0 }, + { T6050_MASK, 39, 0x8, 0x2, 0x0, 0, 0 }, + { T6050_DELAY, 0, 0x0, 0x0, 0xa, 0, 0 }, + { T6050_DELAY, 0, 0x0, 0x0, 0xa, 0, 0 }, + { T6050_MASK, 38, 0x40, 0x1, 0x1, 0, 0 }, + { T6050_DELAY, 0, 0x0, 0x0, 0xa, 0, 0 }, + { T6050_FIRMWARE, 0, 0x0, 0x0, 0x1, 0, 0 }, + { T6050_DELAY, 0, 0x0, 0x0, 0xa, 0, 0 }, + { T6050_DELAY, 0, 0x0, 0x0, 0x1388, 0, 0 }, + { T6050_POLL, 2, 0x260, 0x400, 0x400, 1000000, 0 }, + { T6050_MASK, 39, 0x0, 0x10, 0x10, 0, 0 }, + { T6050_DELAY, 0, 0x0, 0x0, 0x64, 0, 0 }, +}; +static const struct apple_t6050_usb_step apple_t6050_usb3_init_reverse[] = { + { T6050_MASK, 39, 0x0, 0x1, 0x1, 0, 0 }, + { T6050_DELAY, 0, 0x0, 0x0, 0x1, 0, 0 }, + { T6050_POLL, 39, 0x4, 0x1, 0x1, 1000, 0 }, + { T6050_DELAY, 0, 0x0, 0x0, 0x1, 0, 0 }, + { T6050_MASK, 39, 0x0, 0x2, 0x2, 0, 0 }, + { T6050_DELAY, 0, 0x0, 0x0, 0x1, 0, 0 }, + { T6050_POLL, 39, 0x4, 0x2, 0x2, 1000, 0 }, + { T6050_DELAY, 0, 0x0, 0x0, 0x1, 0, 0 }, + { T6050_MASK, 39, 0x0, 0x4, 0x0, 0, 0 }, + { T6050_DELAY, 0, 0x0, 0x0, 0x1, 0, 0 }, + { T6050_POWER, 0, 0x0, 0x0, 0x1, 0, 0 }, + { T6050_MASK, 39, 0x2c, 0x1, 0x0, 0, 0 }, + { T6050_POLL, 39, 0x4, 0x100, 0x0, 1000, 0 }, + { T6050_DELAY, 0, 0x0, 0x0, 0x1, 0, 0 }, + { T6050_MASK, 39, 0x0, 0x8, 0x8, 0, 0 }, + { T6050_TUNABLE, 41, 0x0, 0x0, 0x0, 0, "tunable_ATC0AXI2AF" }, + { T6050_TUNABLE, 40, 0x0, 0x0, 0x0, 0, "tunable_ATC_FABRIC" }, + { T6050_TUNABLE, 43, 0x0, 0x0, 0x0, 0, "tunable_ATC_COMMON_CFG" }, + { T6050_TUNABLE, 42, 0x0, 0x0, 0x0, 0, "tunable_ATC0AXI2AF_LIOA" }, + { T6050_TUNABLE, 20, 0x0, 0x0, 0x0, 0, "tunable_LN0_RX_CFG_TX_OF_RXCLK" }, + { T6050_TUNABLE, 29, 0x0, 0x0, 0x0, 0, "tunable_LN1_RX_CFG_TX_OF_RXCLK" }, + { T6050_TUNABLE, 18, 0x0, 0x0, 0x0, 0, "tunable_LN0_RX_EQ_DFLT" }, + { T6050_TUNABLE, 27, 0x0, 0x0, 0x0, 0, "tunable_LN1_RX_EQ_DFLT" }, + { T6050_TUNABLE, 5, 0x0, 0x0, 0x0, 0, "tunable_AUS40CMN_SHM" }, + { T6050_TUNABLE, 8, 0x0, 0x0, 0x0, 0, "tunable_AUSPLL_CORE" }, + { T6050_TUNABLE, 10, 0x0, 0x0, 0x0, 0, "tunable_CIO4PLL_CORE" }, + { T6050_MASK, 4, 0x0, 0x3f0, 0x1c0, 0, 0 }, + { T6050_MASK, 4, 0x18, 0xf000, 0x3000, 0, 0 }, + { T6050_MASK, 9, 0x88, 0xff, 0x27, 0, 0 }, + { T6050_MASK, 33, 0x4, 0x38, 0x8, 0, 0 }, + { T6050_MASK, 10, 0xdc, 0x3ff0, 0x1760, 0, 0 }, + { T6050_MASK, 21, 0xcc, 0x1f80000, 0x380000, 0, 0 }, + { T6050_MASK, 30, 0xcc, 0x1f80000, 0x380000, 0, 0 }, + { T6050_MASK, 20, 0x90, 0x1f80000, 0x380000, 0, 0 }, + { T6050_MASK, 29, 0x90, 0x1f80000, 0x380000, 0, 0 }, + { T6050_TUNABLE, 8, 0x0, 0x0, 0x0, 0, "tunable_AUSPLL_CORE" }, + { T6050_MASK, 2, 0x6c, 0x14000fc, 0x28, 0, 0 }, + { T6050_MASK, 2, 0x78, 0x1ffff, 0x111, 0, 0 }, + { T6050_MASK, 2, 0x6c, 0x3, 0x2, 0, 0 }, + { T6050_MASK, 2, 0xc, 0x2a, 0x0, 0, 0 }, + { T6050_DELAY, 0, 0x0, 0x0, 0x1, 0, 0 }, + { T6050_MASK, 2, 0x10, 0x8, 0x0, 0, 0 }, + { T6050_DELAY, 0, 0x0, 0x0, 0x1, 0, 0 }, + { T6050_MASK, 2, 0x10, 0x80, 0x0, 0, 0 }, + { T6050_DELAY, 0, 0x0, 0x0, 0x1, 0, 0 }, + { T6050_MASK, 2, 0xc, 0x20000, 0x0, 0, 0 }, + { T6050_DELAY, 0, 0x0, 0x0, 0x1, 0, 0 }, + { T6050_MASK, 2, 0xc, 0x200, 0x0, 0, 0 }, + { T6050_DELAY, 0, 0x0, 0x0, 0x1, 0, 0 }, + { T6050_MASK, 2, 0xc, 0x2000, 0x0, 0, 0 }, + { T6050_DELAY, 0, 0x0, 0x0, 0x1, 0, 0 }, + { T6050_MASK, 2, 0x120, 0x80, 0x0, 0, 0 }, + { T6050_DELAY, 0, 0x0, 0x0, 0x1, 0, 0 }, + { T6050_MASK, 2, 0x120, 0x8, 0x0, 0, 0 }, + { T6050_DELAY, 0, 0x0, 0x0, 0x1, 0, 0 }, + { T6050_MASK, 2, 0x120, 0x800, 0x0, 0, 0 }, + { T6050_DELAY, 0, 0x0, 0x0, 0x1, 0, 0 }, + { T6050_MASK, 39, 0x8, 0x2, 0x2, 0, 0 }, + { T6050_DELAY, 0, 0x0, 0x0, 0xa, 0, 0 }, + { T6050_DELAY, 0, 0x0, 0x0, 0xa, 0, 0 }, + { T6050_MASK, 38, 0x40, 0x1, 0x1, 0, 0 }, + { T6050_DELAY, 0, 0x0, 0x0, 0xa, 0, 0 }, + { T6050_FIRMWARE, 0, 0x0, 0x0, 0x1, 0, 0 }, + { T6050_DELAY, 0, 0x0, 0x0, 0xa, 0, 0 }, + { T6050_DELAY, 0, 0x0, 0x0, 0x1388, 0, 0 }, + { T6050_POLL, 2, 0x260, 0x400, 0x400, 1000000, 0 }, + { T6050_MASK, 39, 0x0, 0x10, 0x10, 0, 0 }, + { T6050_DELAY, 0, 0x0, 0x0, 0x64, 0, 0 }, +}; +static const struct apple_t6050_usb_step apple_t6050_usb3_shutdown[] = { + { T6050_MASK, 35, 0x4, 0x1, 0x1, 0, 0 }, + { T6050_MASK, 35, 0x10c, 0x100000, 0x0, 0, 0 }, + { T6050_MASK, 35, 0x110, 0x60, 0x20, 0, 0 }, + { T6050_MASK, 35, 0x4, 0x8, 0x8, 0, 0 }, + { T6050_MASK, 35, 0x4, 0x8, 0x0, 0, 0 }, + { T6050_POLL, 35, 0x104, 0x100, 0x100, 1000000, 0 }, + { T6050_MASK, 35, 0x10c, 0x1e000000, 0x4000000, 0, 0 }, + { T6050_MASK, 35, 0x110, 0x600, 0x200, 0, 0 }, + { T6050_MASK, 35, 0x4, 0x8, 0x8, 0, 0 }, + { T6050_MASK, 35, 0x4, 0x8, 0x0, 0, 0 }, + { T6050_POLL, 35, 0x104, 0x100, 0x100, 1000000, 0 }, + { T6050_MASK, 35, 0x4, 0x1, 0x0, 0, 0 }, + { T6050_MASK, 2, 0x120, 0x100, 0x100, 0, 0 }, + { T6050_DELAY, 0, 0x0, 0x0, 0x1, 0, 0 }, + { T6050_MASK, 2, 0x120, 0x1, 0x0, 0, 0 }, + { T6050_DELAY, 0, 0x0, 0x0, 0x1, 0, 0 }, + { T6050_MASK, 2, 0x120, 0x10, 0x0, 0, 0 }, + { T6050_DELAY, 0, 0x0, 0x0, 0x1, 0, 0 }, + { T6050_MASK, 2, 0x120, 0x200, 0x200, 0, 0 }, + { T6050_DELAY, 0, 0x0, 0x0, 0x1, 0, 0 }, + { T6050_MASK, 2, 0x120, 0x2, 0x0, 0, 0 }, + { T6050_DELAY, 0, 0x0, 0x0, 0x1, 0, 0 }, + { T6050_MASK, 2, 0x120, 0x20, 0x0, 0, 0 }, + { T6050_DELAY, 0, 0x0, 0x0, 0x1, 0, 0 }, + { T6050_MASK, 5, 0x0, 0x2, 0x0, 0, 0 }, + { T6050_DELAY, 0, 0x0, 0x0, 0x1, 0, 0 }, + { T6050_MASK, 2, 0xc, 0x10, 0x10, 0, 0 }, + { T6050_DELAY, 0, 0x0, 0x0, 0x1, 0, 0 }, + { T6050_MASK, 2, 0xc, 0x1, 0x0, 0, 0 }, + { T6050_DELAY, 0, 0x0, 0x0, 0x1, 0, 0 }, + { T6050_MASK, 2, 0xc, 0x4, 0x0, 0, 0 }, + { T6050_DELAY, 0, 0x0, 0x0, 0x1, 0, 0 }, + { T6050_DELAY, 0, 0x0, 0x0, 0xc8, 0, 0 }, + { T6050_FIRMWARE, 0, 0x0, 0x0, 0x0, 0, 0 }, + { T6050_POWER, 0, 0x0, 0x0, 0x0, 0, 0 }, + { T6050_MASK, 39, 0x2c, 0x1, 0x1, 0, 0 }, + { T6050_POLL, 39, 0x4, 0x100, 0x100, 1000, 0 }, + { T6050_MASK, 39, 0x0, 0x10, 0x0, 0, 0 }, + { T6050_DELAY, 0, 0x0, 0x0, 0x1, 0, 0 }, + { T6050_MASK, 39, 0x0, 0x8, 0x0, 0, 0 }, + { T6050_DELAY, 0, 0x0, 0x0, 0x1, 0, 0 }, + { T6050_MASK, 39, 0x0, 0x4, 0x4, 0, 0 }, + { T6050_DELAY, 0, 0x0, 0x0, 0x1, 0, 0 }, + { T6050_MASK, 39, 0x0, 0x2, 0x0, 0, 0 }, + { T6050_DELAY, 0, 0x0, 0x0, 0x1, 0, 0 }, + { T6050_POLL, 39, 0x4, 0x2, 0x0, 1000, 0 }, + { T6050_DELAY, 0, 0x0, 0x0, 0x1, 0, 0 }, + { T6050_MASK, 39, 0x0, 0x1, 0x0, 0, 0 }, + { T6050_DELAY, 0, 0x0, 0x0, 0x1, 0, 0 }, + { T6050_POLL, 39, 0x4, 0x1, 0x0, 1000, 0 }, + { T6050_DELAY, 0, 0x0, 0x0, 0x1, 0, 0 }, + { T6050_MASK, 39, 0x8, 0x1, 0x0, 0, 0 }, + { T6050_DELAY, 0, 0x0, 0x0, 0x1, 0, 0 }, +}; + +static inline int +apple_t6050_usb_phy_run(const struct apple_t6050_usb_platform *p, + const struct apple_t6050_usb_step *steps, unsigned int count) +{ + if (!p || !p->io || !p->io->read || !p->io->write || + !p->io->poll || !p->io->delay || !p->wrapper_power || + !p->firmware || !p->tunable) + return -1; + for (unsigned int i = 0; i < count; i++) { + const struct apple_t6050_usb_step *s = &steps[i]; + int ret = 0; + switch (s->op) { + case T6050_MASK: + apple_t6050_usb_mask(p->io, s->bank, s->offset, s->mask, s->value); + break; + case T6050_POLL: + ret = p->io->poll(p->io->context, s->bank, s->offset, + s->mask, s->value, s->timeout); + break; + case T6050_DELAY: p->io->delay(p->io->context, s->value); break; + case T6050_POWER: ret = p->wrapper_power(p->context, s->value); break; + case T6050_FIRMWARE: ret = p->firmware(p->context, s->value); break; + case T6050_TUNABLE: ret = p->tunable(p->context, s->bank, s->name); break; + default: return -1; + } + if (ret) + return ret; + } + return 0; +} +#define APPLE_T6050_USB_RUN(p, steps) \ + apple_t6050_usb_phy_run((p), (steps), sizeof(steps) / sizeof((steps)[0])) + +/* Native shutdown first asserts PORT_RESET only if it was not already set. + * The 5 ms wait belongs to that transition, not every redundant stop call. + * Preserve IOPause(500 ns), whose unit differs from IODelay(microseconds). + */ +static inline int apple_t6050_usb2_stop(const struct apple_t6050_usb_platform *p) +{ + if (!p || !p->io || !p->io->read || !p->io->write || + !p->io->delay || !p->pause_ns) + return -1; + if (!(p->io->read(p->io->context, 0, 4) & 2)) { + apple_t6050_usb_mask(p->io, 0, 4, 2, 2); + p->io->delay(p->io->context, 5000); + } + apple_t6050_usb_mask(p->io, 0, 0, 7, 4); + apple_t6050_usb_mask(p->io, 0, 4, 8, 8); + apple_t6050_usb_mask(p->io, 0, 4, 1, 1); + apple_t6050_usb_mask(p->io, 0, 0x1c, 0x20000000, 0x20000000); + apple_t6050_usb_mask(p->io, 0, 0x1c, 0x40000000, 0x40000000); + p->pause_ns(p->context, 500); + return 0; +} +#endif From ad990d38db1b3ba42bc9a5023defa866e3b42606 Mon Sep 17 00:00:00 2001 From: Jeff Queisser Date: Mon, 2 Nov 2026 14:19:46 -0800 Subject: [PATCH 09/43] dt-bindings: phy: apple,t6050-dp-phy: Allow the USB domain on combo PHYs The combo PHY's USB2 PHY and digital banks are in the USB controller's power domain. Let a combo PHY name it fourth, as "usb", so the PHY can hold it on a port with no USB controller bound. The fixed DisplayPort PHY keeps exactly three domains. Signed-off-by: Jeff Queisser Co-Authored-By: Claude Opus 5.5 Signed-off-by: Erik Bourget --- .../devicetree/bindings/phy/apple,t6050-dp-phy.yaml | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/phy/apple,t6050-dp-phy.yaml b/Documentation/devicetree/bindings/phy/apple,t6050-dp-phy.yaml index 52cb96f4a965b..b055369faf9f6 100644 --- a/Documentation/devicetree/bindings/phy/apple,t6050-dp-phy.yaml +++ b/Documentation/devicetree/bindings/phy/apple,t6050-dp-phy.yaml @@ -74,14 +74,20 @@ properties: description: Offset, mask and value tuples for the PIPE handler power-domains: + description: + A combo PHY may name the USB controller's domain fourth ("usb"). Its + USB2 PHY and digital banks are in that domain, and the PHY then holds it + while it runs, also on a port with no USB controller bound. minItems: 3 - maxItems: 3 + maxItems: 4 power-domain-names: + minItems: 3 items: - const: usb-aon - const: common - const: wrapper + - const: usb apple,tunable-axi2af: $ref: /schemas/types.yaml#/definitions/uint32-array @@ -184,6 +190,10 @@ allOf: properties: '#phy-cells': const: 0 + power-domains: + maxItems: 3 + power-domain-names: + maxItems: 3 if: required: - mode-switch From 42b87ef7de672fdab3cc5fc4175e976c8840f807 Mon Sep 17 00:00:00 2001 From: Jeff Queisser Date: Mon, 2 Nov 2026 14:19:48 -0800 Subject: [PATCH 10/43] phy: apple: t6050: Hold the USB domain for the combo PHY's USB2 banks The combo PHY's USB2 PHY and digital banks are in the USB controller's power domain. Only a bound USB controller holds that domain, so on a port with no controller (a DisplayPort-only port, as on J775D with Linux on the hardware) the first Type-C mode switch wrote an unclocked bank and took an SError. A combo PHY node may now name that domain as a fourth power domain, "usb". When it does, the PHY holds it from combo start to combo stop, after the common domain on the way up and before it on the way down. Nodes that name three domains probe and run exactly as before, and the fixed DisplayPort PHY still takes exactly three. Testing: on J775D (M5 Ultra), with the fourth domain named on each combo port, Type-C mode switches and DisplayPort on the rear ports ran without an SError on every boot since 2026-10-30. Signed-off-by: Jeff Queisser Co-Authored-By: Claude Opus 5.5 Signed-off-by: Erik Bourget --- drivers/phy/apple/atc-t6050-combo.h | 8 ++++++++ drivers/phy/apple/atc-t6050-dp.c | 20 ++++++++++++++------ 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/drivers/phy/apple/atc-t6050-combo.h b/drivers/phy/apple/atc-t6050-combo.h index 1066748dc1afe..3c3454ceb64de 100644 --- a/drivers/phy/apple/atc-t6050-combo.h +++ b/drivers/phy/apple/atc-t6050-combo.h @@ -94,6 +94,8 @@ static int combo_stop(struct t6050_dp *dp) if (!ret) dp->usb2_active = false; } + if (!ret && dp->ndomains > 3) + ret = atc_domain_set(&dp->domain[3], false); if (!ret) ret = atc_domain_set(&dp->domain[1], false); if (!ret) @@ -118,6 +120,12 @@ static int combo_start(struct t6050_dp *dp, enum combo_mode mode) ret = atc_domain_set(&dp->domain[0], true); if (!ret) ret = atc_domain_set(&dp->domain[1], true); + /* + * The USB2 PHY and digital banks are unclocked without the USB domain, + * which a USB controller holds only while one is bound to this port. + */ + if (!ret && dp->ndomains > 3) + ret = atc_domain_set(&dp->domain[3], true); if (!ret) ret = APPLE_T6050_USB_RUN(&dp->usb_platform, apple_t6050_usb2_host); if (!ret) diff --git a/drivers/phy/apple/atc-t6050-dp.c b/drivers/phy/apple/atc-t6050-dp.c index cafee096a6250..4479153919488 100644 --- a/drivers/phy/apple/atc-t6050-dp.c +++ b/drivers/phy/apple/atc-t6050-dp.c @@ -83,7 +83,9 @@ struct t6050_dp { void __iomem *banks[45]; struct resource resources[45]; struct apple_tunable *tunables[ARRAY_SIZE(tunable_names)]; - struct atc_domain domain[3]; /* USB AON, common, wrapper: native order. */ + /* USB AON, common, wrapper: native order; a combo may add USB. */ + struct atc_domain domain[4]; + unsigned int ndomains; u32 training[4][16]; u32 voltage[4], pre[4]; int rate; @@ -605,14 +607,14 @@ static int t6050_typec_register(struct t6050_dp *dp) static int t6050_dp_probe(struct platform_device *pdev) { - static const char * const domains[] = {"usb-aon", "common", "wrapper"}; + static const char * const domains[] = {"usb-aon", "common", "wrapper", "usb"}; struct device *dev = &pdev->dev; struct t6050_dp *dp; struct phy_provider *provider; struct phy *phy; struct resource *core; void __iomem *base; - int ret; + int ndomains, ret; dp = devm_kzalloc(dev, sizeof(*dp), GFP_KERNEL); if (!dp) @@ -741,10 +743,16 @@ static int t6050_dp_probe(struct platform_device *pdev) if (ret) return ret; } - /* Three named domains prevent automatic wrapper enable at probe time. */ - if (of_count_phandle_with_args(dev->of_node, "power-domains", "#power-domain-cells") != 3) + /* + * Named domains prevent automatic wrapper enable at probe time. A combo + * PHY may also name the USB controller's domain, which its USB2 banks + * are in (combo_start()). + */ + ndomains = of_count_phandle_with_args(dev->of_node, "power-domains", "#power-domain-cells"); + if (ndomains != 3 && !(dp->combo && ndomains == 4)) return -EINVAL; - for (unsigned int i = 0; i < ARRAY_SIZE(domains); i++) { + dp->ndomains = ndomains; + for (unsigned int i = 0; i < dp->ndomains; i++) { dp->domain[i].dev = dev_pm_domain_attach_by_name(dev, domains[i]); if (IS_ERR_OR_NULL(dp->domain[i].dev)) return dev_err_probe(dev, dp->domain[i].dev ? PTR_ERR(dp->domain[i].dev) : -ENODEV, From 4ef9db44d4d951531b431a4d8ed26bbd1f720e33 Mon Sep 17 00:00:00 2001 From: Jeff Queisser Date: Mon, 2 Nov 2026 17:06:32 -0800 Subject: [PATCH 11/43] dt-bindings: phy: apple,t6050-dp-phy: Type apple,port-index as uint32 apple,port-index has an enum but no type, so dtschema cannot decode the cell and every combo PHY node fails with "b'\x00\x00\x00\x02' is not one of [1, 2]" and a core-schema type error, whatever port it names. Give it the uint32 type. Signed-off-by: Jeff Queisser Co-Authored-By: Claude Opus 5.5 Signed-off-by: Erik Bourget --- Documentation/devicetree/bindings/phy/apple,t6050-dp-phy.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/phy/apple,t6050-dp-phy.yaml b/Documentation/devicetree/bindings/phy/apple,t6050-dp-phy.yaml index b055369faf9f6..fdc2e60461700 100644 --- a/Documentation/devicetree/bindings/phy/apple,t6050-dp-phy.yaml +++ b/Documentation/devicetree/bindings/phy/apple,t6050-dp-phy.yaml @@ -37,6 +37,7 @@ properties: const: 1 apple,port-index: + $ref: /schemas/types.yaml#/definitions/uint32 enum: [1, 2] description: Native Type-C port; the debug/proxy port is excluded From 8b23bd4d3468912947de0790651739028b6d30b9 Mon Sep 17 00:00:00 2001 From: Jeff Queisser Date: Wed, 4 Nov 2026 11:59:40 -0800 Subject: [PATCH 12/43] usb: dwc3: apple: Add the bare-metal T6050 controller Add apple,t6050-dwc3, the T6050 (J775D) DWC3 on bare metal. It uses the shared tunable table, extended with the tables Diljit Singh's T6050 USB support applies, in the same order: the T6040 tables plus CIO (the Apple region from 0xcd20, after the fixed CIO values), bulk fabric and AUSBC debug (the optional drd-bulk and drd-ausbc regions). It is host-only, like T6040. The USB2 port reset follows the root hub's start (a no-op without a PHY reset op), and a USB3 PHY failure is fatal, as on T8132. Signed-off-by: Jeff Queisser Co-Authored-By: Claude Opus 5.5 Signed-off-by: Erik Bourget --- .../devicetree/bindings/usb/apple,dwc3.yaml | 36 ++++++- drivers/usb/dwc3/dwc3-apple.c | 95 +++++++++++++++---- 2 files changed, 107 insertions(+), 24 deletions(-) diff --git a/Documentation/devicetree/bindings/usb/apple,dwc3.yaml b/Documentation/devicetree/bindings/usb/apple,dwc3.yaml index 706d2866f61ba..7745fef5b686f 100644 --- a/Documentation/devicetree/bindings/usb/apple,dwc3.yaml +++ b/Documentation/devicetree/bindings/usb/apple,dwc3.yaml @@ -35,6 +35,23 @@ allOf: required: - resets - usb-role-switch + - if: + properties: + compatible: + contains: + const: apple,t6050-dwc3 + then: + properties: + reg: + minItems: 3 + reg-names: + minItems: 3 + else: + properties: + reg: + maxItems: 3 + reg-names: + maxItems: 3 $defs: apple,tunable: @@ -48,7 +65,7 @@ $defs: - description: Bits to be set after applying the mask description: > Controller table from the device's Apple device tree, applied after - every core reset (T6040). + every core reset (T6040, T6050). properties: compatible: @@ -74,7 +91,9 @@ properties: items: - description: Core DWC3 region - description: Apple-specific DWC3 region - - description: Controller configuration block (T6040) + - description: Controller configuration block (T6040, T6050) + - description: Bulk fabric block (T6050) + - description: AUSBC debug block (T6050) reg-names: minItems: 2 @@ -82,6 +101,8 @@ properties: - const: dwc3-core - const: dwc3-apple - const: drd-cfg + - const: drd-bulk + - const: drd-ausbc interrupts: maxItems: 1 @@ -118,7 +139,16 @@ properties: apple,tunable-cio: $ref: "#/$defs/apple,tunable" description: - Tunables applied to the CIO (USB4 tunnel) registers of the Apple region. + Tunables applied to the CIO registers of the Apple region, from offset + 0x20 of that region, after the driver's fixed CIO values. + + apple,tunable-bulk: + $ref: "#/$defs/apple,tunable" + description: Tunables applied to the drd-bulk region. + + apple,tunable-ausbc: + $ref: "#/$defs/apple,tunable" + description: Tunables applied to the drd-ausbc region. required: - compatible diff --git a/drivers/usb/dwc3/dwc3-apple.c b/drivers/usb/dwc3/dwc3-apple.c index f16aa6f21014b..f2484ee172862 100644 --- a/drivers/usb/dwc3/dwc3-apple.c +++ b/drivers/usb/dwc3/dwc3-apple.c @@ -100,7 +100,8 @@ * enum dwc3_apple_state is defined in dwc3-apple-state.h. */ -#define DWC3_APPLE_NR_TUNABLES 5 +#define DWC3_APPLE_NR_TUNABLES 8 +#define DWC3_APPLE_NR_DRD_REGS 3 /** * struct dwc3_apple - Apple-specific DWC3 USB controller @@ -127,7 +128,8 @@ * @rpm_held: The glue holds a runtime PM reference for the running core * @pre_core_rpm: Runtime PM is the glue's own until the first core bring-up * @dt_dis_u3_susphy: The device tree keeps USB3 PHY suspend off itself - * @drd_cfg: The controller's configuration block (optional, for its tunable) + * @drd_regs: The controller's optional blocks outside dwc3-core (drd-cfg, drd-bulk, drd-ausbc), + * mapped for their tunables * @tunables: Controller tunables from the device tree, see dwc3_apple_tunable_list */ struct dwc3_apple { @@ -158,7 +160,7 @@ struct dwc3_apple { bool pre_core_rpm; bool dt_dis_u3_susphy; - void __iomem *drd_cfg; + void __iomem *drd_regs[DWC3_APPLE_NR_DRD_REGS]; struct apple_tunable *tunables[DWC3_APPLE_NR_TUNABLES]; }; @@ -232,22 +234,43 @@ enum dwc3_apple_tunable_block { DWC3_APPLE_TUNABLE_GLOBALS, /* DWC3_GLOBALS_REGS_START */ DWC3_APPLE_TUNABLE_LINK, /* dwc3-core + 0xd000 */ DWC3_APPLE_TUNABLE_DBG, /* dwc3-core + 0xd800 */ + DWC3_APPLE_TUNABLE_CIO, /* dwc3-core + 0xcd20, in dwc3-apple */ + /* The optional blocks outside dwc3-core, in drd_regs[] order */ DWC3_APPLE_TUNABLE_DRD_CFG, /* drd-cfg */ + DWC3_APPLE_TUNABLE_DRD_BULK, /* drd-bulk */ + DWC3_APPLE_TUNABLE_DRD_AUSBC, /* drd-ausbc */ + DWC3_APPLE_NR_TUNABLE_BLOCKS }; +static const char * const dwc3_apple_drd_reg_names[DWC3_APPLE_NR_DRD_REGS] = { + "drd-cfg", "drd-bulk", "drd-ausbc", +}; + +static_assert(DWC3_APPLE_NR_TUNABLE_BLOCKS - DWC3_APPLE_TUNABLE_DRD_CFG == DWC3_APPLE_NR_DRD_REGS); + #define DWC3_APPLE_LINK_REGS_START 0xd000 #define DWC3_APPLE_DBG_REGS_START 0xd800 #define DWC3_APPLE_LINK_DBG_SIZE 0x800 +/* The CIO registers, the fixed ones of dwc3_apple_setup_cio() included */ +#define DWC3_APPLE_CIO_REGS_START 0xcd20 +/* + * In the order they are applied. A table is optional, and one for a block that the device tree + * does not describe is refused. The CIO table follows dwc3_apple_setup_cio() and overrides its + * fixed values (T6050). + */ static const struct { const char *prop; enum dwc3_apple_tunable_block block; } dwc3_apple_tunable_list[] = { + { "apple,tunable-bulk", DWC3_APPLE_TUNABLE_DRD_BULK }, { "apple,tunable-gbl-default", DWC3_APPLE_TUNABLE_GLOBALS }, { "apple,tunable-gbl-host", DWC3_APPLE_TUNABLE_GLOBALS }, + { "apple,tunable-cio", DWC3_APPLE_TUNABLE_CIO }, { "apple,tunable-dbg", DWC3_APPLE_TUNABLE_DBG }, { "apple,tunable-cfg-host", DWC3_APPLE_TUNABLE_DRD_CFG }, { "apple,tunable-link", DWC3_APPLE_TUNABLE_LINK }, + { "apple,tunable-ausbc", DWC3_APPLE_TUNABLE_DRD_AUSBC }, }; static_assert(ARRAY_SIZE(dwc3_apple_tunable_list) == DWC3_APPLE_NR_TUNABLES); @@ -262,8 +285,14 @@ static void __iomem *dwc3_apple_tunable_base(struct dwc3_apple *appledwc, return appledwc->apple_regs + DWC3_APPLE_LINK_REGS_START - APPLE_DWC3_REGS_START; case DWC3_APPLE_TUNABLE_DBG: return appledwc->apple_regs + DWC3_APPLE_DBG_REGS_START - APPLE_DWC3_REGS_START; + case DWC3_APPLE_TUNABLE_CIO: + return appledwc->apple_regs + DWC3_APPLE_CIO_REGS_START - APPLE_DWC3_REGS_START; case DWC3_APPLE_TUNABLE_DRD_CFG: - return appledwc->drd_cfg; + case DWC3_APPLE_TUNABLE_DRD_BULK: + case DWC3_APPLE_TUNABLE_DRD_AUSBC: + return appledwc->drd_regs[block - DWC3_APPLE_TUNABLE_DRD_CFG]; + case DWC3_APPLE_NR_TUNABLE_BLOCKS: + break; } return NULL; } @@ -281,8 +310,9 @@ static void dwc3_apple_apply_tunables(struct dwc3_apple *appledwc) /* The tunables are optional; a malformed one fails the probe. */ static int dwc3_apple_parse_tunables(struct dwc3_apple *appledwc, struct platform_device *pdev) { - struct resource *core = appledwc->mmio_resource, *drd_cfg; - struct resource blocks[] = { + struct resource *core = appledwc->mmio_resource; + /* A block the device tree does not describe stays empty, and its table is refused. */ + struct resource blocks[DWC3_APPLE_NR_TUNABLE_BLOCKS] = { [DWC3_APPLE_TUNABLE_GLOBALS] = DEFINE_RES_MEM(core->start + DWC3_GLOBALS_REGS_START, DWC3_GLOBALS_REGS_END - DWC3_GLOBALS_REGS_START + 1), @@ -290,17 +320,24 @@ static int dwc3_apple_parse_tunables(struct dwc3_apple *appledwc, struct platfor DWC3_APPLE_LINK_DBG_SIZE), [DWC3_APPLE_TUNABLE_DBG] = DEFINE_RES_MEM(core->start + DWC3_APPLE_DBG_REGS_START, DWC3_APPLE_LINK_DBG_SIZE), - [DWC3_APPLE_TUNABLE_DRD_CFG] = DEFINE_RES_MEM(0, 0), + [DWC3_APPLE_TUNABLE_CIO] = DEFINE_RES_MEM(core->start + DWC3_APPLE_CIO_REGS_START, + APPLE_DWC3_REGS_END - + DWC3_APPLE_CIO_REGS_START + 1), }; struct device *dev = appledwc->dev; int i; - drd_cfg = platform_get_resource_byname(pdev, IORESOURCE_MEM, "drd-cfg"); - if (drd_cfg) { - appledwc->drd_cfg = devm_ioremap_resource(dev, drd_cfg); - if (IS_ERR(appledwc->drd_cfg)) - return PTR_ERR(appledwc->drd_cfg); - blocks[DWC3_APPLE_TUNABLE_DRD_CFG] = *drd_cfg; + for (i = 0; i < DWC3_APPLE_NR_DRD_REGS; i++) { + struct resource *res; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + dwc3_apple_drd_reg_names[i]); + if (!res) + continue; + appledwc->drd_regs[i] = devm_ioremap_resource(dev, res); + if (IS_ERR(appledwc->drd_regs[i])) + return PTR_ERR(appledwc->drd_regs[i]); + blocks[DWC3_APPLE_TUNABLE_DRD_CFG + i] = *res; } for (i = 0; i < ARRAY_SIZE(dwc3_apple_tunable_list); i++) { @@ -628,8 +665,13 @@ static int dwc3_apple_set_role(struct dwc3_apple *appledwc, enum dwc3_apple_stat dwc3_enable_susphy(&appledwc->dwc, true); ret = phy_set_mode(appledwc->usb3_phy, host ? PHY_MODE_USB_HOST : PHY_MODE_USB_DEVICE); if (ret) { - /* A failed T8132 BIST or PIPE handoff must not start xHCI. */ - if (of_device_is_compatible(appledwc->dev->of_node, "apple,t8132-dwc3")) + /* + * A failed T8132 BIST or PIPE handoff must not start xHCI. Neither must a failed + * T6050 one: that PHY reports a port left on USB2 (four DisplayPort lanes) as + * success, and its errors leave the PIPE without a known producer. + */ + if (of_device_is_compatible(appledwc->dev->of_node, "apple,t8132-dwc3") || + of_device_is_compatible(appledwc->dev->of_node, "apple,t6050-dwc3")) return ret; dev_warn(appledwc->dev, "USB3 PHY setup failed, USB2 only, err=%d\n", ret); } else if (host && appledwc->usb3_phy && @@ -648,18 +690,19 @@ static int dwc3_apple_set_role(struct dwc3_apple *appledwc, enum dwc3_apple_stat } /* - * No device mode: the T6040 USB2 PHY implements host mode only (phy-apple-t6040-usb2), and a - * kernel built without gadget support turns dwc3_gadget_init() into a stub that succeeds. Such a - * controller must not follow a USB_ROLE_DEVICE request from the Type-C port controller (a charger - * or a host on the other end), or the core would end up in device mode on top of a PHY powered - * in host mode. + * No device mode: the T6040 USB2 PHY (phy-apple-t6040-usb2) and the T6050 Type-C PHY implement + * host mode only, and a kernel built without gadget support turns dwc3_gadget_init() into a stub + * that succeeds. Such a controller must not follow a USB_ROLE_DEVICE request from the Type-C port + * controller (a charger or a host on the other end), or the core would end up in device mode on + * top of a PHY powered in host mode. */ static bool dwc3_apple_host_only(struct device *dev) { if (!IS_ENABLED(CONFIG_USB_DWC3_GADGET) && !IS_ENABLED(CONFIG_USB_DWC3_DUAL_ROLE)) return true; - return of_device_is_compatible(dev->of_node, "apple,t6040-dwc3"); + return of_device_is_compatible(dev->of_node, "apple,t6040-dwc3") || + of_device_is_compatible(dev->of_node, "apple,t6050-dwc3"); } static int dwc3_apple_init(struct dwc3_apple *appledwc, enum dwc3_apple_state state) @@ -691,6 +734,16 @@ static int dwc3_apple_init(struct dwc3_apple *appledwc, enum dwc3_apple_state st dev_err(appledwc->dev, "Failed to initialize host, ret=%d\n", ret); goto core_exit; } + /* + * A USB2 PHY with a port reset (the T6050 Type-C PHY) takes it once the root hub is + * up; for the other PHYs, which have none, this is a no-op. + */ + ret = phy_reset(appledwc->usb2_phy); + if (ret) { + dev_err(appledwc->dev, "Failed to reset the USB2 port, ret=%d\n", ret); + dwc3_host_exit(&appledwc->dwc); + goto core_exit; + } break; case DWC3_APPLE_DEVICE: ret = dwc3_gadget_init(&appledwc->dwc); From 498e9b1a3ab1fb2c3f637ecb5f82c110bf987f4b Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Mon, 9 Nov 2026 23:01:02 +1100 Subject: [PATCH 13/43] selftests: apple: Model phy_reset() in the dwc3-apple host tests dwc3_apple_init() now resets the USB2 port through phy_reset() once the host is up. The dwc3 teardown model extracts dwc3_apple_init() and the PHY core functions it calls, so it needs phy_reset() and a reset member in its phy_ops; none of its PHYs has one, as on every SoC before T6050. neo-stability-tests also builds dwc3_apple_init() against a reduced dwc3_apple. Give it the usb2_phy member and a no-op phy_reset(). Assisted-by: LLM Signed-off-by: Ryan Murray --- tools/asahi/neo-stability-tests.py | 4 +++- tools/testing/selftests/drivers/gpu/apple-dcp/dwc3/mocks.c | 1 + tools/testing/selftests/drivers/gpu/apple-dcp/lifecycle.py | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/tools/asahi/neo-stability-tests.py b/tools/asahi/neo-stability-tests.py index 067a9d502801c..1d0a5302af4ce 100644 --- a/tools/asahi/neo-stability-tests.py +++ b/tools/asahi/neo-stability-tests.py @@ -80,7 +80,7 @@ def function(source, name): enum dwc3_apple_state {DWC3_APPLE_PROBE_PENDING,DWC3_APPLE_NO_CABLE,DWC3_APPLE_HOST,DWC3_APPLE_DEVICE,DWC3_APPLE_SUSPENDED}; #define DWC3_GCTL_PRTCAP_HOST 1 struct dwc3 {int num_usb3_ports,num_usb2_ports;int usb3_generic_phy[2],usb2_generic_phy[2],usb3_phy,usb2_phy;int current_dr_role;bool dis_u3_susphy_quirk;}; -struct dwc3_apple {struct device*dev;struct dwc3 dwc;int lock,reset;enum dwc3_apple_state state;bool fixed_hub,host_only,rpm_held,core_active,core_exit_pending,reset_pending,dt_dis_u3_susphy;unsigned usb3_power_off_pending,usb2_power_off_pending;}; +struct dwc3_apple {struct device*dev;struct dwc3 dwc;int lock,reset;enum dwc3_apple_state state;bool fixed_hub,host_only,rpm_held,core_active,core_exit_pending,reset_pending,dt_dis_u3_susphy;unsigned usb3_power_off_pending,usb2_power_off_pending;int usb2_phy;}; static int core_error,host_error,gadget_error,reset_error,role_error,start_role; static unsigned rpm_puts; /* A started core holds the glue runtime PM reference until core_stop drops it. */ @@ -98,6 +98,8 @@ def function(source, name): #define BIT(x) (1UL << (x)) static void dwc3_event_buffers_cleanup(struct dwc3*d){} static int phy_power_off(int p){return 0;} +/* Only the T6050 Type-C USB2 PHY has a port reset; for the PHYs here it is a no-op. */ +static int phy_reset(int p){return 0;} static void usb_phy_set_suspend(int p,int suspend){} static void dwc3_core_exit_after_phy_power_off(struct dwc3*d){core_exits++;} static u16 get_unaligned_le16(const u8*p){return p[0]|p[1]<<8;} diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/dwc3/mocks.c b/tools/testing/selftests/drivers/gpu/apple-dcp/dwc3/mocks.c index 806ec38dd7fd4..4c9f0ac4faad6 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/dwc3/mocks.c +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/dwc3/mocks.c @@ -36,6 +36,7 @@ struct phy_ops { int (*exit)(struct phy *); int (*power_on)(struct phy *); int (*power_off)(struct phy *); + int (*reset)(struct phy *); }; struct phy { struct apple_atcphy *data; diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/lifecycle.py b/tools/testing/selftests/drivers/gpu/apple-dcp/lifecycle.py index f42f8251fee56..0bf6edeac8c19 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/lifecycle.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/lifecycle.py @@ -1091,7 +1091,7 @@ def fn(n, name): 'atcphy_usb2_init', 'atcphy_usb3_power_off', 'atcphy_usb3_route', 'atcphy_usb3_set_mode', 'atcphy_usb2_power_off', '_atcphy_dwc3_reset_assert', 'atcphy_dwc3_reset_assert', 'atcphy_mux_set'), - 'phy': ('phy_init', 'phy_exit', 'phy_power_on', 'phy_power_off'), + 'phy': ('phy_init', 'phy_exit', 'phy_reset', 'phy_power_on', 'phy_power_off'), 'core': ('dwc3_phy_power_on', 'dwc3_phy_power_off', 'dwc3_phy_exit', 'dwc3_core_exit'), 'apple': ('dwc3_apple_host_before_core_init', 'dwc3_apple_core_stop', 'dwc3_apple_core_init', 'dwc3_apple_core_start', 'dwc3_apple_init', 'dwc3_apple_exit', 'dwc3_usb_role_switch_set'), From 44826a01ea0b34b1745b5255eb72985e7e9b7ab9 Mon Sep 17 00:00:00 2001 From: Jeff Queisser Date: Wed, 4 Nov 2026 11:59:40 -0800 Subject: [PATCH 14/43] phy: apple: t6050: Enable the dummy PIPE PHY for the USB3 lock The PIPE lock that starts the USB3 handoff is acknowledged only while the combo PHY's dummy PIPE PHY is enabled (pipehandler NONSELECTED_OVERRIDE bit 15), as on the earlier ATC PHYs (T8122, T8140), and the bit resets to clear with the controller's domain. Without it the lock timed out, the PHY faulted and rear port 2 had no USB at all. Enable it before the controller leaves reset. Each PIPE-switch step returns its own code, and every fault of the PHY is logged with its reason (the pipehandler registers for PIPE faults); dwc3-apple logs a fatal USB3 PHY failure. Signed-off-by: Jeff Queisser Co-Authored-By: Claude Opus 5.5 Signed-off-by: Erik Bourget --- drivers/phy/apple/atc-t6050-combo.h | 37 ++++++++++++++++--- .../apple/usb-t6050/apple_t6050_usb3_pipe.h | 19 +++++++--- drivers/usb/dwc3/dwc3-apple.c | 4 +- 3 files changed, 48 insertions(+), 12 deletions(-) diff --git a/drivers/phy/apple/atc-t6050-combo.h b/drivers/phy/apple/atc-t6050-combo.h index 3c3454ceb64de..6a9d97d233dcd 100644 --- a/drivers/phy/apple/atc-t6050-combo.h +++ b/drivers/phy/apple/atc-t6050-combo.h @@ -79,6 +79,17 @@ static int combo_quiesce(void *ctx) return ret; } +/* The pipehandler bank is the controller's: read it only while the controller holds its domain. */ +static void combo_pipe_fault(struct t6050_dp *dp, const char *what, int ret) +{ + dev_err(dp->dev, "%s failed (%d): pipe 00=%08x 04=%08x 0c=%08x 10=%08x 14=%08x 1c=%08x 20=%08x\n", + what, ret, readl(dp->banks[44]), readl(dp->banks[44] + 0x4), + readl(dp->banks[44] + 0xc), readl(dp->banks[44] + 0x10), + readl(dp->banks[44] + 0x14), readl(dp->banks[44] + 0x1c), + readl(dp->banks[44] + 0x20)); + dp->faulted = true; +} + static int combo_stop(struct t6050_dp *dp) { int ret; @@ -101,6 +112,7 @@ static int combo_stop(struct t6050_dp *dp) if (!ret) ret = atc_domain_set(&dp->domain[0], false); if (ret) { + dev_err(dp->dev, "stop failed (%d)\n", ret); dp->faulted = true; return ret < 0 ? ret : -EIO; } @@ -142,6 +154,7 @@ static int combo_start(struct t6050_dp *dp, enum combo_mode mode) } if (ret) { /* A partial shared transition has no cold-start ownership proof. */ + dev_err(dp->dev, "start of mode %d failed (%d)\n", mode, ret); dp->faulted = true; return ret < 0 ? ret : -EIO; } @@ -217,10 +230,12 @@ static int combo_usb2_mode(struct phy *phy, enum phy_mode mode, int submode) if (dp->usb2_active) return 0; ret = APPLE_T6050_USB_RUN(&dp->usb_platform, apple_t6050_usb2_host); - if (ret) + if (ret) { + dev_err(dp->dev, "USB2 host start failed (%d)\n", ret); dp->faulted = true; - else + } else { dp->usb2_active = true; + } return ret; } @@ -233,8 +248,10 @@ static int combo_usb2_reset(struct phy *phy) if (dp->faulted || !dp->usb2_active) return -ENOLINK; ret = apple_t6050_usb2_port_reset(&dp->usb_io, dp, combo_sleep); - if (ret) + if (ret) { + dev_err(dp->dev, "USB2 port reset failed (%d)\n", ret); dp->faulted = true; + } return ret ? -EIO : 0; } @@ -261,7 +278,7 @@ static int combo_usb3_mode(struct phy *phy, enum phy_mode mode, int submode) apple_tunable_apply(dp->banks[44], dp->pipe_default); ret = apple_t6050_usb3_pipe_switch(&dp->usb_io); if (ret) - dp->faulted = true; + combo_pipe_fault(dp, "USB3 PIPE switch", ret); else dp->pipe_active = true; return ret ? -EIO : 0; @@ -287,8 +304,10 @@ static int combo_usb3_poweroff(struct phy *phy) if (!ret) dp->usb2_active = false; } - if (ret) + if (ret) { + dev_err(dp->dev, "USB3 power-off failed (%d)\n", ret); dp->faulted = true; + } return ret ? -EIO : 0; } static const struct phy_ops combo_usb2_ops = { @@ -312,7 +331,7 @@ static int combo_reset_assert(struct reset_controller_dev *reset, unsigned long apple_t6050_usb_mask(&dp->usb_io, 44, 0x1c, 0x10, 0x10); ret = apple_t6050_usb3_pipe_cold_dummy(&dp->usb_io); if (ret) - dp->faulted = true; + combo_pipe_fault(dp, "dummy PIPE park", ret); else dp->controller_active = false; return ret ? -EIO : 0; @@ -326,6 +345,12 @@ static int combo_reset_deassert(struct reset_controller_dev *reset, unsigned lon return -EINVAL; if (dp->faulted || !dp->usb2_active) return -ENOLINK; + /* + * The PIPE lock that starts the USB3 handoff is acknowledged only while the dummy PHY + * behind the mux is enabled (NONSELECTED_OVERRIDE bit 15), as on the earlier ATC PHYs. + * The bit resets to clear with the controller's domain, so set it for every session. + */ + apple_t6050_usb_mask(&dp->usb_io, 44, 0x20, BIT(15), BIT(15)); apple_t6050_usb_mask(&dp->usb_io, 44, 0x1c, 0x10, 0); apple_t6050_usb_mask(&dp->usb_io, 44, 0x1c, 1, 1); dp->controller_active = true; diff --git a/drivers/phy/apple/usb-t6050/apple_t6050_usb3_pipe.h b/drivers/phy/apple/usb-t6050/apple_t6050_usb3_pipe.h index 953a42dbc7aab..f70f6f1090381 100644 --- a/drivers/phy/apple/usb-t6050/apple_t6050_usb3_pipe.h +++ b/drivers/phy/apple/usb-t6050/apple_t6050_usb3_pipe.h @@ -36,6 +36,11 @@ apple_t6050_usb3_pipe_cold_dummy(const struct apple_t6050_usb_io *io) return (io->read(io->context, 44, 0x0c) & 0x73) == 0x42 ? 0 : -1; } +/* + * Returns 0, or the failing step: -2 the PIPE was not parked on a known producer, -3 the lock was + * not acknowledged, -4..-6 the BIST, -7 the mux did not read back USB3, -8 the BIST did not stop, + * -9 the lock release was not acknowledged. Up to -6 the mux is untouched. + */ static inline int apple_t6050_usb3_pipe_switch(const struct apple_t6050_usb_io *io) { @@ -48,22 +53,26 @@ apple_t6050_usb3_pipe_switch(const struct apple_t6050_usb_io *io) mux = io->read(io->context, 44, 0x0c) & 0x73; /* Never reroute an active USB4 producer or guess an unknown state. */ if (!(aon & 1) || (mux != 0x42 && mux != 0x10)) - return -1; + return -2; /* Native T8142 xHCI USB3 mode switch, native27 T6050-compatible path. */ apple_t6050_usb_mask(io, 44, 0x04, 0x0e, 0); apple_t6050_usb_mask(io, 44, 0x00, 1, 1); apple_t6050_usb_mask(io, 44, 0x10, 1, 1); locked = 1; + result = -3; if (io->poll(io->context, 44, 0x14, 1, 1, 6000)) goto out; bist = 1; + result = -4; if (apple_t6050_usb_bist_on(io)) goto out; apple_t6050_usb_mask(io, 44, 0x20, 0x100f, 3); + result = -5; if (io->poll(io->context, 44, 0x20, 0xf, 3, 1000)) goto out; + result = -6; if (apple_t6050_usb_bist_off(io)) goto out; bist = 0; @@ -73,16 +82,16 @@ apple_t6050_usb3_pipe_switch(const struct apple_t6050_usb_io *io) io->delay(io->context, 5); apple_t6050_usb_mask(io, 44, 0x0c, 0x73, 0); apple_t6050_usb_mask(io, 44, 0x0c, 0x73, 0x10); - result = (io->read(io->context, 44, 0x0c) & 0x73) == 0x10 ? 0 : -1; + result = (io->read(io->context, 44, 0x0c) & 0x73) == 0x10 ? 0 : -7; out: if (bist && apple_t6050_usb_bist_off(io)) - result = -1; + result = -8; apple_t6050_usb_mask(io, 44, 0x00, 5, 0); if (locked) { apple_t6050_usb_mask(io, 44, 0x10, 1, 0); - if (io->poll(io->context, 44, 0x14, 1, 0, 6000)) - result = -1; + if (io->poll(io->context, 44, 0x14, 1, 0, 6000) && !result) + result = -9; } return result; } diff --git a/drivers/usb/dwc3/dwc3-apple.c b/drivers/usb/dwc3/dwc3-apple.c index f2484ee172862..f66370e6b5f88 100644 --- a/drivers/usb/dwc3/dwc3-apple.c +++ b/drivers/usb/dwc3/dwc3-apple.c @@ -671,8 +671,10 @@ static int dwc3_apple_set_role(struct dwc3_apple *appledwc, enum dwc3_apple_stat * success, and its errors leave the PIPE without a known producer. */ if (of_device_is_compatible(appledwc->dev->of_node, "apple,t8132-dwc3") || - of_device_is_compatible(appledwc->dev->of_node, "apple,t6050-dwc3")) + of_device_is_compatible(appledwc->dev->of_node, "apple,t6050-dwc3")) { + dev_err(appledwc->dev, "USB3 PHY setup failed, err=%d\n", ret); return ret; + } dev_warn(appledwc->dev, "USB3 PHY setup failed, USB2 only, err=%d\n", ret); } else if (host && appledwc->usb3_phy && of_device_is_compatible(appledwc->dev->of_node, "apple,t8132-dwc3")) { From 4e780eb1f424544e685674f2460dd0f0cbdf1860 Mon Sep 17 00:00:00 2001 From: Jeff Queisser Date: Wed, 4 Nov 2026 11:59:40 -0800 Subject: [PATCH 15/43] phy: apple: t6050: Stay on USB2 when the PIPE never left the dummy A USB3 PIPE switch can fail before the mux moves, with the PIPE still on its dummy producer. The PHY then faulted and lost USB2 with it. Leave the port on USB2 in that case, as four DisplayPort lanes do. Signed-off-by: Jeff Queisser Co-Authored-By: Claude Opus 5.5 Signed-off-by: Erik Bourget --- drivers/phy/apple/atc-t6050-combo.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/phy/apple/atc-t6050-combo.h b/drivers/phy/apple/atc-t6050-combo.h index 6a9d97d233dcd..d4aa80de61ac8 100644 --- a/drivers/phy/apple/atc-t6050-combo.h +++ b/drivers/phy/apple/atc-t6050-combo.h @@ -277,6 +277,11 @@ static int combo_usb3_mode(struct phy *phy, enum phy_mode mode, int submode) return (readl(dp->banks[44] + 0xc) & 0x73) == 0x10 ? 0 : -EIO; apple_tunable_apply(dp->banks[44], dp->pipe_default); ret = apple_t6050_usb3_pipe_switch(&dp->usb_io); + if (ret >= -6 && ret < 0 && (readl(dp->banks[44] + 0xc) & 0x73) == 0x42) { + /* The PIPE never left its dummy producer: the port stays USB2, as with four DP lanes. */ + dev_warn(dp->dev, "USB3 PIPE switch failed (%d), the port runs USB2 only\n", ret); + return 0; + } if (ret) combo_pipe_fault(dp, "USB3 PIPE switch", ret); else From 931f247beeb2cdc67b078f511e3825373e1f95c1 Mon Sep 17 00:00:00 2001 From: Jeff Queisser Date: Tue, 3 Nov 2026 01:50:54 -0800 Subject: [PATCH 16/43] phy: apple: t6050: Accept every Type-C port but the debug port The combo PHY refused any apple,port-index other than 1 or 2, the two ports a guest description produced. Nothing else in the driver reads the index: the firmware buffer, banks and sequences are port-agnostic. Validate it against the board's port set instead: 1 to 5, with 0 (the port that carries the boot loader's debug link) still excluded, here and in the binding. Signed-off-by: Jeff Queisser Co-Authored-By: Claude Opus 5.5 Signed-off-by: Erik Bourget --- Documentation/devicetree/bindings/phy/apple,t6050-dp-phy.yaml | 4 ++-- drivers/phy/apple/atc-t6050-dp.c | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Documentation/devicetree/bindings/phy/apple,t6050-dp-phy.yaml b/Documentation/devicetree/bindings/phy/apple,t6050-dp-phy.yaml index fdc2e60461700..8cbfe94b62ecd 100644 --- a/Documentation/devicetree/bindings/phy/apple,t6050-dp-phy.yaml +++ b/Documentation/devicetree/bindings/phy/apple,t6050-dp-phy.yaml @@ -38,8 +38,8 @@ properties: apple,port-index: $ref: /schemas/types.yaml#/definitions/uint32 - enum: [1, 2] - description: Native Type-C port; the debug/proxy port is excluded + enum: [1, 2, 3, 4, 5] + description: Type-C port; the debug port (0) is excluded apple,guest-port-reserved: type: boolean diff --git a/drivers/phy/apple/atc-t6050-dp.c b/drivers/phy/apple/atc-t6050-dp.c index 4479153919488..01c666bb9e7dc 100644 --- a/drivers/phy/apple/atc-t6050-dp.c +++ b/drivers/phy/apple/atc-t6050-dp.c @@ -623,8 +623,9 @@ static int t6050_dp_probe(struct platform_device *pdev) dp->combo = of_device_is_compatible(dev->of_node, "apple,t6050-combo-phy"); if (dp->combo) { u32 port; + /* Every Type-C port but 0, the debug port that carries the boot link. */ if (of_property_read_u32(dev->of_node, "apple,port-index", &port) || - (port != 1 && port != 2) || + port < 1 || port > 5 || !of_property_present(dev->of_node, "apple,guest-port-reserved") || !of_property_present(dev->of_node, "mode-switch")) return -EINVAL; From 03a0475542f1a47ffdc5cb4b452a5086b31b1ec8 Mon Sep 17 00:00:00 2001 From: Erik Bourget Date: Tue, 3 Nov 2026 22:22:09 -0500 Subject: [PATCH 17/43] arm64: dts: apple: j716c: Add the right USB-C port (ATC2) Describe the right-hand USB-C port: its SN201202x PD controller, the ATC2 combo PHY, usb-drd2 and its DARTs, and their power domains. On J716c the port's HPM is ADT /arm-io/nub-spmi-a1/hpm2 (SPMI address 0xc, port-location "right"), on its own SPMI bus, not on nub-spmi-a0 at address 8 as on J775D; on J716c that address is hpm5 (port-type 0x11), not a port's controller. Add nub-spmi-a1 to t6050-max.dtsi, polled as nub-spmi-a0 is, put the HPM on it, and enable the ATC2 chain that J775D leaves to the display handoff. Add the atcphy2 alias so that the boot loader fills the PHY's tunables from /arm-io/atc-phy2, and turn dcpext0's Type-C route off with dcpext0: a mode-switch endpoint whose mux is never registered would defer the PD controller forever. Developed with Claude Code. Signed-off-by: Erik Bourget --- arch/arm64/boot/dts/apple/t6050-j716c.dts | 122 +++++++++++++++++- arch/arm64/boot/dts/apple/t6050-max.dtsi | 13 ++ .../boot/dts/apple/t6050-peripherals.dtsi | 2 +- 3 files changed, 135 insertions(+), 2 deletions(-) diff --git a/arch/arm64/boot/dts/apple/t6050-j716c.dts b/arch/arm64/boot/dts/apple/t6050-j716c.dts index 33fd4e1c9b03c..550aef6f8768f 100644 --- a/arch/arm64/boot/dts/apple/t6050-j716c.dts +++ b/arch/arm64/boot/dts/apple/t6050-j716c.dts @@ -13,6 +13,7 @@ /dts-v1/; +#define T6050_J716C #include "t6050-max.dtsi" / { @@ -220,5 +221,124 @@ apple,output-commands = /bits/ 64 <0x001a000000000000 0x000f000000800000 0x011c000000010000>; }; -#define T6050_J716C #include "t6050-peripherals.dtsi" + +/* + * ==== The right USB-C port (ATC2) ==== + * + * ADT /arm-io/nub-spmi-a1/hpm2 (SN201202x, SPMI address 0xc, port-number 3, port-location + * "right", acio-parent acio2), atc-phy2, usb-drd2 and dart-usb2. The HPM is on its own bus, + * nub-spmi-a1, and nothing hands the port off, so the chain is enabled outright. Aura's + * kboot_setup_atc() fills the PHY's tunables through the atcphy2 alias. + */ +/ { + aliases { + atcphy2 = &atc2_phy; + }; +}; + +&ps_atc2_common { + status = "okay"; +}; + +&ps_atc2_usb_aon { + status = "okay"; +}; + +&ps_atc2_phymxwrap { + status = "okay"; +}; + +&ps_atc2_usb { + status = "okay"; +}; + +&atc2_phy_mbox { + status = "okay"; +}; + +&atc2_phy { + status = "okay"; + + port { + atc2_phy_ep: endpoint { + remote-endpoint = <&hpm2_ss>; + }; + }; +}; + +&usb_drd2 { + status = "okay"; + + port { + usb_drd2_hs: endpoint { + remote-endpoint = <&hpm2_hs>; + }; + }; +}; + +&usb_drd2_dart_0 { + status = "okay"; +}; + +&usb_drd2_dart_1 { + status = "okay"; +}; + +/* + * dcpext0's Type-C route stays off with dcpext0: a mode-switch endpoint whose mux is never + * registered would defer the PD controller forever. + */ +&dcpext0 { + typec-routes { + route@0 { + status = "disabled"; + + port { + dcpext0_typec: endpoint { + remote-endpoint = <&hpm2_dp>; + }; + }; + }; + }; +}; + +&nub_spmi_a1 { + hpm2: usb-pd@c { + compatible = "apple,sn201202x"; + reg = <0xc SPMI_USID>; + + connector { + compatible = "usb-c-connector"; + label = "USB-C Right"; + power-role = "dual"; + data-role = "dual"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + hpm2_hs: endpoint { + remote-endpoint = <&usb_drd2_hs>; + }; + }; + + port@1 { + reg = <1>; + hpm2_ss: endpoint { + remote-endpoint = <&atc2_phy_ep>; + }; + }; + + port@2 { + reg = <2>; + hpm2_dp: endpoint { + remote-endpoint = <&dcpext0_typec>; + }; + }; + }; + }; + }; +}; diff --git a/arch/arm64/boot/dts/apple/t6050-max.dtsi b/arch/arm64/boot/dts/apple/t6050-max.dtsi index 760094fce8097..55cf22a98e2a2 100644 --- a/arch/arm64/boot/dts/apple/t6050-max.dtsi +++ b/arch/arm64/boot/dts/apple/t6050-max.dtsi @@ -115,4 +115,17 @@ interrupt-controller; #interrupt-cells = <2>; }; + +#ifdef T6050_J716C + /* + * /arm-io/nub-spmi-a1: reg[0] 0x8a1a8000, generation 4, a bus of USB-C PD controllers (the + * board's). They are polled, as on nub-spmi-a0, so the bus takes no interrupt. + */ + nub_spmi_a1: spmi@28a1a8000 { + compatible = "apple,t6050-spmi", "apple,t8142-spmi"; + reg = <0x2 0x8a1a8000 0x0 0x4000>; + #address-cells = <2>; + #size-cells = <0>; + }; +#endif }; diff --git a/arch/arm64/boot/dts/apple/t6050-peripherals.dtsi b/arch/arm64/boot/dts/apple/t6050-peripherals.dtsi index eef667d647d3d..ae450252f2acb 100644 --- a/arch/arm64/boot/dts/apple/t6050-peripherals.dtsi +++ b/arch/arm64/boot/dts/apple/t6050-peripherals.dtsi @@ -1319,7 +1319,7 @@ }; #endif -#if defined(T6050_J714C) || defined(T6050_J714S) || defined(T6050_J716C) || defined(T6050_K114C) || defined(T6050_K114S) || defined(T6050_K116C) || defined(T6050_K116S) +#if defined(T6050_J714C) || defined(T6050_J714S) || defined(T6050_K114C) || defined(T6050_K114S) || defined(T6050_K116C) || defined(T6050_K116S) nub_spmi_a1: spmi@28a1a8000 { compatible = "apple,t6050-spmi", "apple,t8142-spmi"; reg = <0x2 0x8a1a8000 0x0 0x4000>, From a1d086a6f287ddb74788f6a634ecfc4de343c4e8 Mon Sep 17 00:00:00 2001 From: Erik Bourget Date: Tue, 3 Nov 2026 22:35:50 -0500 Subject: [PATCH 18/43] phy: apple: t6050: carry on when the PIPE lock is not acknowledged On J716c the PIPE handler does not acknowledge the lock request when the controller switches from the dummy producer to USB3, even with the dummy PHY behind the mux enabled. The switch then fails and the port falls back to USB2. The switch works without the ACK. Wait 6 ms for the lock ACK, and for an unlock ACK to clear, then log the PIPE handler state if it is missing and complete the switch. With this, USB3 comes up on that port. Developed with Claude Code. Signed-off-by: Erik Bourget --- drivers/phy/apple/atc-t6050-combo.h | 12 +++++++++++- .../phy/apple/usb-t6050/apple_t6050_usb3_pipe.h | 14 ++++++-------- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/drivers/phy/apple/atc-t6050-combo.h b/drivers/phy/apple/atc-t6050-combo.h index d4aa80de61ac8..1da782c4f9de4 100644 --- a/drivers/phy/apple/atc-t6050-combo.h +++ b/drivers/phy/apple/atc-t6050-combo.h @@ -18,9 +18,19 @@ static int combo_poll(void *ctx, unsigned int bank, unsigned int offset, { struct t6050_dp *dp = ctx; u32 value; - return readl_poll_timeout(dp->banks[bank] + offset, value, + int ret = readl_poll_timeout(dp->banks[bank] + offset, value, (value & mask) == expected, (bank == 44 || bank == 35 || usec <= 1000) ? 1 : 1000, usec); + + /* The PIPE switch carries on without the lock ACK; say so. */ + if (ret && bank == 44 && offset == 0x14 && usec == 6000) + dev_warn(dp->dev, + "PIPE %slock not acknowledged; continuing (override=%x values=%x mux=%x req=%x ack=%x aon=%x nonsel=%x)\n", + expected ? "" : "un", readl(dp->banks[44]), + readl(dp->banks[44] + 4), readl(dp->banks[44] + 0xc), + readl(dp->banks[44] + 0x10), value, + readl(dp->banks[44] + 0x1c), readl(dp->banks[44] + 0x20)); + return ret; } static void combo_delay(void *ctx, unsigned int usec) { udelay(usec); } static void combo_pause(void *ctx, unsigned int nsec) { ndelay(nsec); } diff --git a/drivers/phy/apple/usb-t6050/apple_t6050_usb3_pipe.h b/drivers/phy/apple/usb-t6050/apple_t6050_usb3_pipe.h index f70f6f1090381..812227cac03e9 100644 --- a/drivers/phy/apple/usb-t6050/apple_t6050_usb3_pipe.h +++ b/drivers/phy/apple/usb-t6050/apple_t6050_usb3_pipe.h @@ -37,9 +37,10 @@ apple_t6050_usb3_pipe_cold_dummy(const struct apple_t6050_usb_io *io) } /* - * Returns 0, or the failing step: -2 the PIPE was not parked on a known producer, -3 the lock was - * not acknowledged, -4..-6 the BIST, -7 the mux did not read back USB3, -8 the BIST did not stop, - * -9 the lock release was not acknowledged. Up to -6 the mux is untouched. + * Returns 0, or the failing step: -2 the PIPE was not parked on a known producer, -4..-6 the + * BIST, -7 the mux did not read back USB3, -8 the BIST did not stop. Up to -6 the mux is + * untouched. A lock or unlock that is not acknowledged within 6 ms is not fatal: it is logged + * and the switch carries on. On J716c the lock ACK does not come. */ static inline int apple_t6050_usb3_pipe_switch(const struct apple_t6050_usb_io *io) @@ -60,9 +61,7 @@ apple_t6050_usb3_pipe_switch(const struct apple_t6050_usb_io *io) apple_t6050_usb_mask(io, 44, 0x00, 1, 1); apple_t6050_usb_mask(io, 44, 0x10, 1, 1); locked = 1; - result = -3; - if (io->poll(io->context, 44, 0x14, 1, 1, 6000)) - goto out; + io->poll(io->context, 44, 0x14, 1, 1, 6000); bist = 1; result = -4; @@ -90,8 +89,7 @@ apple_t6050_usb3_pipe_switch(const struct apple_t6050_usb_io *io) apple_t6050_usb_mask(io, 44, 0x00, 5, 0); if (locked) { apple_t6050_usb_mask(io, 44, 0x10, 1, 0); - if (io->poll(io->context, 44, 0x14, 1, 0, 6000) && !result) - result = -9; + io->poll(io->context, 44, 0x14, 1, 0, 6000); } return result; } From 1856c663bd2501de5e7f3aa86d3216ab3c34d071 Mon Sep 17 00:00:00 2001 From: Erik Bourget Date: Mon, 2 Nov 2026 23:41:32 -0500 Subject: [PATCH 19/43] reset: apple-cio: add the T6050 CIO reconfig request On T6050 the CPU wrapper of each USB4 router is clocked through the CIO reconfig request (the router CPU's clock gate CIOn_RECONFIG_V). Until it has run, any access to the wrapper faults. The request register uses the T8142 bit positions (request BIT(2 * port), four ports) and sits at offset 0x60 of a page in the die's PMGR block, 0x280620000 on die 0. Its odd bit is a done flag rather than a busy flag: it is set once the request is taken, then request and done clear by themselves about 4 ms later. The wrapper takes an SError when it is accessed before they clear. Wait for both bits to clear (a reconfiguration still under way), store the request, wait for the done flag, then wait for both bits to clear again. Developed with Claude Code. Signed-off-by: Erik Bourget --- .../bindings/reset/apple,t8103-cio-reset.yaml | 3 + drivers/reset/reset-apple-cio.c | 65 +++++++++++++++++-- 2 files changed, 64 insertions(+), 4 deletions(-) diff --git a/Documentation/devicetree/bindings/reset/apple,t8103-cio-reset.yaml b/Documentation/devicetree/bindings/reset/apple,t8103-cio-reset.yaml index a7bc6bf4885c9..5d7e40b564f91 100644 --- a/Documentation/devicetree/bindings/reset/apple,t8103-cio-reset.yaml +++ b/Documentation/devicetree/bindings/reset/apple,t8103-cio-reset.yaml @@ -22,6 +22,9 @@ description: | On t8142, a standalone register page contains a shared request register with alternating request and busy bits for four ports. + On t6050 the same request register sits at offset 0x60 of a page in the + die's PMGR block. + In all cases the reset id is the CIO port index on the die. properties: diff --git a/drivers/reset/reset-apple-cio.c b/drivers/reset/reset-apple-cio.c index af299473a6c87..e5755ebb39aff 100644 --- a/drivers/reset/reset-apple-cio.c +++ b/drivers/reset/reset-apple-cio.c @@ -37,7 +37,12 @@ * separate 16 KiB page at 0x350780000, so the register is 0x350780008. The * device tree describes that page as its own syscon. t8122 is unresolved. * - * On t8142, RegMap 0x62 is a separate MMIO region at 0x380780000. + * On t8142, RegMap 0x62 is a separate MMIO region at 0x380780000. On t6050 + * the register is at +0x60 in a page of the die's PMGR block (0x280620000 + * on die 0). There the odd bit is a done flag rather than a busy flag: it + * is set once the request is taken, then request and done clear by + * themselves within a few ms. Only after that is the port's CPU wrapper + * accessible. * The t8132 layout has not been decoded. * * Reference firmware polls the busy bits, stores the request, then polls @@ -56,6 +61,8 @@ #define APPLE_CIO_RESET_POLL_US 100 #define APPLE_CIO_RESET_TIMEOUT_US 100000 +/* A t6050 reconfiguration takes a few ms; allow ample margin. */ +#define APPLE_CIO_RESET_T6050_TIMEOUT_US 192000 #define T8103_CIO_CTRL_STRIDE 0x4000 #define T8103_CIO_CTRL_INIT_REQ BIT(0) @@ -75,6 +82,7 @@ struct apple_cio_reset; struct apple_cio_reset_variant { unsigned int nr_resets; bool pmgr_child; + u32 ctrl; /* request register, t8142 layout */ int (*deassert)(struct apple_cio_reset *priv, unsigned long id); }; @@ -174,19 +182,19 @@ static int t8142_cio_deassert(struct apple_cio_reset *priv, unsigned long id) guard(mutex)(&priv->lock); /* Serialize all four ports sharing the request register. */ - ret = regmap_read_poll_timeout(priv->regmap, T8142_CIO_CTRL, val, + ret = regmap_read_poll_timeout(priv->regmap, priv->variant->ctrl, val, !(val & T8142_CIO_CTRL_BUSY_MASK), APPLE_CIO_RESET_POLL_US, APPLE_CIO_RESET_TIMEOUT_US); if (ret) return ret; - ret = regmap_write(priv->regmap, T8142_CIO_CTRL, + ret = regmap_write(priv->regmap, priv->variant->ctrl, T8142_CIO_CTRL_INIT_REQ(id)); if (ret) return ret; - return regmap_read_poll_timeout(priv->regmap, T8142_CIO_CTRL, val, + return regmap_read_poll_timeout(priv->regmap, priv->variant->ctrl, val, !(val & T8142_CIO_CTRL_INIT_BUSY(id)), APPLE_CIO_RESET_POLL_US, APPLE_CIO_RESET_TIMEOUT_US); @@ -194,9 +202,54 @@ static int t8142_cio_deassert(struct apple_cio_reset *priv, unsigned long id) static const struct apple_cio_reset_variant apple_t8142_cio_reset = { .nr_resets = 4, + .ctrl = T8142_CIO_CTRL, .deassert = t8142_cio_deassert, }; +static int t6050_cio_deassert(struct apple_cio_reset *priv, unsigned long id) +{ + u32 mask = T8142_CIO_CTRL_INIT_REQ(id) | T8142_CIO_CTRL_INIT_BUSY(id); + u32 val; + int ret; + + guard(mutex)(&priv->lock); + + /* Let a reconfiguration still under way finish first. */ + ret = regmap_read_poll_timeout(priv->regmap, priv->variant->ctrl, val, + !(val & mask), APPLE_CIO_RESET_POLL_US, + APPLE_CIO_RESET_T6050_TIMEOUT_US); + if (ret) + return ret; + + ret = regmap_write(priv->regmap, priv->variant->ctrl, + T8142_CIO_CTRL_INIT_REQ(id)); + if (ret) + return ret; + + ret = regmap_read_poll_timeout(priv->regmap, priv->variant->ctrl, val, + val & T8142_CIO_CTRL_INIT_BUSY(id), + APPLE_CIO_RESET_POLL_US, + APPLE_CIO_RESET_T6050_TIMEOUT_US); + if (ret) + return ret; + + /* + * The CPU wrapper is usable only once the reconfiguration is over, when + * the request and done bits clear again, a few ms later. An access + * before that takes an SError. + */ + return regmap_read_poll_timeout(priv->regmap, priv->variant->ctrl, val, + !(val & mask), APPLE_CIO_RESET_POLL_US, + APPLE_CIO_RESET_T6050_TIMEOUT_US); +} + +/* The t8142 bit layout in a register mapped on its own, with a done flag. */ +static const struct apple_cio_reset_variant apple_t6050_cio_reset = { + .nr_resets = 4, + .ctrl = 0x60, + .deassert = t6050_cio_deassert, +}; + static const struct apple_cio_reset_variant apple_t8103_cio_reset = { .nr_resets = 2, .deassert = t8103_cio_deassert, @@ -277,6 +330,10 @@ static int apple_cio_reset_probe(struct platform_device *pdev) } static const struct of_device_id apple_cio_reset_match[] = { + { + .compatible = "apple,t6050-cio-reset", + .data = &apple_t6050_cio_reset, + }, { .compatible = "apple,t8142-cio-reset", .data = &apple_t8142_cio_reset, From 4aee080f4ed7f9a5eee673772f1dc8aa8906a4da Mon Sep 17 00:00:00 2001 From: Erik Bourget Date: Wed, 4 Nov 2026 18:08:07 -0500 Subject: [PATCH 20/43] dt-bindings: phy: apple,t6050-dp-phy: Add the CIO tunables The CIO (USB4/Thunderbolt) mode has its own firmware register and receiver tunables. They are optional: an absent one is skipped. Developed with Claude Code. Signed-off-by: Erik Bourget --- .../bindings/phy/apple,t6050-dp-phy.yaml | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/Documentation/devicetree/bindings/phy/apple,t6050-dp-phy.yaml b/Documentation/devicetree/bindings/phy/apple,t6050-dp-phy.yaml index 8cbfe94b62ecd..3b22c3f19ee9b 100644 --- a/Documentation/devicetree/bindings/phy/apple,t6050-dp-phy.yaml +++ b/Documentation/devicetree/bindings/phy/apple,t6050-dp-phy.yaml @@ -126,6 +126,28 @@ properties: $ref: /schemas/types.yaml#/definitions/uint32-array description: Offset, mask and value tuples within the named bank + apple,tunable-uc-regs-cio: + $ref: /schemas/types.yaml#/definitions/uint32-array + description: + Offset, mask and value tuples for the PHY firmware's registers in CIO + (USB4/Thunderbolt) mode. Optional; an absent CIO tunable is skipped. + + apple,tunable-lane0-rx-top-cio: + $ref: /schemas/types.yaml#/definitions/uint32-array + description: As apple,tunable-uc-regs-cio, for lane 0's receiver top bank + + apple,tunable-lane1-rx-top-cio: + $ref: /schemas/types.yaml#/definitions/uint32-array + description: As apple,tunable-uc-regs-cio, for lane 1's receiver top bank + + apple,tunable-lane0-rx-eq-cio: + $ref: /schemas/types.yaml#/definitions/uint32-array + description: As apple,tunable-uc-regs-cio, for lane 0's receiver equalizer + + apple,tunable-lane1-rx-eq-cio: + $ref: /schemas/types.yaml#/definitions/uint32-array + description: As apple,tunable-uc-regs-cio, for lane 1's receiver equalizer + patternProperties: '^apple,dp-training-table(-rbr|-hbr|-hbr2|-hbr3)?$': $ref: /schemas/types.yaml#/definitions/uint32-array From 8726ee82db1b8939997fa9468ddc962f56348f19 Mon Sep 17 00:00:00 2001 From: Erik Bourget Date: Tue, 3 Nov 2026 22:29:27 -0500 Subject: [PATCH 21/43] phy: apple: t6050: add the CIO (USB4/Thunderbolt) mode The T6050 combo PHY rejects TYPEC_MODE_USB4 and the Thunderbolt alternate mode, so a USB4 dock that the PD controller has already entered USB4 with gets no link: the PHY is never configured and DWC3 cannot start. Add the CIO mode as a step recipe in the style of the USB ones: - the mode-independent power-up; - the common tunables, then the CIO set (AUS40CMN_SHM, AUSPLL_CORE, CIO4PLL_CORE) and the CIO80 set (UC_REGS_CIO, LN0/LN1_RX_TOP_CIO, LN0/LN1_RX_EQ_CIO), with no USB or DP lane masks; - the crossbar in CIO, where orientation only selects TOP +0x78 bit 0; - the sleep-control release of both lanes; - the firmware start, UC_TNBL_DONE and the ACIOPHY reset release, without the PIPE BIST and the ATCPHY_CFG +0x8 orientation bit. The shutdown powers both transmitters and receivers down, shuts the common block down, stops the firmware and runs the usual sleep, clamp and reset sequence. USB2 runs beside the link, so the USB2 PHY is brought up as for USB. USB3 arrives through a tunnel later; in CIO the USB3 PHY set_mode leaves DWC3 on its dummy PIPE producer until a tunnel exists. The five CIO80 tunables are optional DT properties (apple,tunable-uc-regs-cio, -lane0/1-rx-top-cio, -lane0/1-rx-eq-cio); a missing one is skipped. On J716c only the RX_TOP pair is non-empty. Developed with Claude Code. Signed-off-by: Erik Bourget --- drivers/phy/apple/atc-t6050-combo.h | 55 ++++- drivers/phy/apple/atc-t6050-dp.c | 7 +- .../phy/apple/usb-t6050/apple_t6050_cio_phy.h | 189 ++++++++++++++++++ 3 files changed, 247 insertions(+), 4 deletions(-) create mode 100644 drivers/phy/apple/usb-t6050/apple_t6050_cio_phy.h diff --git a/drivers/phy/apple/atc-t6050-combo.h b/drivers/phy/apple/atc-t6050-combo.h index 1da782c4f9de4..c57779075ec05 100644 --- a/drivers/phy/apple/atc-t6050-combo.h +++ b/drivers/phy/apple/atc-t6050-combo.h @@ -54,6 +54,11 @@ static int combo_tunable(void *ctx, unsigned int bank, const char *name) {8, 8, "tunable_AUSPLL_CORE"}, {5, 9, "tunable_AUS40CMN_SHM"}, {10, 10, "tunable_CIO4PLL_CORE"}, }; + static const struct { u8 bank, index; const char *name; } cio[] = { + {3, 0, "tunable_UC_REGS_CIO_DFLT"}, {16, 1, "tunable_LN0_RX_TOP_CIO_DFLT"}, + {25, 2, "tunable_LN1_RX_TOP_CIO_DFLT"}, {18, 3, "tunable_LN0_RX_EQ_CIO_DFLT"}, + {27, 4, "tunable_LN1_RX_EQ_CIO_DFLT"}, + }; struct t6050_dp *dp = ctx; if (bank == 0 && !strcmp(name, "tunable_USB2PHY_DFLT")) { @@ -69,6 +74,12 @@ static int combo_tunable(void *ctx, unsigned int bank, const char *name) apple_tunable_apply(dp->banks[bank], dp->tunables[common[i].index]); return 0; } + /* A missing CIO80 tunable is skipped. */ + for (unsigned int i = 0; i < ARRAY_SIZE(cio); i++) + if (bank == cio[i].bank && !strcmp(name, cio[i].name)) { + apple_tunable_apply(dp->banks[bank], dp->cio_tunables[cio[i].index]); + return 0; + } return -EINVAL; } @@ -81,6 +92,10 @@ static int combo_quiesce(void *ctx) ret = APPLE_T6050_USB_RUN(&dp->usb_platform, apple_t6050_usb3_shutdown); if (!ret) dp->usb_prepared = false; + } else if (dp->cio_prepared) { + ret = APPLE_T6050_USB_RUN(&dp->usb_platform, apple_t6050_cio_shutdown); + if (!ret) + dp->cio_prepared = false; } else if (dp->enabled) { /* Multifunction has a distinct native shutdown; never run the * USB-only analog sequence over live DisplayPort lane state. */ @@ -158,6 +173,19 @@ static int combo_start(struct t6050_dp *dp, enum combo_mode mode) APPLE_T6050_USB_RUN(&dp->usb_platform, apple_t6050_usb3_init_normal); if (!ret) dp->usb_prepared = true; + } else if (!ret && mode == COMBO_CIO) { + ktime_t start = ktime_get(); + + /* USB2 stays native beside a USB4/TBT link; USB3 comes tunnelled. */ + ret = dp->reverse ? + APPLE_T6050_USB_RUN(&dp->usb_platform, apple_t6050_cio_init_reverse) : + APPLE_T6050_USB_RUN(&dp->usb_platform, apple_t6050_cio_init_normal); + if (!ret) + dp->cio_prepared = true; + dev_info(dp->dev, "CIO mode %s (%d), %s orientation, %lld ms\n", + ret ? "failed" : "up: UC_TNBL_DONE", ret, + dp->reverse ? "reverse" : "normal", + ktime_ms_delta(ktime_get(), start)); } else if (!ret) { dp->multifunction = mode == COMBO_DP2; ret = atc_start(dp); @@ -201,6 +229,11 @@ static int combo_mux(struct t6050_dp *dp, struct typec_mux_state *state) mode = COMBO_SAFE; else if (state->mode == TYPEC_STATE_USB && !state->alt) mode = COMBO_USB; + else if (!state->alt && state->mode == TYPEC_MODE_USB4) + mode = COMBO_CIO; + else if (state->alt && state->alt->svid == USB_TYPEC_TBT_SID && + state->mode == TYPEC_TBT_MODE) + mode = COMBO_CIO; else if (state->alt && state->alt->svid == USB_TYPEC_DP_SID) { switch (state->mode) { case TYPEC_DP_STATE_C: @@ -215,7 +248,7 @@ static int combo_mux(struct t6050_dp *dp, struct typec_mux_state *state) if (dp->faulted) return -EIO; if (mode == dp->combo_mode && - (mode == COMBO_SAFE || dp->usb_prepared || dp->enabled)) + (mode == COMBO_SAFE || dp->usb_prepared || dp->cio_prepared || dp->enabled)) return 0; if (mode != COMBO_SAFE && dp->orientation == TYPEC_ORIENTATION_NONE) return -ENOLINK; @@ -235,7 +268,7 @@ static int combo_usb2_mode(struct phy *phy, enum phy_mode mode, int submode) return -EIO; if (mode != PHY_MODE_USB_HOST) return -EOPNOTSUPP; - if (!dp->usb_prepared && !dp->enabled) + if (!dp->usb_prepared && !dp->cio_prepared && !dp->enabled) return -ENOLINK; if (dp->usb2_active) return 0; @@ -281,6 +314,10 @@ static int combo_usb3_mode(struct phy *phy, enum phy_mode mode, int submode) * uses its independent D+/D- PHY. Do not switch USB3 onto DP lanes. */ if (dp->combo_mode == COMBO_DP4 && dp->enabled) return 0; + /* In CIO the lanes carry USB4/TBT. DWC3 keeps its dummy PIPE until a + * USB3 tunnel hands PIPE to the router; native USB3 mode is 0 too. */ + if (dp->combo_mode == COMBO_CIO && dp->cio_prepared) + return 0; if (!dp->usb_prepared && !(dp->combo_mode == COMBO_DP2 && dp->enabled)) return -ENOLINK; if (dp->pipe_active) @@ -390,6 +427,12 @@ static struct phy *combo_xlate(struct device *dev, const struct of_phandle_args static int combo_setup(struct platform_device *pdev, struct t6050_dp *dp) { + /* Same order as the cio[] table in combo_tunable(). */ + static const struct { u8 bank; const char *name; } cio_tunables[] = { + {3, "apple,tunable-uc-regs-cio"}, {16, "apple,tunable-lane0-rx-top-cio"}, + {25, "apple,tunable-lane1-rx-top-cio"}, {18, "apple,tunable-lane0-rx-eq-cio"}, + {27, "apple,tunable-lane1-rx-eq-cio"}, + }; static const struct { u8 bank; u32 minimum; const char *name; } extra[] = { {0, 0x20, "usb2phy"}, {1, 0x24, "usb2-digital"}, {44, 0x48, "pipehandler"}, }; @@ -417,6 +460,14 @@ static int combo_setup(struct platform_device *pdev, struct t6050_dp *dp) "apple,tunable-pipe-default", &dp->resources[44]); if (IS_ERR(dp->pipe_default)) return PTR_ERR(dp->pipe_default); + for (unsigned int i = 0; i < ARRAY_SIZE(cio_tunables); i++) { + struct apple_tunable *t = devm_apple_tunable_parse(dev, dev->of_node, + cio_tunables[i].name, &dp->resources[cio_tunables[i].bank]); + + if (IS_ERR(t) && PTR_ERR(t) != -ENOENT) + return PTR_ERR(t); + dp->cio_tunables[i] = IS_ERR(t) ? NULL : t; + } dp->usb_io = (struct apple_t6050_usb_io) { .context = dp, .read = combo_read, .write = combo_write, .poll = combo_poll, .delay = combo_delay, diff --git a/drivers/phy/apple/atc-t6050-dp.c b/drivers/phy/apple/atc-t6050-dp.c index 01c666bb9e7dc..e6941cf86c8ad 100644 --- a/drivers/phy/apple/atc-t6050-dp.c +++ b/drivers/phy/apple/atc-t6050-dp.c @@ -14,6 +14,7 @@ #include "usb-t6050/apple_t6050_usb_phy.h" #include "usb-t6050/apple_t6050_usb3_pipe.h" #include "usb-t6050/apple_t6050_usb2_reset.h" +#include "usb-t6050/apple_t6050_cio_phy.h" #include #include #include @@ -23,6 +24,7 @@ #include #include #include +#include enum atc_operation { ATC_MASK, ATC_TUNABLE, ATC_DELAY, ATC_POLL, ATC_POWER, ATC_FIRMWARE }; struct atc_op { @@ -68,9 +70,9 @@ struct atc_domain { struct device *dev; bool active; }; -enum combo_mode { COMBO_SAFE, COMBO_USB, COMBO_DP4, COMBO_DP2 }; +enum combo_mode { COMBO_SAFE, COMBO_USB, COMBO_DP4, COMBO_DP2, COMBO_CIO }; struct t6050_dp { - bool combo, usb_prepared, usb2_active, pipe_active, controller_active; + bool combo, usb_prepared, usb2_active, pipe_active, controller_active, cio_prepared; enum combo_mode combo_mode; enum typec_orientation orientation; struct phy *combo_phy[3]; @@ -78,6 +80,7 @@ struct t6050_dp { struct apple_t6050_usb_io usb_io; struct apple_t6050_usb_platform usb_platform; struct apple_tunable *usb2_default, *usb2_host, *pipe_default; + struct apple_tunable *cio_tunables[5]; /* optional; empty on J716c except RX_TOP */ struct device *dev; struct mutex lock; void __iomem *banks[45]; diff --git a/drivers/phy/apple/usb-t6050/apple_t6050_cio_phy.h b/drivers/phy/apple/usb-t6050/apple_t6050_cio_phy.h new file mode 100644 index 0000000000000..4af28f8c2ccff --- /dev/null +++ b/drivers/phy/apple/usb-t6050/apple_t6050_cio_phy.h @@ -0,0 +1,189 @@ +/* SPDX-License-Identifier: MIT */ +/* ACIO80 PHY CIO (USB4/Thunderbolt) mode. + * + * The bring-up runs the mode-independent power-up (the USB recipes' first + * block), the common and CIO/CIO80 tunables, the crossbar, the sleep-control + * release and the firmware start. CIO applies no DP or USB lane masks, + * no DP clock switch and no orientation bit in ATCPHY_CFG +0x8; orientation + * only selects ACIOPHY_TOP +0x78 bit 0. Both lanes are released. + * Spread-spectrum stays on. + */ +#ifndef APPLE_T6050_CIO_PHY_H +#define APPLE_T6050_CIO_PHY_H +#include "apple_t6050_usb_phy.h" + +/* The power-up and reset release, identical to the USB recipes. */ +#define APPLE_T6050_CIO_POWERUP \ + { T6050_MASK, 39, 0x0, 0x1, 0x1, 0, 0 }, \ + { T6050_DELAY, 0, 0x0, 0x0, 0x1, 0, 0 }, \ + { T6050_POLL, 39, 0x4, 0x1, 0x1, 1000, 0 }, \ + { T6050_DELAY, 0, 0x0, 0x0, 0x1, 0, 0 }, \ + { T6050_MASK, 39, 0x0, 0x2, 0x2, 0, 0 }, \ + { T6050_DELAY, 0, 0x0, 0x0, 0x1, 0, 0 }, \ + { T6050_POLL, 39, 0x4, 0x2, 0x2, 1000, 0 }, \ + { T6050_DELAY, 0, 0x0, 0x0, 0x1, 0, 0 }, \ + { T6050_MASK, 39, 0x0, 0x4, 0x0, 0, 0 }, \ + { T6050_DELAY, 0, 0x0, 0x0, 0x1, 0, 0 }, \ + { T6050_POWER, 0, 0x0, 0x0, 0x1, 0, 0 }, \ + { T6050_MASK, 39, 0x2c, 0x1, 0x0, 0, 0 }, \ + { T6050_POLL, 39, 0x4, 0x100, 0x0, 1000, 0 }, \ + { T6050_DELAY, 0, 0x0, 0x0, 0x1, 0, 0 }, \ + { T6050_MASK, 39, 0x0, 0x8, 0x8, 0, 0 } + +/* The common tunables, then the CIO set and the CIO80 set. An absent CIO80 + * tunable is skipped. + */ +#define APPLE_T6050_CIO_TUNABLES \ + { T6050_TUNABLE, 41, 0x0, 0x0, 0x0, 0, "tunable_ATC0AXI2AF" }, \ + { T6050_TUNABLE, 40, 0x0, 0x0, 0x0, 0, "tunable_ATC_FABRIC" }, \ + { T6050_TUNABLE, 43, 0x0, 0x0, 0x0, 0, "tunable_ATC_COMMON_CFG" }, \ + { T6050_TUNABLE, 42, 0x0, 0x0, 0x0, 0, "tunable_ATC0AXI2AF_LIOA" }, \ + { T6050_TUNABLE, 20, 0x0, 0x0, 0x0, 0, "tunable_LN0_RX_CFG_TX_OF_RXCLK" }, \ + { T6050_TUNABLE, 29, 0x0, 0x0, 0x0, 0, "tunable_LN1_RX_CFG_TX_OF_RXCLK" }, \ + { T6050_TUNABLE, 18, 0x0, 0x0, 0x0, 0, "tunable_LN0_RX_EQ_DFLT" }, \ + { T6050_TUNABLE, 27, 0x0, 0x0, 0x0, 0, "tunable_LN1_RX_EQ_DFLT" }, \ + { T6050_TUNABLE, 5, 0x0, 0x0, 0x0, 0, "tunable_AUS40CMN_SHM" }, \ + { T6050_TUNABLE, 8, 0x0, 0x0, 0x0, 0, "tunable_AUSPLL_CORE" }, \ + { T6050_TUNABLE, 10, 0x0, 0x0, 0x0, 0, "tunable_CIO4PLL_CORE" }, \ + { T6050_TUNABLE, 3, 0x0, 0x0, 0x0, 0, "tunable_UC_REGS_CIO_DFLT" }, \ + { T6050_TUNABLE, 16, 0x0, 0x0, 0x0, 0, "tunable_LN0_RX_TOP_CIO_DFLT" }, \ + { T6050_TUNABLE, 25, 0x0, 0x0, 0x0, 0, "tunable_LN1_RX_TOP_CIO_DFLT" }, \ + { T6050_TUNABLE, 18, 0x0, 0x0, 0x0, 0, "tunable_LN0_RX_EQ_CIO_DFLT" }, \ + { T6050_TUNABLE, 27, 0x0, 0x0, 0x0, 0, "tunable_LN1_RX_EQ_CIO_DFLT" } + +/* The crossbar: clear the lane selects, set the orientation bit (+0x78) + * and select CIO. + */ +#define APPLE_T6050_CIO_XBAR(reverse) \ + { T6050_MASK, 2, 0x6c, 0xfc, 0x0, 0, 0 }, \ + { T6050_MASK, 2, 0x78, 0x1f, (reverse), 0, 0 }, \ + { T6050_MASK, 2, 0x6c, 0x3, 0x2, 0, 0 } + +/* Power a lane's receiver up, then release its transmitter overrides. */ +#define APPLE_T6050_CIO_LANE(l) \ + { T6050_MASK, 2, 0x10, 0x10 << (l), 0x10 << (l), 0, 0 }, \ + { T6050_DELAY, 0, 0x0, 0x0, 0x1, 0, 0 }, \ + { T6050_MASK, 2, 0x10, 0x40 << (l), 0x40 << (l), 0, 0 }, \ + { T6050_DELAY, 0, 0x0, 0x0, 0x1, 0, 0 }, \ + { T6050_MASK, 2, 0x10, 0x1 << (l), 0x1 << (l), 0, 0 }, \ + { T6050_DELAY, 0, 0x0, 0x0, 0x1, 0, 0 }, \ + { T6050_MASK, 2, 0x10, 0x4 << (l), 0x4 << (l), 0, 0 }, \ + { T6050_DELAY, 0, 0x0, 0x0, 0x1, 0, 0 }, \ + { T6050_MASK, 2, 0xc, 0x400 << (l), 0x400 << (l), 0, 0 }, \ + { T6050_DELAY, 0, 0x0, 0x0, 0x1, 0, 0 }, \ + { T6050_MASK, 2, 0xc, 0x1000 << (l), 0x1000 << (l), 0, 0 }, \ + { T6050_DELAY, 0, 0x0, 0x0, 0x1, 0, 0 }, \ + { T6050_MASK, 2, 0xc, 0x40 << (l), 0x40 << (l), 0, 0 }, \ + { T6050_DELAY, 0, 0x0, 0x0, 0x1, 0, 0 }, \ + { T6050_MASK, 2, 0xc, 0x100 << (l), 0x100 << (l), 0, 0 }, \ + { T6050_DELAY, 0, 0x0, 0x0, 0x1, 0, 0 }, \ + { T6050_MASK, 2, 0xc, 0x4000 << (l), 0x0, 0, 0 }, \ + { T6050_DELAY, 0, 0x0, 0x0, 0x1, 0, 0 }, \ + { T6050_MASK, 2, 0xc, 0x10000 << (l), 0x10000 << (l), 0, 0 }, \ + { T6050_DELAY, 0, 0x0, 0x0, 0x1, 0, 0 }, \ + { T6050_MASK, 2, 0x120, 0x40 << (l), 0x0, 0, 0 }, \ + { T6050_DELAY, 0, 0x0, 0x0, 0x1, 0, 0 }, \ + { T6050_MASK, 2, 0x120, 0x4 << (l), 0x0, 0, 0 }, \ + { T6050_DELAY, 0, 0x0, 0x0, 0x1, 0, 0 }, \ + { T6050_MASK, 2, 0x120, 0x400 << (l), 0x0, 0, 0 }, \ + { T6050_DELAY, 0, 0x0, 0x0, 0x1, 0, 0 } + +/* The sleep-control release: the common block's overrides, then both lanes. */ +#define APPLE_T6050_CIO_SLPCTRL \ + { T6050_MASK, 2, 0xc, 0x2a, 0x0, 0, 0 }, \ + { T6050_DELAY, 0, 0x0, 0x0, 0x1, 0, 0 }, \ + APPLE_T6050_CIO_LANE(0), \ + APPLE_T6050_CIO_LANE(1) + +/* Start the PHY firmware, wait for UC_TNBL_DONE and + * deassert the ACIOPHY reset. No PIPE BIST and no ATCPHY_CFG +0x8 write. + */ +#define APPLE_T6050_CIO_START \ + { T6050_DELAY, 0, 0x0, 0x0, 0xa, 0, 0 }, \ + { T6050_DELAY, 0, 0x0, 0x0, 0xa, 0, 0 }, \ + { T6050_MASK, 38, 0x40, 0x1, 0x1, 0, 0 }, \ + { T6050_DELAY, 0, 0x0, 0x0, 0xa, 0, 0 }, \ + { T6050_FIRMWARE, 0, 0x0, 0x0, 0x1, 0, 0 }, \ + { T6050_DELAY, 0, 0x0, 0x0, 0xa, 0, 0 }, \ + { T6050_DELAY, 0, 0x0, 0x0, 0x1388, 0, 0 }, \ + { T6050_POLL, 2, 0x260, 0x400, 0x400, 1000000, 0 }, \ + { T6050_MASK, 39, 0x0, 0x10, 0x10, 0, 0 }, \ + { T6050_DELAY, 0, 0x0, 0x0, 0x64, 0, 0 } + +static const struct apple_t6050_usb_step apple_t6050_cio_init_normal[] = { + APPLE_T6050_CIO_POWERUP, + APPLE_T6050_CIO_TUNABLES, + APPLE_T6050_CIO_XBAR(0x0), + APPLE_T6050_CIO_SLPCTRL, + APPLE_T6050_CIO_START, +}; + +static const struct apple_t6050_usb_step apple_t6050_cio_init_reverse[] = { + APPLE_T6050_CIO_POWERUP, + APPLE_T6050_CIO_TUNABLES, + APPLE_T6050_CIO_XBAR(0x1), + APPLE_T6050_CIO_SLPCTRL, + APPLE_T6050_CIO_START, +}; + +/* Power a lane's transmitter down. */ +#define APPLE_T6050_CIO_TX_DOWN(l) \ + { T6050_MASK, 2, 0x120, 0x100 << (l), 0x100 << (l), 0, 0 }, \ + { T6050_DELAY, 0, 0x0, 0x0, 0x1, 0, 0 }, \ + { T6050_MASK, 2, 0x120, 0x1 << (l), 0x0, 0, 0 }, \ + { T6050_DELAY, 0, 0x0, 0x0, 0x1, 0, 0 }, \ + { T6050_MASK, 2, 0x120, 0x10 << (l), 0x0, 0, 0 }, \ + { T6050_DELAY, 0, 0x0, 0x0, 0x1, 0, 0 } + +/* Power a lane's receiver down; CIO powers the receivers down, USB does not. */ +#define APPLE_T6050_CIO_RX_DOWN(l) \ + { T6050_MASK, 2, 0xc, 0x4000 << (l), 0x4000 << (l), 0, 0 }, \ + { T6050_DELAY, 0, 0x0, 0x0, 0x1, 0, 0 }, \ + { T6050_MASK, 2, 0xc, 0x40 << (l), 0x0, 0, 0 }, \ + { T6050_DELAY, 0, 0x0, 0x0, 0x1, 0, 0 }, \ + { T6050_MASK, 2, 0xc, 0x400 << (l), 0x0, 0, 0 }, \ + { T6050_DELAY, 0, 0x0, 0x0, 0x1, 0, 0 }, \ + { T6050_MASK, 2, 0x10, 0x1 << (l), 0x0, 0, 0 }, \ + { T6050_DELAY, 0, 0x0, 0x0, 0x1, 0, 0 }, \ + { T6050_MASK, 2, 0x10, 0x10 << (l), 0x0, 0, 0 }, \ + { T6050_DELAY, 0, 0x0, 0x0, 0x1, 0, 0 } + +/* The shutdown: both lanes down without the PIPE BIST, the firmware + * power-down, then the USB recipes' sleep, clamp and reset sequence. + */ +static const struct apple_t6050_usb_step apple_t6050_cio_shutdown[] = { + APPLE_T6050_CIO_TX_DOWN(0), + APPLE_T6050_CIO_TX_DOWN(1), + { T6050_MASK, 5, 0x0, 0x2, 0x0, 0, 0 }, + { T6050_DELAY, 0, 0x0, 0x0, 0x1, 0, 0 }, + { T6050_MASK, 2, 0xc, 0x10, 0x10, 0, 0 }, + { T6050_DELAY, 0, 0x0, 0x0, 0x1, 0, 0 }, + { T6050_MASK, 2, 0xc, 0x1, 0x0, 0, 0 }, + { T6050_DELAY, 0, 0x0, 0x0, 0x1, 0, 0 }, + { T6050_MASK, 2, 0xc, 0x4, 0x0, 0, 0 }, + { T6050_DELAY, 0, 0x0, 0x0, 0x1, 0, 0 }, + APPLE_T6050_CIO_RX_DOWN(0), + APPLE_T6050_CIO_RX_DOWN(1), + { T6050_DELAY, 0, 0x0, 0x0, 0xc8, 0, 0 }, + { T6050_FIRMWARE, 0, 0x0, 0x0, 0x0, 0, 0 }, + { T6050_POWER, 0, 0x0, 0x0, 0x0, 0, 0 }, + { T6050_MASK, 39, 0x2c, 0x1, 0x1, 0, 0 }, + { T6050_POLL, 39, 0x4, 0x100, 0x100, 1000, 0 }, + { T6050_MASK, 39, 0x0, 0x10, 0x0, 0, 0 }, + { T6050_DELAY, 0, 0x0, 0x0, 0x1, 0, 0 }, + { T6050_MASK, 39, 0x0, 0x8, 0x0, 0, 0 }, + { T6050_DELAY, 0, 0x0, 0x0, 0x1, 0, 0 }, + { T6050_MASK, 39, 0x0, 0x4, 0x4, 0, 0 }, + { T6050_DELAY, 0, 0x0, 0x0, 0x1, 0, 0 }, + { T6050_MASK, 39, 0x0, 0x2, 0x0, 0, 0 }, + { T6050_DELAY, 0, 0x0, 0x0, 0x1, 0, 0 }, + { T6050_POLL, 39, 0x4, 0x2, 0x0, 1000, 0 }, + { T6050_DELAY, 0, 0x0, 0x0, 0x1, 0, 0 }, + { T6050_MASK, 39, 0x0, 0x1, 0x0, 0, 0 }, + { T6050_DELAY, 0, 0x0, 0x0, 0x1, 0, 0 }, + { T6050_POLL, 39, 0x4, 0x1, 0x0, 1000, 0 }, + { T6050_DELAY, 0, 0x0, 0x0, 0x1, 0, 0 }, + { T6050_MASK, 39, 0x8, 0x1, 0x0, 0, 0 }, + { T6050_DELAY, 0, 0x0, 0x0, 0x1, 0, 0 }, +}; +#endif From 024beea2bff7b3aed7af357dcf713465f1c93235 Mon Sep 17 00:00:00 2001 From: Erik Bourget Date: Wed, 4 Nov 2026 18:08:06 -0500 Subject: [PATCH 22/43] dt-bindings: thunderbolt: apple: Add the T6050 router and NHI The T6050 host router has the T8132 MxWrap CPU wrapper and LSTX pin, four ACIO power domains instead of three, and a PCIe router AP firmware block with its own tunables. Its NHI has a single register range, one DART stream per ring, and an SRAM parity interrupt after its 24 ring interrupts. Developed with Claude Code. Signed-off-by: Erik Bourget --- .../thunderbolt/apple,t8103-usb4-acio.yaml | 48 +++++++++++++++++-- .../thunderbolt/apple,t8103-usb4-nhi.yaml | 20 +++++++- 2 files changed, 61 insertions(+), 7 deletions(-) diff --git a/Documentation/devicetree/bindings/thunderbolt/apple,t8103-usb4-acio.yaml b/Documentation/devicetree/bindings/thunderbolt/apple,t8103-usb4-acio.yaml index d9dd4cd1aa756..fe61df65c5c27 100644 --- a/Documentation/devicetree/bindings/thunderbolt/apple,t8103-usb4-acio.yaml +++ b/Documentation/devicetree/bindings/thunderbolt/apple,t8103-usb4-acio.yaml @@ -52,7 +52,8 @@ properties: PCIe adapter configuration space of the USB4 router. Present where the root complex exposes the adapter separately, so that the tunnel can be configured before a PCIe-C link is brought up. - - description: MxWrap co-processor control registers + - description: MxWrap co-processor control registers, on T8132 and T6050 + - description: PCIe router AP firmware registers, on T6050 reg-names: oneOf: @@ -62,6 +63,7 @@ properties: - const: sram - const: pcie-adapter - const: cpu + - const: router-ap-fw - description: T6050, which has no separate PCIe adapter window items: - const: rc @@ -87,12 +89,12 @@ properties: power-domains: minItems: 3 - maxItems: 4 + maxItems: 5 description: | - The first three domains power the ACIO co-processor complex. When - apple,pcie-tunnel is present, the fourth domain powers its PCIe-C + The first three domains (four on T6050, the fourth being the routers' + shared audio (AUSS) domain) power the ACIO co-processor complex. When + apple,pcie-tunnel is present, the domain after them powers its PCIe-C controller and is enabled only after the USB4 PCIe tunnel is active. - On T6050 the fourth domain is the routers' shared audio (AUSS) domain. thunderbolt-switch: type: boolean @@ -135,6 +137,18 @@ properties: named by the pcie-adapter reg entry. Only meaningful where that entry is present. + apple,tunable-router-ap-fw: + $ref: /schemas/types.yaml#/definitions/uint32-matrix + items: + items: + - description: Register offset + - description: Mask of bits to clear + - description: Bits to set + description: | + As apple,tunable-rc, but applied to the PCIe router AP firmware + registers named by the router-ap-fw reg entry, with the other router + tunables once the co-processor firmware is ready. + '#address-cells': const: 1 @@ -216,6 +230,16 @@ allOf: minItems: 4 reg-names: minItems: 4 + - if: + properties: + compatible: + contains: + enum: + - apple,t6040-usb4-acio + - apple,t6050-usb4-acio + - apple,t8132-usb4-acio + then: + properties: pinctrl-names: items: - const: lstx @@ -237,6 +261,20 @@ allOf: required: - iommu@a80000 - nhi@f00000 + - if: + properties: + compatible: + contains: + const: apple,t6050-usb4-acio + then: + properties: + power-domains: + minItems: 4 + else: + properties: + power-domains: + maxItems: 4 + apple,tunable-router-ap-fw: false additionalProperties: false diff --git a/Documentation/devicetree/bindings/thunderbolt/apple,t8103-usb4-nhi.yaml b/Documentation/devicetree/bindings/thunderbolt/apple,t8103-usb4-nhi.yaml index ede4cea33eb2d..28718fed167c5 100644 --- a/Documentation/devicetree/bindings/thunderbolt/apple,t8103-usb4-nhi.yaml +++ b/Documentation/devicetree/bindings/thunderbolt/apple,t8103-usb4-nhi.yaml @@ -52,10 +52,12 @@ properties: interrupts: description: One interrupt for each of the 12 transmit rings followed by one for each - of the 12 receive rings. - maxItems: 24 + of the 12 receive rings. On T6050 an SRAM parity interrupt follows them. + minItems: 24 + maxItems: 25 interrupt-names: + minItems: 24 items: - const: txring0 - const: txring1 @@ -81,6 +83,7 @@ properties: - const: rxring9 - const: rxring10 - const: rxring11 + - const: parity iommus: minItems: 1 @@ -177,4 +180,17 @@ allOf: iommus: maxItems: 1 + - if: + properties: + compatible: + not: + contains: + const: apple,t6050-usb4-nhi + then: + properties: + interrupts: + maxItems: 24 + interrupt-names: + maxItems: 24 + additionalProperties: false From ea31a9a60c40b660641dc5368d5b3fca8968c6f4 Mon Sep 17 00:00:00 2001 From: Erik Bourget Date: Tue, 3 Nov 2026 22:31:48 -0500 Subject: [PATCH 23/43] thunderbolt: apple: support the T6050 host routers The M5 Max (T6050) host routers are Type 7 routers with an MxWrap CPU wrapper, close to the M3 ones. They differ at start: - The wrapper is only clocked once the CIO reset (the reconfig request) has run; any access before it faults. The reset is required, as on the other generations, and runs before the wrapper is touched. - The CPU is released with a plain store of the RUN value rather than a read-modify-write, and halted again when the router stops. - Both the rc block and the wrapper take an external abort when read before the CPU runs, so the M3_CTRL start of the other generations is skipped and their state is not logged. The rc block is first touched to hand LSTX over, once RTKit is up. - The router takes the LSTX pin over only once its RTKit is up: write the LSTX control from the level the AP held, then mux the pin to the router. While the router is off, hold the pin as a GPIO at the level the router last drove. - The PCIe router AP firmware tunable is applied with the other router tunables once the firmware is ready. - The router firmware trains Gen4 links only when the cable word says the cable is Gen4. For a USB4 Gen4 cable from Enter_USB it expects 0x801 (present and Gen4), without the 20 Gb/s bit. - The NHI has a named SRAM parity interrupt after its 24 ring interrupts, which is not a ring. Match apple,t6050-usb4-acio and apple,t6050-usb4-nhi. DisplayPort tunnels stay off on T6050 for now. Developed with Claude Code. Signed-off-by: Erik Bourget --- drivers/thunderbolt/apple.c | 158 +++++++++++++++++++++++++++++++++--- 1 file changed, 145 insertions(+), 13 deletions(-) diff --git a/drivers/thunderbolt/apple.c b/drivers/thunderbolt/apple.c index 3374e0d3e794f..0b0c25d5adb69 100644 --- a/drivers/thunderbolt/apple.c +++ b/drivers/thunderbolt/apple.c @@ -141,6 +141,17 @@ struct apple_cio_hw { bool strict_fw_ready; /* Keep the links of the host router out of CL states. */ bool no_host_clx; + /* Type 7 router: Enter_USB cable words, bootloader RC tuning, Type 7 NHI. */ + bool type7; + /* + * Release the CPU with a plain store of the RUN value rather than a + * read-modify-write, and halt it again when the router stops. + */ + bool run_store; + /* Hold LSTX low as a GPIO from probe until the router first takes it. */ + bool lstx_hold_from_probe; + /* The router trains Gen4 only on a cable word that says Gen4. */ + bool gen4_cable; const struct apple_dpin_policy *dp; }; @@ -178,11 +189,30 @@ static const struct apple_cio_hw apple_cio_t8132_hw = { /* Type 7 uses its existing ready-state check and has no qualified DP route. */ static const struct apple_cio_hw apple_cio_t8142_hw = { + .type7 = true, + .dp = &apple_dpin_disabled, +}; + +/* + * T6050 is Type 7 with an MxWrap CPU wrapper that is only clocked once the + * CIO reset (the reconfig request) has run. Its CPU is released with a plain + * store of RUN; as on T8132 it takes LSTX over only once its RTKit is up, and + * the AP holds the pin while the router is off. DP tunnels stay off for now. + */ +static const struct apple_cio_hw apple_cio_t6050_hw = { + .mxwrap = true, + .type7 = true, + .run_store = true, + .lstx_after_boot = true, + .lstx_hold_from_probe = true, + .strict_fw_ready = true, + .gen4_cable = true, .dp = &apple_dpin_disabled, }; static const struct apple_cio_hw apple_cio_t6040_hw = { .mxwrap = true, + .type7 = true, .lstx_explicit = true, .skip_run = true, .lstx_after_boot = true, @@ -256,6 +286,10 @@ MODULE_PARM_DESC(s2idle_teardown, #define TB_VSE_CAP_APPLE_CABLE_INFO_20_GBPS BIT(4) #define TB_VSE_CAP_APPLE_CABLE_INFO_LEGACY_ADAPTER BIT(9) #define TB_VSE_CAP_APPLE_CABLE_INFO_TBT2_3 BIT(10) +#define TB_VSE_CAP_APPLE_CABLE_INFO_GEN4 BIT(11) + +/* Enter_USB cable speed: USB PD 3.2 adds USB4 Gen4 after Gen3. */ +#define APPLE_EUDO_CABLE_SPEED_USB4_GEN4 4 static unsigned int dpin_mode_value = 8; module_param(dpin_mode_value, uint, 0644); @@ -306,6 +340,10 @@ struct apple_cio { struct apple_tunable *rc_tunable; void __iomem *pcie_adapter_base; struct apple_tunable *pcie_adapter_tunable; + void __iomem *router_ap_fw_base; + struct apple_tunable *router_ap_fw_tunable; + /* A run_store router CPU that apple_cio_start() released */ + bool cpu_running; struct resource *sram_res; void __iomem *sram_base; @@ -1108,6 +1146,10 @@ static int apple_nhi_probe_irqs(struct apple_nhi *anhi) n_irqs = platform_irq_count(anhi->pdev); if (n_irqs < 0) return dev_err_probe(anhi->dev, n_irqs, "platform_irq_count failed\n"); + /* T6050 adds a named SRAM parity interrupt after the ring interrupts. */ + if (n_irqs > 0 && + platform_get_irq_byname_optional(anhi->pdev, "parity") > 0) + n_irqs--; if (n_irqs == 0) { dev_err(anhi->dev, "No interrupts found\n"); return -EINVAL; @@ -2084,9 +2126,13 @@ static int apple_nhi_probe(struct platform_device *pdev) int cap_apple; int ret = 0; + /* The T6050 router is the Type 7 one released by a RUN store. */ if (acio->type7 != (of_device_is_compatible(pdev->dev.of_node, "apple,t8142-usb4-nhi") || - of_device_is_compatible(pdev->dev.of_node, "apple,t6040-usb4-nhi"))) { + of_device_is_compatible(pdev->dev.of_node, "apple,t6040-usb4-nhi") || + of_device_is_compatible(pdev->dev.of_node, "apple,t6050-usb4-nhi")) || + acio->hw->run_store != + !!of_device_is_compatible(pdev->dev.of_node, "apple,t6050-usb4-nhi")) { ret = dev_err_probe(&pdev->dev, -EINVAL, "ACIO/NHI generation mismatch\n"); goto err; } @@ -2515,6 +2561,9 @@ static const struct of_device_id apple_nhi_match[] = { { .compatible = "apple,t6040-usb4-nhi", }, + { + .compatible = "apple,t6050-usb4-nhi", + }, { .compatible = "apple,t8142-usb4-nhi", }, @@ -2648,6 +2697,22 @@ static int apple_cio_lstx_release(struct apple_cio *acio) return pinctrl_select_state(acio->pinctrl, acio->lstx_pins); } +/* + * Halt a run_store router CPU that apple_cio_start() released. Its wrapper is + * only clocked after the CIO reset, so never touch it otherwise. + */ +static void apple_cio_cpu_halt(struct apple_cio *acio) +{ + u32 val; + + if (!acio->hw->run_store || !acio->cpu_running) + return; + acio->cpu_running = false; + val = readl(acio->cpu_base + APPLE_MXWRAP_CPU_CTRL); + writel(val & ~APPLE_MXWRAP_CPU_CTRL_RUN, + acio->cpu_base + APPLE_MXWRAP_CPU_CTRL); +} + /** * apple_cio_stop - Stop and power down the ACIO complex * @acio: ACIO complex to stop @@ -2672,6 +2737,7 @@ static void apple_cio_stop(struct apple_cio *acio) "Failed to shutdown M3 RTKit, continuing ACIO shutdown anyway\n"); apple_rtkit_free(acio->rtk); acio->rtk = NULL; + apple_cio_cpu_halt(acio); /* High only if the router left it driven high with the cable still in */ if (acio->hw->lstx_after_boot) @@ -2691,9 +2757,15 @@ static void apple_cio_stop(struct apple_cio *acio) acio->resume_link_failed = false; } -/* Only called while the ACIO power domains are on. */ +/* + * Only called while the ACIO power domains are on. Neither a T6050 router's rc + * block nor its CPU wrapper is known to be readable while it starts (see + * apple_cio_start()), so log nothing for it. + */ static void apple_cio_log_state(struct apple_cio *acio, const char *when) { + if (acio->hw->run_store) + return; if (acio->cpu_base) dev_info(acio->dev, "%s: LSTX 0x%08x, FW state 0x%08x, CPU ctrl 0x%08x, status 0x%08x\n", when, readl(acio->rc_base + APPLE_CIO_M3_CTRL), @@ -2809,7 +2881,11 @@ static int apple_cio_start(struct apple_cio *acio) * them with a bare store. */ if (acio->hw->lstx_after_boot) { - /* Written below, once RTKit is up. */ + /* + * Written below, once RTKit is up. A T6050 CPU is started through + * its wrapper below, and its rc block takes an external abort until + * the CPU runs, so M3_CTRL is left alone here. + */ } else if (acio->hw->lstx_explicit) { writel(APPLE_CIO_LSTX_CTRL_ENABLE | APPLE_CIO_LSTX_CTRL_LEVEL, acio->rc_base + APPLE_CIO_M3_CTRL); @@ -2820,9 +2896,21 @@ static int apple_cio_start(struct apple_cio *acio) if (acio->hw->mxwrap && !acio->hw->skip_run) { apple_cio_log_state(acio, "before RUN"); - val = readl(acio->cpu_base + APPLE_MXWRAP_CPU_CTRL); - writel(val | APPLE_MXWRAP_CPU_CTRL_RUN, - acio->cpu_base + APPLE_MXWRAP_CPU_CTRL); + if (acio->hw->run_store) { + /* + * The CIO reset above clocks the wrapper; the CPU runs the + * image the boot loader left in its SRAM once RUN is set. + * The run value is stored outright, not merged. + */ + writel(APPLE_MXWRAP_CPU_CTRL_RUN, + acio->cpu_base + APPLE_MXWRAP_CPU_CTRL); + acio->cpu_running = true; + apple_cio_log_state(acio, "after RUN"); + } else { + val = readl(acio->cpu_base + APPLE_MXWRAP_CPU_CTRL); + writel(val | APPLE_MXWRAP_CPU_CTRL_RUN, + acio->cpu_base + APPLE_MXWRAP_CPU_CTRL); + } } acio->rtk = apple_rtkit_init(acio->dev, acio, NULL, 0, &apple_cio_rtkit_ops); @@ -2881,6 +2969,8 @@ static int apple_cio_start(struct apple_cio *acio) apple_tunable_apply(acio->pcie_adapter_base, acio->pcie_adapter_tunable); } + /* The router AP firmware tunable goes with the others once the firmware is ready. */ + apple_tunable_apply(acio->router_ap_fw_base, acio->router_ap_fw_tunable); /* * Bring up devices which are part of ACIO and are now accessible by the main SoC @@ -2918,6 +3008,7 @@ static int apple_cio_start(struct apple_cio *acio) apple_rtkit_free(acio->rtk); remove_links: acio->rtk = NULL; + apple_cio_cpu_halt(acio); /* Cut power to reset the entire block */ for (i = 0; i < acio->pd_list->num_pds; i++) { if (acio->pd_list->pd_links[i]) @@ -3131,7 +3222,16 @@ 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) + /* + * The T6050 router firmware trains Gen4 only on a Gen4 cable, and + * takes Gen4 alone: no 20 Gb/s bit beside it. + */ + if (acio->hw->gen4_cable && + FIELD_GET(EUDO_CABLE_SPEED_MASK, data->usb4.eudo) >= + APPLE_EUDO_CABLE_SPEED_USB4_GEN4) + acio->target_cable_info |= TB_VSE_CAP_APPLE_CABLE_INFO_GEN4; + else 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; @@ -3335,16 +3435,15 @@ 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->type7 = of_device_is_compatible(acio->np, "apple,t8142-usb4-acio") || - of_device_is_compatible(acio->np, "apple,t6040-usb4-acio"); - if (acio->type7 && - of_property_count_u32_elems(acio->np, "apple,tunable-rc") <= 0) - return dev_err_probe(dev, -EINVAL, "Missing bootloader RC tuning\n"); - acio->hw = of_device_get_match_data(dev); if (!acio->hw) return -EINVAL; + acio->type7 = acio->hw->type7; + if (acio->type7 && + of_property_count_u32_elems(acio->np, "apple,tunable-rc") <= 0) + return dev_err_probe(dev, -EINVAL, "Missing bootloader RC tuning\n"); + if (acio->hw->mxwrap) { struct resource *cpu_res; @@ -3367,6 +3466,9 @@ static int apple_cio_probe(struct platform_device *pdev) acio->lstx_pins = NULL; acio->lstx_hold = acio->lstx_pins && of_property_present(dev->of_node, "lstx-gpios"); + /* Hold the line low until the router first takes it. */ + if (acio->hw->lstx_hold_from_probe) + apple_cio_lstx_hold(acio, false); } acio->pcie_tunnel_np = of_parse_phandle(dev->of_node, "apple,pcie-tunnel", 0); @@ -3435,6 +3537,32 @@ static int apple_cio_probe(struct platform_device *pdev) } } + { + struct resource *ap_fw; + + ap_fw = platform_get_resource_byname(pdev, IORESOURCE_MEM, "router-ap-fw"); + if (ap_fw) { + acio->router_ap_fw_base = devm_ioremap_resource(dev, ap_fw); + if (IS_ERR(acio->router_ap_fw_base)) + return dev_err_probe(dev, PTR_ERR(acio->router_ap_fw_base), + "Unable to map router AP firmware regs\n"); + acio->router_ap_fw_tunable = + devm_apple_tunable_parse(dev, acio->np, + "apple,tunable-router-ap-fw", ap_fw); + if (IS_ERR(acio->router_ap_fw_tunable)) { + if (PTR_ERR(acio->router_ap_fw_tunable) != -ENOENT) + return dev_err_probe(dev, + PTR_ERR(acio->router_ap_fw_tunable), + "Unable to load router AP firmware tunable\n"); + acio->router_ap_fw_tunable = NULL; + } + } + } + + /* + * On T6050 this is the CIO reconfig request, which also clocks the + * router CPU's wrapper: nothing may touch the wrapper before it. + */ acio->reset = devm_reset_control_get_exclusive(dev, NULL); if (IS_ERR(acio->reset)) return dev_err_probe(dev, PTR_ERR(acio->reset), "Unable to get CIO reset\n"); @@ -3548,6 +3676,10 @@ static const struct of_device_id apple_acio_match[] = { .compatible = "apple,t6040-usb4-acio", .data = &apple_cio_t6040_hw, }, + { + .compatible = "apple,t6050-usb4-acio", + .data = &apple_cio_t6050_hw, + }, { .compatible = "apple,t8142-usb4-acio", .data = &apple_cio_t8142_hw, From ab6654bbbc8e6204c7d3a78eebce8fbd9a4267da Mon Sep 17 00:00:00 2001 From: Erik Bourget Date: Wed, 4 Nov 2026 18:08:06 -0500 Subject: [PATCH 24/43] dt-bindings: iommu: apple,dart: Describe the setup of a DART nothing initialises A T8110 DART that no boot loader sets up, such as a USB4 router's or its PCIe-C tunnel's, needs the device tree to describe what the boot loader otherwise does: its tunables, its bypass streams, every error reported and the context cache flushed. Add apple,full-error-reporting and apple,ctc-flush, and extend apple,bypass-sids beyond the PCIe-C tunnel DARTs. Developed with Claude Code. Signed-off-by: Erik Bourget --- .../devicetree/bindings/iommu/apple,dart.yaml | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/Documentation/devicetree/bindings/iommu/apple,dart.yaml b/Documentation/devicetree/bindings/iommu/apple,dart.yaml index 9ba41ab9806d2..0c91127ba8505 100644 --- a/Documentation/devicetree/bindings/iommu/apple,dart.yaml +++ b/Documentation/devicetree/bindings/iommu/apple,dart.yaml @@ -151,9 +151,25 @@ properties: items: maximum: 255 description: - Reserved streams of a USB4 PCIe tunnel DART that its PCIe-C port issues + Streams set to bypass after every reset of the DART, outside any domain. + On a USB4 PCIe tunnel DART, the reserved streams its PCIe-C port issues its own writes on, such as the interrupt controller writes its MSIs - turn into. They are set to bypass after every reset of the DART. + turn into. On a T8110 DART that nothing initialises before Linux, such + as a USB4 router's, the streams its co-processor uses without + translation. + + apple,full-error-reporting: + type: boolean + description: + Unmask every error and enable the error interrupt for every stream, + with the registers of the DART's revision, and leave the DART unlocked + on error. For a T8110 DART that nothing initialises before Linux. + + apple,ctc-flush: + type: boolean + description: + Flush the DART's context cache after a stream flush, where the DART + has one. For a T8110 DART that nothing initialises before Linux. apple,remap-streams: $ref: /schemas/types.yaml#/definitions/uint32-matrix From 331043fa765040e3e2b370894660e191ff3d45e4 Mon Sep 17 00:00:00 2001 From: Erik Bourget Date: Tue, 3 Nov 2026 22:34:56 -0500 Subject: [PATCH 25/43] iommu: apple-dart: set up a T8110 DART that nothing initialised A T8110 DART that the boot loader leaves alone, such as the M5 Max USB4 router's, comes up with its tunables unapplied, its errors masked and no stream in bypass. Its own driver otherwise only clears the streams, enables them and flushes the TLBs. Let the device tree describe the rest, for non-tunneled T8110 DARTs: - apple,tunable: the DART's own tunables, applied at every reset before the first stream is configured. Client partition registers (0x800..0xfff) are skipped unless PARAMS1 bit 29 says they exist. - apple,bypass-sids: streams set to bypass translation at every reset, outside any domain. - apple,full-error-reporting: unmask every error and enable the error interrupt for every stream with the registers of the DART's revision (the error mask moved to 0x4060 in 2.2; status, enable and the per-stream enables moved from 0x700 to 0x4200 in 2.4), and leave the DART unlocked on error. On 2.4 the interrupt handler also reports and acknowledges the error status. - apple,ctc-flush: where PARAMS2 bit 4 says there is a context cache, flush it (0x600) after a V2 stream flush, which leaves it alone. Log the identification registers of a DART set up this way at probe. Developed with Claude Code. Signed-off-by: Erik Bourget --- drivers/iommu/apple-dart.c | 165 +++++++++++++++++++++++++++++++++++++ 1 file changed, 165 insertions(+) diff --git a/drivers/iommu/apple-dart.c b/drivers/iommu/apple-dart.c index d5b60c897a4c2..eb79cad148163 100644 --- a/drivers/iommu/apple-dart.c +++ b/drivers/iommu/apple-dart.c @@ -190,6 +190,26 @@ #define DART_T8110_ENABLE_STREAMS 0xc00 #define DART_T8110_DISABLE_STREAMS 0xc20 +/* + * Revision 2.2 and later moved the error mask; 2.4 also moved the error + * status, the error interrupt enable and the per-stream error enables. + */ +#define DART_T8110_V2_ERROR_MASK 0x4060 +#define DART_T8110_ERR_IRQ_STATUS 0x704 +#define DART_T8110_ERR_IRQ_ENABLE 0x700 +#define DART_T8110_ERR_SID_ENABLE(i) (0x720 + 4 * (i)) +#define DART_T8110_V24_ERR_IRQ_STATUS 0x4204 +#define DART_T8110_V24_ERR_IRQ_ENABLE 0x4200 +#define DART_T8110_V24_ERR_SID_ENABLE(i) (0x4220 + 4 * (i)) + +/* PARAMS1 bit 29: client partitions (the 0x800.. registers) exist. */ +#define DART_T8110_PARAMS1_CLIENT_PARTITIONS BIT(29) +#define DART_T8110_CLIENT_PARTITION_BASE 0x800 +#define DART_T8110_CLIENT_PARTITION_END 0x1000 +/* PARAMS2 bit 4: a translation context cache that a full flush leaves alone. */ +#define DART_T8110_PARAMS2_CTC BIT(4) +#define DART_T8110_CTC_FLUSH 0x600 + #define DART_T8110_TCR 0x1000 #define DART_T8110_TCR_REMAP GENMASK(11, 8) #define DART_T8110_TCR_REMAP_ENABLE BIT(7) @@ -311,6 +331,16 @@ struct apple_dart { struct apple_tunable *tunables; u16 version; + /* + * Set up from the device tree for a DART nothing else initialised: + * tunables applied at every reset, streams kept in static bypass, every + * error reported, and the context cache flushed after a full TLB flush. + */ + DECLARE_BITMAP(static_bypass, DART_MAX_STREAMS); + bool full_errors; + bool ctc_flush; + bool client_partitions; + /* J713 internal display DARTs, borrowed from the running DCP. */ struct apple_dart_dcp *dcp; struct apple_dart_dcp *frontend[2]; @@ -1377,6 +1407,10 @@ apple_dart_t8110_hw_tlb_command_range(struct apple_dart_stream_map *stream_map, if (ret) break; } + /* A V2 flush leaves the context cache alone: flush all of it after. */ + if (!ret && dart->ctc_flush && dart->version >= 0x0202 && + command == DART_T8110_TLB_CMD_OP_FLUSH_SID) + apple_dart_writel(dart, 0, DART_T8110_CTC_FLUSH); spin_unlock_irqrestore(&dart->lock, flags); if (ret) dev_err_ratelimited(dart->dev, @@ -1750,6 +1784,59 @@ apple_dart_t8110_hw_enable_remap(struct apple_dart *dart, int sid, int remap_to) * acknowledge the errors latched so far, unlock a DART that locks on errors, * unmask the error interrupt and flush the TLBs. */ +/* + * Unmask every error and enable its interrupt for each stream, with the + * registers of this revision: the T8110 reset value leaves them masked on + * 2.2 and later, where the error mask is no longer at ERROR_MASK. + */ +static void apple_dart_t8110_enable_errors(struct apple_dart *dart) +{ + bool v24 = dart->version >= 0x0204; + unsigned int i; + + apple_dart_writel(dart, 0, dart->version >= 0x0202 ? + DART_T8110_V2_ERROR_MASK : DART_T8110_ERROR_MASK); + apple_dart_readl(dart, dart->version >= 0x0202 ? + DART_T8110_V2_ERROR_MASK : DART_T8110_ERROR_MASK); + apple_dart_writel(dart, U32_MAX, v24 ? DART_T8110_V24_ERR_IRQ_STATUS : + DART_T8110_ERR_IRQ_STATUS); + apple_dart_writel(dart, U32_MAX, v24 ? DART_T8110_V24_ERR_IRQ_ENABLE : + DART_T8110_ERR_IRQ_ENABLE); + for (i = 0; i < DIV_ROUND_UP(dart->num_streams, 32); i++) { + u32 sids = dart->num_streams >= 32 * (i + 1) ? U32_MAX : + GENMASK(dart->num_streams - 32 * i - 1, 0); + + apple_dart_writel(dart, sids, v24 ? DART_T8110_V24_ERR_SID_ENABLE(i) : + DART_T8110_ERR_SID_ENABLE(i)); + } + /* No lock on error: the driver acknowledges and carries on. */ + apple_dart_writel(dart, 0, DART_T8110_DIAG_LOCK); +} + +/* + * Apply the DART's own tunables. Client partition registers exist only where + * PARAMS1 says so; a write to one that does not exist is skipped. + */ +static void apple_dart_apply_own_tunables(struct apple_dart *dart) +{ + size_t i; + + for (i = 0; i < dart->tunables->sz; ++i) { + u32 offset = dart->tunables->values[i].offset; + u32 old_value, value; + + if (offset >= DART_T8110_CLIENT_PARTITION_BASE && + offset < DART_T8110_CLIENT_PARTITION_END && + !dart->client_partitions) + continue; + old_value = apple_dart_readl(dart, offset); + value = old_value & ~dart->tunables->values[i].mask; + value |= dart->tunables->values[i].value; + if (value != old_value) + apple_dart_writel(dart, value, offset); + } +} + static int apple_dart_hw_finish_reset(struct apple_dart *dart) { struct apple_dart_stream_map stream_map = { .dart = dart }; @@ -1795,6 +1882,9 @@ static int apple_dart_hw_finish_reset(struct apple_dart *dart) if (ret) dev_err(dart->dev, "failed to flush the TLBs after reset: %d\n", ret); + if (dart->full_errors) + apple_dart_t8110_enable_errors(dart); + return ret; } @@ -2012,10 +2102,18 @@ 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); + /* Before the first stream is configured. */ + if (!dart->tunneled && dart->tunables) + apple_dart_apply_own_tunables(dart); + apple_dart_hw_disable_dma(&stream_map); apple_dart_hw_clear_all_ttbrs(&stream_map); apple_dart_hw_apply_remaps(dart); + /* Streams that bypass translation for good, outside any domain. */ + for_each_set_bit(i, dart->static_bypass, dart->num_streams) + apple_dart_writel(dart, DART_T8110_TCR_BYPASS_DART, DART_TCR(dart, i)); + /* enable all streams globally since TCR is used to control isolation */ for (i = 0; i < BITS_TO_U32(dart->num_streams); i++) apple_dart_writel(dart, U32_MAX, @@ -3855,6 +3953,17 @@ static irqreturn_t apple_dart_t8110_v2_irq(int irq, void *dev) readl(dart->regs + DART_T8110_EXCEPTION(0)); } + /* With every error enabled, 2.4 latches the rest in its own status. */ + if (dart->full_errors && dart->version >= 0x0204) { + u32 status = readl(dart->regs + DART_T8110_V24_ERR_IRQ_STATUS); + + if (status) { + dev_err_ratelimited(dart->dev, "error status: 0x%08x\n", status); + writel(status, dart->regs + DART_T8110_V24_ERR_IRQ_STATUS); + ret = IRQ_HANDLED; + } + } + if (dart->version >= 0x0203) { block_error = readl(dart->regs + DART_T8110_BLOCK_ERROR); if (apple_dart_t8110_v2_block_pending(dart->version, block_error)) { @@ -4127,6 +4236,56 @@ static bool apple_dart_has_live_rtkit_client(struct device_node *np) return false; } +/* + * The setup a T8110 DART needs when nothing initialised it before Linux: + * apple,tunable (applied at every reset), apple,bypass-sids (streams kept in + * static bypass), apple,full-error-reporting and apple,ctc-flush. Log the + * identification registers of such a DART. + */ +static int apple_dart_parse_own_setup(struct apple_dart *dart, struct resource *res, + const u32 *params) +{ + struct device *dev = dart->dev; + struct device_node *np = dev->of_node; + int count, i; + u32 sid; + + dart->full_errors = of_property_read_bool(np, "apple,full-error-reporting"); + dart->ctc_flush = of_property_read_bool(np, "apple,ctc-flush") && + (params[1] & DART_T8110_PARAMS2_CTC); + dart->client_partitions = params[0] & DART_T8110_PARAMS1_CLIENT_PARTITIONS; + + if (of_property_present(np, "apple,tunable")) { + dart->tunables = devm_apple_tunable_parse(dev, np, "apple,tunable", res); + if (IS_ERR(dart->tunables)) + return dev_err_probe(dev, PTR_ERR(dart->tunables), + "failed to parse DART tunables\n"); + } + + count = of_property_count_u32_elems(np, "apple,bypass-sids"); + for (i = 0; i < count; i++) { + if (of_property_read_u32_index(np, "apple,bypass-sids", i, &sid) || + sid >= dart->num_streams) + return dev_err_probe(dev, -EINVAL, "invalid bypass stream\n"); + __set_bit(sid, dart->static_bypass); + } + + if (dart->tunables || count > 0 || dart->full_errors || + of_property_read_bool(np, "apple,ctc-flush")) + dev_info(dev, + "params %08x %08x %08x %08x, version %04x, %u streams, AS %u -> %u, protect %08x/%08x, diag lock %08x; %zu tunables%s, %d bypass streams, CTC flush %s\n", + params[0], params[1], params[2], params[3], dart->version, + dart->num_streams, dart->ias, dart->oas, + apple_dart_readl(dart, DART_T8110_PROTECT), + apple_dart_readl(dart, DART_T8110_PROTECT_LOCK), + apple_dart_readl(dart, DART_T8110_DIAG_LOCK), + dart->tunables ? dart->tunables->sz : 0, + dart->tunables && !dart->client_partitions ? + " (no client partitions)" : "", + max(count, 0), dart->ctc_flush ? "on" : "off"); + return 0; +} + static int apple_dart_probe(struct platform_device *pdev) { int ret, sid; @@ -4433,6 +4592,12 @@ static int apple_dart_probe(struct platform_device *pdev) } params_done: + if (!tunneled && dart->hw->type == DART_T8110) { + ret = apple_dart_parse_own_setup(dart, res, dart_params); + if (ret) + goto err_clk_disable; + } + dart->dma_min = 0; dart->dma_max = DMA_BIT_MASK(dart->ias); dart->dma_offset = 0; From 1102e101fc521998854f047da156d9089ae909ec Mon Sep 17 00:00:00 2001 From: Erik Bourget Date: Tue, 3 Nov 2026 22:35:02 -0500 Subject: [PATCH 26/43] arm64: dts: apple: j716c: Add the right port's USB4/Thunderbolt router Describe the USB4/Thunderbolt host router behind the right-hand USB-C port (ADT /arm-io/acio2, acio-cpu2 and dart-acio2). In t6050-max.dtsi: - the ATC2_CIO, ATC2_CIO_PCIE, ATC2_CIO_USB and ATC2_CIO_AUSS power domains; - the router CPU's mailbox and the router's LSTX pin function on the AOP GPIO controller; - the router itself, with its DART and NHI as children, so that both are populated only once the router runs: nothing is touched at boot; - the DART's stream 15 in bypass, full error reporting and the context cache flush. In the board: AUSS, kept as the boot loader leaves it, the CIO reconfig request (port 2 of the die's CIO reset), the AOP GPIO controller with only the LSTX pin exposed, the PHY's CIO80 RX_TOP tunables, the link from the PD controller to the router, and, until the boot loader fills them, this board's router, NHI and DART tunables and host DROM, from its ADT. The router node is named acio@: the boot loader disables a cio@ node unless its first register is the third register of the matching /arm-io/acioN, which on T6050 is not the router's rc block. Developed with Claude Code. Signed-off-by: Erik Bourget --- arch/arm64/boot/dts/apple/t6050-j716c.dts | 221 ++++++++++++++++++++++ arch/arm64/boot/dts/apple/t6050-max.dtsi | 178 +++++++++++++++++ 2 files changed, 399 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t6050-j716c.dts b/arch/arm64/boot/dts/apple/t6050-j716c.dts index 550aef6f8768f..abdf7ca85b592 100644 --- a/arch/arm64/boot/dts/apple/t6050-j716c.dts +++ b/arch/arm64/boot/dts/apple/t6050-j716c.dts @@ -342,3 +342,224 @@ }; }; }; + +/* + * ==== The right USB-C port's USB4/Thunderbolt router (ATC2) ==== + * + * Until the boot loader fills them, the router, NHI, PHY CIO and DART tunables and the host + * DROM are this board's, from its ADT (the DROM's router UUID from an earlier boot). + */ +/* ATC2_CIO_AUSS's other parent; kept as the boot loader leaves it */ +&ps_auss { + status = "okay"; +}; + +/* The CIO reconfig request, port 2 (clock gate CIO2_RECONFIG_V) */ +&cio_reconfig0 { + status = "okay"; +}; + +/* The PHY's CIO80 RX_TOP tunables (ADT tunable_LN0/LN1_RX_TOP_CIO_DFLT); the other three are empty */ +&atc2_phy { + apple,tunable-lane0-rx-top-cio = <0x35c 0xe00 0x0>; + apple,tunable-lane1-rx-top-cio = <0x35c 0xe00 0x0>; +}; + +/* Only the router's LSTX pin (81) of the AOP GPIO controller is exposed. */ +&pinctrl_aop { + gpio-reserved-ranges = <0 81>, <82 3>; + status = "okay"; +}; + +&hpm2 { + connector { + ports { + port@3 { + reg = <3>; + hpm2_usb4: endpoint { + remote-endpoint = <&acio2_tbt>; + }; + }; + }; + }; +}; + +&acio2 { + apple,tunable-rc = <0x0 0xfff0 0x4020>, + <0x2c 0x4 0x0>, + <0x4004 0x1ff01ff 0x1000100>, + <0x4008 0x801f 0x4>, + <0x4010 0x7f 0x40>, + <0x4014 0x801f 0x4>, + <0x401c 0x7f 0x40>, + <0x4024 0x801f 0x4>, + <0x402c 0x7f 0x40>, + <0x4030 0x801f 0x4>, + <0x4038 0xffffffff 0x1010101>, + <0x403c 0xffff 0x101>, + <0x4040 0x1 0x1>, + <0x4044 0xffffffff 0x1010101>, + <0x4048 0x1 0x1>, + <0x404c 0x801f 0x4>, + <0x8004 0x70007 0x40004>, + <0x8008 0x801f 0x4>, + <0x8010 0x70007 0x40004>, + <0x8018 0x801f 0x4>, + <0x8020 0x70007 0x40004>, + <0x8028 0x801f 0x4>, + <0x8030 0x801f 0x4>, + <0x8038 0x801f 0x4>, + <0x8040 0x801f 0x4>, + <0x8048 0x801f 0x4>, + <0x8050 0x801f 0x4>, + <0x8058 0x801f 0x4>, + <0x8060 0x801f 0x4>; + apple,tunable-pcie-adapter = <0x1008 0x6 0x6>; + apple,tunable-router-ap-fw = <0x8 0xff2 0x482>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@1 { + reg = <1>; + acio2_tbt: endpoint { + remote-endpoint = <&hpm2_usb4>; + }; + }; + }; +}; + +&acio2_dart { + apple,tunable = <0x20c 0x80000121 0x80000121>, + <0x220 0xf0f 0xf0f>, + <0x224 0xffff 0x909>, + <0x300 0x1f31 0x1>, + <0x308 0x3ffffffc 0x10000000>, + <0x310 0x3ffffffc 0x3ffffffc>, + <0x800 0xf003f 0x10000>, + <0x804 0xf003f 0x10002>, + <0x808 0xf003f 0x10004>, + <0x80c 0xf003f 0x20006>, + <0x810 0xf003f 0x2000a>, + <0x814 0xf003f 0x2000e>, + <0x818 0xf003f 0x20012>, + <0x81c 0xf003f 0x20016>, + <0x820 0xf003f 0x2001a>, + <0x824 0xf003f 0x2001e>, + <0x828 0xf003f 0x20022>, + <0x82c 0xf003f 0x20026>, + <0x830 0xf003f 0x2002a>, + <0x834 0xf003f 0x2002e>, + <0x838 0xf003f 0x20032>, + <0x83c 0xf003f 0x10036>; +}; + +&acio2_nhi { + apple,tunable-nhi = <0xf8004 0xff 0x80>, + <0xf8008 0x801f 0x4>, + <0xf8010 0x3f 0x20>, + <0xf8014 0x801f 0x4>, + <0xf801c 0x1f 0x10>, + <0xf8020 0x801f 0x4>, + <0xf8028 0xff 0x80>, + <0xf802c 0x801f 0x4>, + <0xf8034 0xff 0x80>, + <0xf8038 0x801f 0x4>, + <0xf8040 0x3f 0x20>, + <0xf8044 0x801f 0x4>, + <0xf804c 0x8007 0x1>, + <0xfc004 0x3 0x2>, + <0xfc008 0x801f 0x4>, + <0xfc010 0x7 0x4>, + <0xfc014 0x801f 0x4>, + <0xfc01c 0x7 0x4>, + <0xfc020 0x801f 0x4>, + <0xfc028 0x7 0x4>, + <0xfc02c 0x801f 0x4>, + <0xfc034 0x7 0x4>, + <0xfc038 0x801f 0x4>, + <0xfc040 0x3 0x2>, + <0xfc044 0x801f 0x4>, + <0xfc04c 0x8007 0x1>, + <0x4000 0x1000000f 0x10000002>, + <0x4004 0x1000000f 0x10000002>, + <0x4008 0x1000000f 0x10000002>, + <0x400c 0x1000000f 0x10000002>, + <0x4010 0x1000000f 0x10000002>, + <0x4014 0x1000000f 0x10000002>, + <0x4018 0x1000000f 0x10000002>, + <0x401c 0x1000000f 0x10000002>, + <0x4020 0x1000000f 0x10000002>, + <0x4024 0x1000000f 0x10000002>, + <0x4028 0x1000000f 0x10000002>, + <0x402c 0x1000000f 0x10000002>, + <0x4040 0x1000000f 0x10000002>, + <0x4044 0x1000000f 0x10000002>, + <0x4048 0x1000000f 0x10000002>, + <0x404c 0x1000000f 0x10000002>, + <0x4050 0x1000000f 0x10000002>, + <0x4054 0x1000000f 0x10000002>, + <0x4058 0x1000000f 0x10000002>, + <0x405c 0x1000000f 0x10000002>, + <0x4060 0x1000000f 0x10000002>, + <0x4064 0x1000000f 0x10000002>, + <0x4068 0x1000000f 0x10000002>, + <0x406c 0x1000000f 0x10000002>, + <0x4080 0x1000000f 0x10000002>, + <0x4084 0x1000000f 0x10000002>, + <0x4088 0x1000000f 0x10000002>, + <0x408c 0x1000000f 0x10000002>, + <0x4090 0x1000000f 0x10000002>, + <0x4094 0x1000000f 0x10000002>, + <0x4098 0x1000000f 0x10000002>, + <0x409c 0x1000000f 0x10000002>, + <0x40a0 0x1000000f 0x10000002>, + <0x40a4 0x1000000f 0x10000002>, + <0x40a8 0x1000000f 0x10000002>, + <0x40ac 0x1000000f 0x10000002>, + <0x40c0 0x1000000f 0x10000002>, + <0x40c4 0x1000000f 0x10000002>, + <0x40c8 0x1000000f 0x10000002>, + <0x40cc 0x1000000f 0x10000002>, + <0x40d0 0x1000000f 0x10000002>, + <0x40d4 0x1000000f 0x10000002>, + <0x40d8 0x1000000f 0x10000002>, + <0x40dc 0x1000000f 0x10000002>, + <0x40e0 0x1000000f 0x10000002>, + <0x40e4 0x1000000f 0x10000002>, + <0x40e8 0x1000000f 0x10000002>, + <0x40ec 0x1000000f 0x10000002>, + <0x4100 0x1000000f 0x10000002>, + <0x4104 0x1000000f 0x10000002>, + <0x4108 0x1000000f 0x10000002>, + <0x410c 0x1000000f 0x10000002>, + <0x4110 0x1000000f 0x10000002>, + <0x4114 0x1000000f 0x10000002>, + <0x4118 0x1000000f 0x10000002>, + <0x411c 0x1000000f 0x10000002>, + <0x4120 0x1000000f 0x10000002>, + <0x4124 0x1000000f 0x10000002>, + <0x4128 0x1000000f 0x10000002>, + <0x412c 0x1000000f 0x10000002>, + <0x4140 0x1000000f 0x10000002>, + <0x4144 0x1000000f 0x10000002>, + <0x4148 0x1000000f 0x10000002>, + <0x414c 0x1000000f 0x10000002>, + <0x4150 0x1000000f 0x10000002>, + <0x4154 0x1000000f 0x10000002>, + <0x4158 0x1000000f 0x10000002>, + <0x415c 0x1000000f 0x10000002>, + <0x4160 0x1000000f 0x10000002>, + <0x4164 0x1000000f 0x10000002>, + <0x4168 0x1000000f 0x10000002>, + <0x416c 0x1000000f 0x10000002>, + <0x43e4 0x7 0x7>; + apple,thunderbolt-drom = [ff 92 e6 6e ee 18 7c ac 05 90 19 c3 8c 00 5c 00 + 00 00 00 00 00 00 08 81 82 02 82 00 00 00 08 82 + 92 01 82 00 00 00 03 83 20 08 84 00 00 00 00 00 + 00 08 85 00 00 00 00 00 00 05 86 09 01 00 08 87 + 00 00 00 00 00 00 05 88 09 01 00 05 89 09 01 00 + 05 8a 09 01 00 0e 01 41 70 70 6c 65 2c 20 49 6e + 63 2e 00 06 02 69 4f 53 00 00 00 00]; +}; diff --git a/arch/arm64/boot/dts/apple/t6050-max.dtsi b/arch/arm64/boot/dts/apple/t6050-max.dtsi index 55cf22a98e2a2..87bd77e4b4b99 100644 --- a/arch/arm64/boot/dts/apple/t6050-max.dtsi +++ b/arch/arm64/boot/dts/apple/t6050-max.dtsi @@ -129,3 +129,181 @@ }; #endif }; + +#ifdef T6050_J716C +/* + * ATC2's USB4/Thunderbolt router (ADT /arm-io/acio2, acio-cpu2 and dart-acio2), powered per + * connection: the PHY enters CIO mode, then the router's domains come on (ATC2_CIO, _PCIE, _USB, + * _AUSS), the CIO reset (reconfig request) clocks the CPU wrapper, the CPU runs from the image the + * boot loader left in its SRAM, and once its firmware is ready the DART and the NHI are populated. + * Nothing here is touched at boot. The router is named acio@: the boot loader disables a cio@ node + * unless its first register is the third register of the matching /arm-io/acioN, which on T6050 + * is not the router's rc block. The board gives the tunables and the host DROM until the boot + * loader fills them. + */ +&pmgr0 { + ps_atc2_cio: power-controller@2e8 { + compatible = "apple,t6050-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x2e8 4>; + label = "atc2_cio"; + #power-domain-cells = <0>; + #reset-cells = <0>; + power-domains = <&ps_atc2_common>; + }; + + ps_atc2_cio_pcie: power-controller@3f8 { + compatible = "apple,t6050-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x3f8 4>; + label = "atc2_cio_pcie"; + #power-domain-cells = <0>; + #reset-cells = <0>; + power-domains = <&ps_atc2_cio>; + }; + + ps_atc2_cio_usb: power-controller@400 { + compatible = "apple,t6050-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x400 4>; + label = "atc2_cio_usb"; + #power-domain-cells = <0>; + #reset-cells = <0>; + power-domains = <&ps_atc2_cio>; + }; + + ps_atc2_cio_auss: power-controller@408 { + compatible = "apple,t6050-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x408 4>; + label = "atc2_cio_auss"; + #power-domain-cells = <0>; + #reset-cells = <0>; + power-domains = <&ps_atc2_cio>, <&ps_auss>; + }; +}; + +/* LSTX: /arm-io/acio2 gpio-lstx, AOP GPIO pin 81 (0x51), function 1 for the router */ +&pinctrl_aop { + acio2_lstx_pins: acio2-lstx-pins { + pinmux = <0x10051>; /* APPLE_PINMUX(81, 1) */ + }; +}; + +&die0 { + /* /arm-io/acio-cpu2 reg[0]: the router CPU's mailbox, inside its wrapper */ + acio2_mbox: mailbox@381108000 { + compatible = "apple,t6050-asc-mailbox", "apple,asc-mailbox-v4"; + reg = <0x3 0x81108000 0x0 0x4000>; + interrupts = , + , + , + ; + interrupt-names = "send-empty", "send-not-empty", + "recv-empty", "recv-not-empty"; + #mbox-cells = <0>; + }; + + /* + * /arm-io/acio2: rc (reg[1]), the RTKit data segment in its SRAM, the PCIe adapter, + * acio-cpu2 reg[1] (the CPU wrapper) and the PCIe router AP firmware block. + */ + acio2: acio@381ac0000 { + compatible = "apple,t6050-usb4-acio"; + reg = <0x3 0x81ac0000 0x0 0xc000>, + <0x3 0x81080000 0x0 0x26000>, + <0x3 0x81e44000 0x0 0x4000>, + <0x3 0x81100000 0x0 0x4000>, + <0x3 0x81c80000 0x0 0x4000>; + reg-names = "rc", "sram", "pcie-adapter", "cpu", "router-ap-fw"; + mboxes = <&acio2_mbox>; + resets = <&cio_reconfig0 2>; + power-domains = <&ps_atc2_cio>, <&ps_atc2_cio_pcie>, + <&ps_atc2_cio_usb>, <&ps_atc2_cio_auss>; + thunderbolt-switch; + pinctrl-0 = <&acio2_lstx_pins>; + pinctrl-names = "lstx"; + lstx-gpios = <&pinctrl_aop 81 GPIO_ACTIVE_HIGH>; + + #address-cells = <1>; + #size-cells = <1>; + ranges = <0x0 0x3 0x81000000 0x1000000>; + nonposted-mmio; + + /* + * /arm-io/dart-acio2, populated with the NHI once the router runs, so that it is + * first read with the router up. Nothing initialises it before Linux: its tunables + * (dart-tunables-instance-0), stream 15 (bypass-15) in bypass, every error + * reported and the context cache flushed. vm-base 0x100_0000_0000. + */ + acio2_dart: iommu@a80000 { + compatible = "apple,t6050-dart", "apple,t8110-dart"; + reg = <0xa80000 0xc000>; + interrupts = ; + power-domains = <&ps_atc2_cio>; + #iommu-cells = <1>; + apple,dma-range = <0x100 0x4000 0x0 0xffff0000>; + apple,bypass-sids = <15>; + apple,full-error-reporting; + apple,ctc-flush; + }; + + /* Interrupts 0x66b.. are the TX rings and 0x677.. the RX rings */ + acio2_nhi: nhi@f00000 { + compatible = "apple,t6050-usb4-nhi"; + reg = <0xf00000 0x100000>; + reg-names = "nhi"; + interrupts = , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + , + ; + interrupt-names = "txring0", "txring1", "txring2", "txring3", "txring4", "txring5", + "txring6", "txring7", "txring8", "txring9", "txring10", "txring11", + "rxring0", "rxring1", "rxring2", "rxring3", "rxring4", "rxring5", + "rxring6", "rxring7", "rxring8", "rxring9", "rxring10", "rxring11", + "parity"; + iommus = <&acio2_dart 0>, + <&acio2_dart 1>, + <&acio2_dart 2>, + <&acio2_dart 3>, + <&acio2_dart 4>, + <&acio2_dart 5>, + <&acio2_dart 6>, + <&acio2_dart 7>, + <&acio2_dart 8>, + <&acio2_dart 9>, + <&acio2_dart 10>, + <&acio2_dart 11>, + <&acio2_dart 16>, + <&acio2_dart 17>, + <&acio2_dart 18>, + <&acio2_dart 19>, + <&acio2_dart 20>, + <&acio2_dart 21>, + <&acio2_dart 22>, + <&acio2_dart 23>, + <&acio2_dart 24>, + <&acio2_dart 25>, + <&acio2_dart 26>, + <&acio2_dart 27>; + }; + }; +}; +#endif From a0b688d65a3055183ed38a19376d8c060474e9c9 Mon Sep 17 00:00:00 2001 From: Erik Bourget Date: Wed, 4 Nov 2026 08:46:58 -0500 Subject: [PATCH 27/43] phy: apple: t6050: park the PIPE from a dummy producer on any clock At the first controller reset the USB3 PIPE mux is expected unset (0), on the dummy producer (2) or already parked (0x42). When nothing has reset the controller domain since boot, as on a boot without the SPTM, the mux reads 0x22: the dummy producer on another clock. The park then refused it and dwc3 failed to probe. The park clears the clock field before it selects the dummy producer and its clock, so start from any clock while the producer is the dummy one. Developed with Claude Code. Signed-off-by: Erik Bourget --- drivers/phy/apple/usb-t6050/apple_t6050_usb3_pipe.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/phy/apple/usb-t6050/apple_t6050_usb3_pipe.h b/drivers/phy/apple/usb-t6050/apple_t6050_usb3_pipe.h index 812227cac03e9..f5962152444bf 100644 --- a/drivers/phy/apple/usb-t6050/apple_t6050_usb3_pipe.h +++ b/drivers/phy/apple/usb-t6050/apple_t6050_usb3_pipe.h @@ -23,7 +23,9 @@ apple_t6050_usb3_pipe_cold_dummy(const struct apple_t6050_usb_io *io) if (io->read(io->context, 44, 0x1c) & 1) return -1; mux = io->read(io->context, 44, 0x0c) & 0x73; - if (mux != 0 && mux != 2 && mux != 0x42) + /* Unset, or already on the dummy producer (1:0 = 2) with any clock: when nothing + * has reset the controller domain since boot, the mux can read 0x22. */ + if (mux != 0 && (mux & 3) != 2) return -1; if (mux == 0x42) return 0; From 178e80cc2f0c09f601919110709d604e6f744268 Mon Sep 17 00:00:00 2001 From: Erik Bourget Date: Wed, 4 Nov 2026 10:49:52 -0500 Subject: [PATCH 28/43] thunderbolt: Ignore DROM entries for ports the router does not implement The host DROM of the M5 Max USB4 router lists port 8, which the router reports as not implemented. Reading its port config fails with -ENODEV, which made the whole DROM unusable and left the host router without one, so the NHI failed to probe. Skip such entries, as is already done for entries past the last port. Developed with Claude Code. Signed-off-by: Erik Bourget --- drivers/thunderbolt/eeprom.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/thunderbolt/eeprom.c b/drivers/thunderbolt/eeprom.c index 389de7df84f65..f2d56acb21a10 100644 --- a/drivers/thunderbolt/eeprom.c +++ b/drivers/thunderbolt/eeprom.c @@ -381,6 +381,12 @@ static int tb_drom_parse_entry_port(struct tb_switch *sw, return 0; res = tb_port_read(port, &type, TB_CFG_PORT, 2, 1); + /* Listed, but not implemented by this router: skip it as well */ + if (res == -ENODEV) { + tb_sw_dbg(sw, "DROM entry for unimplemented port %u ignored\n", + header->index); + return 0; + } if (res) return res; type &= 0xffffff; From a1d6ef9718682a2a321798783e636c724a0cf913 Mon Sep 17 00:00:00 2001 From: Erik Bourget Date: Wed, 4 Nov 2026 12:17:36 -0500 Subject: [PATCH 29/43] phy: apple: t6050: restart the PHY firmware on its original data The PHY firmware is preloaded by the boot loader, data segment included, and keeps its state in that segment. Restarted after a stop, it ran on what its last run had left there and never finished calibrating the lanes: UC_TNBL_DONE did not come, and every second connection of a port failed, in USB as in CIO mode, until reboot. Save the data SRAM when the firmware is first started and put it back before every later start. Developed with Claude Code. Signed-off-by: Erik Bourget --- drivers/phy/apple/atc-t6050-dp.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/drivers/phy/apple/atc-t6050-dp.c b/drivers/phy/apple/atc-t6050-dp.c index e6941cf86c8ad..57d0587cdaf24 100644 --- a/drivers/phy/apple/atc-t6050-dp.c +++ b/drivers/phy/apple/atc-t6050-dp.c @@ -97,6 +97,7 @@ struct t6050_dp { struct apple_rtkit *rtkit; void __iomem *fw_data; bool fw_running; + void *fw_data_saved; /* the firmware data SRAM as the first start found it */ }; static int atc_domain_set(struct atc_domain *pd, bool active) @@ -183,6 +184,20 @@ static int atc_firmware(struct t6050_dp *dp, bool start) if (dp->fw_running == start) return 0; if (start) { + /* + * The firmware is the boot loader's, preloaded with its data segment, and keeps + * its state there: restarted on the state its last run left, it never finishes + * calibrating the lanes (UC_TNBL_DONE). Give every start the data segment the + * first one found. The PHY's domains are on here, so the SRAM is accessible. + */ + if (!dp->fw_data_saved) { + dp->fw_data_saved = devm_kmalloc(dp->dev, 0x10000, GFP_KERNEL); + if (!dp->fw_data_saved) + return -ENOMEM; + memcpy_fromio(dp->fw_data_saved, dp->fw_data, 0x10000); + } else { + memcpy_toio(dp->fw_data, dp->fw_data_saved, 0x10000); + } /* Native coprocessor run, after its +0x40 firmware enable. */ writel(BIT(4), dp->banks[38] + 0x28); ret = apple_rtkit_wake(dp->rtkit); From 625229d3881c6a2319a524a1029ef5b3c884bcab Mon Sep 17 00:00:00 2001 From: Erik Bourget Date: Wed, 4 Nov 2026 12:21:55 -0500 Subject: [PATCH 30/43] phy: apple: t6050: carry USB3 over a USB4 router's tunnel In CIO mode the lanes carry USB4/TBT, and USB3 reaches DWC3 through the router's USB3 adapter. DWC3 was left on its dummy PIPE producer, so a USB3 tunnel came up with nothing on the host side. When DWC3 asks for USB3 in CIO mode, move its PIPE mux to the tunnel producer (0x21: PIPE mode 1, clock 2) under the PIPE lock, as the older Apple PHYs do in USB4 mode. A lock that is not acknowledged is not fatal, as for native USB3. On power-off the PIPE goes back to the dummy producer while the CIO PHY is left to the router. Developed with Claude Code. Signed-off-by: Erik Bourget --- drivers/phy/apple/atc-t6050-combo.h | 38 +++++++++++++++-- drivers/phy/apple/atc-t6050-dp.c | 1 + .../apple/usb-t6050/apple_t6050_usb3_pipe.h | 42 ++++++++++++++++++- 3 files changed, 76 insertions(+), 5 deletions(-) diff --git a/drivers/phy/apple/atc-t6050-combo.h b/drivers/phy/apple/atc-t6050-combo.h index c57779075ec05..185f71cd6e138 100644 --- a/drivers/phy/apple/atc-t6050-combo.h +++ b/drivers/phy/apple/atc-t6050-combo.h @@ -298,6 +298,34 @@ static int combo_usb2_reset(struct phy *phy) return ret ? -EIO : 0; } +/* A tunnelled PIPE leaves the CIO PHY to the router: nothing to quiesce on the PHY side. */ +static int combo_pipe_keep(void *ctx) +{ + return 0; +} + +/* + * In CIO mode the lanes carry USB4/TBT and USB3 reaches DWC3 through the router's USB3 adapter: + * move DWC3 from its dummy PIPE producer to the tunnel producer, as is done on the older PHYs. + */ +static int combo_pipe_tunnel(struct t6050_dp *dp) +{ + int ret; + + lockdep_assert_held(&dp->lock); + if (dp->pipe_active) + return dp->pipe_tunnel ? 0 : -EBUSY; + ret = apple_t6050_usb3_pipe_tunnel(&dp->usb_io); + if (ret) { + combo_pipe_fault(dp, "USB3 tunnel PIPE switch", ret); + return -EIO; + } + dp->pipe_active = true; + dp->pipe_tunnel = true; + dev_info(dp->dev, "USB3 PIPE on the USB4 tunnel\n"); + return 0; +} + static int combo_usb3_mode(struct phy *phy, enum phy_mode mode, int submode) { struct t6050_dp *dp = phy_get_drvdata(phy); @@ -314,10 +342,8 @@ static int combo_usb3_mode(struct phy *phy, enum phy_mode mode, int submode) * uses its independent D+/D- PHY. Do not switch USB3 onto DP lanes. */ if (dp->combo_mode == COMBO_DP4 && dp->enabled) return 0; - /* In CIO the lanes carry USB4/TBT. DWC3 keeps its dummy PIPE until a - * USB3 tunnel hands PIPE to the router; native USB3 mode is 0 too. */ if (dp->combo_mode == COMBO_CIO && dp->cio_prepared) - return 0; + return combo_pipe_tunnel(dp); if (!dp->usb_prepared && !(dp->combo_mode == COMBO_DP2 && dp->enabled)) return -ENOLINK; if (dp->pipe_active) @@ -347,7 +373,10 @@ static int combo_usb3_poweroff(struct phy *phy) /* DWC3 calls power_off only after host/gadget shutdown and SUSPHY. * PIPE must select its dummy producer before the controller is reset. */ if (dp->pipe_active) { - ret = apple_t6050_usb3_pipe_stop(&dp->usb_io, combo_quiesce, dp); + ret = apple_t6050_usb3_pipe_stop(&dp->usb_io, + dp->pipe_tunnel ? combo_pipe_keep : combo_quiesce, dp); + if (!ret) + dp->pipe_tunnel = false; if (!ret) dp->pipe_active = false; } @@ -491,3 +520,4 @@ static int combo_setup(struct platform_device *pdev, struct t6050_dp *dp) } return 0; } + diff --git a/drivers/phy/apple/atc-t6050-dp.c b/drivers/phy/apple/atc-t6050-dp.c index 57d0587cdaf24..3eadc358589ff 100644 --- a/drivers/phy/apple/atc-t6050-dp.c +++ b/drivers/phy/apple/atc-t6050-dp.c @@ -73,6 +73,7 @@ struct atc_domain { enum combo_mode { COMBO_SAFE, COMBO_USB, COMBO_DP4, COMBO_DP2, COMBO_CIO }; struct t6050_dp { bool combo, usb_prepared, usb2_active, pipe_active, controller_active, cio_prepared; + bool pipe_tunnel; /* the active PIPE is a USB4 router's USB3 tunnel */ enum combo_mode combo_mode; enum typec_orientation orientation; struct phy *combo_phy[3]; diff --git a/drivers/phy/apple/usb-t6050/apple_t6050_usb3_pipe.h b/drivers/phy/apple/usb-t6050/apple_t6050_usb3_pipe.h index f5962152444bf..30e462e416580 100644 --- a/drivers/phy/apple/usb-t6050/apple_t6050_usb3_pipe.h +++ b/drivers/phy/apple/usb-t6050/apple_t6050_usb3_pipe.h @@ -96,6 +96,46 @@ apple_t6050_usb3_pipe_switch(const struct apple_t6050_usb_io *io) return result; } +/* + * Move a running DWC3 from the dummy producer (0x42) to a USB4 router's USB3 tunnel (0x21: PIPE mode 1, + * clock 2), once the tunnel is active. As for native USB3, the PIPE is locked only when DWC3 is out of + * reset, and a lock that is not acknowledged within 6 ms is not fatal. + * Returns 0, -2 if the PIPE is on another producer, or -7 if the mux did not read back the tunnel. + */ +static inline int +apple_t6050_usb3_pipe_tunnel(const struct apple_t6050_usb_io *io) +{ + unsigned int mux; + int locked; + + if (!io || !io->read || !io->write || !io->poll || !io->delay) + return -1; + mux = io->read(io->context, 44, 0x0c) & 0x73; + if (mux == 0x21) + return 0; + if (mux != 0x42) + return -2; + locked = io->read(io->context, 44, 0x1c) & 1; + + apple_t6050_usb_mask(io, 44, 0x04, 0x0e, 0); + apple_t6050_usb_mask(io, 44, 0x00, 1, 1); + if (locked) { + apple_t6050_usb_mask(io, 44, 0x10, 1, 1); + io->poll(io->context, 44, 0x14, 1, 1, 6000); + } + apple_t6050_usb_mask(io, 44, 0x0c, 0x70, 0); + io->delay(io->context, 5); + apple_t6050_usb_mask(io, 44, 0x0c, 3, 1); + apple_t6050_usb_mask(io, 44, 0x0c, 0x70, 0x20); + apple_t6050_usb_mask(io, 44, 0x00, 1, 0); + apple_t6050_usb_mask(io, 44, 0x00, 4, 0); + if (locked) { + apple_t6050_usb_mask(io, 44, 0x10, 1, 0); + io->poll(io->context, 44, 0x14, 1, 0, 6000); + } + return (io->read(io->context, 44, 0x0c) & 0x73) == 0x21 ? 0 : -7; +} + /* The native close() call quiesces the ACIO PHY while PIPE is locked, * before selecting the dummy producer. The caller supplies that lifecycle * operation; merely changing MUX is not a complete disconnect sequence. @@ -110,7 +150,7 @@ apple_t6050_usb3_pipe_stop(const struct apple_t6050_usb_io *io, if (!io || !io->read || !io->write || !io->poll || !io->delay || !quiesce) return -1; mux = io->read(io->context, 44, 0x0c) & 0x73; - if (mux != 0x10 && mux != 0x42) + if (mux != 0x10 && mux != 0x21 && mux != 0x42) return -1; apple_t6050_usb_mask(io, 44, 0x04, 0x0e, 0); apple_t6050_usb_mask(io, 44, 0x00, 1, 1); From 37f3967b602a092e7016cdc0e293a5b1b62f19ac Mon Sep 17 00:00:00 2001 From: Erik Bourget Date: Wed, 4 Nov 2026 18:08:07 -0500 Subject: [PATCH 31/43] dt-bindings: pci: apple,pcie: Add the T6050 PCIe-C tunnel The T6050 PCIe-C root port has the T600x register layout and is always initialized by the kernel. Developed with Claude Code. Signed-off-by: Erik Bourget --- Documentation/devicetree/bindings/pci/apple,pcie.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/devicetree/bindings/pci/apple,pcie.yaml b/Documentation/devicetree/bindings/pci/apple,pcie.yaml index 64f0a2d43e9aa..36cf94442e064 100644 --- a/Documentation/devicetree/bindings/pci/apple,pcie.yaml +++ b/Documentation/devicetree/bindings/pci/apple,pcie.yaml @@ -75,6 +75,7 @@ properties: - const: apple,t8112-pciec - const: apple,t8103-pciec - const: apple,t6000-pciec + - const: apple,t6050-pciec - items: - const: apple,t8132-pciec - const: apple,t8122-pciec @@ -318,6 +319,7 @@ allOf: - apple,t8103-pciec - apple,t6000-pciec - apple,t6040-pciec + - apple,t6050-pciec then: properties: reg: From b1ffa4722f3d2a10872e1b7f1501a58af0de0e0d Mon Sep 17 00:00:00 2001 From: Erik Bourget Date: Wed, 4 Nov 2026 12:54:13 -0500 Subject: [PATCH 32/43] PCI: apple: support the T6050 PCIe-C tunnel T6050 PCIe-C is the T8103 tunnel root port with a larger RID2SID table, a different MSI layout and no reset sequence of its own: - RID2SID has 256 entries, which run over the T8103 tunnel block: the posted request counters, TUNCTRL and TUNSTAT move up by 0x700, to 0x1080..0x108c. The release, assert and acknowledge paths use them. - The MSI doorbell takes its high word at 0x16c. MSIs go through the T602x map, indexed by the vector the endpoint sends: the bridge's slice of the window it shares with the internal host, vector n at AIC 1824 + n. Endpoints get base + i as their MSI data. - Nothing hands the port over, so it is always cold-initialized, as the T600x/T602x ports are with pcie_apple.tunnel_kernel_init: debug, fabric and port tunables, PERST and APPCLK, RUN, then the root port's tunables, before the router populates the DART. There is no port reset table to replay and no PMGR reset; only RID2SID is cleared. - The root port's ACK latency limit (0x700) and flow control update timer (0x73c) depend on the max payload size. Set them after every link-up and whenever the root port's DevCtl is written. The parity error interrupt is left alone. Developed with Claude Code. Signed-off-by: Erik Bourget --- drivers/pci/controller/pcie-apple.c | 118 +++++++++++++++++++++++++++- 1 file changed, 114 insertions(+), 4 deletions(-) diff --git a/drivers/pci/controller/pcie-apple.c b/drivers/pci/controller/pcie-apple.c index a28781a22ed0d..c19f271e40d2b 100644 --- a/drivers/pci/controller/pcie-apple.c +++ b/drivers/pci/controller/pcie-apple.c @@ -200,6 +200,21 @@ MODULE_PARM_DESC(link_up_timeout, "PCIe link training timeout in milliseconds"); #define RC_T6050_MISC_CONTROL_1 0x8bc #define RC_T6050_DBI_RO_WR_EN BIT(0) +/* + * T6050 PCIe-C: the 256-entry RID2SID table runs over the T8103 tunnel block, + * which moves up by 0x700 (posted request counters, TUNCTRL and TUNSTAT). + */ +#define PORT_T6050_TUNCTRL 0x1088 + +/* T6050 PCIe-C root port config space: the MPS-dependent link timers */ +#define PCIEC_RP_DEVCAP 0x074 +#define PCIEC_RP_DEVCTL 0x078 +#define PCIEC_RP_ACK_LATENCY 0x700 +#define PCIEC_RP_FC_TIMER 0x73c +#define PCIEC_RP_FC_TIMER_KEEP 0x6000ffff +#define PCIEC_RP_FC_TIMER_LIMIT GENMASK(28, 16) +#define PCIEC_RP_FC_TIMER_EN BIT(31) + /* * The doorbell address must be in the bottom 4GB because the base register * is only 32 bits wide. Exclude it from the IOVA range and share the same @@ -239,6 +254,8 @@ struct hw_info { * lane PIPE reset status, then release PORT_T8152_PIPE_RST. */ bool pipe_rst; + bool no_port_reset; + bool mps_timers; }; static const struct hw_info t8103_hw = { @@ -384,6 +401,32 @@ static const struct hw_info t6040_pciec_hw = { .s2idle_hold_domain = true, }; +/* + * T6050 PCIe-C keeps the t8103 doorbell, PERST and RID2SID registers, with + * the doorbell's high word at 0x16c and 256 RID2SID entries. MSIs go through + * the t602x map, indexed by the vector the endpoint sends: the bridge's slice + * of the shared window, not a local index. The port has no reset sequence of + * its own, and the root port's ACK latency and flow control timers follow + * the max payload size. + * + * Nothing hands the port over: apple,pciec-kernel-init brings it up with the + * T600x Intr2AXI sequence. + */ +static const struct hw_info t6050_pciec_hw = { + .port_msiaddr = PORT_MSIADDR, + .port_msiaddr_hi = PORT_MSIADDR + 4, + .port_perst = PORT_PERST, + .port_rid2sid = PORT_RID2SID, + .port_msimap = PORT_T602X_MSIMAP, + .port_tunctrl = PORT_T6050_TUNCTRL, + .max_rid2sid = 256, + .tunneled = true, + .tunnel_intr2axi = true, + .no_port_reset = true, + .msimap_by_vector = true, + .mps_timers = true, +}; + static const struct of_device_id apple_pcie_of_match[]; /** @@ -1076,6 +1119,10 @@ static void apple_pcie_tunnel_reset_hardware(struct apple_pcie_port *port) { int i; + /* T6050 has no such sequence: only its RID2SID table is cleared. */ + if (port->pcie->hw->no_port_reset) + goto clear_rid2sid; + for (i = 0; i < ARRAY_SIZE(apple_pcie_tunnel_reset_regs); i++) { u32 value = apple_pcie_tunnel_reset_regs[i].value; @@ -1103,6 +1150,7 @@ static void apple_pcie_tunnel_reset_hardware(struct apple_pcie_port *port) apple_pcie_port_writel(port, value, apple_pcie_tunnel_reset_regs[i].offset); } +clear_rid2sid: for (i = 0; i < port->pcie->hw->max_rid2sid; i++) apple_pcie_rid2sid_write(port, i, 0); if (port->pcie->hw->t8132_reset) @@ -1175,8 +1223,8 @@ static int apple_pcie_tunnel_reinitialize(struct apple_pcie_port *port) /* * Bring up a port selected by apple,pciec-kernel-init that no firmware - * handoff left running: tunables and the reset table, then PERST and APPCLK, - * and only then the root-port config-space + * handoff left running: tunables and the reset table (T6050 has none), + * then PERST and APPCLK, and only then the root-port config-space * tunables. Stop before releasing the tunnel reset or starting link * training. */ @@ -1301,10 +1349,48 @@ static void apple_pcie_tunnel_restore_irq_hw(struct apple_pcie_port *port) */ #define APPLE_PCIE_TUNNEL_READY_MS 1000 +/* + * The T6050 PCIe-C root port's ACK latency limit (0x700, low half) and flow + * control update timer (0x73c, bits 28:16) are sized for the max payload + * size. Each word holds both, for MPS 128, 256 and 512. + */ +static const u32 apple_pcie_mps_timers[] = { + 0x003c003c, 0x005a005a, 0x006b006b, +}; + +static void apple_pcie_update_mps_timers(struct apple_pcie *pcie, u32 mps) +{ + void __iomem *rp = pcie->cfg->win; + u32 cap, word, val; + + cap = apple_pcie_tunnel_readl(rp, PCIEC_RP_DEVCAP) & PCI_EXP_DEVCAP_PAYLOAD; + if (mps > cap || mps >= ARRAY_SIZE(apple_pcie_mps_timers)) { + dev_warn(pcie->dev, "no link timers for a %u-byte MPS\n", 128 << mps); + return; + } + + word = apple_pcie_mps_timers[mps]; + writew_relaxed(lower_16_bits(word), rp + PCIEC_RP_ACK_LATENCY); + /* Complete the write, as apple_pcie_tunnel_writel() does. */ + mb(); + isb(); + val = apple_pcie_tunnel_readl(rp, PCIEC_RP_FC_TIMER); + val &= PCIEC_RP_FC_TIMER_KEEP; + val |= FIELD_PREP(PCIEC_RP_FC_TIMER_LIMIT, upper_16_bits(word)) | + PCIEC_RP_FC_TIMER_EN; + apple_pcie_tunnel_writel(rp, val, PCIEC_RP_FC_TIMER); +} + +/* Process context: the link of a tunnel came up. */ static void apple_pcie_tunnel_link_up(struct apple_pcie *pcie) { WRITE_ONCE(pcie->tunnel_ready_at, jiffies + msecs_to_jiffies(APPLE_PCIE_TUNNEL_READY_MS)); + if (pcie->hw->mps_timers) + apple_pcie_update_mps_timers(pcie, + FIELD_GET(PCI_EXP_DEVCTL_PAYLOAD, + apple_pcie_tunnel_readl(pcie->cfg->win, + PCIEC_RP_DEVCTL))); } /* Call before scanning the bus behind a tunnel; elapsed time counts. */ @@ -2778,6 +2864,23 @@ static int apple_msi_init(struct apple_pcie *pcie) goto invalid_slice; } + /* + * T6050's PCIe hosts share one MSI window, vector n at AIC 1824 + n. + * The internal host's bridges own vectors 0..127 and each PCIe-C + * bridge a 32-vector slice of the rest. + */ + if (pcie->hw == &t6050_pciec_hw) { + ret = of_property_read_u32(to_of_node(fwnode), "apple,msi-vector-base", + &pcie->msi_vector_base); + if (ret || pcie->msi_vector_base < 128 || pcie->msi_vector_base % 32 || + pcie->nvecs != 32 || + pcie->msi_vector_base + pcie->nvecs > FIELD_MAX(PORT_MSIMAP_TARGET) + 1 || + args.args_count != 4 || args.args[0] != 0 || args.args[1] != 0 || + args.args[2] != 1824 + pcie->msi_vector_base || + args.args[3] != IRQ_TYPE_EDGE_RISING || DOORBELL_ADDR != 0xfffff000) + goto invalid_slice; + } + /* * The controller-wide ADT count (512) is not a per-host allocation. * Internal PCIe owns vectors 0..31. Each PCIe-C bridge owns a distinct @@ -3294,11 +3397,17 @@ static int apple_pcie_config_write(struct pci_bus *bus, unsigned int devfn, int ret; ret = pci_generic_config_write(bus, devfn, where, size, val); - if (ret || !pcie || !pcie->kernel_init) + if (ret || !pcie) return ret; - /* Root-port prefetchable window. The port block has to be told too. */ if (bus->number != cfg->busr.start || devfn) return ret; + /* The root port's link timers follow its max payload size. */ + if (pcie->hw->mps_timers && where == PCIEC_RP_DEVCTL) + apple_pcie_update_mps_timers(pcie, + FIELD_GET(PCI_EXP_DEVCTL_PAYLOAD, val)); + if (!pcie->kernel_init) + return ret; + /* Root-port prefetchable window. The port block has to be told too. */ if (where < 0x24 || where >= 0x30) return ret; @@ -4830,6 +4939,7 @@ static const struct dev_pm_ops apple_pcie_pm_ops = { static const struct of_device_id apple_pcie_of_match[] = { { .compatible = "apple,t6040-pciec", .data = &t6040_pciec_hw }, + { .compatible = "apple,t6050-pciec", .data = &t6050_pciec_hw }, { .compatible = "apple,t8132-pciec", .data = &t8132_pciec_hw }, { .compatible = "apple,t8122-pciec", .data = &t8122_pciec_hw }, { .compatible = "apple,t8112-pciec", .data = &t8112_pciec_hw }, From 79432bf9312e383e5f9e7688c26a5b6f93bd0b9d Mon Sep 17 00:00:00 2001 From: Erik Bourget Date: Wed, 4 Nov 2026 12:54:13 -0500 Subject: [PATCH 33/43] thunderbolt: apple: recognize the T6050 PCIe-C host A router finds its PCIe-C host among the children of its apple,pcie-tunnel node by compatible: to tell whether the port gets initialized, to map its Intr2AXI bridge, to find its reset and to find the bound host once a tunnel is up. Accept apple,t6050-pciec there too. The T6050 port has no reset of its own, and the kernel cold-initializes it, which apple_pcie_tunnel_needs_cold_init() reports. Other SoCs see no change. Developed with Claude Code. Signed-off-by: Erik Bourget --- drivers/thunderbolt/apple.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/thunderbolt/apple.c b/drivers/thunderbolt/apple.c index 0b0c25d5adb69..9cfd12a33dc06 100644 --- a/drivers/thunderbolt/apple.c +++ b/drivers/thunderbolt/apple.c @@ -935,7 +935,8 @@ static bool apple_cio_pciec_eligible(const struct device_node *np) { return of_device_is_compatible(np, "apple,t6040-pciec") || of_device_is_compatible(np, "apple,t8103-pciec") || - of_device_is_compatible(np, "apple,t6000-pciec"); + of_device_is_compatible(np, "apple,t6000-pciec") || + of_device_is_compatible(np, "apple,t6050-pciec"); } static bool From ec5485290aa55c484af18f623f2e30d98bb2c442 Mon Sep 17 00:00:00 2001 From: Erik Bourget Date: Wed, 4 Nov 2026 12:54:14 -0500 Subject: [PATCH 34/43] arm64: dts: apple: j716c: Add the right port's PCIe tunnel Describe the PCIe-C root port behind the right port's router and its DART, in a node the router points at with apple,pcie-tunnel and populates once it has enabled a PCIe tunnel, and the ATC2_PCIE power state they sit in, in t6050-max.dtsi. The router powers ATC2_PCIE with its other domains, so the port can be cold-initialized before its children are populated. Nothing initialises the DART, so like the router's it carries full error reporting and its two bypass streams, 253 and 255, on which the port issues its own writes, MSIs included; the board gives its tunables and the port's. Its container is not named usb4-pcie-tunnel-N, which would give it the fixed 64-stream topology of the older tunnel DARTs instead of the 256 streams it reports. The bridge's MSIs are vectors 192-223 of the window shared with the internal host. Developed with Claude Code. Signed-off-by: Erik Bourget --- arch/arm64/boot/dts/apple/t6050-j716c.dts | 58 ++++++++++ arch/arm64/boot/dts/apple/t6050-max.dtsi | 103 +++++++++++++++++- .../boot/dts/apple/t6050-peripherals.dtsi | 2 + 3 files changed, 162 insertions(+), 1 deletion(-) diff --git a/arch/arm64/boot/dts/apple/t6050-j716c.dts b/arch/arm64/boot/dts/apple/t6050-j716c.dts index abdf7ca85b592..55af2f818a954 100644 --- a/arch/arm64/boot/dts/apple/t6050-j716c.dts +++ b/arch/arm64/boot/dts/apple/t6050-j716c.dts @@ -563,3 +563,61 @@ 05 8a 09 01 00 0e 01 41 70 70 6c 65 2c 20 49 6e 63 2e 00 06 02 69 4f 53 00 00 00 00]; }; + +/* ==== The right USB-C port's PCIe tunnel (ATC2): this board's tunables ==== */ +&apciec2_dart { + apple,tunable = <0x20c 0x80000121 0x80000121>, + <0x220 0xf0f 0xf0f>, + <0x224 0xffff 0x909>, + <0x300 0x1f31 0x1>, + <0x308 0x3ffffffc 0x10000000>, + <0x310 0x3ffffffc 0x3ffffffc>; +}; + +&apciec2 { + apple,tunable-debug = <0x1000 0x1 0x0>; + apple,tunable-rc = <0x78 0x7000 0x0>, + <0x718 0x7c000 0x0>, + <0x814 0xffffffff 0x2020f060>, + <0x8bc 0x1 0x0>; + apple,tunable-fabric = <0x4 0x3f003f 0x240024>, + <0x8 0x1 0x1>, + <0xc 0x1 0x1>, + <0x10 0x801f 0x4>, + <0x18 0x1ff 0x1e0>, + <0x1c 0x1 0x1>, + <0x20 0x801f 0x4>, + <0x28 0x1f 0x10>, + <0x2c 0x801f 0x4>, + <0x34 0x3f 0x20>, + <0x38 0x801f 0x4>, + <0x40 0x3f 0x20>, + <0x44 0x801f 0x4>, + <0x4c 0x10001 0x10001>, + <0x50 0x1 0x1>, + <0x54 0x1 0x1>, + <0x58 0x801f 0x4>, + <0x60 0x7f007f 0x450045>, + <0x64 0x801f 0x4>, + <0x6c 0x1f001f 0x100010>, + <0x70 0xf1 0x1>, + <0x74 0x801f 0x4>, + <0x7c 0x801f 0x4>, + <0x84 0x1 0x1>, + <0x88 0x1 0x1>, + <0x8c 0x801f 0x4>, + <0x94 0x1 0x1>, + <0x98 0x801f 0x4>, + <0xa0 0x1 0x1>, + <0xa4 0x801f 0x4>, + <0xac 0x1 0x1>, + <0xb0 0x1 0x1>, + <0xb4 0x801f 0x4>, + <0xbc 0x801f 0x4>, + <0xc4 0x801f 0x4>; +}; + +&apciec2_port { + apple,tunable = <0x140 0x1 0x1>, + <0x2a0 0x7e 0xa>; +}; diff --git a/arch/arm64/boot/dts/apple/t6050-max.dtsi b/arch/arm64/boot/dts/apple/t6050-max.dtsi index 87bd77e4b4b99..c04b66519953c 100644 --- a/arch/arm64/boot/dts/apple/t6050-max.dtsi +++ b/arch/arm64/boot/dts/apple/t6050-max.dtsi @@ -214,9 +214,11 @@ reg-names = "rc", "sram", "pcie-adapter", "cpu", "router-ap-fw"; mboxes = <&acio2_mbox>; resets = <&cio_reconfig0 2>; + /* ATC2_PCIE last: the PCIe tunnel root port, set up before it is populated */ power-domains = <&ps_atc2_cio>, <&ps_atc2_cio_pcie>, - <&ps_atc2_cio_usb>, <&ps_atc2_cio_auss>; + <&ps_atc2_cio_usb>, <&ps_atc2_cio_auss>, <&ps_atc2_pcie>; thunderbolt-switch; + apple,pcie-tunnel = <&acio2_pcie_tunnel>; pinctrl-0 = <&acio2_lstx_pins>; pinctrl-names = "lstx"; lstx-gpios = <&pinctrl_aop 81 GPIO_ACTIVE_HIGH>; @@ -306,4 +308,103 @@ }; }; }; + +/* + * ATC2's PCIe tunnel: /arm-io/apciec2 and dart-apciec2, the PCIe-C root port behind the router's + * PCIe down adapter, and its DART. The router populates both once it has enabled a PCIe tunnel, + * after the kernel has cold-initialized the port: no boot loader sets it up, and it has no reset + * line of its own. Both sit in ATC2_PCIE, which the router powers with its own domains and the + * host keeps on once it is bound. + * + * The container is not a usb4-pcie-tunnel-N node, whose DART would get the fixed 64-stream + * topology of the earlier tunnel DARTs: this one is read like any T8110 DART. Nothing + * initialises it before Linux, so like the router's it carries every error reported and its + * bypass streams, 253 and 255, which carry the port's own writes (MSIs); the board gives its + * tunables, and the port's. + * + * MSIs use the bridge's 32-vector slice (192-223) of the window shared with the internal host. + */ +&pmgr0 { + /* With the ATC2 USB always-on clock gate, which has no state register of its own */ + ps_atc2_pcie: power-controller@2f0 { + compatible = "apple,t6050-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x2f0 4>; + label = "atc2_pcie"; + #power-domain-cells = <0>; + #reset-cells = <0>; + power-domains = <&ps_atc2_common>, <&ps_atc2_usb_aon>; + }; +}; + +/ { + acio2_pcie_tunnel: pcie-tunnel-2 { + #address-cells = <2>; + #size-cells = <2>; + ranges; + nonposted-mmio; + + apciec2_dart: iommu@384000000 { + compatible = "apple,t6050-dart", "apple,t8110-dart"; + reg = <0x3 0x84000000 0x0 0x20000>; + interrupts = ; + power-domains = <&ps_atc2_pcie>; + #iommu-cells = <1>; + apple,bypass-sids = <253 255>; + apple,full-error-reporting; + apple,ctc-flush; + }; + + apciec2: pcie@1c70000000 { + compatible = "apple,t6050-pciec"; + device_type = "pci"; + reg = <0x1c 0x70000000 0x0 0x10000000>, + <0x3 0x84034000 0x0 0x4000>, + <0x3 0x84020000 0x0 0x8000>, + <0x3 0x84038000 0x0 0x4000>, + <0x3 0x84060000 0x0 0x4000>, + <0x3 0x8402c000 0x0 0x4000>, + <0x3 0x84028000 0x0 0x4000>; + reg-names = "config", "rc", "port0", "fabric", "debug", "ltssm", "intr2axi"; + /* The port's: link events and INTx */ + interrupts = ; + interrupt-names = "port0"; + power-domains = <&ps_atc2_pcie>; + /* No boot loader hands the port over: the kernel cold-initializes it. */ + apple,pciec-kernel-init; + + msi-controller; + msi-parent = <&apciec2>; + msi-ranges = <&aic AIC_IRQ 0 2016 IRQ_TYPE_EDGE_RISING 32>; + apple,msi-vector-base = <192>; + + iommu-map = <0x0 &apciec2_dart 0x0 0x40>; + iommu-map-mask = <0x3f>; + linux,pci-domain = <3>; + bus-range = <0x0 0x7f>; + #address-cells = <3>; + #size-cells = <2>; + ranges = <0x43000000 0x10 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>; + }; + }; + }; +}; #endif diff --git a/arch/arm64/boot/dts/apple/t6050-peripherals.dtsi b/arch/arm64/boot/dts/apple/t6050-peripherals.dtsi index ae450252f2acb..05526b53d8bfb 100644 --- a/arch/arm64/boot/dts/apple/t6050-peripherals.dtsi +++ b/arch/arm64/boot/dts/apple/t6050-peripherals.dtsi @@ -1469,6 +1469,7 @@ status = "disabled"; }; +#ifndef T6050_J716C apciec2_dart: iommu@384000000 { compatible = "apple,t6050-dart", "apple,t8110-dart"; reg = <0x3 0x84000000 0x0 0x20000>; @@ -1476,6 +1477,7 @@ #iommu-cells = <1>; status = "disabled"; }; +#endif apciec2_piodma: dma-controller@384068000 { compatible = "apple,t6050-pciec-piodma", "apple,pciec-piodma"; From 5a99d43ab4c88986cb30bec2946333c6e023f5c6 Mon Sep 17 00:00:00 2001 From: Erik Bourget Date: Sun, 8 Nov 2026 19:49:04 -0500 Subject: [PATCH 35/43] mux: apple-display-crossbar: Accept busy T6050 connection status T6050 can report a busy crossbar connection after selecting a valid output. Log that status while retaining the selected route, so an active USB4 DisplayPort tunnel can complete its display setup. Developed with Claude Code. Signed-off-by: Erik Bourget --- drivers/mux/apple-display-crossbar.c | 86 ++++++++++++++++------------ 1 file changed, 49 insertions(+), 37 deletions(-) diff --git a/drivers/mux/apple-display-crossbar.c b/drivers/mux/apple-display-crossbar.c index 223d7b813dc15..f45d296eebef4 100644 --- a/drivers/mux/apple-display-crossbar.c +++ b/drivers/mux/apple-display-crossbar.c @@ -108,6 +108,8 @@ struct apple_dpxbar_hw { bool no_tunable; /* power domain held only while an output is selected */ bool runtime_pm; + /* T8142 layout: a connection status still busy after its poll is only logged */ + bool status_log_only; }; struct apple_dpxbar { @@ -901,9 +903,33 @@ static const u8 t8142_dest_shift[T8142_MUX_MAX] = { 8, 0, 4, 12, 16 }; static const u8 t8142_select_shift[T8142_MUX_MAX] = { 8, 0, 4, 24, 0 }; static const u8 t8142_select_shift2[T8142_MUX_MAX] = { 20, 12, 16, 28, 4 }; +/* + * Wait up to 1 ms for @mask to clear in status word @reg. Where the hardware + * only reports these bits (T6050), one still set is logged and the change + * goes on; elsewhere the crossbar is taken as faulted. + */ +static int t8142_settle(struct apple_dpxbar *xbar, unsigned int reg, u32 mask, + unsigned int index, const char *what) +{ + u32 value; + int ret; + + ret = readl_poll_timeout_atomic(xbar->regs + reg, value, !(value & mask), 1, 1000); + if (!ret) + return 0; + if (xbar->hw->status_log_only) { + dev_warn_ratelimited(xbar->dev, + "output %u %s: status +%#x = %#x, %#x still set; going on\n", + index, what, reg, value, mask); + return 0; + } + xbar->faulted = true; + return ret; +} + static int t8142_link_down(struct apple_dpxbar *xbar, unsigned int index) { - u32 src, dst, clock, value; + u32 src, dst, clock; int ret; if (!xbar->enabled[index]) @@ -917,24 +943,13 @@ static int t8142_link_down(struct apple_dpxbar *xbar, unsigned int index) dpxbar_clear32(xbar, 0x0c + 4 * clock, src); dpxbar_clear32(xbar, 0x24 + 4 * clock, dst); udelay(1); - ret = readl_poll_timeout_atomic(xbar->regs + 0x800, value, - !(value & src), 1, 1000); - if (ret) { - xbar->faulted = true; - return ret; - } - ret = readl_poll_timeout_atomic(xbar->regs + 0x808 + 4 * clock, value, - !(value & src), 1, 1000); - if (ret) { - xbar->faulted = true; - return ret; - } - ret = readl_poll_timeout_atomic(xbar->regs + 0x81c + 4 * clock, value, - !(value & dst), 1, 1000); - if (ret) { - xbar->faulted = true; + ret = t8142_settle(xbar, 0x800, src, index, "link down"); + if (!ret) + ret = t8142_settle(xbar, 0x808 + 4 * clock, src, index, "link down"); + if (!ret) + ret = t8142_settle(xbar, 0x81c + 4 * clock, dst, index, "link down"); + if (ret) return ret; - } dpxbar_clear32(xbar, 0x08, src); dpxbar_clear32(xbar, 0x20, 7 << (3 * xbar->selected_dispext[index])); dpxbar_clear32(xbar, 0x38, 7 << t8142_dest_shift[index]); @@ -948,7 +963,7 @@ static int t8142_link_down(struct apple_dpxbar *xbar, unsigned int index) static int t8142_link_up(struct apple_dpxbar *xbar, unsigned int index) { int state = xbar->selected_dispext[index]; - u32 clock, select, src, dst, value; + u32 clock, select, src, dst; int ret; if (state < 0) @@ -973,24 +988,13 @@ static int t8142_link_up(struct apple_dpxbar *xbar, unsigned int index) dpxbar_clear32(xbar, 0x1c, src); dpxbar_clear32(xbar, 0x34, dst); udelay(1); - ret = readl_poll_timeout_atomic(xbar->regs + 0x804, value, - !(value & src), 1, 1000); - if (ret) { - xbar->faulted = true; - return ret; - } - ret = readl_poll_timeout_atomic(xbar->regs + 0x818, value, - !(value & src), 1, 1000); - if (ret) { - xbar->faulted = true; - return ret; - } - ret = readl_poll_timeout_atomic(xbar->regs + 0x82c, value, - !(value & dst), 1, 1000); - if (ret) { - xbar->faulted = true; + ret = t8142_settle(xbar, 0x804, src, index, "link up"); + if (!ret) + ret = t8142_settle(xbar, 0x818, src, index, "link up"); + if (!ret) + ret = t8142_settle(xbar, 0x82c, dst, index, "link up"); + if (ret) return ret; - } dpxbar_set32(xbar, 0x08, src); dpxbar_mask32(xbar, 0x20, 7 << (3 * state), select << (3 * state)); dpxbar_mask32(xbar, 0x38, 7 << t8142_dest_shift[index], @@ -1466,8 +1470,16 @@ static const struct apple_dpxbar_hw apple_dpxbar_hw_t8142 = { .ops = &apple_dpxbar_t8142_ops, }; +/* T6050: the T8142 layout; a status that stays busy is logged after settling. */ +static const struct apple_dpxbar_hw apple_dpxbar_hw_t6050 = { + .n_mux = T8142_MUX_MAX, + .n_ufp = 8, + .ops = &apple_dpxbar_t8142_ops, + .status_log_only = true, +}; + static const struct of_device_id apple_dpxbar_ids[] = { - { .compatible = "apple,t6050-display-crossbar", .data = &apple_dpxbar_hw_t8142 }, + { .compatible = "apple,t6050-display-crossbar", .data = &apple_dpxbar_hw_t6050 }, { .compatible = "apple,t8142-display-crossbar", .data = &apple_dpxbar_hw_t8142 }, { .compatible = "apple,t8103-display-crossbar", From 0c906ebf668b7e481c62342a116524b6eedb02a8 Mon Sep 17 00:00:00 2001 From: Erik Bourget Date: Sun, 8 Nov 2026 20:05:40 -0500 Subject: [PATCH 36/43] usb: typec: tipd: Restore J716c dock USB across boot Reinitialize an inherited J716c boot-port USB2 connection before its first connect notification, after suppliers and the Type-C port exist. Limit the operation to the board property and normal host operation; exclude the debug bridge modes. Allow SN201202x up to five seconds to wake so a connected dock can restore its USB hub after boot. Developed with Claude Code. Signed-off-by: Erik Bourget --- .../bindings/usb/apple,sn201202x.yaml | 7 ++ drivers/usb/typec/tipd/core.c | 68 ++++++++++++++++++- 2 files changed, 74 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/usb/apple,sn201202x.yaml b/Documentation/devicetree/bindings/usb/apple,sn201202x.yaml index c2d2eb1068ea8..282d26ae0c6d2 100644 --- a/Documentation/devicetree/bindings/usb/apple,sn201202x.yaml +++ b/Documentation/devicetree/bindings/usb/apple,sn201202x.yaml @@ -44,6 +44,13 @@ properties: connector: $ref: /schemas/connector/usb-connector.yaml# + apple,reset-inherited-usb2-connection: + type: boolean + description: + The boot USB port retains a USB2 repeater connection that cannot be + recovered by resetting the host PHY. Renegotiate a normal application + connection before starting the host controllers. Debug routes are preserved. + required: - compatible - reg diff --git a/drivers/usb/typec/tipd/core.c b/drivers/usb/typec/tipd/core.c index 74c820dd0be24..5d157996b214a 100644 --- a/drivers/usb/typec/tipd/core.c +++ b/drivers/usb/typec/tipd/core.c @@ -2017,7 +2017,14 @@ static int cd321x_switch_power_state(struct tps6598x *tps, u8 target_state) if (state == target_state) return 0; - ret = tps6598x_exec_cmd(tps, "SSPS", sizeof(u8), &target_state, 0, NULL); + /* SN201202x can still be negotiating an attached dock while waking. */ + if (target_state == TPS_SYSTEM_POWER_STATE_S0 && + device_is_compatible(tps->dev, "apple,sn201202x")) + ret = tps6598x_exec_cmd_tmo(tps, "SSPS", sizeof(target_state), + &target_state, 0, NULL, 5000, 0); + else + ret = tps6598x_exec_cmd(tps, "SSPS", sizeof(target_state), + &target_state, 0, NULL); if (ret) return ret; @@ -2905,6 +2912,61 @@ static int cd321x_remove(struct tps6598x *tps) return 0; } +static int cd321x_reset_inherited_connection(struct tps6598x *tps, u32 *status) +{ + /* J716c's port-controller key is the reversed target identifier J716. */ + static const u8 key[] = { '6', '1', '7', 'J' }; + static const u8 locked[4]; + u8 delay = 5, result; + unsigned long timeout; + int ret, lock_ret, mode; + + if (!device_property_read_bool(tps->dev, "apple,reset-inherited-usb2-connection") || + !of_machine_is_compatible("apple,j716c") || + !device_is_compatible(tps->dev, "apple,sn201202x") || + tps->dbma_at_probe || !(*status & TPS_STATUS_PLUG_PRESENT) || + !TPS_STATUS_TO_TYPEC_DATAROLE(*status)) + return 0; + mode = tps6598x_check_mode(tps); + if (mode != TPS_MODE_APP && mode != TPS_MODE_APP1) + return 0; + + /* All suppliers are available, but no USB or router session has started. */ + ret = tps6598x_exec_cmd(tps, "LOCK", sizeof(key), key, sizeof(result), &result); + if (ret) + return ret; + if (result) { + ret = -EIO; + goto relock; + } + + /* Nonzero DISC delay restores normal CC detection automatically. */ + ret = tps6598x_exec_cmd(tps, "DISC", sizeof(delay), &delay, 0, NULL); + if (!ret) { + msleep(5100); + timeout = jiffies + msecs_to_jiffies(2000); + do { + mode = tps6598x_check_mode(tps); + if (mode == TPS_MODE_APP || mode == TPS_MODE_APP1) + break; + msleep(20); + } while (time_before(jiffies, timeout)); + if (mode != TPS_MODE_APP && mode != TPS_MODE_APP1) + ret = mode < 0 ? mode : -ETIMEDOUT; + } +relock: + /* CC/PD negotiation may still be finishing when application mode returns. */ + lock_ret = tps6598x_exec_cmd_tmo(tps, "LOCK", sizeof(locked), locked, 0, NULL, + 5000, 0); + if (ret || lock_ret) + return ret ?: lock_ret; + if (!tps6598x_read_status(tps, status)) + return -EIO; + tps->status = *status; + dev_info(tps->dev, "reinitialized inherited USB2 connection\n"); + return 0; +} + int tipd_init(struct tps6598x *tps) { struct fwnode_handle *fwnode; @@ -3017,6 +3079,10 @@ int tipd_init(struct tps6598x *tps) } } + ret = cd321x_reset_inherited_connection(tps, &status); + if (ret) + goto err_disconnect; + if (status & TPS_STATUS_PLUG_PRESENT) { ret = -EINVAL; if (!tps->data->read_power_status(tps)) From 371a42c0dfda5def5478b79634971ec14688a551 Mon Sep 17 00:00:00 2001 From: Ryan Murray Date: Mon, 9 Nov 2026 23:01:09 +1100 Subject: [PATCH 37/43] selftests: apple-dcp: Stub the inherited-connection reset in the tipd probe test tipd_init() now calls cd321x_reset_inherited_connection() before it reads the plug state. The probe-transaction model extracts tipd_init() with device_property_read_bool() returning false, for which the real function returns 0 without touching the controller, so stub it that way. Assisted-by: LLM Signed-off-by: Ryan Murray --- tools/testing/selftests/drivers/gpu/apple-dcp/transaction.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/transaction.py b/tools/testing/selftests/drivers/gpu/apple-dcp/transaction.py index b41f7e2f80689..631237b2ac7b3 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/transaction.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/transaction.py @@ -397,6 +397,7 @@ def probe(root): static int register_port(struct tps6598x *t,struct fwnode_handle *f) { (void)t;(void)f;return register_error; } static bool read_data_status(struct tps6598x *t) { (void)t;return true; } static int connect(struct tps6598x *t,u32 status) { (void)t;(void)status;return connect_error; } +static int cd321x_reset_inherited_connection(struct tps6598x *t,u32 *s) { (void)t;(void)s;return 0; } """ helpers += '\n#define of_machine_is_compatible(name) (!strcmp((name), \"apple,t8140\"))\n#define BIT_ULL(n) (1ULL << (n))\n#define SN201202X_INT_DP_SID_UPDATE BIT_ULL(37)\n' helpers += block(core, 'static bool cd321x_uses_dp_sid(') + '\n' + block(core, 'static u64 cd321x_irq_mask(') + '\n' From b4d99c2f65664836da862c9bfae497d22e38e0b9 Mon Sep 17 00:00:00 2001 From: Erik Bourget Date: Sun, 8 Nov 2026 22:04:50 -0500 Subject: [PATCH 38/43] usb: typec: tipd: Wait for the inherited disconnect task An attached J716c dock can leave DISC running beyond the default one-second command timeout. Probe then attempts LOCK while DISC still owns the command register, receives EBUSY, and abandons the port even though a later reprobe works. Allow five seconds for DISC to acknowledge, matching the surrounding startup operations. Keep the automatic CC restore delay and the post-command application-mode check unchanged. Developed with Claude Code. Signed-off-by: Erik Bourget --- drivers/usb/typec/tipd/core.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/drivers/usb/typec/tipd/core.c b/drivers/usb/typec/tipd/core.c index 5d157996b214a..a6eb842552f1e 100644 --- a/drivers/usb/typec/tipd/core.c +++ b/drivers/usb/typec/tipd/core.c @@ -2940,8 +2940,13 @@ static int cd321x_reset_inherited_connection(struct tps6598x *tps, u32 *status) goto relock; } - /* Nonzero DISC delay restores normal CC detection automatically. */ - ret = tps6598x_exec_cmd(tps, "DISC", sizeof(delay), &delay, 0, NULL); + /* + * Nonzero DISC delay restores normal CC detection automatically. The + * task can take more than a second to acknowledge at boot; wait for it + * before issuing LOCK, which a still-busy controller would reject. + */ + ret = tps6598x_exec_cmd_tmo(tps, "DISC", sizeof(delay), &delay, 0, NULL, + 5000, 0); if (!ret) { msleep(5100); timeout = jiffies + msecs_to_jiffies(2000); From a442efa4477ad2f39086f53c854839cc3e0a09fb Mon Sep 17 00:00:00 2001 From: Erik Bourget Date: Sun, 8 Nov 2026 22:32:16 -0500 Subject: [PATCH 39/43] usb: typec: tipd: preserve dry result during LDCM startup An empty SN201202x port can take 1.6 seconds to acknowledge LDCM at startup. Allow five seconds so a command still running is not treated as a failed mitigation change. Removing mitigation can start a new measurement after a valid dry snapshot. Preserve that result while the new measurement is in progress, as the existing entry path does for a published result. Re-enabling mitigation immediately can hold the detector in the measuring state and permanently suppress a later dock attachment. Keep failures and liquid results mitigated. Exercise the dry-to-measuring transition from every previous state and the failed re-read path in KUnit. Developed with Claude Code. Signed-off-by: Erik Bourget --- drivers/usb/typec/tipd/core.c | 5 +- drivers/usb/typec/tipd/sn201202x-pm-test.c | 53 ++++++++++++++++++++++ drivers/usb/typec/tipd/spmi.c | 18 +++++++- 3 files changed, 72 insertions(+), 4 deletions(-) diff --git a/drivers/usb/typec/tipd/core.c b/drivers/usb/typec/tipd/core.c index a6eb842552f1e..25325dde72a8c 100644 --- a/drivers/usb/typec/tipd/core.c +++ b/drivers/usb/typec/tipd/core.c @@ -3078,8 +3078,9 @@ int tipd_init(struct tps6598x *tps) if (tps->contaminant_ops) { ret = tps->contaminant_ops->update(tps); if (ret) { - dev_warn(tps->dev, - "connector contaminant state unavailable: %d\n", ret); + if (ret != -EINPROGRESS) + dev_warn(tps->dev, + "connector contaminant state unavailable: %d\n", ret); ret = 0; } } diff --git a/drivers/usb/typec/tipd/sn201202x-pm-test.c b/drivers/usb/typec/tipd/sn201202x-pm-test.c index 68e36f6d951c8..ae55166c33179 100644 --- a/drivers/usb/typec/tipd/sn201202x-pm-test.c +++ b/drivers/usb/typec/tipd/sn201202x-pm-test.c @@ -826,6 +826,57 @@ static void sn201202x_ldcm_dry_order_test(struct kunit *test) sn201202x_ldcm_test_actions(test, &t, actions, ARRAY_SIZE(actions)); } +static void sn201202x_ldcm_dry_then_busy_test(struct kunit *test) +{ + static const enum typec_contaminant last[] = { + TYPEC_CONTAMINANT_UNKNOWN, + TYPEC_CONTAMINANT_NONE, + TYPEC_CONTAMINANT_LIQUID, + }; + const enum sn201202x_ldcm_test_action actions[] = { + LDCM_TEST_READ, + LDCM_TEST_MITIGATE_OFF, + LDCM_TEST_READ, + LDCM_TEST_PUBLISH_NONE, + }; + unsigned int i; + + /* Removing mitigation can start a measurement after a fresh dry result. */ + for (i = 0; i < ARRAY_SIZE(last); i++) { + struct sn201202x_ldcm_test t = { + .reads = { TYPEC_CONTAMINANT_NONE, TYPEC_CONTAMINANT_UNKNOWN }, + }; + + KUNIT_EXPECT_EQ(test, sn201202x_ldcm_update_state(&t, + &sn201202x_ldcm_test_ops, + last[i]), + -EINPROGRESS); + sn201202x_ldcm_test_actions(test, &t, actions, + ARRAY_SIZE(actions)); + } +} + +static void sn201202x_ldcm_dry_then_read_failure_test(struct kunit *test) +{ + const enum sn201202x_ldcm_test_action actions[] = { + LDCM_TEST_READ, + LDCM_TEST_MITIGATE_OFF, + LDCM_TEST_READ, + LDCM_TEST_MITIGATE_ON, + LDCM_TEST_PUBLISH_UNKNOWN, + }; + struct sn201202x_ldcm_test t = { + .reads = { TYPEC_CONTAMINANT_NONE, TYPEC_CONTAMINANT_UNKNOWN }, + .read_rets = { 0, -EIO }, + }; + + /* A failed second read must still mitigate, even after a dry result. */ + KUNIT_EXPECT_EQ(test, sn201202x_ldcm_update_state(&t, + &sn201202x_ldcm_test_ops, + TYPEC_CONTAMINANT_UNKNOWN), -EIO); + sn201202x_ldcm_test_actions(test, &t, actions, ARRAY_SIZE(actions)); +} + static void sn201202x_ldcm_dry_race_test(struct kunit *test) { const enum sn201202x_ldcm_test_action actions[] = { @@ -945,6 +996,8 @@ static struct kunit_case sn201202x_pm_cases[] = { KUNIT_CASE(sn201202x_ldcm_read_failure_order_test), KUNIT_CASE(sn201202x_ldcm_mitigation_failure_order_test), KUNIT_CASE(sn201202x_ldcm_dry_order_test), + KUNIT_CASE(sn201202x_ldcm_dry_then_busy_test), + KUNIT_CASE(sn201202x_ldcm_dry_then_read_failure_test), KUNIT_CASE(sn201202x_ldcm_dry_race_test), KUNIT_CASE(sn201202x_ldcm_dry_race_mitigation_failure_test), KUNIT_CASE(sn201202x_ldcm_busy_keeps_result_test), diff --git a/drivers/usb/typec/tipd/spmi.c b/drivers/usb/typec/tipd/spmi.c index 32c3778871013..0ea062474dc0f 100644 --- a/drivers/usb/typec/tipd/spmi.c +++ b/drivers/usb/typec/tipd/spmi.c @@ -300,9 +300,13 @@ static int sn201202x_ldcm_set_mitigation(void *ctx, bool enable) if (sn->ldcm_mitigation_known && sn->ldcm_mitigated == enable) return 0; - /* Successful task completion is the controller's mitigation ACK. */ + /* + * Successful task completion is the controller's mitigation ACK. + * Startup commands can take more than a second on SN201202x. Wait for + * completion before a retry can request the opposite mitigation state. + */ ret = tps6598x_exec_cmd_tmo(tps, "LDCM", sizeof(command), &command, - 0, NULL, 1000, 0); + 0, NULL, 5000, 0); sn->ldcm_mitigation_known = !ret; if (!ret) sn->ldcm_mitigated = enable; @@ -354,6 +358,16 @@ sn201202x_ldcm_update_state(void *ctx, const struct sn201202x_ldcm_ops *ops, /* Do not report dry if liquid appeared while mitigation was removed. */ read_ret = ops->read(ctx, &contaminant); + if (!read_ret && contaminant == TYPEC_CONTAMINANT_UNKNOWN) { + /* + * Removing mitigation can start another measurement. The dry + * snapshot above is still the latest result; preserve it just + * as we preserve a previously published result at entry. Forcing + * mitigation back on here can prevent the measurement finishing. + */ + ops->publish(ctx, TYPEC_CONTAMINANT_NONE); + return -EINPROGRESS; + } if (read_ret || contaminant != TYPEC_CONTAMINANT_NONE) { ret = ops->set_mitigation(ctx, true); if (ret) { From 0471ea2c525c8e6d041479c60593e60f0ad90158 Mon Sep 17 00:00:00 2001 From: Erik Bourget Date: Sun, 8 Nov 2026 22:46:40 -0500 Subject: [PATCH 40/43] usb: typec: tipd: disable LDCM work during removal Reset recovery can invalidate the detector after tipd_remove cancels its work but before cd321x_remove drains resume work. Disable delayed work permanently at removal so this late producer cannot restart it after driver resources are released. Exercise the production stop and invalidate callbacks in KUnit. The regression test runs the callback after cancellation on the old implementation and passes when work is disabled. Developed with Claude Code. Signed-off-by: Erik Bourget --- drivers/usb/typec/tipd/sn201202x-pm-test.c | 50 ++++++++++++++++++++++ drivers/usb/typec/tipd/spmi.c | 6 ++- drivers/usb/typec/tipd/tps6598x.h | 1 + 3 files changed, 55 insertions(+), 2 deletions(-) diff --git a/drivers/usb/typec/tipd/sn201202x-pm-test.c b/drivers/usb/typec/tipd/sn201202x-pm-test.c index ae55166c33179..7fab562ea81d8 100644 --- a/drivers/usb/typec/tipd/sn201202x-pm-test.c +++ b/drivers/usb/typec/tipd/sn201202x-pm-test.c @@ -982,6 +982,55 @@ static void sn201202x_ldcm_read_failure_with_result_test(struct kunit *test) sn201202x_ldcm_test_actions(test, &t, actions, ARRAY_SIZE(actions)); } +struct sn201202x_ldcm_stop_test { + struct sn201202x sn; + atomic_t runs; +}; + +static void sn201202x_ldcm_test_work(struct work_struct *work) +{ + struct sn201202x *sn = container_of(to_delayed_work(work), + struct sn201202x, ldcm_work); + struct sn201202x_ldcm_stop_test *t = container_of(sn, + struct sn201202x_ldcm_stop_test, sn); + + atomic_inc(&t->runs); +} + +static void sn201202x_ldcm_test_cancel(void *data) +{ + cancel_delayed_work_sync(data); +} + +static void sn201202x_ldcm_stop_requeue_test(struct kunit *test) +{ + struct sn201202x_ldcm_stop_test *t; + struct tps6598x *tps; + int ret; + + t = kunit_kzalloc(test, sizeof(*t), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, t); + tps = &t->sn.cd.tps; + mutex_init(&tps->lock); + atomic_set(&t->runs, 0); + INIT_DELAYED_WORK(&t->sn.ldcm_work, sn201202x_ldcm_test_work); + ret = kunit_add_action_or_reset(test, sn201202x_ldcm_test_cancel, &t->sn.ldcm_work); + KUNIT_ASSERT_EQ(test, ret, 0); + + /* Removal must cancel a pending retry and prevent later producers. */ + mod_delayed_work(system_freezable_wq, &t->sn.ldcm_work, 60 * HZ); + sn201202x_contaminant_ops.stop(tps); + KUNIT_EXPECT_FALSE(test, delayed_work_pending(&t->sn.ldcm_work)); + + /* Reset recovery can still invalidate the detector before it is drained. */ + mutex_lock(&tps->lock); + sn201202x_contaminant_ops.invalidate(tps); + mutex_unlock(&tps->lock); + flush_delayed_work(&t->sn.ldcm_work); + KUNIT_EXPECT_EQ(test, atomic_read(&t->runs), 0); + KUNIT_EXPECT_FALSE(test, delayed_work_pending(&t->sn.ldcm_work)); +} + static struct kunit_case sn201202x_pm_cases[] = { KUNIT_CASE(sn201202x_sleep_cycle_test), KUNIT_CASE(sn201202x_failed_wakeup_test), @@ -1003,6 +1052,7 @@ static struct kunit_case sn201202x_pm_cases[] = { KUNIT_CASE(sn201202x_ldcm_busy_keeps_result_test), KUNIT_CASE(sn201202x_ldcm_busy_without_result_test), KUNIT_CASE(sn201202x_ldcm_read_failure_with_result_test), + KUNIT_CASE(sn201202x_ldcm_stop_requeue_test), KUNIT_CASE(sn201202x_idle_sleep_test), KUNIT_CASE(sn201202x_atomic_access_test), KUNIT_CASE(sn201202x_idle_sleep_retry_test), diff --git a/drivers/usb/typec/tipd/spmi.c b/drivers/usb/typec/tipd/spmi.c index 0ea062474dc0f..30d472c3d83a4 100644 --- a/drivers/usb/typec/tipd/spmi.c +++ b/drivers/usb/typec/tipd/spmi.c @@ -467,16 +467,18 @@ static void sn201202x_ldcm_invalidate(struct tps6598x *tps) static void sn201202x_ldcm_stop(struct tps6598x *tps) { - cancel_delayed_work_sync(&tps_to_sn(tps)->ldcm_work); + /* Reset recovery is drained later and may still try to queue an update. */ + disable_delayed_work_sync(&tps_to_sn(tps)->ldcm_work); } -static const struct tipd_contaminant_ops sn201202x_contaminant_ops = { +VISIBLE_IF_KUNIT const struct tipd_contaminant_ops sn201202x_contaminant_ops = { .update = sn201202x_ldcm_update, .refresh = sn201202x_ldcm_refresh, .event = sn201202x_ldcm_event, .invalidate = sn201202x_ldcm_invalidate, .stop = sn201202x_ldcm_stop, }; +EXPORT_SYMBOL_IF_KUNIT(sn201202x_contaminant_ops); static struct regmap *__devm_regmap_init_sn201202x(struct spmi_device *sdev, const struct regmap_config *config, diff --git a/drivers/usb/typec/tipd/tps6598x.h b/drivers/usb/typec/tipd/tps6598x.h index c39feab87c20f..20b6b73436a29 100644 --- a/drivers/usb/typec/tipd/tps6598x.h +++ b/drivers/usb/typec/tipd/tps6598x.h @@ -564,6 +564,7 @@ int sn201202x_runtime_resume(struct device *dev); void sn201202x_idle_sleep_start(struct tps6598x *tps); void sn201202x_idle_sleep_stop(struct tps6598x *tps); extern const struct regmap_bus regmap_sn201202x; +extern const struct tipd_contaminant_ops sn201202x_contaminant_ops; u32 sn201202x_sleep_links(const struct cd321x_sleep_state *st); enum typec_contaminant sn201202x_ldcm_decode(const u8 status[SN201202X_LDCM_STATUS_LEN]); From 0f2bdecdbfd1a9f6562ff367cb2d728e535fcd9e Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Mon, 9 Nov 2026 09:50:46 -0500 Subject: [PATCH 41/43] dt-bindings: phy: Drop the duplicate T6050 combo PHY schema integration/aurora already describes apple,t6050-dp-phy and apple,t6050-combo-phy in apple,combo-phy.yaml, with matrix-typed tunables, the CIO tunables and the optional "usb" domain. The copy in apple,t6050-dp-phy.yaml claims the same compatibles, so dt-validate reports a duplicate schema, picks up its uint32-array types and its required tunables, and every T6050 combo PHY node that the boot loader fills in (J714s, J716s, J775c/d, J873s, K114/K116) fails validation. Remove the copy, as erikwb's restacked #1268 does, so each compatible has one schema. Signed-off-by: Diljit Singh --- .../bindings/phy/apple,t6050-dp-phy.yaml | 257 ------------------ 1 file changed, 257 deletions(-) delete mode 100644 Documentation/devicetree/bindings/phy/apple,t6050-dp-phy.yaml diff --git a/Documentation/devicetree/bindings/phy/apple,t6050-dp-phy.yaml b/Documentation/devicetree/bindings/phy/apple,t6050-dp-phy.yaml deleted file mode 100644 index 3b22c3f19ee9b..0000000000000 --- a/Documentation/devicetree/bindings/phy/apple,t6050-dp-phy.yaml +++ /dev/null @@ -1,257 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0-only -%YAML 1.2 ---- -$id: http://devicetree.org/schemas/phy/apple,t6050-dp-phy.yaml# -$schema: http://devicetree.org/meta-schemas/core.yaml# - -title: Apple T6050 DisplayPort and Type-C combo PHY - -maintainers: - - Dj - -description: | - Fixed four-lane DisplayPort PHY for HDMI bridges, or one shared owner of - Type-C USB2, USB3 and DisplayPort alternate mode. DisplayPort supports RBR - through HBR3 without spread-spectrum clocking. The combo provider serializes - controller shutdown, PIPE handoff, orientation and alternate-mode changes. - Register resources, power domains and board tunables come from the producer. - -properties: - compatible: - enum: - - apple,t6050-dp-phy - - apple,t6050-combo-phy - - reg: - minItems: 6 - maxItems: 11 - - reg-names: - minItems: 6 - maxItems: 11 - - '#phy-cells': - enum: [0, 1] - - '#reset-cells': - const: 1 - - apple,port-index: - $ref: /schemas/types.yaml#/definitions/uint32 - enum: [1, 2, 3, 4, 5] - description: Type-C port; the debug port (0) is excluded - - apple,guest-port-reserved: - type: boolean - description: The boot producer has excluded this port from host ownership - - mode-switch: true - orientation-switch: true - - mboxes: - maxItems: 1 - - port: - $ref: /schemas/graph.yaml#/properties/port - - apple,tunable-aus40cmn-shm: - $ref: /schemas/types.yaml#/definitions/uint32-array - description: Offset, mask and value tuples for the common analog bank - - apple,tunable-cio4pll: - $ref: /schemas/types.yaml#/definitions/uint32-array - description: Offset, mask and value tuples for the CIO PLL bank - - apple,tunable-usb2-default: - $ref: /schemas/types.yaml#/definitions/uint32-array - description: Offset, mask and value tuples for the USB2 PHY bank - - apple,tunable-usb2-host: - $ref: /schemas/types.yaml#/definitions/uint32-array - description: Offset, mask and value tuples for the USB2 host bank - - apple,tunable-pipe-default: - $ref: /schemas/types.yaml#/definitions/uint32-array - description: Offset, mask and value tuples for the PIPE handler - - power-domains: - description: - A combo PHY may name the USB controller's domain fourth ("usb"). Its - USB2 PHY and digital banks are in that domain, and the PHY then holds it - while it runs, also on a port with no USB controller bound. - minItems: 3 - maxItems: 4 - - power-domain-names: - minItems: 3 - items: - - const: usb-aon - - const: common - - const: wrapper - - const: usb - - apple,tunable-axi2af: - $ref: /schemas/types.yaml#/definitions/uint32-array - description: Offset, mask and value tuples within the named bank - - apple,tunable-fabric: - $ref: /schemas/types.yaml#/definitions/uint32-array - description: Offset, mask and value tuples within the named bank - - apple,tunable-common-cfg: - $ref: /schemas/types.yaml#/definitions/uint32-array - description: Offset, mask and value tuples within the named bank - - apple,tunable-lioa: - $ref: /schemas/types.yaml#/definitions/uint32-array - description: Offset, mask and value tuples within the named bank - - apple,tunable-lane0-rx-clock: - $ref: /schemas/types.yaml#/definitions/uint32-array - description: Offset, mask and value tuples within the named bank - - apple,tunable-lane1-rx-clock: - $ref: /schemas/types.yaml#/definitions/uint32-array - description: Offset, mask and value tuples within the named bank - - apple,tunable-lane0-rx-eq: - $ref: /schemas/types.yaml#/definitions/uint32-array - description: Offset, mask and value tuples within the named bank - - apple,tunable-lane1-rx-eq: - $ref: /schemas/types.yaml#/definitions/uint32-array - description: Offset, mask and value tuples within the named bank - - apple,tunable-auspll: - $ref: /schemas/types.yaml#/definitions/uint32-array - description: Offset, mask and value tuples within the named bank - - apple,tunable-uc-regs-cio: - $ref: /schemas/types.yaml#/definitions/uint32-array - description: - Offset, mask and value tuples for the PHY firmware's registers in CIO - (USB4/Thunderbolt) mode. Optional; an absent CIO tunable is skipped. - - apple,tunable-lane0-rx-top-cio: - $ref: /schemas/types.yaml#/definitions/uint32-array - description: As apple,tunable-uc-regs-cio, for lane 0's receiver top bank - - apple,tunable-lane1-rx-top-cio: - $ref: /schemas/types.yaml#/definitions/uint32-array - description: As apple,tunable-uc-regs-cio, for lane 1's receiver top bank - - apple,tunable-lane0-rx-eq-cio: - $ref: /schemas/types.yaml#/definitions/uint32-array - description: As apple,tunable-uc-regs-cio, for lane 0's receiver equalizer - - apple,tunable-lane1-rx-eq-cio: - $ref: /schemas/types.yaml#/definitions/uint32-array - description: As apple,tunable-uc-regs-cio, for lane 1's receiver equalizer - -patternProperties: - '^apple,dp-training-table(-rbr|-hbr|-hbr2|-hbr3)?$': - $ref: /schemas/types.yaml#/definitions/uint32-array - minItems: 16 - maxItems: 16 - description: Drive presets indexed by voltage swing times four plus pre-emphasis - -required: - - compatible - - reg - - reg-names - - '#phy-cells' - - power-domains - - power-domain-names - - apple,tunable-axi2af - - apple,tunable-fabric - - apple,tunable-common-cfg - - apple,tunable-lioa - - apple,tunable-lane0-rx-clock - - apple,tunable-lane1-rx-clock - - apple,tunable-lane0-rx-eq - - apple,tunable-lane1-rx-eq - - apple,tunable-auspll - -allOf: - - if: - properties: - compatible: - const: apple,t6050-combo-phy - then: - properties: - '#phy-cells': - const: 1 - reg: - minItems: 11 - maxItems: 11 - reg-names: - items: - - const: core - - const: usb2phy - - const: usb2-digital - - const: reset - - const: fabric - - const: axi2af - - const: lioa - - const: common-cfg - - const: usb-control - - const: firmware-data - - const: pipehandler - required: - - '#reset-cells' - - apple,port-index - - apple,guest-port-reserved - - mode-switch - - orientation-switch - - mboxes - - apple,tunable-aus40cmn-shm - - apple,tunable-cio4pll - - apple,tunable-usb2-default - - apple,tunable-usb2-host - - apple,tunable-pipe-default - else: - properties: - '#phy-cells': - const: 0 - power-domains: - maxItems: 3 - power-domain-names: - maxItems: 3 - if: - required: - - mode-switch - then: - properties: - reg: - minItems: 8 - maxItems: 8 - reg-names: - items: - - const: core - - const: reset - - const: fabric - - const: axi2af - - const: lioa - - const: common-cfg - - const: usb-control - - const: firmware-data - required: - - orientation-switch - - mboxes - - apple,tunable-aus40cmn-shm - - apple,tunable-cio4pll - else: - properties: - reg: - minItems: 6 - maxItems: 6 - reg-names: - items: - - const: core - - const: reset - - const: fabric - - const: axi2af - - const: lioa - - const: common-cfg - -additionalProperties: false From 4df5c8c23e0e1b633796efe4061c4852b0754ec9 Mon Sep 17 00:00:00 2001 From: Erik Bourget Date: Sun, 8 Nov 2026 14:49:09 -0500 Subject: [PATCH 42/43] arm64: dts: apple: Describe the J716c camera capture resources Provide the J716c ISP's named register windows, host DART streams, sensor preset and power gates. Reuse the existing hardware inventory nodes and add the aliases needed by the loader. Leave the camera and its DARTs disabled until the loader reserves the required memory and supplies the runtime properties. Developed with Claude Code. Signed-off-by: Erik Bourget --- .../arm64/boot/dts/apple/t6050-j716c-isp.dtsi | 146 ++++++++++++++++++ arch/arm64/boot/dts/apple/t6050-j716c.dts | 2 + 2 files changed, 148 insertions(+) create mode 100644 arch/arm64/boot/dts/apple/t6050-j716c-isp.dtsi diff --git a/arch/arm64/boot/dts/apple/t6050-j716c-isp.dtsi b/arch/arm64/boot/dts/apple/t6050-j716c-isp.dtsi new file mode 100644 index 0000000000000..b6b1675d26022 --- /dev/null +++ b/arch/arm64/boot/dts/apple/t6050-j716c-isp.dtsi @@ -0,0 +1,146 @@ +// SPDX-License-Identifier: GPL-2.0+ OR MIT +/* J716c camera: enabled only after the loader reserves the current firmware. */ +/ { + aliases { + isp = &isp0; + isp-dart0 = &isp0_dart; + isp-dart1 = &isp0_dart1; + isp-dart2 = &isp0_dart2; + }; +}; + +&isp0 { + /delete-property/ clocks; + compatible = "apple,t6050-isp", "apple,isp"; + reg = <0x5 0x80000000 0 0x1604000>, + <0x5 0x82924000 0 0x40>, + <0x5 0x829241d0 0 0x20>, + <0x5 0x82924464 0 0x10>, + <0x5 0xc3b50000 0 0x8000>; + reg-names = "coproc", "mbox", "gpio", "mbox2", "wdt"; + interrupts = ; + iommus = <&isp0_dart 0>, <&isp0_dart 12>, + <&isp0_dart1 0>, <&isp0_dart1 12>, + <&isp0_dart2 0>, <&isp0_dart2 12>; + power-domains = <&ps_isp_sys>, <&ps_isp_cpu>, <&ps_isp_fe>, + <&ps_ispsens0>, <&ps_isp_dprx>, <&ps_isp_secure>, + <&ps_isp_be>, <&ps_isp_clr>; + apple,dart-vm-size = /bits/ 64 <0xde000000>; + status = "disabled"; + sensor-presets { + preset0 { + apple,config-index = <5>; + apple,input-size = <1920 2160>; + apple,output-size = <1920 1080>; + apple,crop = <0 0 1920 1080>; + }; + }; +}; + +&isp0_dart { + compatible = "apple,t6050-isp-dart", "apple,t8110-dart"; + reg = <0x5 0x828c0000 0 0xc000>, <0x5 0x828d0000 0 0x4000>; + reg-names = "dart", "dapf"; + #iommu-cells = <1>; + interrupts = ; + power-domains = <&ps_isp_sys>; + apple,remap-streams = <1 0>, <6 0>, <7 0>, <10 0>, <11 0>; + status = "disabled"; +}; + +&isp0_dart1 { + compatible = "apple,t6050-isp-dart", "apple,t8110-dart"; + reg = <0x5 0x828e0000 0 0xc000>; + reg-names = "dart"; + #iommu-cells = <1>; + interrupts = ; + power-domains = <&ps_isp_sys>; + apple,remap-streams = <1 0>, <6 0>, <7 0>, <10 0>, <11 0>; + status = "disabled"; +}; + +&isp0_dart2 { + compatible = "apple,t6050-isp-dart", "apple,t8110-dart"; + reg = <0x5 0x82900000 0 0xc000>; + reg-names = "dart"; + #iommu-cells = <1>; + interrupts = ; + power-domains = <&ps_isp_sys>; + apple,remap-streams = <1 0>, <6 0>, <7 0>, <10 0>, <11 0>; + status = "disabled"; +}; + +&pmgr1 { + ps_isp_cpu: power-controller@3b0 { + compatible = "apple,t6050-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x3b0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "isp_cpu"; + apple,no-auto-pm; + power-domains = <&ps_isp_sys>; + }; + ps_isp_fe: power-controller@3b8 { + compatible = "apple,t6050-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x3b8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "isp_fe"; + apple,no-auto-pm; + power-domains = <&ps_isp_sys>; + }; + ps_isp_dprx: power-controller@3d8 { + compatible = "apple,t6050-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x3d8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "isp_dprx"; + apple,no-auto-pm; + power-domains = <&ps_isp_sys>; + }; + ps_isp_secure: power-controller@3e0 { + compatible = "apple,t6050-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x3e0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "isp_secure"; + apple,no-auto-pm; + power-domains = <&ps_isp_sys>; + }; + ps_isp_be: power-controller@3e8 { + compatible = "apple,t6050-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x3e8 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "isp_be"; + apple,no-auto-pm; + power-domains = <&ps_isp_sys>; + }; + ps_isp_clr: power-controller@3f0 { + compatible = "apple,t6050-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x3f0 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "isp_clr"; + apple,no-auto-pm; + power-domains = <&ps_isp_sys>; + }; +}; + +&ps_fab9_soc { + /delete-property/ power-domains; + apple,no-auto-pm; + apple,firmware-owned; + status = "okay"; +}; + +&ps_isp_sys { + apple,no-auto-pm; + apple,always-on; + status = "okay"; +}; + +&ps_ispsens0 { + apple,no-auto-pm; + status = "okay"; +}; diff --git a/arch/arm64/boot/dts/apple/t6050-j716c.dts b/arch/arm64/boot/dts/apple/t6050-j716c.dts index 55af2f818a954..25ba638862ca0 100644 --- a/arch/arm64/boot/dts/apple/t6050-j716c.dts +++ b/arch/arm64/boot/dts/apple/t6050-j716c.dts @@ -621,3 +621,5 @@ apple,tunable = <0x140 0x1 0x1>, <0x2a0 0x7e 0xa>; }; + +#include "t6050-j716c-isp.dtsi" From 955b69739397d6bf969fb8877bbb2cd90b87fc09 Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Mon, 9 Nov 2026 04:30:48 -0500 Subject: [PATCH 43/43] arm64: dts: apple: Describe the J714s camera capture resources The 14-inch M5 Pro has the same T6050 camera as J716c. The J714sAP ADT describes the same ISP registers, the three DARTs with host streams 0 and 12, the same DAPF filter mask (0xcc3) and the same two firmware segments. Only the sensor type differs (0x74 instead of 0x75), and the loader supplies that. Include the J716c camera description. Tested on J714s: the ISP and its DARTs bind, and the camera streams 1920x1080 NV12 at 30 fps. Signed-off-by: Diljit Singh --- arch/arm64/boot/dts/apple/t6050-j714s.dts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t6050-j714s.dts b/arch/arm64/boot/dts/apple/t6050-j714s.dts index d98f6ac1d63a0..b95609c723f32 100644 --- a/arch/arm64/boot/dts/apple/t6050-j714s.dts +++ b/arch/arm64/boot/dts/apple/t6050-j714s.dts @@ -46,3 +46,6 @@ #define T6050_J714S #include "t6050-peripherals.dtsi" + +/* The same T6050 camera as J716c (J714sAP ADT); the loader sets the J714s sensor type. */ +#include "t6050-j716c-isp.dtsi"