From 7f887322c250959342e261dd08c1480ca7035fcf Mon Sep 17 00:00:00 2001 From: ed Date: Fri, 6 Nov 2026 13:17:30 +0100 Subject: [PATCH 01/12] PCI: apple: T6050 adopted ports: no ASPM, and a shutdown that holds the port On the J714c the one-boot kernel (aurora's adopt path, the N1 in its ROM personality without firmware) froze 20-40 s after boot: user space first, then the whole SoC; a boot with pcie_aspm=off ran clean, a boot with the PCIe node disabled ran clean. The bus notifier that strips ASPM L1 from tunneled root ports now serves adopt_link hosts too and strips L0s and L1 from the adopted root ports, so the link stays in L0. A restart with the adopted link alive never came back to m1n1 (hard reset needed), while the same restart with the PCIe node disabled did. The platform driver gets a shutdown that puts each adopted port back into the boot stage's cold state: bridge decoding off, PME_Turn_Off and L2, PERST# asserted, the port and its lane clocks stopped (the first half of apple_pcie_t6050_reset_root_port, without the restart). Link: https://github.com/edimoldovan/linux-aurora/commit/ab24d156868e Signed-off-by: Eduard Moldovan Signed-off-by: Rolando Trevino --- drivers/pci/controller/pcie-apple.c | 58 ++++++++++++++++++++++++++++- 1 file changed, 57 insertions(+), 1 deletion(-) diff --git a/drivers/pci/controller/pcie-apple.c b/drivers/pci/controller/pcie-apple.c index 67d4f3050e06c..5d9a3c77ebef1 100644 --- a/drivers/pci/controller/pcie-apple.c +++ b/drivers/pci/controller/pcie-apple.c @@ -3665,11 +3665,24 @@ static int apple_pcie_tunnel_pci_notify(struct notifier_block *nb, switch (action) { case BUS_NOTIFY_ADD_DEVICE: + /* + * T6050 adopted ports: no ASPM at all. On the J714c the N1 in its ROM + * personality (no firmware yet) takes the SoC down 20-40 s after the link + * first idles into L1; pcie_aspm=off runs clean. + */ + if (pcie->hw->adopt_link) { + if (pci_pcie_type(pdev) == PCI_EXP_TYPE_ROOT_PORT) + pcie_aspm_remove_cap(pdev, PCI_EXP_LNKCAP_ASPM_L0S | + PCI_EXP_LNKCAP_ASPM_L1); + break; + } pm_runtime_get_noresume(&pdev->dev); if (pci_pcie_type(pdev) == PCI_EXP_TYPE_ROOT_PORT) pcie_aspm_remove_cap(pdev, PCI_EXP_LNKCAP_ASPM_L1); break; case BUS_NOTIFY_DEL_DEVICE: + if (pcie->hw->adopt_link) + break; pm_runtime_put_noidle(&pdev->dev); break; } @@ -4112,7 +4125,7 @@ static int apple_pcie_probe(struct platform_device *pdev) } /* Every function is added after this and removed before devres runs. */ - if (pcie->tunnel_cold_init) { + if (pcie->tunnel_cold_init || pcie->hw->adopt_link) { pcie->tunnel_pci_nb.notifier_call = apple_pcie_tunnel_pci_notify; ret = bus_register_notifier(&pci_bus_type, &pcie->tunnel_pci_nb); if (ret) @@ -4667,6 +4680,48 @@ static void apple_pcie_remove(struct platform_device *pdev) pci_unlock_rescan_remove(); } +/* + * A restart with an adopted port's link alive never reaches m1n1 on the J714c (hard + * reset needed). Put each adopted port back the way the boot stage leaves it on a + * cold start: bridge decoding off, link to L2, PERST# asserted, port and lane clocks + * stopped (the first half of apple_pcie_t6050_reset_root_port, without the restart). + */ +static void apple_pcie_shutdown(struct platform_device *pdev) +{ + struct pci_host_bridge *bridge = platform_get_drvdata(pdev); + struct apple_pcie *pcie = pci_host_bridge_priv(bridge); + struct apple_pcie_port *port; + struct pci_dev *rp; + u32 value; + + if (!pcie->hw->adopt_link) + return; + mutex_lock(&pcie->lock); + list_for_each_entry(port, &pcie->ports, entry) { + if (!port->perst || !port->base || !port->phy) + continue; + rp = pci_get_domain_bus_and_slot(pci_domain_nr(bridge->bus), 0, + PCI_DEVFN(port->idx, 0)); + if (!rp) + continue; + pci_write_config_word(rp, PCI_COMMAND, 0); + if (!pci_read_config_dword(rp, PCI_PRIMARY_BUS, &value)) + pci_write_config_dword(rp, PCI_PRIMARY_BUS, value & 0xff0000ff); + apple_pcie_port_writel(port, apple_pcie_port_readl(port, PORT_APPCLK) | 0x100, + PORT_APPCLK); + apple_pcie_port_writel(port, 0x11, 0x88); + 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); + WRITE_ONCE(port->link_failed, true); + apple_pcie_t6050_perst(port, true); + writel(readl(port->phy) & ~BIT(31), port->phy); + apple_pcie_t6050_port_disable(port, rp); + dev_info(pcie->dev, "port %d held for the restart\n", port->idx); + pci_dev_put(rp); + } + mutex_unlock(&pcie->lock); +} + static int apple_pcie_mark_power_lost(struct pci_dev *pdev, void *unused) { /* @@ -5097,6 +5152,7 @@ MODULE_DEVICE_TABLE(of, apple_pcie_of_match); static struct platform_driver apple_pcie_driver = { .probe = apple_pcie_probe, .remove = apple_pcie_remove, + .shutdown = apple_pcie_shutdown, .driver = { .name = "pcie-apple", .of_match_table = apple_pcie_of_match, From f05a1a8ca8009261c138b7abbfaf3e8e4967b815 Mon Sep 17 00:00:00 2001 From: Rolando Trevino Date: Sat, 7 Nov 2026 16:43:29 -0500 Subject: [PATCH 02/12] dt-bindings: pci: apple,pcie: Let the OS train a T6050 root port A T6050 root port that names its endpoint's power line can be left untrained by the boot loader: the OS powers the endpoint and trains the link itself, then adopts it like a port the boot loader trained. Allow pwren-gpios on such a port, the windows the train uses in reg-names (phy-shared, phy-common, axi2af, pcieclkgen, phy-window, phy-ip) and the six host tunable lists the boot loader copies from the Apple device tree, as (offset, width, mask, value) groups. Signed-off-by: Rolando Trevino Assisted-by: LLM Claude --- .../devicetree/bindings/pci/apple,pcie.yaml | 80 ++++++++++++++++--- 1 file changed, 68 insertions(+), 12 deletions(-) diff --git a/Documentation/devicetree/bindings/pci/apple,pcie.yaml b/Documentation/devicetree/bindings/pci/apple,pcie.yaml index 11f3cfe160c6c..41f9a779b1e6c 100644 --- a/Documentation/devicetree/bindings/pci/apple,pcie.yaml +++ b/Documentation/devicetree/bindings/pci/apple,pcie.yaml @@ -46,6 +46,19 @@ $defs: - description: Bits to set description: Register initialization tuples for one controller region. + t6050-tunable: + $ref: /schemas/types.yaml#/definitions/uint32-matrix + items: + items: + - description: Register offset in the named window + - description: Access width in bytes + const: 4 + - description: Mask of the bits to replace + - description: Value of those bits + description: + A T6050 host tunable list, copied by the boot loader from the Apple + device tree. + properties: compatible: oneOf: @@ -99,11 +112,11 @@ properties: reg: minItems: 3 - maxItems: 14 + maxItems: 20 reg-names: minItems: 3 - maxItems: 14 + maxItems: 20 ranges: minItems: 2 @@ -175,6 +188,30 @@ properties: $ref: '#/$defs/tunable' description: Initialization tuples for the oe-fabric register region. + apple,tunable-axi2af: + $ref: '#/$defs/t6050-tunable' + description: T6050 tuples for the axi2af window, applied before a train. + + apple,tunable-cio3pllcore: + $ref: '#/$defs/t6050-tunable' + description: T6050 tuples for the CIO3 PLL core, in the pcieclkgen window. + + apple,tunable-pcieclkgen: + $ref: '#/$defs/t6050-tunable' + description: T6050 tuples for the pcieclkgen window. + + apple,tunable-phy: + $ref: '#/$defs/t6050-tunable' + description: T6050 tuples for the phy-window window. + + apple,tunable-phy-ip-pll: + $ref: '#/$defs/t6050-tunable' + description: T6050 PHY PLL tuples for the phy-ip window. + + apple,tunable-phy-ip-auspma: + $ref: '#/$defs/t6050-tunable' + description: T6050 PHY lane tuples for the phy-ip window. + apple,piodma: $ref: /schemas/types.yaml#/definitions/phandle description: @@ -201,7 +238,9 @@ patternProperties: description: The boot loader trained this port's link and handed it over up. The driver adopts the link as it is instead of resetting the port, so - the port needs no PERST# line. + the port needs no PERST# line. A T6050 port that also names + pwren-gpios may be left untrained; the OS then trains it first and + adopts it the same way. apple,tunable: $ref: '#/$defs/tunable' @@ -249,15 +288,18 @@ allOf: properties: reg: minItems: 5 - maxItems: 14 + maxItems: 20 reg-names: description: config and rc, then port, phy and intr2axi for each - bootloader-trained root port 00:.0. + adopted root port 00:.0. A host with a port the OS trains + itself (pwren-gpios) also names the shared PHY (phy-shared), its + common block (phy-common) and the windows its tunable lists + apply to (axi2af, pcieclkgen, phy-window, phy-ip). minItems: 5 - maxItems: 14 + maxItems: 20 items: - pattern: "^(config|rc|(port|phy|intr2axi)[0-3])$" + pattern: "^(config|rc|(port|phy|intr2axi)[0-3]|phy-shared|phy-common|axi2af|pcieclkgen|phy-window|phy-ip)$" interrupts: maxItems: 4 interrupt-names: @@ -277,11 +319,24 @@ allOf: 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 + bootloader set it until that reset, or until the OS trains + the port itself. + pwren-gpios: + description: + The endpoint's power line. A port that names it may be left + untrained by the bootloader; the OS then powers the endpoint + and trains the link itself (the host then needs phy-shared + and phy-common), and adopts it as a bootloader-trained one. required: - apple,link-trained-by-bootloader else: + properties: + apple,tunable-axi2af: false + apple,tunable-cio3pllcore: false + apple,tunable-pcieclkgen: false + apple,tunable-phy: false + apple,tunable-phy-ip-pll: false + apple,tunable-phy-ip-auspma: false patternProperties: "^pci@": properties: @@ -443,14 +498,15 @@ allOf: properties: reg: minItems: 5 - maxItems: 14 + maxItems: 20 reg-names: minItems: 5 - maxItems: 14 + maxItems: 20 items: enum: [config, rc, port0, phy0, intr2axi0, port1, phy1, intr2axi1, port2, phy2, intr2axi2, port3, phy3, - intr2axi3] + intr2axi3, phy-shared, phy-common, axi2af, + pcieclkgen, phy-window, phy-ip] interrupts: maxItems: 4 else: From 6ccf8595e2269f945ea6bf84c678178aa9417073 Mon Sep 17 00:00:00 2001 From: ed Date: Sat, 7 Nov 2026 01:09:20 +0100 Subject: [PATCH 03/12] PCI: apple: T6050 adopt: train a port the boot loader left down The adopt path takes a T6050 port only if the boot loader trained its link. iBoot does not on the J714c: its N1 is held off (SMC GPIO gP13 low, PERST# low), the shared PCIe PHY is in reset with its clocks off, and Wi-Fi works only when the port is trained over the m1n1 proxy before the kernel starts. When the adopt checks find the link down and the port names pwren-gpios, train it: the shared PHY clocked and out of reset (phy-shared, phy-common), the RC running, PERST# held and the endpoint powered, then the port enable, configure and train sequences the root-port reset already uses, and the bus numbers a boot loader leaves. The adopt checks then run unchanged. The adopt gate accepts pwren-gpios on such a port. The root-port configure step now reaches the root port through the ECAM window: before enumeration there is no pci_dev. The root-port reset uses the same code. Link: https://github.com/edimoldovan/linux-aurora/commit/0f96cc6c00e1 Signed-off-by: Eduard Moldovan Signed-off-by: Rolando Trevino --- drivers/pci/controller/pcie-apple.c | 282 +++++++++++++++++++++------- 1 file changed, 214 insertions(+), 68 deletions(-) diff --git a/drivers/pci/controller/pcie-apple.c b/drivers/pci/controller/pcie-apple.c index 5d9a3c77ebef1..b6674241aad55 100644 --- a/drivers/pci/controller/pcie-apple.c +++ b/drivers/pci/controller/pcie-apple.c @@ -482,6 +482,7 @@ struct apple_pcie { void __iomem *intr2axi_base; void __iomem *oe_fabric_base; void __iomem *early_cfg; + void __iomem *t6050_phy; /* shared PHY, mapped by a cold train */ struct pci_config_window *cfg; struct apple_tunable *rc_tunable; struct apple_tunable *fabric_tunable; @@ -1880,7 +1881,8 @@ static int apple_pcie_adopt_link(struct apple_pcie_port *port) return ret; snprintf(name, sizeof(name), "intr2axi%d", port->idx); - port->intr2axi = devm_platform_ioremap_resource_byname(pdev, name); + if (!port->intr2axi) + port->intr2axi = devm_platform_ioremap_resource_byname(pdev, name); if (IS_ERR(port->intr2axi)) return PTR_ERR(port->intr2axi); /* @@ -1889,8 +1891,9 @@ static int apple_pcie_adopt_link(struct apple_pcie_port *port) * 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 (!port->perst) + 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); @@ -1992,15 +1995,30 @@ 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) +/* The root port's config space through the ECAM window: there is no pci_dev before enumeration */ +static void __iomem *apple_pcie_t6050_dbi(struct apple_pcie_port *port) { - u32 value; - int ret; + return port->pcie->cfg->win + (PCI_DEVFN(port->idx, 0) << 12); +} - 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 void apple_pcie_t6050_dbi_rmw(void __iomem *dbi, int where, u32 clear, u32 set) +{ + writel((readl(dbi + where) & ~clear) | set, dbi + where); +} + +static int apple_pcie_t6050_exp_cap(void __iomem *dbi) +{ + u8 cap = readb(dbi + PCI_CAPABILITY_LIST); + int i; + + for (i = 0; cap && i < 48; i++) { + if (cap < 0x40 || (cap & 3)) + return 0; + if (readb(dbi + cap + PCI_CAP_LIST_ID) == PCI_CAP_ID_EXP) + return cap; + cap = readb(dbi + cap + PCI_CAP_LIST_NEXT); + } + return 0; } static int apple_pcie_t6050_wait(struct apple_pcie_port *port, void __iomem *base, @@ -2021,7 +2039,7 @@ static int apple_pcie_t6050_wait(struct apple_pcie_port *port, void __iomem *bas * (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, +static int apple_pcie_t6050_tune(struct apple_pcie_port *port, void __iomem *dbi, const char *name) { u32 cells[64]; @@ -2037,18 +2055,11 @@ static int apple_pcie_t6050_tune(struct apple_pcie_port *port, struct pci_dev *r 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)) + cells[i] >= (dbi ? 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; - } + for (i = 0; i < count; i += 4) + apple_pcie_t6050_rmw((dbi ?: port->base) + cells[i], cells[i + 2], cells[i + 3]); return 0; } @@ -2163,7 +2174,7 @@ static int apple_pcie_t6050_port_enable(struct apple_pcie_port *port) } /* 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 int apple_pcie_t6050_port_configure(struct apple_pcie_port *port) { static const char * const shadow[] = { "apple,pcie-rc-gen3-shadow-tunables", @@ -2171,57 +2182,42 @@ static int apple_pcie_t6050_port_configure(struct apple_pcie_port *port, struct "apple,pcie-rc-gen5-shadow-tunables", }; static const u32 shadow_sel[] = { 0, BIT(24), 0 }; - int cap = pci_pcie_cap(rp); - u32 value; + void __iomem *dbi = apple_pcie_t6050_dbi(port); + int cap = apple_pcie_t6050_exp_cap(dbi); u16 ctl2; - int ret, err, i; + int ret, 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; + apple_pcie_t6050_dbi_rmw(dbi, RC_T6050_MISC_CONTROL_1, 0, RC_T6050_DBI_RO_WR_EN); + ret = apple_pcie_t6050_tune(port, dbi, "apple,pcie-rc-tunables"); + for (i = 0; !ret && i < ARRAY_SIZE(shadow); i++) { + apple_pcie_t6050_dbi_rmw(dbi, RC_T6050_GEN3_RELATED, RC_T6050_RATE_SHADOW_SEL, + shadow_sel[i]); + ret = apple_pcie_t6050_tune(port, dbi, shadow[i]); + } + if (!ret) { + apple_pcie_t6050_dbi_rmw(dbi, 0x194, 0, BIT(0)); + if (!(readl(dbi + cap + PCI_EXP_LNKCAP2) & PCI_EXP_LNKCAP2_SLS_8_0GB)) + ret = -EIO; } - 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) { + ctl2 = readw(dbi + cap + PCI_EXP_LNKCTL2); + writew((ctl2 & ~PCI_EXP_LNKCTL2_TLS) | PCI_EXP_LNKCTL2_TLS_8_0GT, + dbi + cap + PCI_EXP_LNKCTL2); + apple_pcie_t6050_dbi_rmw(dbi, RC_T6050_PORT_LINK_CTRL, RC_T6050_LINK_MODE, + FIELD_PREP(RC_T6050_LINK_MODE, 1)); + apple_pcie_t6050_dbi_rmw(dbi, RC_T6050_LINK_WIDTH_SPEED_CTRL, RC_T6050_LINK_WIDTH, + FIELD_PREP(RC_T6050_LINK_WIDTH, 1)); + apple_pcie_t6050_dbi_rmw(dbi, cap + PCI_EXP_LNKCAP, PCI_EXP_LNKCAP_MLW, + FIELD_PREP(PCI_EXP_LNKCAP_MLW, 1)); + } + apple_pcie_t6050_dbi_rmw(dbi, RC_T6050_MISC_CONTROL_1, RC_T6050_DBI_RO_WR_EN, 0); 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; + apple_pcie_t6050_dbi_rmw(dbi, cap + PCI_EXP_LNKCTL, 0, PCI_EXP_LNKCTL_LBMIE); + apple_pcie_t6050_dbi_rmw(dbi, RC_T6050_LINK_WIDTH_SPEED_CTRL, 0, RC_T6050_SPEED_CHANGE); writel(PCIEC_INTR2AXI_ENABLE, port->intr2axi + PCIEC_INTR2AXI_CTRL); return 0; } @@ -2231,7 +2227,7 @@ static int apple_pcie_t6050_port_restart(struct apple_pcie_port *port, struct pc { return apple_pcie_t6050_port_disable(port, rp) ?: apple_pcie_t6050_port_enable(port) ?: - apple_pcie_t6050_port_configure(port, rp); + apple_pcie_t6050_port_configure(port); } /* Lane clocks, PERST# released, then the LTSSM to link up */ @@ -2271,6 +2267,151 @@ static int apple_pcie_t6050_port_train(struct apple_pcie_port *port) return 0; } +/* + * A T6050 port the boot loader left untrained (the J714c's N1 on every boot): the shared + * PCIe PHY clocked and out of reset, the RC running, the endpoint powered (pwren-gpios) + * with PERST# held, then the port enable, configure and train sequences the root-port + * reset uses, and the bus numbers a boot loader leaves. The adopt checks then run as for + * a port trained before Linux. Order and values are m1n1's controller bring-up and the + * kernel's own T6050 port sequence. + */ +#define T6050_PHY_CTRL 0x0 +#define T6050_PHY_CTRL_CLK0REQ BIT(0) +#define T6050_PHY_CTRL_CLK1REQ BIT(1) +#define T6050_PHY_CTRL_CLK0ACK BIT(2) +#define T6050_PHY_CTRL_CLK1ACK BIT(3) +#define T6050_PHY_CTRL_RESET_T8132 BIT(4) +#define T6050_PHY_CTRL_RESET_T8103 BIT(7) +#define T6050_PHY_CTRL_LANES GENMASK(9, 8) +#define T6050_PHY_CFG 0x4 +#define T6050_PHY_STAT 0x8 +#define T6050_PHYCMN_CLK 0x0 +#define T6050_PHYCMN_CLK_MODE GENMASK(1, 0) +#define CORE_RC_T6050_PRE_RUN 0x54 /* m1n1 writes 0x140 here before RUN */ + +static int apple_pcie_t6050_phy_wait(struct apple_pcie *pcie, u32 mask, u32 value) +{ + u32 v; + int ret; + + ret = readl_poll_timeout(pcie->t6050_phy + T6050_PHY_CTRL, v, (v & mask) == value, + 10, 50000); + if (ret) + dev_err(pcie->dev, "shared PHY: %#x, waiting for %#x/%#x\n", v, value, mask); + return ret; +} + +static int apple_pcie_t6050_shared_phy_on(struct apple_pcie *pcie) +{ + struct platform_device *pdev = to_platform_device(pcie->dev); + const u32 up = T6050_PHY_CTRL_CLK0ACK | T6050_PHY_CTRL_CLK1ACK | T6050_PHY_CTRL_LANES; + void __iomem *phy, *cmn; + u32 v; + int ret; + + if (!pcie->t6050_phy) { + phy = devm_platform_ioremap_resource_byname(pdev, "phy-shared"); + if (IS_ERR(phy)) + return PTR_ERR(phy); + pcie->t6050_phy = phy; + } + phy = pcie->t6050_phy; + if ((readl(phy + T6050_PHY_CTRL) & (up | T6050_PHY_CTRL_RESET_T8132 | + T6050_PHY_CTRL_RESET_T8103)) == up) + return 0; + cmn = devm_platform_ioremap_resource_byname(pdev, "phy-common"); + if (IS_ERR(cmn)) + return PTR_ERR(cmn); + + apple_pcie_t6050_rmw(phy + T6050_PHY_CTRL, 0, T6050_PHY_CTRL_CLK0REQ); + ret = apple_pcie_t6050_phy_wait(pcie, T6050_PHY_CTRL_CLK0ACK, T6050_PHY_CTRL_CLK0ACK); + if (ret) + return ret; + apple_pcie_t6050_rmw(phy + T6050_PHY_CTRL, 0, T6050_PHY_CTRL_CLK1REQ); + ret = apple_pcie_t6050_phy_wait(pcie, T6050_PHY_CTRL_CLK1ACK, T6050_PHY_CTRL_CLK1ACK); + if (ret) + return ret; + apple_pcie_t6050_rmw(phy + T6050_PHY_CTRL, T6050_PHY_CTRL_RESET_T8132, 0); + ret = apple_pcie_t6050_phy_wait(pcie, T6050_PHY_CTRL_RESET_T8132, 0); + if (ret) + return ret; + apple_pcie_t6050_rmw(phy + T6050_PHY_CTRL, T6050_PHY_CTRL_RESET_T8103, 0); + ret = apple_pcie_t6050_phy_wait(pcie, T6050_PHY_CTRL_RESET_T8103, 0); + if (ret) + return ret; + apple_pcie_t6050_rmw(phy + T6050_PHY_CFG, 0, BIT(0)); + apple_pcie_t6050_rmw(phy + T6050_PHY_CFG, 0, BIT(4)); + apple_pcie_t6050_rmw(cmn + T6050_PHYCMN_CLK, T6050_PHYCMN_CLK_MODE, + FIELD_PREP(T6050_PHYCMN_CLK_MODE, 1)); + if (readl_poll_timeout(phy + T6050_PHY_STAT, v, v & BIT(0), 10, 250000)) + dev_info(pcie->dev, "shared PHY: clock status %#x\n", v); + apple_pcie_t6050_rmw(phy + T6050_PHY_CTRL, 0, T6050_PHY_CTRL_LANES); + return 0; +} + +static int apple_pcie_t6050_cold_train(struct apple_pcie_port *port) +{ + struct apple_pcie *pcie = port->pcie; + struct platform_device *pdev = to_platform_device(pcie->dev); + struct gpio_desc *pwren; + void __iomem *dbi; + char name[16]; + u32 v, bus; + int ret; + + bus = pcie->cfg->busr.start + 1 + port->idx; + if (bus > pcie->cfg->busr.end) + return -EINVAL; + ret = apple_pcie_t6050_shared_phy_on(pcie); + if (ret) + return ret; + if (!(readl(pcie->base + CORE_RC_CTL) & CORE_RC_CTL_RUN)) { + writel(0x140, pcie->base + CORE_RC_T6050_PRE_RUN); + writel(CORE_RC_CTL_RUN, pcie->base + CORE_RC_CTL); + } + ret = readl_poll_timeout(pcie->base + CORE_RC_STAT, v, v & CORE_RC_STAT_READY, 10, 250000); + if (ret) + return ret; + + snprintf(name, sizeof(name), "intr2axi%d", port->idx); + if (!port->intr2axi) + port->intr2axi = devm_platform_ioremap_resource_byname(pdev, name); + if (IS_ERR(port->intr2axi)) + return PTR_ERR(port->intr2axi); + if (!port->perst) + port->perst = devm_fwnode_gpiod_get(pcie->dev, of_fwnode_handle(port->np), + "reset", GPIOD_ASIS, "PERST#"); + if (IS_ERR(port->perst)) + return PTR_ERR(port->perst); + ret = apple_pcie_t6050_perst(port, true); + if (ret) + return ret; + + /* The endpoint held, then powered: the boot loader leaves it off */ + pwren = devm_fwnode_gpiod_get(pcie->dev, of_fwnode_handle(port->np), "pwren", + GPIOD_OUT_LOW, "PWREN"); + if (IS_ERR(pwren)) + return PTR_ERR(pwren); + msleep(50); + ret = gpiod_set_value_cansleep(pwren, 1); + if (ret) + return ret; + msleep(150); + + ret = apple_pcie_t6050_port_enable(port) ?: + apple_pcie_t6050_port_configure(port) ?: + apple_pcie_t6050_port_train(port); + if (ret) + return ret; + + /* Primary bus 0, the port's secondary and subordinate bus, as a boot loader leaves them */ + dbi = apple_pcie_t6050_dbi(port); + v = readl(dbi + PCI_PRIMARY_BUS) & 0xff000000; + writel(v | (bus << 8) | (bus << 16), dbi + PCI_PRIMARY_BUS); + dev_info(pcie->dev, "port %d: trained by Linux, the boot loader left it down\n", port->idx); + 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 @@ -2640,6 +2781,12 @@ static int apple_pcie_setup_port(struct apple_pcie *pcie, } } else if (pcie->hw->adopt_link) { ret = apple_pcie_adopt_link(port); + /* A port left down that names its endpoint's power line is trained here */ + if (ret == -ENOLINK && of_property_present(np, "pwren-gpios")) { + ret = apple_pcie_t6050_cold_train(port); + if (!ret) + ret = apple_pcie_adopt_link(port); + } if (ret) goto err_teardown; } else { @@ -4013,8 +4160,7 @@ static int apple_pcie_probe(struct platform_device *pdev) /* 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) > 3 || - !of_property_read_bool(child, "apple,link-trained-by-bootloader") || - of_property_present(child, "pwren-gpios")) + !of_property_read_bool(child, "apple,link-trained-by-bootloader")) return -EINVAL; /* * A port whose endpoint needs a root-port reset names its PERST# From 808194338047a6520affc5150b471885dde7e411 Mon Sep 17 00:00:00 2001 From: ed Date: Sat, 7 Nov 2026 01:40:27 +0100 Subject: [PATCH 04/12] PCI: apple: T6050 cold train: apply the host tunables A port trained without the boot loader's tunables comes up, but the PHY tunables are what clear lane bit 28 (and the shared PHY's bit 26); the adopt checks expect that state, and every proxy-trained boot has had it. Apply the host lists the DT carries (apple,tunable-axi2af, -cio3pllcore, -pcieclkgen, -phy, then -phy-ip-pll and -phy-ip-auspma after the PHY clocks), in m1n1's order. The windows may contain windows mapped elsewhere, so they are mapped without claiming them. Link: https://github.com/edimoldovan/linux-aurora/commit/17ada56a25c9 Signed-off-by: Eduard Moldovan Signed-off-by: Rolando Trevino --- drivers/pci/controller/pcie-apple.c | 57 +++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/drivers/pci/controller/pcie-apple.c b/drivers/pci/controller/pcie-apple.c index b6674241aad55..f01673d035a8b 100644 --- a/drivers/pci/controller/pcie-apple.c +++ b/drivers/pci/controller/pcie-apple.c @@ -2301,6 +2301,52 @@ static int apple_pcie_t6050_phy_wait(struct apple_pcie *pcie, u32 mask, u32 valu return ret; } +/* + * A host tunable list (apple,tunable-, groups of (offset, 4, mask, value)) applied + * to the host window @win. The window may contain windows mapped elsewhere (the PHY + * window holds the port and shared PHYs), so it is mapped without claiming it. + */ +static int apple_pcie_t6050_tune_host(struct apple_pcie *pcie, const char *name, + const char *win) +{ + struct platform_device *pdev = to_platform_device(pcie->dev); + struct device_node *np = pcie->dev->of_node; + struct resource *res; + void __iomem *base; + char prop[48]; + u32 *cells; + int count, i, ret; + + snprintf(prop, sizeof(prop), "apple,tunable-%s", name); + if (!of_property_present(np, prop)) + return 0; + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, win); + count = of_property_count_u32_elems(np, prop); + if (!res || count <= 0 || count % 4) + return -EINVAL; + cells = kmalloc_array(count, sizeof(*cells), GFP_KERNEL); + if (!cells) + return -ENOMEM; + ret = of_property_read_u32_array(np, prop, cells, count); + for (i = 0; !ret && i < count; i += 4) + if (cells[i + 1] != 4 || (cells[i] & 3) || cells[i] > resource_size(res) - 4) + ret = -EINVAL; + if (!ret) { + base = ioremap(res->start, resource_size(res)); + if (base) { + for (i = 0; i < count; i += 4) + apple_pcie_t6050_rmw(base + cells[i], cells[i + 2], cells[i + 3]); + iounmap(base); + } else { + ret = -ENOMEM; + } + } + kfree(cells); + if (ret) + dev_err(pcie->dev, "%s: %d\n", prop, ret); + return ret; +} + static int apple_pcie_t6050_shared_phy_on(struct apple_pcie *pcie) { struct platform_device *pdev = to_platform_device(pcie->dev); @@ -2323,6 +2369,13 @@ static int apple_pcie_t6050_shared_phy_on(struct apple_pcie *pcie) if (IS_ERR(cmn)) return PTR_ERR(cmn); + /* Fabric, clock generator and PHY tunables, then the PHY clocks (m1n1's order) */ + ret = apple_pcie_t6050_tune_host(pcie, "axi2af", "axi2af") ?: + apple_pcie_t6050_tune_host(pcie, "cio3pllcore", "pcieclkgen") ?: + apple_pcie_t6050_tune_host(pcie, "pcieclkgen", "pcieclkgen") ?: + apple_pcie_t6050_tune_host(pcie, "phy", "phy-window"); + if (ret) + return ret; apple_pcie_t6050_rmw(phy + T6050_PHY_CTRL, 0, T6050_PHY_CTRL_CLK0REQ); ret = apple_pcie_t6050_phy_wait(pcie, T6050_PHY_CTRL_CLK0ACK, T6050_PHY_CTRL_CLK0ACK); if (ret) @@ -2340,6 +2393,10 @@ static int apple_pcie_t6050_shared_phy_on(struct apple_pcie *pcie) if (ret) return ret; apple_pcie_t6050_rmw(phy + T6050_PHY_CFG, 0, BIT(0)); + ret = apple_pcie_t6050_tune_host(pcie, "phy-ip-pll", "phy-ip") ?: + apple_pcie_t6050_tune_host(pcie, "phy-ip-auspma", "phy-ip"); + if (ret) + return ret; apple_pcie_t6050_rmw(phy + T6050_PHY_CFG, 0, BIT(4)); apple_pcie_t6050_rmw(cmn + T6050_PHYCMN_CLK, T6050_PHYCMN_CLK_MODE, FIELD_PREP(T6050_PHYCMN_CLK_MODE, 1)); From afb120bcd6798c674ad014c2b89e6fa6ae3a7f36 Mon Sep 17 00:00:00 2001 From: ed Date: Sat, 7 Nov 2026 09:50:13 +0100 Subject: [PATCH 05/12] dt-bindings: iommu: apple,dart: add apple,manual-availability A DART behind a PCIe root complex answers only once the root complex is up. Mark such a DART so it probes only after its client says so. Link: https://github.com/edimoldovan/linux-aurora/commit/8805d2777c8b Signed-off-by: Eduard Moldovan Signed-off-by: Rolando Trevino --- Documentation/devicetree/bindings/iommu/apple,dart.yaml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Documentation/devicetree/bindings/iommu/apple,dart.yaml b/Documentation/devicetree/bindings/iommu/apple,dart.yaml index 38a24d0e27c36..7cc4b466965bc 100644 --- a/Documentation/devicetree/bindings/iommu/apple,dart.yaml +++ b/Documentation/devicetree/bindings/iommu/apple,dart.yaml @@ -146,6 +146,13 @@ properties: before the domain powers off, and the slice is enabled again when it powers up. Only the J713 internal display DARTs use it. + apple,manual-availability: + type: boolean + description: > + The DART's registers answer only once its client, a PCIe root complex, + is up. The client's driver makes the DART available after bringing the + root complex up, and the DART probes then. + apple,tunable: $ref: /schemas/types.yaml#/definitions/uint32-matrix items: From 7308e26fce94d32e20f939823e2b74d12904a634 Mon Sep 17 00:00:00 2001 From: ed Date: Sat, 7 Nov 2026 09:50:13 +0100 Subject: [PATCH 06/12] iommu: apple-dart: probe a manual-availability DART after its client A DART marked apple,manual-availability defers its probe until its client calls apple_dart_make_available() for its node. [Rolando: kzalloc_obj(), which this tree prefers] Link: https://github.com/edimoldovan/linux-aurora/commit/dbd43f4ff3da Signed-off-by: Eduard Moldovan Signed-off-by: Rolando Trevino --- drivers/iommu/apple-dart.c | 41 ++++++++++++++++++++++++++++++++++ include/linux/soc/apple/dart.h | 11 +++++++++ 2 files changed, 52 insertions(+) diff --git a/drivers/iommu/apple-dart.c b/drivers/iommu/apple-dart.c index e9929282d0f78..a91528c3b7575 100644 --- a/drivers/iommu/apple-dart.c +++ b/drivers/iommu/apple-dart.c @@ -4372,6 +4372,43 @@ static int apple_dart_parse_own_setup(struct apple_dart *dart, struct resource * return 0; } +struct apple_dart_available { + struct list_head list; + struct device_node *np; +}; + +static DEFINE_MUTEX(apple_dart_available_lock); +static LIST_HEAD(apple_dart_available_list); + +void apple_dart_make_available(struct device_node *np) +{ + struct apple_dart_available *a; + + guard(mutex)(&apple_dart_available_lock); + list_for_each_entry(a, &apple_dart_available_list, list) + if (a->np == np) + return; + a = kzalloc_obj(*a); + if (!a) + return; + a->np = of_node_get(np); + list_add(&a->list, &apple_dart_available_list); +} +EXPORT_SYMBOL_GPL(apple_dart_make_available); + +static bool apple_dart_is_available(struct device_node *np) +{ + struct apple_dart_available *a; + + if (!of_property_read_bool(np, "apple,manual-availability")) + return true; + guard(mutex)(&apple_dart_available_lock); + list_for_each_entry(a, &apple_dart_available_list, list) + if (a->np == np) + return true; + return false; +} + static int apple_dart_probe(struct platform_device *pdev) { int ret, sid; @@ -4475,6 +4512,10 @@ static int apple_dart_probe(struct platform_device *pdev) } } + /* Its registers answer only once its client is up */ + if (!apple_dart_is_available(dev->of_node)) + return dev_err_probe(dev, -EPROBE_DEFER, "waiting for its client\n"); + dart = kzalloc(sizeof(*dart), GFP_KERNEL); if (!dart) return -ENOMEM; diff --git a/include/linux/soc/apple/dart.h b/include/linux/soc/apple/dart.h index 10d05a7cc042b..01b1c7564673c 100644 --- a/include/linux/soc/apple/dart.h +++ b/include/linux/soc/apple/dart.h @@ -8,6 +8,7 @@ #include struct device; +struct device_node; /* Retained internal-display ownership is qualified only with a built-in * DART provider. Other consumers, including modular DART, keep the APIs below. @@ -161,4 +162,14 @@ static inline int apple_dart_restore_tunnel_state(struct device *dev) { return - static inline int apple_dart_radio_check_idle(struct device *dev) { return -EOPNOTSUPP; } #endif +/* + * A DART marked apple,manual-availability answers only once its client (a + * PCIe root complex) is up. The client makes it available, and it probes then. + */ +#if IS_ENABLED(CONFIG_APPLE_DART) +void apple_dart_make_available(struct device_node *np); +#else +static inline void apple_dart_make_available(struct device_node *np) { } +#endif + #endif From dfcab4a1b04f8a0e067b64004d209a511b6deeb0 Mon Sep 17 00:00:00 2001 From: ed Date: Sat, 7 Nov 2026 09:50:13 +0100 Subject: [PATCH 07/12] PCI: apple: make an adopted port's DART available Once a port is adopted, trained by the bootloader or by Linux, the root complex is up: make the port's DART available. Link: https://github.com/edimoldovan/linux-aurora/commit/3291060c462a Signed-off-by: Eduard Moldovan Signed-off-by: Rolando Trevino --- drivers/pci/controller/pcie-apple.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/drivers/pci/controller/pcie-apple.c b/drivers/pci/controller/pcie-apple.c index f01673d035a8b..7a937dc68636c 100644 --- a/drivers/pci/controller/pcie-apple.c +++ b/drivers/pci/controller/pcie-apple.c @@ -2406,6 +2406,18 @@ static int apple_pcie_t6050_shared_phy_on(struct apple_pcie *pcie) return 0; } +/* The port's DART answers once the root complex is up (apple,manual-availability) */ +static void apple_pcie_dart_available(struct apple_pcie_port *port) +{ + struct of_phandle_args iommu = {}; + + if (of_map_iommu_id(port->pcie->dev->of_node, port->adopted_bus << 8, &iommu) || + !iommu.np) + return; + apple_dart_make_available(iommu.np); + of_node_put(iommu.np); +} + static int apple_pcie_t6050_cold_train(struct apple_pcie_port *port) { struct apple_pcie *pcie = port->pcie; @@ -2846,6 +2858,7 @@ static int apple_pcie_setup_port(struct apple_pcie *pcie, } if (ret) goto err_teardown; + apple_pcie_dart_available(port); } else { if (pcie->hw->retain_clocks) rmw_set(PORT_APPCLK_EN, port->base + PORT_APPCLK); From c3d4db100ec66590e9d8b770ec52291826d64e43 Mon Sep 17 00:00:00 2001 From: Rolando Trevino Date: Sat, 7 Nov 2026 15:45:01 -0500 Subject: [PATCH 08/12] PCI: apple: T6050 cold train: reset a warm N1, power it off at shutdown A restart resets the SoC but not the J716s N1: it keeps running its firmware with its power line (SMC gP13) released. m1n1 found that a 50 ms power cycle does not bring such an N1 back in its ROM personality, and holds the line low for 250 ms when it reads released (the J716s warm-restart runs, three of three). The cold train cannot read the line back, so it always holds it low for 250 ms; a cold boot pays 200 ms. The train takes PERST# and the power line before its first register write: the SMC's GPIO bank probes late, and a probe deferred for it must not leave the shared PHY and the root complex half set up. The J716s N1 got up to 1 s for link up in m1n1 after its power-up; the train now allows the same instead of 500 ms. The root-port reset shares the wait, where only a failure takes longer. A port Linux trained keeps the endpoint's power line, and shutdown drops it after parking the port, so the boot after a restart finds the N1 cold whether its loader trains the port or the kernel does. Signed-off-by: Rolando Trevino Assisted-by: LLM Claude --- drivers/pci/controller/pcie-apple.c | 41 ++++++++++++++++++++--------- 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/drivers/pci/controller/pcie-apple.c b/drivers/pci/controller/pcie-apple.c index 7a937dc68636c..5fa3e7fcee994 100644 --- a/drivers/pci/controller/pcie-apple.c +++ b/drivers/pci/controller/pcie-apple.c @@ -542,6 +542,7 @@ 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) */ + struct gpio_desc *pwren; /* adopt_link: endpoint power, if Linux trained */ }; static int apple_pcie_managed_admit(struct apple_pcie_port *port); @@ -2257,8 +2258,9 @@ static int apple_pcie_t6050_port_train(struct apple_pcie_port *port) if (ret) return ret; apple_pcie_t6050_rmw(port->base + PORT_LTSSMCTL, 0, PORT_LTSSMCTL_START); + /* Up to 1 s: what m1n1 allows the J716s N1 after its power-up */ ret = apple_pcie_t6050_wait(port, port->base, PORT_LINKSTS, PORT_LINKSTS_UP, - PORT_LINKSTS_UP, 500000); + PORT_LINKSTS_UP, 1000000); if (ret) return ret; @@ -2431,6 +2433,26 @@ static int apple_pcie_t6050_cold_train(struct apple_pcie_port *port) bus = pcie->cfg->busr.start + 1 + port->idx; if (bus > pcie->cfg->busr.end) return -EINVAL; + + /* + * Both lines before the first register write: a line that is not there yet (the SMC + * GPIO bank probes late) defers the probe with the hardware untouched. The endpoint + * is held off from here. A restart resets the SoC but not the endpoint: after one + * without a shutdown that powered it off, the J716s N1 still runs its firmware, and + * only a power cycle of 250 ms (what m1n1 measured for a warm N1) brings it back in + * its ROM personality. + */ + if (!port->perst) + port->perst = devm_fwnode_gpiod_get(pcie->dev, of_fwnode_handle(port->np), + "reset", GPIOD_ASIS, "PERST#"); + if (IS_ERR(port->perst)) + return PTR_ERR(port->perst); + pwren = devm_fwnode_gpiod_get(pcie->dev, of_fwnode_handle(port->np), "pwren", + GPIOD_OUT_LOW, "PWREN"); + if (IS_ERR(pwren)) + return PTR_ERR(pwren); + port->pwren = pwren; + ret = apple_pcie_t6050_shared_phy_on(pcie); if (ret) return ret; @@ -2447,21 +2469,12 @@ static int apple_pcie_t6050_cold_train(struct apple_pcie_port *port) port->intr2axi = devm_platform_ioremap_resource_byname(pdev, name); if (IS_ERR(port->intr2axi)) return PTR_ERR(port->intr2axi); - if (!port->perst) - port->perst = devm_fwnode_gpiod_get(pcie->dev, of_fwnode_handle(port->np), - "reset", GPIOD_ASIS, "PERST#"); - if (IS_ERR(port->perst)) - return PTR_ERR(port->perst); ret = apple_pcie_t6050_perst(port, true); if (ret) return ret; /* The endpoint held, then powered: the boot loader leaves it off */ - pwren = devm_fwnode_gpiod_get(pcie->dev, of_fwnode_handle(port->np), "pwren", - GPIOD_OUT_LOW, "PWREN"); - if (IS_ERR(pwren)) - return PTR_ERR(pwren); - msleep(50); + msleep(250); ret = gpiod_set_value_cansleep(pwren, 1); if (ret) return ret; @@ -4932,7 +4945,11 @@ static void apple_pcie_shutdown(struct platform_device *pdev) apple_pcie_t6050_perst(port, true); writel(readl(port->phy) & ~BIT(31), port->phy); apple_pcie_t6050_port_disable(port, rp); - dev_info(pcie->dev, "port %d held for the restart\n", port->idx); + /* An endpoint Linux powered goes off, so the next boot finds it cold */ + if (port->pwren) + gpiod_set_value_cansleep(port->pwren, 0); + dev_info(pcie->dev, "port %d held for the restart%s\n", port->idx, + port->pwren ? ", its endpoint powered off" : ""); pci_dev_put(rp); } mutex_unlock(&pcie->lock); From 55b84a646ffb0116d189da38997237877f63234e Mon Sep 17 00:00:00 2001 From: Rolando Trevino Date: Sat, 7 Nov 2026 15:45:12 -0500 Subject: [PATCH 09/12] gpio: macsmc: drive the J716s N1 power line with its ADT template The J716s N1's power line is /arm-io/apcie0/pci-bridge0 function-reset, pKW4("gP13", 0x00800000): the descriptor J713's /amfm/function-reg_on uses. m1n1 trains the N1's port on the J716s by writing that template with the level in its low bits (0x00800000, then 0x00800001); the generic CMD_OUTPUT write was never tried on this machine. Drive gP13 on the J716s as on J713, so the kernel's own train of the port powers the N1 with the same SMC writes. Signed-off-by: Rolando Trevino Assisted-by: LLM Claude --- drivers/gpio/gpio-macsmc.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/gpio/gpio-macsmc.c b/drivers/gpio/gpio-macsmc.c index 6a26f900bba28..434ed549d4c1e 100644 --- a/drivers/gpio/gpio-macsmc.c +++ b/drivers/gpio/gpio-macsmc.c @@ -41,7 +41,8 @@ * pKW4("gP13", 0x00800000) (apple_t8132_managed_radio()). The firmware * function preserves the template's upper 16 bits and inserts the level into * its low 16 bits. This does not establish generic CMD_OUTPUT semantics, a - * pin-mode command, or a readback operation for this line. + * pin-mode command, or a readback operation for this line. The J716s N1's + * power, /arm-io/apcie0/pci-bridge0 function-reset, is the same descriptor. */ #define MACSMC_J713_RADIO_REG_ON_GPIO 0x13 #define MACSMC_J713_RADIO_REG_ON_TEMPLATE 0x00800000 @@ -526,7 +527,8 @@ static int macsmc_gpio_probe(struct platform_device *pdev) smcgp->base_key = data ? data->base_key : _SMC_KEY("gP\0\0"); smcgp->j700_pcio = of_machine_is_compatible("apple,j700") && device_is_compatible(&pdev->dev, "apple,smc-gpio"); - smcgp->j713 = apple_t8132_managed_radio(); + smcgp->j713 = apple_t8132_managed_radio() || + of_machine_is_compatible("apple,j716s"); mutex_init(&smcgp->j713_reg_on_lock); ret = macsmc_gpio_parse_templates(smcgp); if (ret) From 52826653088b238f08c57bd48125c4cde9971fe3 Mon Sep 17 00:00:00 2001 From: Rolando Trevino Date: Tue, 10 Nov 2026 05:52:56 -0500 Subject: [PATCH 10/12] PCI: apple: T6050: name the PHY and RC run bits, share the L2 entry Name the bits the T6050 shared PHY bring-up and the RC run write use, after what a trace of macOS bringing the port up and down shows them doing: PORT_APPCLK_CGDIS (the clock gate disable the driver already defines) for the 0x100 written before PME_Turn_Off, PORT_PME_TURN_OFF for the 0x88 register macOS writes 0x11 to, PORT_LINKSTS_L2 for the link status bit macOS reads as "reached L2", and PHY_LANE_CFG_REFCLKCGEN_B31 for the lane bit it clears after asserting PERST#. The PHY_CFG bits 0 and 4, PHY_STAT bit 0, the PME_Turn_Off bit 4 and the two bits of the 0x140 written to the RC before it runs have no documented meaning, so they get neutral names and a comment with what the traces show. The PME_Turn_Off and L2 wait was written out twice, in the root-port reset and in the shutdown. Move it into apple_pcie_t6050_port_l2(). The register writes, values, order and timeouts are unchanged. Both polls now always run, so a failed first poll in the root-port reset no longer skips the second one (a read-only poll, up to 500 ms on that error path); the shutdown already ran both. Signed-off-by: Rolando Trevino Assisted-by: LLM Claude --- drivers/pci/controller/pcie-apple.c | 75 +++++++++++++++++++++++------ 1 file changed, 59 insertions(+), 16 deletions(-) diff --git a/drivers/pci/controller/pcie-apple.c b/drivers/pci/controller/pcie-apple.c index 5fa3e7fcee994..95b69512fdda3 100644 --- a/drivers/pci/controller/pcie-apple.c +++ b/drivers/pci/controller/pcie-apple.c @@ -94,11 +94,19 @@ MODULE_PARM_DESC(link_up_timeout, "PCIe link training timeout in milliseconds"); #define PHY_LANE_CFG_REFBUF_OFF BIT(4) #define PHY_LANE_CFG_REFCLKEN (BIT(9) | BIT(10)) #define PHY_LANE_CFG_REFCLKCGEN (BIT(30) | BIT(31)) +/* Bit 31 of the pair: the only one a trace of macOS shows cleared, after PERST# is asserted */ +#define PHY_LANE_CFG_REFCLKCGEN_B31 BIT(31) #define PHY_LANE_CTL 0x00004 #define PHY_LANE_CTL_CFGACC BIT(15) #define PORT_LTSSMCTL 0x00080 #define PORT_LTSSMCTL_START BIT(0) +/* T6050: a trace of macOS shows 0x11 written here to send PME_Turn_Off */ +#define PORT_PME_TURN_OFF 0x00088 +/* Reads back 0 within a few reads of the write (the trace reads 0x1, then 0x10) */ +#define PORT_PME_TURN_OFF_SEND BIT(0) +/* Written with bit 0 and still set afterwards; meaning not known */ +#define PORT_PME_TURN_OFF_BIT4 BIT(4) #define PORT_INTSTAT 0x00100 #define PORT_INT_TUNNEL_ERR 31 #define PORT_INT_CPL_TIMEOUT 23 @@ -127,6 +135,8 @@ MODULE_PARM_DESC(link_up_timeout, "PCIe link training timeout in milliseconds"); #define PORT_LINKSTS 0x00208 #define PORT_LINKSTS_UP BIT(0) #define PORT_LINKSTS_BUSY BIT(2) +/* In traces of macOS: 0x9b00134d for a port that reached L2, 0x9b001001 for one that did not */ +#define PORT_LINKSTS_L2 BIT(6) #define PORT_LINKCMDSTS 0x00210 #define PORT_OUTS_NPREQS 0x00284 #define PORT_OUTS_NPREQS_REQ BIT(24) @@ -2286,10 +2296,29 @@ static int apple_pcie_t6050_port_train(struct apple_pcie_port *port) #define T6050_PHY_CTRL_RESET_T8103 BIT(7) #define T6050_PHY_CTRL_LANES GENMASK(9, 8) #define T6050_PHY_CFG 0x4 +/* + * Set before the PHY-IP tunables. A trace of macOS bringing the port up shows no + * access to this bit; meaning not known. + */ +#define T6050_PHY_CFG_BIT0 BIT(0) +/* + * Set after the PHY-IP tunables. A trace of macOS shows it set too, after which + * T6050_PHY_STAT_BIT0 rises (the trace reads 0x10 seven times, then 0x11); meaning + * not known. + */ +#define T6050_PHY_CFG_BIT4 BIT(4) #define T6050_PHY_STAT 0x8 +/* Rises once T6050_PHY_CFG_BIT4 is set; polled by macOS and by m1n1; meaning not known */ +#define T6050_PHY_STAT_BIT0 BIT(0) #define T6050_PHYCMN_CLK 0x0 #define T6050_PHYCMN_CLK_MODE GENMASK(1, 0) -#define CORE_RC_T6050_PRE_RUN 0x54 /* m1n1 writes 0x140 here before RUN */ +/* + * Written before CORE_RC_CTL_RUN: a trace of macOS shows 0x140 written here, and m1n1 + * writes the same. Neither bit's meaning is known. + */ +#define CORE_RC_T6050_PRE_RUN 0x54 +#define CORE_RC_T6050_PRE_RUN_BIT6 BIT(6) +#define CORE_RC_T6050_PRE_RUN_BIT8 BIT(8) static int apple_pcie_t6050_phy_wait(struct apple_pcie *pcie, u32 mask, u32 value) { @@ -2394,15 +2423,15 @@ static int apple_pcie_t6050_shared_phy_on(struct apple_pcie *pcie) ret = apple_pcie_t6050_phy_wait(pcie, T6050_PHY_CTRL_RESET_T8103, 0); if (ret) return ret; - apple_pcie_t6050_rmw(phy + T6050_PHY_CFG, 0, BIT(0)); + apple_pcie_t6050_rmw(phy + T6050_PHY_CFG, 0, T6050_PHY_CFG_BIT0); ret = apple_pcie_t6050_tune_host(pcie, "phy-ip-pll", "phy-ip") ?: apple_pcie_t6050_tune_host(pcie, "phy-ip-auspma", "phy-ip"); if (ret) return ret; - apple_pcie_t6050_rmw(phy + T6050_PHY_CFG, 0, BIT(4)); + apple_pcie_t6050_rmw(phy + T6050_PHY_CFG, 0, T6050_PHY_CFG_BIT4); apple_pcie_t6050_rmw(cmn + T6050_PHYCMN_CLK, T6050_PHYCMN_CLK_MODE, FIELD_PREP(T6050_PHYCMN_CLK_MODE, 1)); - if (readl_poll_timeout(phy + T6050_PHY_STAT, v, v & BIT(0), 10, 250000)) + if (readl_poll_timeout(phy + T6050_PHY_STAT, v, v & T6050_PHY_STAT_BIT0, 10, 250000)) dev_info(pcie->dev, "shared PHY: clock status %#x\n", v); apple_pcie_t6050_rmw(phy + T6050_PHY_CTRL, 0, T6050_PHY_CTRL_LANES); return 0; @@ -2457,7 +2486,8 @@ static int apple_pcie_t6050_cold_train(struct apple_pcie_port *port) if (ret) return ret; if (!(readl(pcie->base + CORE_RC_CTL) & CORE_RC_CTL_RUN)) { - writel(0x140, pcie->base + CORE_RC_T6050_PRE_RUN); + writel(CORE_RC_T6050_PRE_RUN_BIT6 | CORE_RC_T6050_PRE_RUN_BIT8, + pcie->base + CORE_RC_T6050_PRE_RUN); writel(CORE_RC_CTL_RUN, pcie->base + CORE_RC_CTL); } ret = readl_poll_timeout(pcie->base + CORE_RC_STAT, v, v & CORE_RC_STAT_READY, 10, 250000); @@ -2494,6 +2524,26 @@ static int apple_pcie_t6050_cold_train(struct apple_pcie_port *port) return 0; } +/* + * PME_Turn_Off, then wait for L2: the clock gate disabled, the message sent, and the + * link reported in L2. Both waits are made even when the first fails (the N1 may + * clear the send bit and never reach L2, as under macOS); the first error is returned. + */ +static int apple_pcie_t6050_port_l2(struct apple_pcie_port *port) +{ + int ret, ret2; + + apple_pcie_port_writel(port, apple_pcie_port_readl(port, PORT_APPCLK) | PORT_APPCLK_CGDIS, + PORT_APPCLK); + apple_pcie_port_writel(port, PORT_PME_TURN_OFF_SEND | PORT_PME_TURN_OFF_BIT4, + PORT_PME_TURN_OFF); + ret = apple_pcie_t6050_wait(port, port->base, PORT_PME_TURN_OFF, PORT_PME_TURN_OFF_SEND, + 0, 500000); + ret2 = apple_pcie_t6050_wait(port, port->base, PORT_LINKSTS, PORT_LINKSTS_L2, + PORT_LINKSTS_L2, 500000); + return ret ?: ret2; +} + /* * 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 @@ -2530,17 +2580,14 @@ static int apple_pcie_t6050_reset_root_port(struct pci_host_bridge *bridge, 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); + ret = apple_pcie_t6050_port_l2(port); 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); + writel(readl(port->phy) & ~PHY_LANE_CFG_REFCLKCGEN_B31, port->phy); ret = apple_pcie_t6050_port_restart(port, rp); if (ret) goto out; @@ -4936,14 +4983,10 @@ static void apple_pcie_shutdown(struct platform_device *pdev) pci_write_config_word(rp, PCI_COMMAND, 0); if (!pci_read_config_dword(rp, PCI_PRIMARY_BUS, &value)) pci_write_config_dword(rp, PCI_PRIMARY_BUS, value & 0xff0000ff); - apple_pcie_port_writel(port, apple_pcie_port_readl(port, PORT_APPCLK) | 0x100, - PORT_APPCLK); - apple_pcie_port_writel(port, 0x11, 0x88); - 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); + apple_pcie_t6050_port_l2(port); WRITE_ONCE(port->link_failed, true); apple_pcie_t6050_perst(port, true); - writel(readl(port->phy) & ~BIT(31), port->phy); + writel(readl(port->phy) & ~PHY_LANE_CFG_REFCLKCGEN_B31, port->phy); apple_pcie_t6050_port_disable(port, rp); /* An endpoint Linux powered goes off, so the next boot finds it cold */ if (port->pwren) From de988b6bdad8d56b5d6ea409d0a1df3e6f94eedb Mon Sep 17 00:00:00 2001 From: Rolando Trevino Date: Tue, 10 Nov 2026 07:11:17 -0500 Subject: [PATCH 11/12] PCI: apple: T6050 cold train: park the port when its setup fails When the driver cold-trains a T6050 port it raises the endpoint's power (pwren), releases PERST# and trains the link. If the second adoption check, the IRQ setup or a later step then fails, err_teardown only releases the IRQ and RID state and drops the port from pcie->ports. The shutdown hook walks that list, so it never parks the port: the link stays up and the endpoint powered across a restart, the state the hook exists to prevent. A failure inside the cold train after the pwren write leaves the same. Remember that the cold train raised pwren (port->powered, set just before the write) and, from err_teardown, put the port back the way the shutdown hook does. The park sequence is now one function, apple_pcie_t6050_port_park(), used by the shutdown hook and by the failure path: PME_Turn_Off to L2, PERST# asserted, the lane clock request off, the port disabled and its clocks stopped, the endpoint's power off. The unwind runs after apple_pcie_port_teardown(), which still writes the port's registers and so needs the port clocked. What runs depends on how far the cold train got: - Failure before the pwren write: nothing. pwren was requested low and no port register was written, so there is nothing to undo. - Link not up (the enable, configure or train step failed): no PME_Turn_Off, which has no one to answer and would only wait out two 500 ms timeouts; then the rest of the sequence. The disable is best effort on a port that was never fully enabled: its first wait may time out, in which case the endpoint is still powered off. - Link up (the second adoption check, IRQ setup or a later step failed): the full sequence as at shutdown, after clearing the bus numbers the cold train wrote, as the hook does. The shutdown hook runs the same steps in the same order as before. The disable no longer takes a pci_dev: it read the root port's LTSSM state through config space, which does not exist during probe, and now reads the same register through the ECAM window as the rest of the cold train does. The success path and the adopt-only (loader-trained) path are not changed. Compile-tested only. Signed-off-by: Rolando Trevino Assisted-by: LLM Claude --- drivers/pci/controller/pcie-apple.c | 80 ++++++++++++++++++++++------- 1 file changed, 62 insertions(+), 18 deletions(-) diff --git a/drivers/pci/controller/pcie-apple.c b/drivers/pci/controller/pcie-apple.c index 95b69512fdda3..b0f9cc67858f5 100644 --- a/drivers/pci/controller/pcie-apple.c +++ b/drivers/pci/controller/pcie-apple.c @@ -553,6 +553,7 @@ struct apple_pcie_port { u8 adopted_bus; /* adopt_link: the secondary bus */ unsigned long adopted_slots; /* adopt_link: RID2SID slots routed (slot == SID) */ struct gpio_desc *pwren; /* adopt_link: endpoint power, if Linux trained */ + bool powered; /* cold train: pwren raised, port not parked yet */ }; static int apple_pcie_managed_admit(struct apple_pcie_port *port); @@ -2085,12 +2086,13 @@ static int apple_pcie_t6050_perst(struct apple_pcie_port *port, bool asserted) } /* 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) +static int apple_pcie_t6050_port_disable(struct apple_pcie_port *port) { + void __iomem *dbi = apple_pcie_t6050_dbi(port); void __iomem *perst = port->base + PORT_T602X_PERST; void __iomem *lane = port->phy + PHY_LANE_CFG; u32 stat; - int ret, err; + int ret; if (!(readl(port->base + PORT_STATUS) & BIT(2))) { ret = apple_pcie_t6050_wait(port, port->base, 0x820, BIT(2), BIT(2), 10000); @@ -2105,12 +2107,12 @@ static int apple_pcie_t6050_port_disable(struct apple_pcie_port *port, struct pc 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) != + ret = read_poll_timeout_atomic(readl, stat, + 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); + 5, 5000, false, dbi + RC_T6050_PORT_DEBUG0); + if (ret) + return ret; ret = apple_pcie_t6050_wait(port, port->base, 0xa8, BIT(0), BIT(0), 10000); if (ret) @@ -2234,9 +2236,9 @@ static int apple_pcie_t6050_port_configure(struct apple_pcie_port *port) } /* Disable, reset, enable and configure an adopted port whose endpoint is held in PERST# */ -static int 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) { - return apple_pcie_t6050_port_disable(port, rp) ?: + return apple_pcie_t6050_port_disable(port) ?: apple_pcie_t6050_port_enable(port) ?: apple_pcie_t6050_port_configure(port); } @@ -2505,6 +2507,8 @@ static int apple_pcie_t6050_cold_train(struct apple_pcie_port *port) /* The endpoint held, then powered: the boot loader leaves it off */ msleep(250); + /* From here the endpoint may be on, and a failure has to park the port */ + port->powered = true; ret = gpiod_set_value_cansleep(pwren, 1); if (ret) return ret; @@ -2544,6 +2548,52 @@ static int apple_pcie_t6050_port_l2(struct apple_pcie_port *port) return ret ?: ret2; } +/* + * Put a T6050 port back the way the boot stage leaves it on a cold start: link to L2 + * (only for a link that is up: PME_Turn_Off has no one to answer on a link that never + * trained), PERST# asserted, lane clock request off, the port disabled and its clocks + * stopped, and the endpoint's power off if Linux raised it. Errors are ignored: each + * step is best effort and the rest still has to run. Config space is not touched. + */ +static void apple_pcie_t6050_port_park(struct apple_pcie_port *port, bool link_up) +{ + if (link_up) + apple_pcie_t6050_port_l2(port); + WRITE_ONCE(port->link_failed, true); + apple_pcie_t6050_perst(port, true); + writel(readl(port->phy) & ~PHY_LANE_CFG_REFCLKCGEN_B31, port->phy); + apple_pcie_t6050_port_disable(port); + /* An endpoint Linux powered goes off, so the next boot finds it cold */ + if (port->pwren) + gpiod_set_value_cansleep(port->pwren, 0); + port->powered = false; +} + +/* + * A port this driver cold-trained whose setup then failed: its link may be up and its + * endpoint is powered, and nothing else will stop them (the shutdown hook walks + * pcie->ports, which no longer holds this port). Called after apple_pcie_port_teardown(), + * which still needs the port's registers clocked. A failure before the endpoint was + * powered left nothing running: pwren was requested low and no port register was written. + */ +static void apple_pcie_t6050_cold_unwind(struct apple_pcie_port *port) +{ + bool link_up; + + if (!port->powered) + return; + link_up = apple_pcie_port_readl(port, PORT_LINKSTS) & PORT_LINKSTS_UP; + /* The bus numbers cold_train wrote, as the shutdown hook clears them */ + if (link_up) { + void __iomem *dbi = apple_pcie_t6050_dbi(port); + + writel(readl(dbi + PCI_PRIMARY_BUS) & 0xff0000ff, dbi + PCI_PRIMARY_BUS); + } + apple_pcie_t6050_port_park(port, link_up); + dev_info(port->pcie->dev, "port %d parked after a failed setup, its endpoint powered off\n", + port->idx); +} + /* * 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 @@ -2588,7 +2638,7 @@ static int apple_pcie_t6050_reset_root_port(struct pci_host_bridge *bridge, if (ret) goto out; writel(readl(port->phy) & ~PHY_LANE_CFG_REFCLKCGEN_B31, port->phy); - ret = apple_pcie_t6050_port_restart(port, rp); + ret = apple_pcie_t6050_port_restart(port); if (ret) goto out; @@ -3069,6 +3119,7 @@ static int apple_pcie_setup_port(struct apple_pcie *pcie, err_teardown: apple_pcie_port_teardown(port); + apple_pcie_t6050_cold_unwind(port); return ret; } @@ -4983,14 +5034,7 @@ static void apple_pcie_shutdown(struct platform_device *pdev) pci_write_config_word(rp, PCI_COMMAND, 0); if (!pci_read_config_dword(rp, PCI_PRIMARY_BUS, &value)) pci_write_config_dword(rp, PCI_PRIMARY_BUS, value & 0xff0000ff); - apple_pcie_t6050_port_l2(port); - WRITE_ONCE(port->link_failed, true); - apple_pcie_t6050_perst(port, true); - writel(readl(port->phy) & ~PHY_LANE_CFG_REFCLKCGEN_B31, port->phy); - apple_pcie_t6050_port_disable(port, rp); - /* An endpoint Linux powered goes off, so the next boot finds it cold */ - if (port->pwren) - gpiod_set_value_cansleep(port->pwren, 0); + apple_pcie_t6050_port_park(port, true); dev_info(pcie->dev, "port %d held for the restart%s\n", port->idx, port->pwren ? ", its endpoint powered off" : ""); pci_dev_put(rp); From 22dcb0f32e21f0de65506fc98eeb6b29dbec42ee Mon Sep 17 00:00:00 2001 From: Rolando Trevino Date: Tue, 10 Nov 2026 07:11:18 -0500 Subject: [PATCH 12/12] dt-bindings: pci: apple,pcie: allow post-init-providers for a manual-availability DART A DART marked apple,manual-availability defers its probe until the PCIe host calls apple_dart_make_available() from its own probe. The host's iommu-map makes fw_devlink create a host to DART supplier link; iommu-map is an optional supplier, enforced only under fw_devlink.strict=1, where the host then waits for the DART while the DART waits for the host and neither probes. post-init-providers is the property for this: fw_devlink ignores the link to a supplier listed in it (the flag is merged onto the link that iommu-map created). Allow it on the host node and say in the apple,manual-availability description that the host must list the DART there. No driver change is needed. Compile-tested only. Signed-off-by: Rolando Trevino Assisted-by: LLM Claude --- Documentation/devicetree/bindings/iommu/apple,dart.yaml | 5 ++++- Documentation/devicetree/bindings/pci/apple,pcie.yaml | 7 +++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/iommu/apple,dart.yaml b/Documentation/devicetree/bindings/iommu/apple,dart.yaml index 7cc4b466965bc..0bee5d822fb21 100644 --- a/Documentation/devicetree/bindings/iommu/apple,dart.yaml +++ b/Documentation/devicetree/bindings/iommu/apple,dart.yaml @@ -151,7 +151,10 @@ properties: description: > The DART's registers answer only once its client, a PCIe root complex, is up. The client's driver makes the DART available after bringing the - root complex up, and the DART probes then. + root complex up, and the DART probes then. The root complex lists the DART + in its post-init-providers property as well as in iommu-map: otherwise + fw_devlink.strict=1 makes the root complex wait for a DART that waits for + it. apple,tunable: $ref: /schemas/types.yaml#/definitions/uint32-matrix diff --git a/Documentation/devicetree/bindings/pci/apple,pcie.yaml b/Documentation/devicetree/bindings/pci/apple,pcie.yaml index 41f9a779b1e6c..78f154ce2edcd 100644 --- a/Documentation/devicetree/bindings/pci/apple,pcie.yaml +++ b/Documentation/devicetree/bindings/pci/apple,pcie.yaml @@ -147,6 +147,13 @@ properties: iommu-map: true iommu-map-mask: true + post-init-providers: + description: + The DART of a host whose iommu-map names a DART marked + apple,manual-availability, so that fw_devlink does not make the host wait + for a DART that probes only after the host has brought up its port. + $ref: /schemas/types.yaml#/definitions/phandle-array + power-domains: description: The power domains the controller needs. Internal controllers on some