From 743382e94e6e3d6813846cb0a42da99327febedd Mon Sep 17 00:00:00 2001 From: Sven Peter Date: Mon, 24 Aug 2026 18:07:56 +0200 Subject: [PATCH 01/33] usb: typec: tipd: Fix Thunderbolt altmode VDOs for cd321x The Intel VID status register is actually 9 bytes long and doesn't contain the raw VDOs but only the upper 16bits for device mode and enter mode. Shift those two fields into place and reconstruct the cable discover mode VDO from the data status registers instead. Link: https://www.ti.com/lit/ug/slvubh2b/slvubh2b.pdf Fixes: 0b31c978935f ("usb: typec: tipd: Read USB4, Thunderbolt and DisplayPort status for cd321x") Fixes: 82432bbfb9e8 ("usb: typec: tipd: Handle mode transitions for CD321x") Cc: stable@vger.kernel.org Signed-off-by: Sven Peter --- drivers/usb/typec/tipd/core.c | 16 +++++++++++++--- drivers/usb/typec/tipd/tps6598x.h | 5 ++--- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/drivers/usb/typec/tipd/core.c b/drivers/usb/typec/tipd/core.c index 5c61b7370d0d81..efc0795d8cc42f 100644 --- a/drivers/usb/typec/tipd/core.c +++ b/drivers/usb/typec/tipd/core.c @@ -600,9 +600,19 @@ static void cd321x_typec_update_mode(struct tps6598x *tps, struct cd321x_status cd321x->state.mode == TYPEC_TBT_MODE) return; - tbt_data.cable_mode = le16_to_cpu(st->intel_vid_status.cable_mode); - tbt_data.device_mode = le16_to_cpu(st->intel_vid_status.device_mode); - tbt_data.enter_vdo = le16_to_cpu(st->intel_vid_status.enter_vdo); + tbt_data.cable_mode = TBT_MODE | + TBT_SET_CABLE_SPEED(TPS_DATA_STATUS_TBT_CABLE_SPEED(st->data_status)) | + TBT_SET_CABLE_ROUNDED(TPS_DATA_STATUS_TBT_CABLE_GEN(st->data_status)); + if (st->data_status & TPS_DATA_STATUS_OPTICAL_CABLE) + tbt_data.cable_mode |= TBT_CABLE_OPTICAL; + if (st->data_status & TPS_DATA_STATUS_ACTIVE_LINK_TRAIN) + tbt_data.cable_mode |= TBT_CABLE_LINK_TRAINING; + if (st->data_status & TPS_DATA_STATUS_ACTIVE_CABLE) + tbt_data.cable_mode |= TBT_CABLE_ACTIVE_PASSIVE; + tbt_data.device_mode = TBT_MODE | + (u32)le16_to_cpu(st->intel_vid_status.device_mode) << 16; + tbt_data.enter_vdo = + (u32)le16_to_cpu(st->intel_vid_status.enter_vdo) << 16; cd321x->state.alt = cd321x->port_altmode_tbt; cd321x->state.mode = TYPEC_TBT_MODE; cd321x->state.data = &tbt_data; diff --git a/drivers/usb/typec/tipd/tps6598x.h b/drivers/usb/typec/tipd/tps6598x.h index 16e8dc0b1d207a..e3d80a8f317026 100644 --- a/drivers/usb/typec/tipd/tps6598x.h +++ b/drivers/usb/typec/tipd/tps6598x.h @@ -210,10 +210,10 @@ #define TPS_DATA_STATUS_DP_PIN_ASSIGNMENT(x) \ TPS_FIELD_GET(TPS_DATA_STATUS_DP_PIN_ASSIGNMENT_MASK, (x)) #define TPS_DATA_STATUS_TBT_CABLE_SPEED_MASK GENMASK(27, 25) -#define TPS_DATA_STATUS_TBT_CABLE_SPEED \ +#define TPS_DATA_STATUS_TBT_CABLE_SPEED(x) \ TPS_FIELD_GET(TPS_DATA_STATUS_TBT_CABLE_SPEED_MASK, (x)) #define TPS_DATA_STATUS_TBT_CABLE_GEN_MASK GENMASK(29, 28) -#define TPS_DATA_STATUS_TBT_CABLE_GEN \ +#define TPS_DATA_STATUS_TBT_CABLE_GEN(x) \ TPS_FIELD_GET(TPS_DATA_STATUS_TBT_CABLE_GEN_MASK, (x)) /* Map data status to DP spec assignments */ @@ -342,7 +342,6 @@ struct tps6598x_intel_vid_status_reg { __le32 attention_vdo; __le16 enter_vdo; __le16 device_mode; - __le16 cable_mode; } __packed; struct cd321x_status { From 2f6ec8f4f4aa0c06c4bdd83a5f4e6182ca75bc54 Mon Sep 17 00:00:00 2001 From: Sven Peter Date: Mon, 24 Aug 2026 18:07:56 +0200 Subject: [PATCH 02/33] usb: typec: tbt: Correct swapped TBT adapter type values Table F-10 of the USB Type-C Cable and Connector Specification R2.5 on page 412 defines bit 16 of the TBT3 Device Discover Mode VDO as 0 = TBT3 Adapter and 1 = TBT2 Legacy Adapter. Linux has those two swapped since their original introduction in commit ca469c292edc ("usb: typec: Add definitions for Thunderbolt 3 Alternate Mode"). ChromiumOS EC's include/usb_pd_tbt.h has them the correct way around and references the USB Type-C ECN "Thunderbolt 3 Compatibility Updates" as fixing an error where they were originally swapped which is presumably where the wrong order originally came from. I've also confirmed the correct mapping with an Apple Thunderbolt 3 to Thunderbolt 2 adapter which does set bit 16 in that VDO. Swap the two values and update all users. Also rename the old defines so that no user accidentally ends up with an inverted value. No functional change. Link: https://usb.org/document-library/usb-type-cr-cable-and-connector-specification-release-25 Link: https://chromium.googlesource.com/chromiumos/platform/ec/+/db93814b6e73c8545d23714fe0674c10814d901a/include/usb_pd_tbt.h#90 Signed-off-by: Sven Peter --- I ran into this when bringing up thunderbolt for Apple Silicon SoCs and was very confused why the condition for "tbt2 adapter" vs "tbt3 adapter" seemed to be backwards. I think the qcom pmic_glink_altmode.c actually has a bug there that was hidden and/or caused by the wrong values: It used to always set TBT_ADAPTER_TBT3 which sounds plausible but actually claims to be a legacy TBT2 adapter on the wire. I don't have the hardware or know the intention there though. The other users look correct to me since they just pass the value through. --- drivers/platform/chrome/cros_ec_typec.c | 2 +- drivers/soc/qcom/pmic_glink_altmode.c | 2 +- drivers/usb/typec/mux/intel_pmc_mux.c | 2 +- include/linux/usb/typec_tbt.h | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/platform/chrome/cros_ec_typec.c b/drivers/platform/chrome/cros_ec_typec.c index c0806c562bb931..79968edc16ed11 100644 --- a/drivers/platform/chrome/cros_ec_typec.c +++ b/drivers/platform/chrome/cros_ec_typec.c @@ -586,7 +586,7 @@ static int cros_typec_enable_tbt(struct cros_typec_data *typec, data.device_mode = TBT_MODE; if (pd_ctrl->control_flags & USB_PD_CTRL_TBT_LEGACY_ADAPTER) - data.device_mode = TBT_SET_ADAPTER(TBT_ADAPTER_TBT3); + data.device_mode = TBT_SET_ADAPTER(TBT_ADAPTER_TYPE_TBT2_LEGACY); /* Cable Discover Mode VDO */ data.cable_mode = TBT_MODE; diff --git a/drivers/soc/qcom/pmic_glink_altmode.c b/drivers/soc/qcom/pmic_glink_altmode.c index 619bad2c27eeb3..8d4f54c160c54c 100644 --- a/drivers/soc/qcom/pmic_glink_altmode.c +++ b/drivers/soc/qcom/pmic_glink_altmode.c @@ -215,7 +215,7 @@ static void pmic_glink_altmode_enable_tbt(struct pmic_glink_altmode *altmode, /* Device Discover Mode VDO */ tbt_data.device_mode = TBT_MODE; - tbt_data.device_mode |= TBT_SET_ADAPTER(TBT_ADAPTER_TBT3); + tbt_data.device_mode |= TBT_SET_ADAPTER(TBT_ADAPTER_TYPE_TBT2_LEGACY); /* Cable Discover Mode VDO */ tbt_data.cable_mode = TBT_MODE; diff --git a/drivers/usb/typec/mux/intel_pmc_mux.c b/drivers/usb/typec/mux/intel_pmc_mux.c index 1698428654abbe..45bd0515defd11 100644 --- a/drivers/usb/typec/mux/intel_pmc_mux.c +++ b/drivers/usb/typec/mux/intel_pmc_mux.c @@ -345,7 +345,7 @@ pmc_usb_mux_tbt(struct pmc_usb_port *port, struct typec_mux_state *state) req.mode_data = (port->orientation - 1) << PMC_USB_ALTMODE_ORI_SHIFT; req.mode_data |= (port->role - 1) << PMC_USB_ALTMODE_UFP_SHIFT; - if (TBT_ADAPTER(data->device_mode) == TBT_ADAPTER_TBT3) + if (TBT_ADAPTER(data->device_mode) == TBT_ADAPTER_TYPE_TBT2_LEGACY) req.mode_data |= PMC_USB_ALTMODE_TBT_TYPE; if (data->cable_mode & TBT_CABLE_OPTICAL) diff --git a/include/linux/usb/typec_tbt.h b/include/linux/usb/typec_tbt.h index 0b570f1b8bc8ee..7bdf3795ae506c 100644 --- a/include/linux/usb/typec_tbt.h +++ b/include/linux/usb/typec_tbt.h @@ -27,8 +27,8 @@ struct typec_thunderbolt_data { /* TBT3 Device Discover Mode VDO bits */ #define TBT_MODE BIT(0) #define TBT_ADAPTER(_vdo_) FIELD_GET(BIT(16), _vdo_) -#define TBT_ADAPTER_LEGACY 0 -#define TBT_ADAPTER_TBT3 1 +#define TBT_ADAPTER_TYPE_TBT3 0 +#define TBT_ADAPTER_TYPE_TBT2_LEGACY 1 #define TBT_INTEL_SPECIFIC_B0 BIT(26) #define TBT_VENDOR_SPECIFIC_B0 BIT(30) #define TBT_VENDOR_SPECIFIC_B1 BIT(31) From 3a562dc28b1e46a8f15a4afb0841832d65c69f92 Mon Sep 17 00:00:00 2001 From: Sven Peter Date: Mon, 24 Aug 2026 18:07:56 +0200 Subject: [PATCH 03/33] dt-bindings: reset: Add Apple SoC CIO reset The CIO (USB4/Thunderbolt) blocks on Apple Silicon SoCs have a reset inside the power manager that has to be deasserted before their co-processor can be booted. On t8103 each port comes with a dedicated register page, represented as a standalone node. On t600x a single register shared by all ports of a die is located in the middle of the PMGR MMIO region instead, represented as a sub-node of the PMGR syscon. Signed-off-by: Sven Peter --- .../bindings/arm/apple/apple,pmgr.yaml | 6 ++ .../bindings/reset/apple,t8103-cio-reset.yaml | 74 +++++++++++++++++++ MAINTAINERS | 1 + 3 files changed, 81 insertions(+) create mode 100644 Documentation/devicetree/bindings/reset/apple,t8103-cio-reset.yaml diff --git a/Documentation/devicetree/bindings/arm/apple/apple,pmgr.yaml b/Documentation/devicetree/bindings/arm/apple/apple,pmgr.yaml index da660fa13f7717..242e3fe5782fd9 100644 --- a/Documentation/devicetree/bindings/arm/apple/apple,pmgr.yaml +++ b/Documentation/devicetree/bindings/arm/apple/apple,pmgr.yaml @@ -62,6 +62,12 @@ patternProperties: type: object $ref: /schemas/power/apple,pmgr-pwrstate.yaml# + "reset-controller@[0-9a-f]+$": + description: + Reset controller for the CIO (USB4/Thunderbolt) blocks + type: object + $ref: /schemas/reset/apple,t8103-cio-reset.yaml# + required: - compatible - reg diff --git a/Documentation/devicetree/bindings/reset/apple,t8103-cio-reset.yaml b/Documentation/devicetree/bindings/reset/apple,t8103-cio-reset.yaml new file mode 100644 index 00000000000000..5f5be0636b369b --- /dev/null +++ b/Documentation/devicetree/bindings/reset/apple,t8103-cio-reset.yaml @@ -0,0 +1,74 @@ +# SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/reset/apple,t8103-cio-reset.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Apple SoC CIO (USB4/Thunderbolt) block reset controller + +maintainers: + - Sven Peter + +description: | + The CIO (Converged I/O) blocks on Apple Silicon SoCs have a reset + inside the power manager (PMGR) that has to be deasserted before + their co-processor can be booted. On t8103 each port comes with a + dedicated register page, represented as a standalone node. On t600x a + single register shared by all ports of a die with one request bit per + port is used instead. Since this register is located in the middle of + the PMGR MMIO region it is represented as a sub-node of the PMGR + syscon with reg used as an offset into it. + + In both cases the reset id is the CIO port index on the die. + +properties: + compatible: + oneOf: + - enum: + - apple,t8103-cio-reset + - apple,t6000-cio-reset + - items: + - const: apple,t8112-cio-reset + - const: apple,t8103-cio-reset + - items: + - const: apple,t6020-cio-reset + - const: apple,t6000-cio-reset + + reg: + maxItems: 1 + + '#reset-cells': + const: 1 + +required: + - compatible + - reg + - '#reset-cells' + +additionalProperties: false + +examples: + - | + soc { + #address-cells = <2>; + #size-cells = <2>; + + reset-controller@23b784000 { + compatible = "apple,t8103-cio-reset"; + reg = <0x2 0x3b784000 0x0 0x8000>; + #reset-cells = <1>; + }; + }; + - | + power-management@28e080000 { + compatible = "apple,t6000-pmgr", "apple,pmgr", "syscon", "simple-mfd"; + reg = <0x8e080000 0x1c000>; + #address-cells = <1>; + #size-cells = <1>; + + reset-controller@1a034 { + compatible = "apple,t6000-cio-reset"; + reg = <0x1a034 0x4>; + #reset-cells = <1>; + }; + }; diff --git a/MAINTAINERS b/MAINTAINERS index 95e6cfee170837..0fafdf69bb22f7 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2551,6 +2551,7 @@ F: Documentation/devicetree/bindings/pinctrl/apple,pinctrl.yaml F: Documentation/devicetree/bindings/power/apple* F: Documentation/devicetree/bindings/power/reset/apple,smc-reboot.yaml F: Documentation/devicetree/bindings/pwm/apple,s5l-fpwm.yaml +F: Documentation/devicetree/bindings/reset/apple,t8103-cio-reset.yaml F: Documentation/devicetree/bindings/rtc/apple,smc-rtc.yaml F: Documentation/devicetree/bindings/spi/apple,spi.yaml F: Documentation/devicetree/bindings/spmi/apple,spmi.yaml From 37403b73b44579cfbe4cd6f67874b251995eda6a Mon Sep 17 00:00:00 2001 From: Sven Peter Date: Mon, 24 Aug 2026 18:07:56 +0200 Subject: [PATCH 04/33] reset: Add Apple SoC CIO reset driver Add a driver for the reset of the CIO (USB4/Thunderbolt) blocks on Apple Silicon SoCs which has to be deasserted before their co-processor can be booted. On t8103 each port comes with a dedicated register page while t600x uses a single register with one request bit per port shared by all ports of a die inside the PMGR MMIO region. Signed-off-by: Sven Peter --- MAINTAINERS | 1 + drivers/reset/Kconfig | 11 ++ drivers/reset/Makefile | 1 + drivers/reset/reset-apple-cio.c | 182 ++++++++++++++++++++++++++++++++ 4 files changed, 195 insertions(+) create mode 100644 drivers/reset/reset-apple-cio.c diff --git a/MAINTAINERS b/MAINTAINERS index 0fafdf69bb22f7..f8a24f8243718d 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2583,6 +2583,7 @@ F: drivers/pinctrl/pinctrl-apple-gpio.c F: drivers/power/reset/macsmc-reboot.c F: drivers/power/supply/macsmc-power.c F: drivers/pwm/pwm-apple.c +F: drivers/reset/reset-apple-cio.c F: drivers/rtc/rtc-macsmc.c F: drivers/soc/apple/* F: drivers/spi/spi-apple.c diff --git a/drivers/reset/Kconfig b/drivers/reset/Kconfig index d009eb0849a3bd..6dc66bc116a4db 100644 --- a/drivers/reset/Kconfig +++ b/drivers/reset/Kconfig @@ -22,6 +22,17 @@ config RESET_A10SR This option enables support for the external reset functions for peripheral PHYs on the Altera Arria10 System Resource Chip. +config RESET_APPLE_CIO + tristate "Apple SoC CIO block reset driver" + depends on ARCH_APPLE || COMPILE_TEST + default ARCH_APPLE + select MFD_SYSCON + help + This enables the reset driver for the CIO (USB4/Thunderbolt) blocks + found on Apple Silicon SoCs. It is required to start the blocks + before the USB4/Thunderbolt host routers inside them can be brought + up by the thunderbolt driver. + config RESET_ASPEED tristate "ASPEED Reset Driver" depends on ARCH_ASPEED || COMPILE_TEST diff --git a/drivers/reset/Makefile b/drivers/reset/Makefile index 3e52569bd2768c..6554649bc139a6 100644 --- a/drivers/reset/Makefile +++ b/drivers/reset/Makefile @@ -7,6 +7,7 @@ obj-y += starfive/ obj-y += sti/ obj-y += tegra/ obj-$(CONFIG_RESET_A10SR) += reset-a10sr.o +obj-$(CONFIG_RESET_APPLE_CIO) += reset-apple-cio.o obj-$(CONFIG_RESET_ASPEED) += reset-aspeed.o obj-$(CONFIG_RESET_ATH79) += reset-ath79.o obj-$(CONFIG_RESET_AXS10X) += reset-axs10x.o diff --git a/drivers/reset/reset-apple-cio.c b/drivers/reset/reset-apple-cio.c new file mode 100644 index 00000000000000..896b3460774bd6 --- /dev/null +++ b/drivers/reset/reset-apple-cio.c @@ -0,0 +1,182 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +/* + * Apple SoC CIO (USB4/Thunderbolt) block reset driver + * + * Copyright The Asahi Linux Contributors + * + * The CIO blocks have a reset inside the power manager (PMGR) that + * has to be deasserted before their co-processor can be booted. On + * t8103 each port comes with a dedicated register page while t600x + * uses a single register shared by all ports of a die inside the PMGR + * MMIO region. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define APPLE_CIO_RESET_POLL_US 100 +#define APPLE_CIO_RESET_TIMEOUT_US 100000 + +#define T8103_CIO_CTRL_STRIDE 0x4000 +#define T8103_CIO_CTRL_INIT_REQ BIT(0) +#define T8103_CIO_CTRL_INIT_DONE BIT(2) + +#define T6000_CIO_CTRL_INIT_REQ(port) BIT(port) +#define T6000_CIO_CTRL_INIT_BUSY(port) BIT(16 + (port)) + +struct apple_cio_reset; + +struct apple_cio_reset_variant { + unsigned int nr_resets; + bool pmgr_child; + int (*deassert)(struct apple_cio_reset *priv, unsigned long id); +}; + +struct apple_cio_reset { + struct reset_controller_dev rcdev; + const struct apple_cio_reset_variant *variant; + struct regmap *regmap; + u32 offset; + struct mutex lock; +}; + +static int t8103_cio_deassert(struct apple_cio_reset *priv, unsigned long id) +{ + u32 offset = priv->offset + id * T8103_CIO_CTRL_STRIDE; + u32 val; + int ret; + + ret = regmap_write(priv->regmap, offset, T8103_CIO_CTRL_INIT_REQ); + if (ret) + return ret; + + return regmap_read_poll_timeout(priv->regmap, offset, val, + val == T8103_CIO_CTRL_INIT_DONE, + APPLE_CIO_RESET_POLL_US, + APPLE_CIO_RESET_TIMEOUT_US); +} + +static int t6000_cio_deassert(struct apple_cio_reset *priv, unsigned long id) +{ + u32 val; + int ret; + + guard(mutex)(&priv->lock); + + ret = regmap_write(priv->regmap, priv->offset, T6000_CIO_CTRL_INIT_REQ(id)); + if (ret) + return ret; + + return regmap_read_poll_timeout(priv->regmap, priv->offset, val, + !(val & T6000_CIO_CTRL_INIT_BUSY(id)), + APPLE_CIO_RESET_POLL_US, + APPLE_CIO_RESET_TIMEOUT_US); +} + +static const struct apple_cio_reset_variant apple_t8103_cio_reset = { + .nr_resets = 2, + .deassert = t8103_cio_deassert, +}; + +static const struct apple_cio_reset_variant apple_t6000_cio_reset = { + .nr_resets = 4, + .pmgr_child = true, + .deassert = t6000_cio_deassert, +}; + +static int apple_cio_reset_deassert(struct reset_controller_dev *rcdev, unsigned long id) +{ + struct apple_cio_reset *priv = + container_of(rcdev, struct apple_cio_reset, rcdev); + + return priv->variant->deassert(priv, id); +} + +static const struct reset_control_ops apple_cio_reset_ops = { + .deassert = apple_cio_reset_deassert, +}; + +static const struct regmap_config apple_cio_reset_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, +}; + +static int apple_cio_reset_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct apple_cio_reset *priv; + int ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->variant = of_device_get_match_data(dev); + + ret = devm_mutex_init(dev, &priv->lock); + if (ret) + return ret; + + if (priv->variant->pmgr_child) { + priv->regmap = syscon_node_to_regmap(dev->of_node->parent); + if (IS_ERR(priv->regmap)) + return dev_err_probe(dev, PTR_ERR(priv->regmap), + "Failed to get parent regmap"); + + ret = of_property_read_u32(dev->of_node, "reg", &priv->offset); + if (ret) + return dev_err_probe(dev, ret, "Failed to read reg offset"); + } else { + void __iomem *base; + + base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(base)) + return PTR_ERR(base); + + priv->regmap = devm_regmap_init_mmio(dev, base, + &apple_cio_reset_regmap_config); + if (IS_ERR(priv->regmap)) + return dev_err_probe(dev, PTR_ERR(priv->regmap), + "Failed to init MMIO regmap"); + } + + priv->rcdev.owner = THIS_MODULE; + priv->rcdev.ops = &apple_cio_reset_ops; + priv->rcdev.of_node = dev->of_node; + priv->rcdev.nr_resets = priv->variant->nr_resets; + + return devm_reset_controller_register(dev, &priv->rcdev); +} + +static const struct of_device_id apple_cio_reset_match[] = { + { + .compatible = "apple,t8103-cio-reset", + .data = &apple_t8103_cio_reset, + }, + { + .compatible = "apple,t6000-cio-reset", + .data = &apple_t6000_cio_reset, + }, + {}, +}; +MODULE_DEVICE_TABLE(of, apple_cio_reset_match); + +static struct platform_driver apple_cio_reset_driver = { + .driver = { + .name = "apple-cio-reset", + .of_match_table = apple_cio_reset_match, + }, + .probe = apple_cio_reset_probe, +}; +module_platform_driver(apple_cio_reset_driver); + +MODULE_AUTHOR("Sven Peter "); +MODULE_DESCRIPTION("Apple SoC CIO block reset driver"); +MODULE_LICENSE("Dual MIT/GPL"); From 392a4d952918c614cb28cf7360a4b37856036495 Mon Sep 17 00:00:00 2001 From: Sven Peter Date: Mon, 24 Aug 2026 18:07:56 +0200 Subject: [PATCH 05/33] arm64: dts: apple: Add CIO reset controllers Add the reset controllers used to start the CIO (USB4/Thunderbolt) blocks on t8103 and t600x. On t8103 each port comes with a dedicated register page represented as a standalone node. On t600x a single register shared by all ports of a die is located in the middle of the PMGR MMIO region and represented as a sub-node of its syscon. Signed-off-by: Sven Peter --- arch/arm64/boot/dts/apple/t600x-dieX.dtsi | 8 +++++++- arch/arm64/boot/dts/apple/t602x-dieX.dtsi | 8 +++++++- arch/arm64/boot/dts/apple/t8103.dtsi | 6 ++++++ arch/arm64/boot/dts/apple/t8112.dtsi | 6 ++++++ 4 files changed, 26 insertions(+), 2 deletions(-) diff --git a/arch/arm64/boot/dts/apple/t600x-dieX.dtsi b/arch/arm64/boot/dts/apple/t600x-dieX.dtsi index e48065b70e0b97..845f172929f3b2 100644 --- a/arch/arm64/boot/dts/apple/t600x-dieX.dtsi +++ b/arch/arm64/boot/dts/apple/t600x-dieX.dtsi @@ -220,7 +220,13 @@ compatible = "apple,t6000-pmgr", "apple,pmgr", "syscon", "simple-mfd"; #address-cells = <1>; #size-cells = <1>; - reg = <0x2 0x8e080000 0 0x4000>; + reg = <0x2 0x8e080000 0 0x1c000>; + + DIE_NODE(cio_reset): reset-controller@1a034 { + compatible = "apple,t6000-cio-reset"; + reg = <0x1a034 0x4>; + #reset-cells = <1>; + }; }; DIE_NODE(pmgr_east): power-management@28e580000 { diff --git a/arch/arm64/boot/dts/apple/t602x-dieX.dtsi b/arch/arm64/boot/dts/apple/t602x-dieX.dtsi index 13eb09067c4109..06cc15fa80da36 100644 --- a/arch/arm64/boot/dts/apple/t602x-dieX.dtsi +++ b/arch/arm64/boot/dts/apple/t602x-dieX.dtsi @@ -137,7 +137,13 @@ compatible = "apple,t6020-pmgr", "apple,t8103-pmgr", "syscon", "simple-mfd"; #address-cells = <1>; #size-cells = <1>; - reg = <0x2 0x8e080000 0 0x8000>; + reg = <0x2 0x8e080000 0 0xc000>; + + DIE_NODE(cio_reset): reset-controller@a02c { + compatible = "apple,t6020-cio-reset", "apple,t6000-cio-reset"; + reg = <0xa02c 0x4>; + #reset-cells = <1>; + }; }; DIE_NODE(pmgr_south): power-management@28e680000 { diff --git a/arch/arm64/boot/dts/apple/t8103.dtsi b/arch/arm64/boot/dts/apple/t8103.dtsi index 58c59075f2d2b7..b6f7f1417b04f4 100644 --- a/arch/arm64/boot/dts/apple/t8103.dtsi +++ b/arch/arm64/boot/dts/apple/t8103.dtsi @@ -1061,6 +1061,12 @@ #apple,bw-doorbell-cells = <2>; }; + cio_reset: reset-controller@23b784000 { + compatible = "apple,t8103-cio-reset"; + reg = <0x2 0x3b784000 0x0 0x8000>; + #reset-cells = <1>; + }; + pinctrl_ap: pinctrl@23c100000 { compatible = "apple,t8103-pinctrl", "apple,pinctrl"; reg = <0x2 0x3c100000 0x0 0x100000>; diff --git a/arch/arm64/boot/dts/apple/t8112.dtsi b/arch/arm64/boot/dts/apple/t8112.dtsi index 1215de82221d5d..8a30d51795d76f 100644 --- a/arch/arm64/boot/dts/apple/t8112.dtsi +++ b/arch/arm64/boot/dts/apple/t8112.dtsi @@ -1238,6 +1238,12 @@ power-domains = <&ps_pmp>, <&ps_pms_sram>; }; + cio_reset: reset-controller@23b790000 { + compatible = "apple,t8112-cio-reset", "apple,t8103-cio-reset"; + reg = <0x2 0x3b790000 0x0 0x8000>; + #reset-cells = <1>; + }; + pinctrl_ap: pinctrl@23c100000 { compatible = "apple,t8112-pinctrl", "apple,pinctrl"; reg = <0x2 0x3c100000 0x0 0x100000>; From 244b3465cbad564ec369af804043c454e7e82c2f Mon Sep 17 00:00:00 2001 From: Sven Peter Date: Mon, 24 Aug 2026 18:07:56 +0200 Subject: [PATCH 06/33] dt-bindings: usb: Add thunderbolt-switch property The USB4/Thunderbolt host routers on Apple Silicon SoCs require cable and mode details to be provided out-of-band by the Type-C port controller. Add a thunderbolt-switch property next to the existing orientation and mode switches which marks a node as handler of these notifications. Signed-off-by: Sven Peter --- Documentation/devicetree/bindings/usb/usb-switch.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Documentation/devicetree/bindings/usb/usb-switch.yaml b/Documentation/devicetree/bindings/usb/usb-switch.yaml index f77731493dc490..12f42cda5a3e6e 100644 --- a/Documentation/devicetree/bindings/usb/usb-switch.yaml +++ b/Documentation/devicetree/bindings/usb/usb-switch.yaml @@ -25,4 +25,8 @@ properties: description: Possible handler of SuperSpeed signals retiming type: boolean + thunderbolt-switch: + description: Possible handler of USB4/Thunderbolt mode switching + type: boolean + additionalProperties: true From bd5337fb3c0f93d30193a1869698c322f72561bd Mon Sep 17 00:00:00 2001 From: Sven Peter Date: Mon, 24 Aug 2026 18:07:56 +0200 Subject: [PATCH 07/33] phy: apple: atc: Support DUMMY PIPEHANDLER state in configure_pipehandler For both Thunderbolt and DisplayPort atcphy_configure_pipehandler is reached with a request to switch to the DUMMY state (i.e. usb2 only). With the current code this breaks USB2 when all four SS lanes are used for DisplayPort AltMode because the -EINVAL is passed all the way back to the phy_set_mode() call which results in tearing down xhci and dwc3 again. Let's actually handle that case correctly and also drop the default from the switch such that we get a compiler warning if another pipehandler state is ever added and forgotten here. Reported-by: Paul Cristian Closes: https://github.com/AsahiLinux/linux/pull/515 Fixes: 8e98ca1e74db ("phy: apple: Add Apple Type-C PHY") Cc: stable@vger.kernel.org Signed-off-by: Sven Peter --- drivers/phy/apple/atc.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/drivers/phy/apple/atc.c b/drivers/phy/apple/atc.c index 9eb0a1d106ff93..b361b51eb4e320 100644 --- a/drivers/phy/apple/atc.c +++ b/drivers/phy/apple/atc.c @@ -1245,7 +1245,7 @@ static int atcphy_configure_pipehandler_dummy(struct apple_atcphy *atcphy) static int atcphy_configure_pipehandler(struct apple_atcphy *atcphy, bool host) { - int ret; + int ret = -EINVAL; lockdep_assert_held(&atcphy->lock); @@ -1262,8 +1262,10 @@ static int atcphy_configure_pipehandler(struct apple_atcphy *atcphy, bool host) ret = atcphy_configure_pipehandler_dummy(atcphy); atcphy->pipehandler_up = false; break; - default: - ret = -EINVAL; + case ATCPHY_PIPEHANDLER_STATE_DUMMY: + ret = atcphy_configure_pipehandler_dummy(atcphy); + atcphy->pipehandler_up = false; + break; } return ret; From 7f79d5a78725ad12cc0ec9e2bf68be2740c2cba2 Mon Sep 17 00:00:00 2001 From: Sven Peter Date: Mon, 24 Aug 2026 18:07:56 +0200 Subject: [PATCH 08/33] phy: apple: atc: Factor out the PIPE mux sequence This sequence is already used in three places and we're about to add a fourth copy so let's factor it out to a helper. No functional change. Signed-off-by: Sven Peter --- drivers/phy/apple/atc.c | 48 ++++++++++++++++------------------------- 1 file changed, 19 insertions(+), 29 deletions(-) diff --git a/drivers/phy/apple/atc.c b/drivers/phy/apple/atc.c index b361b51eb4e320..f732c229c724a4 100644 --- a/drivers/phy/apple/atc.c +++ b/drivers/phy/apple/atc.c @@ -1018,6 +1018,19 @@ static int atcphy_pipehandler_check(struct apple_atcphy *atcphy) return 0; } +static void atcphy_pipehandler_set_mux(struct apple_atcphy *atcphy, u32 data, u32 clk) +{ + mask32(atcphy->regs.pipehandler + PIPEHANDLER_MUX_CTRL, PIPEHANDLER_MUX_CTRL_CLK, + FIELD_PREP(PIPEHANDLER_MUX_CTRL_CLK, PIPEHANDLER_MUX_CTRL_CLK_OFF)); + udelay(10); + mask32(atcphy->regs.pipehandler + PIPEHANDLER_MUX_CTRL, PIPEHANDLER_MUX_CTRL_DATA, + FIELD_PREP(PIPEHANDLER_MUX_CTRL_DATA, data)); + udelay(10); + mask32(atcphy->regs.pipehandler + PIPEHANDLER_MUX_CTRL, PIPEHANDLER_MUX_CTRL_CLK, + FIELD_PREP(PIPEHANDLER_MUX_CTRL_CLK, clk)); + udelay(10); +} + static void atcphy_clear_nonselected_phy_reset(struct apple_atcphy *atcphy) { /* Clear reset for non-selected USB3 PHY (?) */ @@ -1178,15 +1191,8 @@ static int atcphy_configure_pipehandler_usb3(struct apple_atcphy *atcphy, bool h } /* Configure PIPE mux to USB3 PHY */ - mask32(atcphy->regs.pipehandler + PIPEHANDLER_MUX_CTRL, PIPEHANDLER_MUX_CTRL_CLK, - FIELD_PREP(PIPEHANDLER_MUX_CTRL_CLK, PIPEHANDLER_MUX_CTRL_CLK_OFF)); - udelay(10); - mask32(atcphy->regs.pipehandler + PIPEHANDLER_MUX_CTRL, PIPEHANDLER_MUX_CTRL_DATA, - FIELD_PREP(PIPEHANDLER_MUX_CTRL_DATA, PIPEHANDLER_MUX_CTRL_DATA_USB3)); - udelay(10); - mask32(atcphy->regs.pipehandler + PIPEHANDLER_MUX_CTRL, PIPEHANDLER_MUX_CTRL_CLK, - FIELD_PREP(PIPEHANDLER_MUX_CTRL_CLK, PIPEHANDLER_MUX_CTRL_CLK_USB3)); - udelay(10); + atcphy_pipehandler_set_mux(atcphy, PIPEHANDLER_MUX_CTRL_DATA_USB3, + PIPEHANDLER_MUX_CTRL_CLK_USB3); /* Remove link detection override */ clear32(atcphy->regs.pipehandler + PIPEHANDLER_OVERRIDE, PIPEHANDLER_OVERRIDE_RXVALID); @@ -1221,15 +1227,8 @@ static int atcphy_configure_pipehandler_dummy(struct apple_atcphy *atcphy) dev_warn(atcphy->dev, "Failed to lock pipehandler"); /* Switch to dummy PHY */ - mask32(atcphy->regs.pipehandler + PIPEHANDLER_MUX_CTRL, PIPEHANDLER_MUX_CTRL_CLK, - FIELD_PREP(PIPEHANDLER_MUX_CTRL_CLK, PIPEHANDLER_MUX_CTRL_CLK_OFF)); - udelay(10); - mask32(atcphy->regs.pipehandler + PIPEHANDLER_MUX_CTRL, PIPEHANDLER_MUX_CTRL_DATA, - FIELD_PREP(PIPEHANDLER_MUX_CTRL_DATA, PIPEHANDLER_MUX_CTRL_DATA_DUMMY)); - udelay(10); - mask32(atcphy->regs.pipehandler + PIPEHANDLER_MUX_CTRL, PIPEHANDLER_MUX_CTRL_CLK, - FIELD_PREP(PIPEHANDLER_MUX_CTRL_CLK, PIPEHANDLER_MUX_CTRL_CLK_DUMMY)); - udelay(10); + atcphy_pipehandler_set_mux(atcphy, PIPEHANDLER_MUX_CTRL_DATA_DUMMY, + PIPEHANDLER_MUX_CTRL_CLK_DUMMY); ret = atcphy_pipehandler_unlock(atcphy); if (ret) @@ -1257,8 +1256,6 @@ static int atcphy_configure_pipehandler(struct apple_atcphy *atcphy, bool host) case ATCPHY_PIPEHANDLER_STATE_USB4: dev_warn(atcphy->dev, "ATCPHY_PIPEHANDLER_STATE_USB4 not implemented; falling back to USB2\n"); - fallthrough; - case ATCPHY_PIPEHANDLER_STATE_DUMMY: ret = atcphy_configure_pipehandler_dummy(atcphy); atcphy->pipehandler_up = false; break; @@ -1275,15 +1272,8 @@ static void atcphy_setup_pipehandler(struct apple_atcphy *atcphy) { lockdep_assert_held(&atcphy->lock); - mask32(atcphy->regs.pipehandler + PIPEHANDLER_MUX_CTRL, PIPEHANDLER_MUX_CTRL_CLK, - FIELD_PREP(PIPEHANDLER_MUX_CTRL_CLK, PIPEHANDLER_MUX_CTRL_CLK_OFF)); - udelay(10); - mask32(atcphy->regs.pipehandler + PIPEHANDLER_MUX_CTRL, PIPEHANDLER_MUX_CTRL_DATA, - FIELD_PREP(PIPEHANDLER_MUX_CTRL_DATA, PIPEHANDLER_MUX_CTRL_DATA_DUMMY)); - udelay(10); - mask32(atcphy->regs.pipehandler + PIPEHANDLER_MUX_CTRL, PIPEHANDLER_MUX_CTRL_CLK, - FIELD_PREP(PIPEHANDLER_MUX_CTRL_CLK, PIPEHANDLER_MUX_CTRL_CLK_DUMMY)); - udelay(10); + atcphy_pipehandler_set_mux(atcphy, PIPEHANDLER_MUX_CTRL_DATA_DUMMY, + PIPEHANDLER_MUX_CTRL_CLK_DUMMY); } static void atcphy_configure_lanes(struct apple_atcphy *atcphy, enum atcphy_mode mode) From 2714a76d497e53eed8da100a4c8a69d9c171bec1 Mon Sep 17 00:00:00 2001 From: Sven Peter Date: Mon, 24 Aug 2026 18:07:56 +0200 Subject: [PATCH 09/33] phy: apple: atc: Implement the USB4 pipehandler state USB3 tunneled via USB4 requires dwc3's PIPE interface to be switched to the USB4 NHI which will take care of the tunneling. Add the required bringup sequence such that USB3 tunnels work once the USB4 NHI is upstream. Signed-off-by: Sven Peter --- drivers/phy/apple/atc.c | 41 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/drivers/phy/apple/atc.c b/drivers/phy/apple/atc.c index f732c229c724a4..06896da59cd6dc 100644 --- a/drivers/phy/apple/atc.c +++ b/drivers/phy/apple/atc.c @@ -1208,6 +1208,41 @@ static int atcphy_configure_pipehandler_usb3(struct apple_atcphy *atcphy, bool h return 0; } +static int atcphy_configure_pipehandler_usb4(struct apple_atcphy *atcphy) +{ + int ret; + + ret = atcphy_pipehandler_check(atcphy); + if (ret) + return ret; + + /* Force disable link detection */ + clear32(atcphy->regs.pipehandler + PIPEHANDLER_OVERRIDE_VALUES, + PIPEHANDLER_OVERRIDE_VAL_RXDETECT0 | PIPEHANDLER_OVERRIDE_VAL_RXDETECT1); + set32(atcphy->regs.pipehandler + PIPEHANDLER_OVERRIDE, PIPEHANDLER_OVERRIDE_RXVALID); + set32(atcphy->regs.pipehandler + PIPEHANDLER_OVERRIDE, PIPEHANDLER_OVERRIDE_RXDETECT); + + ret = atcphy_pipehandler_lock(atcphy); + if (ret) { + dev_err(atcphy->dev, "Failed to lock pipehandler\n"); + return ret; + } + + /* Configure PIPE mux to the USB4/Thunderbolt controller */ + atcphy_pipehandler_set_mux(atcphy, PIPEHANDLER_MUX_CTRL_DATA_USB4, + PIPEHANDLER_MUX_CTRL_CLK_USB4); + + /* Remove link detection override */ + clear32(atcphy->regs.pipehandler + PIPEHANDLER_OVERRIDE, PIPEHANDLER_OVERRIDE_RXVALID); + clear32(atcphy->regs.pipehandler + PIPEHANDLER_OVERRIDE, PIPEHANDLER_OVERRIDE_RXDETECT); + + ret = atcphy_pipehandler_unlock(atcphy); + if (ret) + dev_warn(atcphy->dev, "Failed to unlock pipehandler\n"); + + return 0; +} + static int atcphy_configure_pipehandler_dummy(struct apple_atcphy *atcphy) { int ret; @@ -1254,10 +1289,8 @@ static int atcphy_configure_pipehandler(struct apple_atcphy *atcphy, bool host) atcphy->pipehandler_up = true; break; case ATCPHY_PIPEHANDLER_STATE_USB4: - dev_warn(atcphy->dev, - "ATCPHY_PIPEHANDLER_STATE_USB4 not implemented; falling back to USB2\n"); - ret = atcphy_configure_pipehandler_dummy(atcphy); - atcphy->pipehandler_up = false; + ret = atcphy_configure_pipehandler_usb4(atcphy); + atcphy->pipehandler_up = true; break; case ATCPHY_PIPEHANDLER_STATE_DUMMY: ret = atcphy_configure_pipehandler_dummy(atcphy); From 84ce8c5d4ca8f236cb1fc59e7112ac040d20e72f Mon Sep 17 00:00:00 2001 From: Sven Peter Date: Mon, 24 Aug 2026 18:07:56 +0200 Subject: [PATCH 10/33] usb: typec: Add thunderbolt switch On Apple Silicon the USB4/Thunderbolt host router can only be brought up after a cable has been plugged in and requires the cable details known only to the Type-C port controller. Add a thunderbolt switch similar to the existing orientation and mode switches which Type-C port drivers can use to forward these details out-of-band. Unlike those switches only a single handler per connection is supported since the cable details always go to exactly one host router. Signed-off-by: Sven Peter --- drivers/usb/typec/mux.c | 201 ++++++++++++++++++++++++++++++++++ drivers/usb/typec/mux.h | 12 ++ include/linux/usb/typec_mux.h | 114 +++++++++++++++++++ 3 files changed, 327 insertions(+) diff --git a/drivers/usb/typec/mux.c b/drivers/usb/typec/mux.c index 9b908c46bd7df9..8339846ba355e7 100644 --- a/drivers/usb/typec/mux.c +++ b/drivers/usb/typec/mux.c @@ -479,6 +479,207 @@ void *typec_mux_get_drvdata(struct typec_mux_dev *mux_dev) } EXPORT_SYMBOL_GPL(typec_mux_get_drvdata); +/* ------------------------------------------------------------------------- */ + +struct typec_thunderbolt_switch { + struct typec_thunderbolt_switch_dev *sw_dev; +}; + +static int thunderbolt_switch_fwnode_match(struct device *dev, + const void *fwnode) +{ + if (!is_typec_thunderbolt_switch_dev(dev)) + return 0; + + return device_match_fwnode(dev, fwnode); +} + +static void *typec_thunderbolt_switch_match(const struct fwnode_handle *fwnode, + const char *id, void *data) +{ + struct device *dev; + + if (id && !fwnode_property_present(fwnode, id)) + return NULL; + + dev = class_find_device(&typec_mux_class, NULL, fwnode, + thunderbolt_switch_fwnode_match); + + return dev ? to_typec_thunderbolt_switch_dev(dev) : + ERR_PTR(-EPROBE_DEFER); +} + +/** + * fwnode_typec_thunderbolt_switch_get - Find USB4/Thunderbolt switch + * @fwnode: The caller device node + * + * Finds a USB4/Thunderbolt switch linked with @fwnode. Returns a + * reference to the switch on success, NULL if no matching connection + * was found, or ERR_PTR(-EPROBE_DEFER) when a connection was found but + * the switch has not been enumerated yet. + */ +struct typec_thunderbolt_switch * +fwnode_typec_thunderbolt_switch_get(struct fwnode_handle *fwnode) +{ + struct typec_thunderbolt_switch_dev *sw_dev; + struct typec_thunderbolt_switch *sw; + void *match; + + match = fwnode_connection_find_match(fwnode, "thunderbolt-switch", NULL, + typec_thunderbolt_switch_match); + if (!match) + return NULL; + if (IS_ERR(match)) + return ERR_CAST(match); + sw_dev = match; + + sw = kzalloc_obj(*sw); + if (!sw) { + put_device(&sw_dev->dev); + return ERR_PTR(-ENOMEM); + } + + sw->sw_dev = sw_dev; + WARN_ON(!try_module_get(sw_dev->dev.parent->driver->owner)); + + return sw; +} +EXPORT_SYMBOL_GPL(fwnode_typec_thunderbolt_switch_get); + +/** + * typec_thunderbolt_switch_put - Release handle to USB4/Thunderbolt switch + * @sw: USB4/Thunderbolt switch + * + * Decrements the reference count of @sw and releases the handle. + */ +void typec_thunderbolt_switch_put(struct typec_thunderbolt_switch *sw) +{ + if (IS_ERR_OR_NULL(sw)) + return; + + module_put(sw->sw_dev->dev.parent->driver->owner); + put_device(&sw->sw_dev->dev); + kfree(sw); +} +EXPORT_SYMBOL_GPL(typec_thunderbolt_switch_put); + +/** + * typec_thunderbolt_switch_set - Forward cable details to USB4/Thunderbolt switch + * @sw: USB4/Thunderbolt switch + * @data: Cable state and details + * + * Called by the Type-C port driver to forward the cable details + * out-of-band to the switch handler whenever a USB4/Thunderbolt + * connection comes up or goes away. + */ +int typec_thunderbolt_switch_set(struct typec_thunderbolt_switch *sw, + const struct typec_thunderbolt_switch_data *data) +{ + if (IS_ERR_OR_NULL(sw)) + return 0; + + return sw->sw_dev->set(sw->sw_dev, data); +} +EXPORT_SYMBOL_GPL(typec_thunderbolt_switch_set); + +static void typec_thunderbolt_switch_release(struct device *dev) +{ + kfree(to_typec_thunderbolt_switch_dev(dev)); +} + +const struct device_type typec_thunderbolt_switch_dev_type = { + .name = "thunderbolt_switch", + .release = typec_thunderbolt_switch_release, +}; + +/** + * typec_thunderbolt_switch_register - Register USB4/Thunderbolt switch + * @parent: Parent device + * @desc: USB4/Thunderbolt switch description + * + * This function registers a handler for USB4/Thunderbolt mode switching + * which Type-C port drivers use to forward the cable details once a + * USB4/Thunderbolt connection comes up. + */ +struct typec_thunderbolt_switch_dev * +typec_thunderbolt_switch_register(struct device *parent, + const struct typec_thunderbolt_switch_desc *desc) +{ + struct typec_thunderbolt_switch_dev *sw_dev; + int ret; + + if (!desc || !desc->set) + return ERR_PTR(-EINVAL); + + sw_dev = kzalloc_obj(*sw_dev); + if (!sw_dev) + return ERR_PTR(-ENOMEM); + + sw_dev->set = desc->set; + + device_initialize(&sw_dev->dev); + sw_dev->dev.parent = parent; + sw_dev->dev.fwnode = desc->fwnode; + sw_dev->dev.class = &typec_mux_class; + sw_dev->dev.type = &typec_thunderbolt_switch_dev_type; + sw_dev->dev.driver_data = desc->drvdata; + ret = dev_set_name(&sw_dev->dev, "%s-thunderbolt-switch", + desc->name ? desc->name : dev_name(parent)); + if (ret) { + put_device(&sw_dev->dev); + return ERR_PTR(ret); + } + + ret = device_add(&sw_dev->dev); + if (ret) { + dev_err(parent, "failed to register switch (%d)\n", ret); + put_device(&sw_dev->dev); + return ERR_PTR(ret); + } + + return sw_dev; +} +EXPORT_SYMBOL_GPL(typec_thunderbolt_switch_register); + +/** + * typec_thunderbolt_switch_unregister - Unregister USB4/Thunderbolt switch + * @sw_dev: USB4/Thunderbolt switch + * + * Unregister switch that was registered with + * typec_thunderbolt_switch_register(). + */ +void typec_thunderbolt_switch_unregister(struct typec_thunderbolt_switch_dev *sw_dev) +{ + if (!IS_ERR_OR_NULL(sw_dev)) + device_unregister(&sw_dev->dev); +} +EXPORT_SYMBOL_GPL(typec_thunderbolt_switch_unregister); + +/** + * typec_thunderbolt_switch_set_drvdata - Assign private data to the switch + * @sw_dev: USB4/Thunderbolt switch + * @data: Private data + */ +void typec_thunderbolt_switch_set_drvdata(struct typec_thunderbolt_switch_dev *sw_dev, + void *data) +{ + dev_set_drvdata(&sw_dev->dev, data); +} +EXPORT_SYMBOL_GPL(typec_thunderbolt_switch_set_drvdata); + +/** + * typec_thunderbolt_switch_get_drvdata - Get the private data from the switch + * @sw_dev: USB4/Thunderbolt switch + * + * Returns the private data previously assigned with + * typec_thunderbolt_switch_set_drvdata(). + */ +void *typec_thunderbolt_switch_get_drvdata(struct typec_thunderbolt_switch_dev *sw_dev) +{ + return dev_get_drvdata(&sw_dev->dev); +} +EXPORT_SYMBOL_GPL(typec_thunderbolt_switch_get_drvdata); + const struct class typec_mux_class = { .name = "typec_mux", }; diff --git a/drivers/usb/typec/mux.h b/drivers/usb/typec/mux.h index 58f0f28b6dc84d..a2c92cab12114a 100644 --- a/drivers/usb/typec/mux.h +++ b/drivers/usb/typec/mux.h @@ -23,5 +23,17 @@ extern const struct device_type typec_mux_dev_type; #define is_typec_switch_dev(dev) ((dev)->type == &typec_switch_dev_type) #define is_typec_mux_dev(dev) ((dev)->type == &typec_mux_dev_type) +#define is_typec_thunderbolt_switch_dev(dev) \ + ((dev)->type == &typec_thunderbolt_switch_dev_type) + +struct typec_thunderbolt_switch_dev { + struct device dev; + typec_thunderbolt_switch_set_fn_t set; +}; + +#define to_typec_thunderbolt_switch_dev(_dev_) \ + container_of(_dev_, struct typec_thunderbolt_switch_dev, dev) + +extern const struct device_type typec_thunderbolt_switch_dev_type; #endif /* __USB_TYPEC_MUX__ */ diff --git a/include/linux/usb/typec_mux.h b/include/linux/usb/typec_mux.h index aa9ebb7e2fe0bb..0726407bb6240e 100644 --- a/include/linux/usb/typec_mux.h +++ b/include/linux/usb/typec_mux.h @@ -6,6 +6,7 @@ #include #include #include +#include struct device; struct typec_mux; @@ -141,4 +142,117 @@ static inline struct typec_mux *typec_mux_get(struct device *dev) return fwnode_typec_mux_get(dev_fwnode(dev)); } +struct typec_thunderbolt_switch; +struct typec_thunderbolt_switch_dev; + +enum typec_thunderbolt_switch_state { + TYPEC_THUNDERBOLT_SWITCH_OFF, + TYPEC_THUNDERBOLT_SWITCH_TBT, + TYPEC_THUNDERBOLT_SWITCH_USB4, +}; + +/** + * struct typec_thunderbolt_switch_data - Type-C Thunderbolt/USB4 Switch Data + * @state: Switch state (TBT, USB4, or OFF) + * @orientation: Orientation of the connection + * @tbt: Thunderbolt 3 specific data, only valid in + * TYPEC_THUNDERBOLT_SWITCH_TBT + * @usb4: Enter_USB data, only valid in TYPEC_THUNDERBOLT_SWITCH_USB4 + */ +struct typec_thunderbolt_switch_data { + enum typec_thunderbolt_switch_state state; + enum typec_orientation orientation; + union { + struct typec_thunderbolt_data tbt; + struct enter_usb_data usb4; + }; +}; + +typedef int (*typec_thunderbolt_switch_set_fn_t)(struct typec_thunderbolt_switch_dev *sw, + const struct typec_thunderbolt_switch_data *data); + +/** + * struct typec_thunderbolt_switch_desc - USB4/Thunderbolt switch description + * @fwnode: The fwnode the switch is associated with + * @set: Callback invoked with the cable details on connection changes + * @name: Optional switch name, the parent device name is used if not set + * @drvdata: Private data + */ +struct typec_thunderbolt_switch_desc { + struct fwnode_handle *fwnode; + typec_thunderbolt_switch_set_fn_t set; + const char *name; + void *drvdata; +}; + +#if IS_ENABLED(CONFIG_TYPEC) + +struct typec_thunderbolt_switch * +fwnode_typec_thunderbolt_switch_get(struct fwnode_handle *fwnode); +void typec_thunderbolt_switch_put(struct typec_thunderbolt_switch *sw); +int typec_thunderbolt_switch_set(struct typec_thunderbolt_switch *sw, + const struct typec_thunderbolt_switch_data *data); + +struct typec_thunderbolt_switch_dev * +typec_thunderbolt_switch_register(struct device *parent, + const struct typec_thunderbolt_switch_desc *desc); +void typec_thunderbolt_switch_unregister(struct typec_thunderbolt_switch_dev *sw); + +void typec_thunderbolt_switch_set_drvdata(struct typec_thunderbolt_switch_dev *sw, + void *data); +void * +typec_thunderbolt_switch_get_drvdata(struct typec_thunderbolt_switch_dev *sw); + +#else + +static inline struct typec_thunderbolt_switch * +fwnode_typec_thunderbolt_switch_get(struct fwnode_handle *fwnode) +{ + return NULL; +} + +static inline void +typec_thunderbolt_switch_put(struct typec_thunderbolt_switch *sw) +{ +} + +static inline int +typec_thunderbolt_switch_set(struct typec_thunderbolt_switch *sw, + const struct typec_thunderbolt_switch_data *data) +{ + return 0; +} + +static inline struct typec_thunderbolt_switch_dev * +typec_thunderbolt_switch_register(struct device *parent, + const struct typec_thunderbolt_switch_desc *desc) +{ + return ERR_PTR(-EOPNOTSUPP); +} + +static inline void +typec_thunderbolt_switch_unregister(struct typec_thunderbolt_switch_dev *sw) +{ +} + +static inline void +typec_thunderbolt_switch_set_drvdata(struct typec_thunderbolt_switch_dev *sw, + void *data) +{ +} + +static inline void * +typec_thunderbolt_switch_get_drvdata(struct typec_thunderbolt_switch_dev *sw) +{ + return NULL; +} + +#endif /* CONFIG_TYPEC */ + +static inline struct typec_thunderbolt_switch * +typec_thunderbolt_switch_get(struct device *dev) +{ + return fwnode_typec_thunderbolt_switch_get(dev_fwnode(dev)); +} + #endif /* __USB_TYPEC_MUX */ From fc76d0f20c29e0fc5db14fc8d38becae62e44726 Mon Sep 17 00:00:00 2001 From: Sven Peter Date: Mon, 24 Aug 2026 18:07:56 +0200 Subject: [PATCH 11/33] usb: typec: tipd: Hook up tbt switch for Apple platforms Look up the thunderbolt switch on Apple platforms and forward cable details whenever a Thunderbolt or USB4 connection comes up or disappears. Signed-off-by: Sven Peter --- drivers/usb/typec/tipd/core.c | 35 +++++++++++++++++++++++++++++++ drivers/usb/typec/tipd/tps6598x.h | 1 + 2 files changed, 36 insertions(+) diff --git a/drivers/usb/typec/tipd/core.c b/drivers/usb/typec/tipd/core.c index efc0795d8cc42f..58285c511cfeff 100644 --- a/drivers/usb/typec/tipd/core.c +++ b/drivers/usb/typec/tipd/core.c @@ -546,6 +546,9 @@ static void tps6598x_handle_plug_event(struct tps6598x *tps, u32 status) static void cd321x_typec_update_mode(struct tps6598x *tps, struct cd321x_status *st) { struct cd321x *cd321x = container_of(tps, struct cd321x, tps); + struct typec_thunderbolt_switch_data tbt_switch_data = { + .state = TYPEC_THUNDERBOLT_SWITCH_OFF, + }; if (!(st->data_status & TPS_DATA_STATUS_DATA_CONNECTION)) { if (cd321x->state.mode == TYPEC_STATE_SAFE) @@ -553,6 +556,7 @@ static void cd321x_typec_update_mode(struct tps6598x *tps, struct cd321x_status cd321x->state.alt = NULL; cd321x->state.mode = TYPEC_STATE_SAFE; cd321x->state.data = NULL; + typec_thunderbolt_switch_set(cd321x->tbt_switch, &tbt_switch_data); typec_mux_set(cd321x->mux, &cd321x->state); } else if (st->data_status & TPS_DATA_STATUS_DP_CONNECTION) { struct typec_displayport_data dp_data; @@ -592,6 +596,7 @@ static void cd321x_typec_update_mode(struct tps6598x *tps, struct cd321x_status cd321x->state.alt = cd321x->port_altmode_dp; cd321x->state.data = &dp_data; cd321x->state.mode = mode; + typec_thunderbolt_switch_set(cd321x->tbt_switch, &tbt_switch_data); typec_mux_set(cd321x->mux, &cd321x->state); } else if (st->data_status & TPS_DATA_STATUS_TBT_CONNECTION) { struct typec_thunderbolt_data tbt_data; @@ -617,6 +622,13 @@ static void cd321x_typec_update_mode(struct tps6598x *tps, struct cd321x_status cd321x->state.mode = TYPEC_TBT_MODE; cd321x->state.data = &tbt_data; typec_mux_set(cd321x->mux, &cd321x->state); + + tbt_switch_data.state = TYPEC_THUNDERBOLT_SWITCH_TBT; + tbt_switch_data.tbt = tbt_data; + tbt_switch_data.orientation = TPS_STATUS_TO_UPSIDE_DOWN(st->status) ? + TYPEC_ORIENTATION_REVERSE : + TYPEC_ORIENTATION_NORMAL; + typec_thunderbolt_switch_set(cd321x->tbt_switch, &tbt_switch_data); } else if (st->data_status & CD321X_DATA_STATUS_USB4_CONNECTION) { struct enter_usb_data eusb_data; @@ -631,12 +643,20 @@ static void cd321x_typec_update_mode(struct tps6598x *tps, struct cd321x_status cd321x->state.data = &eusb_data; cd321x->state.mode = TYPEC_MODE_USB4; typec_mux_set(cd321x->mux, &cd321x->state); + + tbt_switch_data.state = TYPEC_THUNDERBOLT_SWITCH_USB4; + tbt_switch_data.usb4 = eusb_data; + tbt_switch_data.orientation = TPS_STATUS_TO_UPSIDE_DOWN(st->status) ? + TYPEC_ORIENTATION_REVERSE : + TYPEC_ORIENTATION_NORMAL; + typec_thunderbolt_switch_set(cd321x->tbt_switch, &tbt_switch_data); } else { if (cd321x->state.alt == NULL && cd321x->state.mode == TYPEC_STATE_USB) return; cd321x->state.alt = NULL; cd321x->state.mode = TYPEC_STATE_USB; cd321x->state.data = NULL; + typec_thunderbolt_switch_set(cd321x->tbt_switch, &tbt_switch_data); typec_mux_set(cd321x->mux, &cd321x->state); } @@ -650,6 +670,9 @@ static void cd321x_update_work(struct work_struct *work) struct cd321x, update_work); struct tps6598x *tps = &cd321x->tps; struct cd321x_status st; + struct typec_thunderbolt_switch_data tbt_switch_data = { + .state = TYPEC_THUNDERBOLT_SWITCH_OFF, + }; guard(mutex)(&tps->lock); @@ -708,6 +731,7 @@ static void cd321x_update_work(struct work_struct *work) /* If there was a disconnection, set PHY to off */ if (!new_connected || was_disconnected) { + typec_thunderbolt_switch_set(cd321x->tbt_switch, &tbt_switch_data); cd321x->state.alt = NULL; cd321x->state.mode = TYPEC_STATE_SAFE; cd321x->state.data = NULL; @@ -1217,6 +1241,12 @@ cd321x_register_port(struct tps6598x *tps, struct fwnode_handle *fwnode) if (!IS_ERR_OR_NULL(connector_fwnode)) cd321x->connector_fwnode = connector_fwnode; + cd321x->tbt_switch = fwnode_typec_thunderbolt_switch_get(fwnode); + if (IS_ERR(cd321x->tbt_switch)) { + ret = PTR_ERR(cd321x->tbt_switch); + goto err_unregister_mux; + } + cd321x->state.alt = NULL; cd321x->state.mode = TYPEC_STATE_SAFE; cd321x->state.data = NULL; @@ -1224,6 +1254,9 @@ cd321x_register_port(struct tps6598x *tps, struct fwnode_handle *fwnode) return 0; +err_unregister_mux: + typec_mux_put(cd321x->mux); + cd321x->mux = NULL; err_unregister_altmodes: typec_unregister_altmode(cd321x->port_altmode_dp); typec_unregister_altmode(cd321x->port_altmode_tbt); @@ -1245,6 +1278,8 @@ cd321x_unregister_port(struct tps6598x *tps) { struct cd321x *cd321x = container_of(tps, struct cd321x, tps); + typec_thunderbolt_switch_put(cd321x->tbt_switch); + cd321x->tbt_switch = NULL; typec_mux_put(cd321x->mux); cd321x->mux = NULL; typec_unregister_altmode(cd321x->port_altmode_dp); diff --git a/drivers/usb/typec/tipd/tps6598x.h b/drivers/usb/typec/tipd/tps6598x.h index e3d80a8f317026..672233ecbfb97f 100644 --- a/drivers/usb/typec/tipd/tps6598x.h +++ b/drivers/usb/typec/tipd/tps6598x.h @@ -368,6 +368,7 @@ struct cd321x { struct typec_mux *mux; struct typec_mux_state state; + struct typec_thunderbolt_switch *tbt_switch; struct cd321x_status update_status; struct delayed_work update_work; From 2e2b60f29328f3445128313fb8aff7e215f3d955 Mon Sep 17 00:00:00 2001 From: Sven Peter Date: Mon, 24 Aug 2026 18:07:56 +0200 Subject: [PATCH 12/33] dt-bindings: thunderbolt: Add Apple USB4/Thunderbolt NHI The Native Host Interface (NHI) provides the TX and RX rings used by the software connection manager on Apple Silicon SoCs. The NHI is part of the ACIO host router and is exposed to the main SoC bus while the ACIO co-processor is running. T6000 and T6020 use one DART stream for the TX and RX control rings and one shared by all other rings, which are used for XDomain connections. This likely improves the isolation of those host-to-host connections. The M3 generation uses a slightly insane set of 24 IOMMU entries. These are probably one DART stream for each TX and RX ring. Signed-off-by: Sven Peter --- .../thunderbolt/apple,t8103-usb4-nhi.yaml | 149 ++++++++++++++++++ MAINTAINERS | 1 + 2 files changed, 150 insertions(+) create mode 100644 Documentation/devicetree/bindings/thunderbolt/apple,t8103-usb4-nhi.yaml diff --git a/Documentation/devicetree/bindings/thunderbolt/apple,t8103-usb4-nhi.yaml b/Documentation/devicetree/bindings/thunderbolt/apple,t8103-usb4-nhi.yaml new file mode 100644 index 00000000000000..4f95132cebf303 --- /dev/null +++ b/Documentation/devicetree/bindings/thunderbolt/apple,t8103-usb4-nhi.yaml @@ -0,0 +1,149 @@ +# SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/thunderbolt/apple,t8103-usb4-nhi.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Apple Silicon USB4/Thunderbolt Native Host Interface (NHI) + +maintainers: + - Sven Peter + +description: | + On Apple Silicon SoCs the Native Host Interface (NHI) is part of the ACIO + host router. It provides the transmit and receive rings used by the software + connection manager. + + The ACIO co-processor exposes its registers to the main SoC bus while it is + running. The NHI is therefore represented as a child of the ACIO node. + +properties: + compatible: + oneOf: + - const: apple,t8103-usb4-nhi + - items: + - enum: + - apple,t6000-usb4-nhi + - apple,t6020-usb4-nhi + - apple,t6030-usb4-nhi + - apple,t6031-usb4-nhi + - apple,t8112-usb4-nhi + - apple,t8122-usb4-nhi + - const: apple,t8103-usb4-nhi + + reg: + items: + - description: NHI registers with the transmit and receive rings + - description: + Protocol Defined Field (PDF) bitmasks selecting which frames are + accepted by each receive ring + + reg-names: + items: + - const: nhi + - const: pdf + + interrupts: + description: + One interrupt for each of the 12 transmit rings followed by one for each + of the 12 receive rings. + maxItems: 24 + + interrupt-names: + items: + - const: txring0 + - const: txring1 + - const: txring2 + - const: txring3 + - const: txring4 + - const: txring5 + - const: txring6 + - const: txring7 + - const: txring8 + - const: txring9 + - const: txring10 + - const: txring11 + - const: rxring0 + - const: rxring1 + - const: rxring2 + - const: rxring3 + - const: rxring4 + - const: rxring5 + - const: rxring6 + - const: rxring7 + - const: rxring8 + - const: rxring9 + - const: rxring10 + - const: rxring11 + + iommus: + minItems: 1 + maxItems: 24 + description: | + T6000 and T6020 use two IOMMUs: The first is used for transmit ring 0 + and receive ring 0 which carry control traffic. The second is shared by + all other rings which are only used for XDomain connections to other + hosts. + + T6030, T6031 and T8122 use 24 entries. These are most likely one DART + stream for each of the 12 transmit and 12 receive rings. + + apple,thunderbolt-drom: + $ref: /schemas/types.yaml#/definitions/uint8-array + description: + DROM of the host router built by the bootloader from Apple's Device Tree. + + apple,tunable-nhi: + $ref: /schemas/types.yaml#/definitions/uint32-matrix + items: + items: + - description: Register offset + - description: Mask of bits to clear + - description: Bits to set + description: | + List of (register offset, mask, value) tuples copied from Apple's Device + Tree, converted by the bootloader and used to configure the NHI after + the co-processor has booted. + +required: + - compatible + - reg + - reg-names + - interrupts + - interrupt-names + - iommus + - apple,thunderbolt-drom + +allOf: + - if: + properties: + compatible: + contains: + enum: + - apple,t6030-usb4-nhi + - apple,t6031-usb4-nhi + - apple,t8122-usb4-nhi + then: + properties: + iommus: + minItems: 24 + maxItems: 24 + else: + if: + properties: + compatible: + contains: + enum: + - apple,t6000-usb4-nhi + - apple,t6020-usb4-nhi + then: + properties: + iommus: + minItems: 2 + maxItems: 2 + else: + properties: + iommus: + maxItems: 1 + +additionalProperties: false diff --git a/MAINTAINERS b/MAINTAINERS index f8a24f8243718d..2e264fab491880 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2555,6 +2555,7 @@ F: Documentation/devicetree/bindings/reset/apple,t8103-cio-reset.yaml F: Documentation/devicetree/bindings/rtc/apple,smc-rtc.yaml F: Documentation/devicetree/bindings/spi/apple,spi.yaml F: Documentation/devicetree/bindings/spmi/apple,spmi.yaml +F: Documentation/devicetree/bindings/thunderbolt/apple,t8103-usb4-nhi.yaml F: Documentation/devicetree/bindings/usb/apple,dwc3.yaml F: Documentation/devicetree/bindings/usb/apple,sn201202x.yaml F: Documentation/devicetree/bindings/watchdog/apple,wdt.yaml From ecfa1787557c2840eb5e932180b9a21753c31f80 Mon Sep 17 00:00:00 2001 From: Sven Peter Date: Mon, 24 Aug 2026 18:07:56 +0200 Subject: [PATCH 13/33] dt-bindings: thunderbolt: Add Apple USB4/Thunderbolt host router Each Type-C port with USB4/Thunderbolt support on Apple Silicon SoCs comes with a host router called ACIO. It contains a Cortex-M3 co-processor and the hardware blocks implementing the USB4 router. The co-processor exposes the NHI and its DART to the main SoC bus while it is running. ACIO can only be powered on after the Type-C PHY has been switched to USB4/Thunderbolt mode. The Type-C controller provides the cable information required to boot it through the thunderbolt-switch connection. Signed-off-by: Sven Peter --- .../thunderbolt/apple,t8103-usb4-acio.yaml | 218 ++++++++++++++++++ MAINTAINERS | 1 + 2 files changed, 219 insertions(+) create mode 100644 Documentation/devicetree/bindings/thunderbolt/apple,t8103-usb4-acio.yaml diff --git a/Documentation/devicetree/bindings/thunderbolt/apple,t8103-usb4-acio.yaml b/Documentation/devicetree/bindings/thunderbolt/apple,t8103-usb4-acio.yaml new file mode 100644 index 00000000000000..6674c7dde2b514 --- /dev/null +++ b/Documentation/devicetree/bindings/thunderbolt/apple,t8103-usb4-acio.yaml @@ -0,0 +1,218 @@ +# SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/thunderbolt/apple,t8103-usb4-acio.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Apple Silicon USB4/Thunderbolt Host Router (ACIO) + +maintainers: + - Sven Peter + +description: | + Each Type-C port with USB4/Thunderbolt support on Apple Silicon SoCs has a + host router complex called ACIO (Apple Converged I/O). It contains a + Cortex-M3 co-processor, the hardware blocks implementing the USB4 router + and its PCIe, USB3 and DisplayPort adapters. + + The co-processor exposes the Native Host Interface (NHI) and its DART IOMMU + to the main SoC bus once it is running. They are therefore represented as + child nodes. All child registers are located in a single 16 MiB MMIO window + and use addresses relative to this window. + + ACIO can only be powered on after the Type-C PHY has been switched to + USB4/Thunderbolt mode. Before the connection to the remote device can be + established the Type-C PD controller has to pass the cable information + through the thunderbolt-switch connection. + +properties: + compatible: + oneOf: + - const: apple,t8103-usb4-acio + - items: + - enum: + - apple,t6000-usb4-acio + - apple,t6020-usb4-acio + - apple,t6030-usb4-acio + - apple,t6031-usb4-acio + - apple,t8112-usb4-acio + - apple,t8122-usb4-acio + - const: apple,t8103-usb4-acio + + reg: + items: + - description: Root complex and co-processor control registers + - description: SRAM used for communication with the co-processor + + reg-names: + items: + - const: rc + - const: sram + + mboxes: + maxItems: 1 + description: Mailbox used for RTKit communication with the co-processor + + resets: + maxItems: 1 + description: Reset used to start the ACIO block before booting the + co-processor + + power-domains: + minItems: 3 + maxItems: 3 + + thunderbolt-switch: true + + apple,tunable-rc: + $ref: /schemas/types.yaml#/definitions/uint32-matrix + items: + items: + - description: Register offset + - description: Mask of bits to clear + - description: Bits to set + description: | + List of (register offset, mask, value) tuples copied from Apple's Device + Tree, converted by the bootloader and used to configure the root complex + after the co-processor has booted. + + '#address-cells': + const: 1 + + '#size-cells': + const: 1 + + ranges: + maxItems: 1 + description: Single 16 MiB MMIO window containing all child registers + + nonposted-mmio: true + + ports: + $ref: /schemas/graph.yaml#/properties/ports + description: + OF graph ports for the USB4 router. The port numbers correspond to the + USB4 router adapter numbers. + properties: + port@1: + $ref: /schemas/graph.yaml#/properties/port + description: USB4 port connected to the USB Type-C connector + + # port@2 is the second, unused USB4 port of the host router + + port@3: + $ref: /schemas/graph.yaml#/properties/port + description: PCIe adapter + + port@4: + $ref: /schemas/graph.yaml#/properties/port + description: USB3 adapter + + port@5: + $ref: /schemas/graph.yaml#/properties/port + description: First DP IN adapter + + port@6: + $ref: /schemas/graph.yaml#/properties/port + description: Second DP IN adapter + + required: + - port@1 + + iommu@a80000: + $ref: /schemas/iommu/apple,dart.yaml# + description: DART IOMMU exposed by ACIO and used for DMA from the NHI + + nhi@f00000: + $ref: /schemas/thunderbolt/apple,t8103-usb4-nhi.yaml# + description: Native Host Interface exposed by ACIO + +required: + - compatible + - reg + - reg-names + - mboxes + - power-domains + - resets + - thunderbolt-switch + - '#address-cells' + - '#size-cells' + - ranges + - nonposted-mmio + - ports + - iommu@a80000 + - nhi@f00000 + +allOf: + - $ref: /schemas/usb/usb-switch.yaml# + +additionalProperties: false + +examples: + - | + soc { + #address-cells = <2>; + #size-cells = <2>; + + cio@501ac0000 { + compatible = "apple,t8103-usb4-acio"; + reg = <0x5 0x01ac0000 0x0 0xc000>, + <0x5 0x01080000 0x0 0x20000>; + reg-names = "rc", "sram"; + mboxes = <&usb4_1_mbox>; + power-domains = <&ps_atc1_cio>, + <&ps_atc1_cio_pcie>, + <&ps_atc1_cio_usb>; + resets = <&cio_reset 1>; + thunderbolt-switch; + pinctrl-0 = <&acio1_pins>; + pinctrl-names = "default"; + #address-cells = <1>; + #size-cells = <1>; + ranges = <0x0 0x5 0x01000000 0x1000000>; + nonposted-mmio; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@1 { + reg = <1>; + endpoint { + remote-endpoint = <&typec1_con_tbt>; + }; + }; + }; + + usb4_1_dart: iommu@a80000 { + compatible = "apple,t8103-dart"; + reg = <0xa80000 0x4000>; + interrupt-parent = <&aic>; + interrupts = <1 890 4>; + #iommu-cells = <1>; + }; + + nhi@f00000 { + compatible = "apple,t8103-usb4-nhi"; + reg = <0xf00000 0x100000>, + <0xdb0000 0x30004>; + reg-names = "nhi", "pdf"; + interrupt-parent = <&aic>; + interrupts = <1 863 4>, <1 864 4>, <1 865 4>, <1 866 4>, + <1 867 4>, <1 868 4>, <1 869 4>, <1 870 4>, + <1 871 4>, <1 872 4>, <1 873 4>, <1 874 4>, + <1 875 4>, <1 876 4>, <1 877 4>, <1 878 4>, + <1 879 4>, <1 880 4>, <1 881 4>, <1 882 4>, + <1 883 4>, <1 884 4>, <1 885 4>, <1 886 4>; + 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"; + iommus = <&usb4_1_dart 1>; + /* To be filled by the loader */ + apple,thunderbolt-drom = [00]; + }; + }; + }; diff --git a/MAINTAINERS b/MAINTAINERS index 2e264fab491880..b2535bb35b5441 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2555,6 +2555,7 @@ F: Documentation/devicetree/bindings/reset/apple,t8103-cio-reset.yaml F: Documentation/devicetree/bindings/rtc/apple,smc-rtc.yaml F: Documentation/devicetree/bindings/spi/apple,spi.yaml F: Documentation/devicetree/bindings/spmi/apple,spmi.yaml +F: Documentation/devicetree/bindings/thunderbolt/apple,t8103-usb4-acio.yaml F: Documentation/devicetree/bindings/thunderbolt/apple,t8103-usb4-nhi.yaml F: Documentation/devicetree/bindings/usb/apple,dwc3.yaml F: Documentation/devicetree/bindings/usb/apple,sn201202x.yaml From fbf221d401e2216df5bf0b1c06509512a51627ef Mon Sep 17 00:00:00 2001 From: Sven Peter Date: Mon, 24 Aug 2026 18:07:56 +0200 Subject: [PATCH 14/33] thunderbolt: Try reading host DROM from device tree first On Apple Silicon SoCs the DROM is provided by a device tree property. Try reading from that property first. Signed-off-by: Sven Peter --- drivers/thunderbolt/eeprom.c | 32 +++++++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/drivers/thunderbolt/eeprom.c b/drivers/thunderbolt/eeprom.c index e50503bb20dc8a..c72092b5cb9ed9 100644 --- a/drivers/thunderbolt/eeprom.c +++ b/drivers/thunderbolt/eeprom.c @@ -468,14 +468,14 @@ static void tb_switch_drom_free(struct tb_switch *sw) } /* - * tb_drom_copy_efi - copy drom supplied by EFI to sw->drom if present + * tb_drom_copy_property - copy drom from a device property if present */ -static int tb_drom_copy_efi(struct tb_switch *sw, u16 *size) +static int tb_drom_copy_property(struct tb_switch *sw, const char *name, u16 *size) { struct device *dev = &sw->tb->nhi->pdev->dev; int len, res; - len = device_property_count_u8(dev, "ThunderboltDROM"); + len = device_property_count_u8(dev, name); if (len < 0 || len < sizeof(struct tb_drom_header)) return -EINVAL; @@ -483,8 +483,7 @@ static int tb_drom_copy_efi(struct tb_switch *sw, u16 *size) if (res) return res; - res = device_property_read_u8_array(dev, "ThunderboltDROM", sw->drom, - len); + res = device_property_read_u8_array(dev, name, sw->drom, len); if (res) goto err; @@ -500,6 +499,22 @@ static int tb_drom_copy_efi(struct tb_switch *sw, u16 *size) return -EINVAL; } +/* + * tb_drom_copy_of_apple - copy drom supplied by the device tree for Apple Silicon to sw->drom. + */ +static int tb_drom_copy_of_apple(struct tb_switch *sw, u16 *size) +{ + return tb_drom_copy_property(sw, "apple,thunderbolt-drom", size); +} + +/* + * tb_drom_copy_efi - copy drom supplied by EFI to sw->drom if present + */ +static int tb_drom_copy_efi(struct tb_switch *sw, u16 *size) +{ + return tb_drom_copy_property(sw, "ThunderboltDROM", size); +} + static int tb_drom_copy_nvm(struct tb_switch *sw, u16 *size) { u16 drom_offset; @@ -675,6 +690,13 @@ static int tb_drom_host_read(struct tb_switch *sw) { u16 size; + /* + * Apple Silicon machines always get their host DROM from the Device Tree, + * so make sure to try reading it first before any other method. + */ + if (!tb_drom_copy_of_apple(sw, &size)) + return tb_drom_parse(sw, size); + if (tb_switch_is_usb4(sw)) { usb4_switch_read_uid(sw, &sw->uid); if (!usb4_copy_drom(sw, &size)) From 10ffa969482b53d4586de3f7974f598aadd035db Mon Sep 17 00:00:00 2001 From: Sven Peter Date: Mon, 24 Aug 2026 18:07:56 +0200 Subject: [PATCH 15/33] thunderbolt: switch: Don't read the UID if we already know it On Apple Silicon the host router's UID comes from the DROM provided by the device tree and is already known by the time tb_switch_set_uuid() runs. Skip reading it from the config space again in that case. For other USB4 hosts this only drops a duplicate read because tb_drom_host_read() has already filled in the UID from ROUTER_CS_7 before tb_switch_set_uuid() runs. Device routers keep reading it unconditionally. Signed-off-by: Sven Peter --- drivers/thunderbolt/switch.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c index c2ad58b19e7b1a..4afbd87ab05937 100644 --- a/drivers/thunderbolt/switch.c +++ b/drivers/thunderbolt/switch.c @@ -2705,9 +2705,11 @@ static int tb_switch_set_uuid(struct tb_switch *sw) return 0; if (tb_switch_is_usb4(sw)) { - ret = usb4_switch_read_uid(sw, &sw->uid); - if (ret) - return ret; + if (tb_route(sw) || !sw->uid) { + ret = usb4_switch_read_uid(sw, &sw->uid); + if (ret) + return ret; + } uid = true; } else { /* From c18bfe2bb6c106e984c8f4c4bbb2dde50e462f7f Mon Sep 17 00:00:00 2001 From: Sven Peter Date: Mon, 24 Aug 2026 18:07:56 +0200 Subject: [PATCH 16/33] thunderbolt: Allocate ring HopID before requesting the ring interrupt The Apple NHI has one interrupt per ring and request_ring_irq picks it based on ring->hop which is still -1 at this point for rings allocated with an automatic HopID. Allocate the HopID first and release it again if the interrupt request fails. Signed-off-by: Sven Peter --- drivers/thunderbolt/nhi.c | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/drivers/thunderbolt/nhi.c b/drivers/thunderbolt/nhi.c index 2bb2e79ca3cb35..44afe699e94d35 100644 --- a/drivers/thunderbolt/nhi.c +++ b/drivers/thunderbolt/nhi.c @@ -576,6 +576,16 @@ static int nhi_alloc_hop(struct tb_nhi *nhi, struct tb_ring *ring) return ret; } +static void nhi_free_hop(struct tb_nhi *nhi, struct tb_ring *ring) +{ + guard(spinlock_irq)(&nhi->lock); + + if (ring->is_tx) + nhi->tx_rings[ring->hop] = NULL; + else + nhi->rx_rings[ring->hop] = NULL; +} + static struct tb_ring *tb_ring_alloc(struct tb_nhi *nhi, u32 hop, int size, bool transmit, unsigned int flags, int e2e_tx_hop, u16 sof_mask, u16 eof_mask, @@ -616,16 +626,16 @@ static struct tb_ring *tb_ring_alloc(struct tb_nhi *nhi, u32 hop, int size, if (!ring->descriptors) goto err_free_ring; - if (ring_request_msix(ring, flags & RING_FLAG_NO_SUSPEND)) + if (nhi_alloc_hop(nhi, ring)) goto err_free_descs; - if (nhi_alloc_hop(nhi, ring)) - goto err_release_msix; + if (ring_request_msix(ring, flags & RING_FLAG_NO_SUSPEND)) + goto err_free_hop; return ring; -err_release_msix: - ring_release_msix(ring); +err_free_hop: + nhi_free_hop(nhi, ring); err_free_descs: dma_free_coherent(&ring->nhi->pdev->dev, ring->size * sizeof(*ring->descriptors), From 1e88d65938b390d50ddedb5a8f6c8ef89c076914 Mon Sep 17 00:00:00 2001 From: Sven Peter Date: Mon, 24 Aug 2026 18:07:56 +0200 Subject: [PATCH 17/33] thunderbolt: Add ring_interrupt_active to tb_nhi_ops The Apple NHI enables and disables ring interrupts differently. Add an optional tb_nhi_ops hook to override ring_interrupt_active which falls back to the standard USB4 method when it is not set. Signed-off-by: Sven Peter --- drivers/thunderbolt/nhi.c | 12 ++++++++++-- drivers/thunderbolt/nhi.h | 16 ++++++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/drivers/thunderbolt/nhi.c b/drivers/thunderbolt/nhi.c index 44afe699e94d35..8cfbd637a745b0 100644 --- a/drivers/thunderbolt/nhi.c +++ b/drivers/thunderbolt/nhi.c @@ -155,6 +155,14 @@ static void ring_interrupt_active(struct tb_ring *ring, bool active) nhi_mask_interrupt(ring->nhi, mask, index); } +static void nhi_ring_interrupt_active(struct tb_ring *ring, bool active) +{ + if (ring->nhi->ops->ring_interrupt_active) + ring->nhi->ops->ring_interrupt_active(ring, active); + else + ring_interrupt_active(ring, active); +} + /* * nhi_disable_interrupts() - disable interrupts for all rings * @@ -756,7 +764,7 @@ void tb_ring_start(struct tb_ring *ring) ring_iowrite32options(ring, flags, 0); } - ring_interrupt_active(ring, true); + nhi_ring_interrupt_active(ring, true); ring->running = true; err: spin_unlock(&ring->lock); @@ -791,7 +799,7 @@ void tb_ring_stop(struct tb_ring *ring) RING_TYPE(ring), ring->hop); goto err; } - ring_interrupt_active(ring, false); + nhi_ring_interrupt_active(ring, false); ring_iowrite32options(ring, 0, 0); ring_iowrite64desc(ring, 0, 0); diff --git a/drivers/thunderbolt/nhi.h b/drivers/thunderbolt/nhi.h index 24ac4246d0cab7..d7bdc61903bd33 100644 --- a/drivers/thunderbolt/nhi.h +++ b/drivers/thunderbolt/nhi.h @@ -38,6 +38,15 @@ enum nhi_fw_mode nhi_mailbox_mode(struct tb_nhi *nhi); * @runtime_suspend: NHI specific runtime_suspend hook * @runtime_resume: NHI specific runtime_resume hook * @shutdown: NHI specific shutdown + * @pre_nvm_auth: hook to run before Thunderbolt 3 NVM authentication + * @post_nvm_auth: hook to run after Thunderbolt 3 NVM authentication + * @request_ring_irq: NHI specific interrupt retrieval hook + * @release_ring_irq: NHI specific interrupt release hook + * @ring_interrupt_active: NHI specific hook to activate/deactivate the + * interrupt of a single ring. If not set the + * standard USB4 NHI registers are used. + * @is_present: Whether the device is currently present on the parent bus + * @init_interrupts: NHI specific interrupt initialization hook */ struct tb_nhi_ops { int (*init)(struct tb_nhi *nhi); @@ -46,6 +55,13 @@ struct tb_nhi_ops { int (*runtime_suspend)(struct tb_nhi *nhi); int (*runtime_resume)(struct tb_nhi *nhi); void (*shutdown)(struct tb_nhi *nhi); + void (*pre_nvm_auth)(struct tb_nhi *nhi); + void (*post_nvm_auth)(struct tb_nhi *nhi); + int (*request_ring_irq)(struct tb_ring *ring, bool no_suspend); + void (*release_ring_irq)(struct tb_ring *ring); + void (*ring_interrupt_active)(struct tb_ring *ring, bool active); + bool (*is_present)(struct tb_nhi *nhi); + int (*init_interrupts)(struct tb_nhi *nhi); }; extern const struct tb_nhi_ops icl_nhi_ops; From 432814cc34258a6bb9e74535b9450192502ed0cc Mon Sep 17 00:00:00 2001 From: Sven Peter Date: Mon, 24 Aug 2026 18:07:56 +0200 Subject: [PATCH 18/33] thunderbolt: Make the ring register layout configurable The ring descriptor and options registers are laid out differently on the Apple NHI. Describe their offsets and strides with a tb_nhi_ring_layout struct that NHI drivers can override. Signed-off-by: Sven Peter --- drivers/thunderbolt/nhi.c | 30 ++++++++++++++++++++++++------ drivers/thunderbolt/nhi.h | 18 ++++++++++++++++++ include/linux/thunderbolt.h | 3 +++ 3 files changed, 45 insertions(+), 6 deletions(-) diff --git a/drivers/thunderbolt/nhi.c b/drivers/thunderbolt/nhi.c index 8cfbd637a745b0..d184d379201cf7 100644 --- a/drivers/thunderbolt/nhi.c +++ b/drivers/thunderbolt/nhi.c @@ -182,19 +182,32 @@ static void nhi_disable_interrupts(struct tb_nhi *nhi) /* ring helper methods */ +static const struct tb_nhi_ring_layout nhi_default_ring_layout = { + .tx_desc_base = REG_TX_RING_BASE, + .rx_desc_base = REG_RX_RING_BASE, + .desc_stride = 16, + .tx_options_base = REG_TX_OPTIONS_BASE, + .rx_options_base = REG_RX_OPTIONS_BASE, + .options_stride = 32, +}; + static void __iomem *ring_desc_base(struct tb_ring *ring) { + const struct tb_nhi_ring_layout *layout = ring->nhi->ring_layout; void __iomem *io = ring->nhi->iobase; - io += ring->is_tx ? REG_TX_RING_BASE : REG_RX_RING_BASE; - io += ring->hop * 16; + + io += ring->is_tx ? layout->tx_desc_base : layout->rx_desc_base; + io += ring->hop * layout->desc_stride; return io; } static void __iomem *ring_options_base(struct tb_ring *ring) { + const struct tb_nhi_ring_layout *layout = ring->nhi->ring_layout; void __iomem *io = ring->nhi->iobase; - io += ring->is_tx ? REG_TX_OPTIONS_BASE : REG_RX_OPTIONS_BASE; - io += ring->hop * 32; + + io += ring->is_tx ? layout->tx_options_base : layout->rx_options_base; + io += ring->hop * layout->options_stride; return io; } @@ -221,8 +234,10 @@ static void ring_iowrite32desc(struct tb_ring *ring, u32 value, u32 offset) static void ring_iowrite64desc(struct tb_ring *ring, u64 value, u32 offset) { - iowrite32(value, ring_desc_base(ring) + offset); - iowrite32(value >> 32, ring_desc_base(ring) + offset + 4); + void __iomem *base = ring_desc_base(ring); + + iowrite32(value, base + offset); + iowrite32(value >> 32, base + offset + 4); } static void ring_iowrite32options(struct tb_ring *ring, u32 value, u32 offset) @@ -1380,6 +1395,9 @@ static int nhi_probe(struct pci_dev *pdev, const struct pci_device_id *id) if (res) return dev_err_probe(dev, res, "cannot obtain PCI resources, aborting\n"); + if (!nhi->ring_layout) + nhi->ring_layout = &nhi_default_ring_layout; + nhi->hop_count = ioread32(nhi->iobase + REG_CAPS) & 0x3ff; dev_dbg(dev, "total paths: %d\n", nhi->hop_count); diff --git a/drivers/thunderbolt/nhi.h b/drivers/thunderbolt/nhi.h index d7bdc61903bd33..4e655d6172f0b4 100644 --- a/drivers/thunderbolt/nhi.h +++ b/drivers/thunderbolt/nhi.h @@ -30,6 +30,24 @@ enum nhi_mailbox_cmd { int nhi_mailbox_cmd(struct tb_nhi *nhi, enum nhi_mailbox_cmd cmd, u32 data); enum nhi_fw_mode nhi_mailbox_mode(struct tb_nhi *nhi); +/** + * struct tb_nhi_ring_layout - Layout of the ring registers in the NHI MMIO space + * @tx_desc_base: Offset of the descriptor registers of the first TX ring + * @rx_desc_base: Offset of the descriptor registers of the first RX ring + * @desc_stride: Stride between the descriptor registers of neighboring rings + * @tx_options_base: Offset of the options registers of the first TX ring + * @rx_options_base: Offset of the options registers of the first RX ring + * @options_stride: Stride between the options registers of neighboring rings + */ +struct tb_nhi_ring_layout { + u32 tx_desc_base; + u32 rx_desc_base; + u32 desc_stride; + u32 tx_options_base; + u32 rx_options_base; + u32 options_stride; +}; + /** * struct tb_nhi_ops - NHI specific optional operations * @init: NHI specific initialization diff --git a/include/linux/thunderbolt.h b/include/linux/thunderbolt.h index 7204586c10c3e4..b49e6bb0d097c3 100644 --- a/include/linux/thunderbolt.h +++ b/include/linux/thunderbolt.h @@ -485,6 +485,8 @@ static inline struct tb_xdomain *tb_service_parent(struct tb_service *svc) * interrupt_work when dispatching interrupts to individual rings. * @pdev: Pointer to the PCI device * @ops: NHI specific optional ops + * @ring_layout: Layout of the ring registers inside @iobase. If not set + * the default layout from the USB4 specification is used. * @iobase: MMIO space of the NHI * @tx_rings: All Tx rings available on this host controller * @rx_rings: All Rx rings available on this host controller @@ -501,6 +503,7 @@ struct tb_nhi { spinlock_t lock; struct pci_dev *pdev; const struct tb_nhi_ops *ops; + const struct tb_nhi_ring_layout *ring_layout; void __iomem *iobase; struct tb_ring **tx_rings; struct tb_ring **rx_rings; From 9048f6515a80ed2ce6cc7124a2da531f860202ca Mon Sep 17 00:00:00 2001 From: Sven Peter Date: Mon, 24 Aug 2026 18:07:56 +0200 Subject: [PATCH 19/33] thunderbolt: Add ring_interrupt_mask to tb_nhi_ops Masking ring interrupts works differently on the Apple NHI as well. Add an optional tb_nhi_ops hook for it which falls back to the standard USB4 NHI method when it is not set. Signed-off-by: Sven Peter --- drivers/thunderbolt/nhi.c | 12 ++++++++++-- drivers/thunderbolt/nhi.h | 4 ++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/drivers/thunderbolt/nhi.c b/drivers/thunderbolt/nhi.c index d184d379201cf7..fff7f485b2a0f7 100644 --- a/drivers/thunderbolt/nhi.c +++ b/drivers/thunderbolt/nhi.c @@ -419,6 +419,14 @@ static void __ring_interrupt_mask(struct tb_ring *ring, bool mask) iowrite32(val, ring->nhi->iobase + reg); } +static void nhi_ring_interrupt_mask(struct tb_ring *ring, bool mask) +{ + if (ring->nhi->ops->ring_interrupt_mask) + ring->nhi->ops->ring_interrupt_mask(ring, mask); + else + __ring_interrupt_mask(ring, mask); +} + /* Both @nhi->lock and @ring->lock should be held */ static void __ring_interrupt(struct tb_ring *ring) { @@ -426,7 +434,7 @@ static void __ring_interrupt(struct tb_ring *ring) return; if (ring->start_poll) { - __ring_interrupt_mask(ring, true); + nhi_ring_interrupt_mask(ring, true); ring->start_poll(ring->poll_data); } else { schedule_work(&ring->work); @@ -447,7 +455,7 @@ void tb_ring_poll_complete(struct tb_ring *ring) spin_lock_irqsave(&ring->nhi->lock, flags); spin_lock(&ring->lock); if (ring->start_poll) - __ring_interrupt_mask(ring, false); + nhi_ring_interrupt_mask(ring, false); spin_unlock(&ring->lock); spin_unlock_irqrestore(&ring->nhi->lock, flags); } diff --git a/drivers/thunderbolt/nhi.h b/drivers/thunderbolt/nhi.h index 4e655d6172f0b4..81285c631bb63a 100644 --- a/drivers/thunderbolt/nhi.h +++ b/drivers/thunderbolt/nhi.h @@ -63,6 +63,9 @@ struct tb_nhi_ring_layout { * @ring_interrupt_active: NHI specific hook to activate/deactivate the * interrupt of a single ring. If not set the * standard USB4 NHI registers are used. + * @ring_interrupt_mask: NHI specific hook to mask/unmask the interrupt of a + * single ring. If not set the standard USB4 NHI + * registers are used. * @is_present: Whether the device is currently present on the parent bus * @init_interrupts: NHI specific interrupt initialization hook */ @@ -78,6 +81,7 @@ struct tb_nhi_ops { int (*request_ring_irq)(struct tb_ring *ring, bool no_suspend); void (*release_ring_irq)(struct tb_ring *ring); void (*ring_interrupt_active)(struct tb_ring *ring, bool active); + void (*ring_interrupt_mask)(struct tb_ring *ring, bool mask); bool (*is_present)(struct tb_nhi *nhi); int (*init_interrupts)(struct tb_nhi *nhi); }; From 0d84762ae37a23756f8ef6dc698b1fee4bd30aed Mon Sep 17 00:00:00 2001 From: Sven Peter Date: Mon, 24 Aug 2026 18:07:56 +0200 Subject: [PATCH 20/33] thunderbolt: Add ring_configure to tb_nhi_ops Signed-off-by: Sven Peter --- drivers/thunderbolt/nhi.c | 55 +++++++++++++++++++++++++-------------- drivers/thunderbolt/nhi.h | 4 +++ 2 files changed, 39 insertions(+), 20 deletions(-) diff --git a/drivers/thunderbolt/nhi.c b/drivers/thunderbolt/nhi.c index fff7f485b2a0f7..cb3ad8ad10ac55 100644 --- a/drivers/thunderbolt/nhi.c +++ b/drivers/thunderbolt/nhi.c @@ -245,6 +245,31 @@ static void ring_iowrite32options(struct tb_ring *ring, u32 value, u32 offset) iowrite32(value, ring_options_base(ring) + offset); } +static void ring_configure(struct tb_ring *ring, u32 flags, u32 e2e_flags) +{ + if (ring->is_tx) + ring_iowrite32options(ring, 0, 4); + else + ring_iowrite32options(ring, ring->sof_mask << 16 | ring->eof_mask, 4); + + ring_iowrite32options(ring, flags, 0); + + /* + * Now that the ring valid bit is set we can configure E2E if + * enabled for the ring. + */ + if (e2e_flags) + ring_iowrite32options(ring, flags | e2e_flags, 0); +} + +static void nhi_ring_configure(struct tb_ring *ring, u32 flags, u32 e2e_flags) +{ + if (ring->nhi->ops->ring_configure) + ring->nhi->ops->ring_configure(ring, flags, e2e_flags); + else + ring_configure(ring, flags, e2e_flags); +} + static bool ring_full(struct tb_ring *ring) { return ((ring->head + 1) % ring->size) == ring->tail; @@ -727,6 +752,7 @@ EXPORT_SYMBOL_GPL(tb_ring_alloc_rx); */ void tb_ring_start(struct tb_ring *ring) { + u32 e2e_flags = 0; u16 frame_size; u32 flags; @@ -750,30 +776,13 @@ void tb_ring_start(struct tb_ring *ring) flags = RING_FLAG_ENABLE | RING_FLAG_RAW; } - ring_iowrite64desc(ring, ring->descriptors_dma, 0); - if (ring->is_tx) { - ring_iowrite32desc(ring, ring->size, 12); - ring_iowrite32options(ring, 0, 4); - ring_iowrite32options(ring, flags, 0); - } else { - u32 sof_eof_mask = ring->sof_mask << 16 | ring->eof_mask; - - ring_iowrite32desc(ring, (frame_size << 16) | ring->size, 12); - ring_iowrite32options(ring, sof_eof_mask, 4); - ring_iowrite32options(ring, flags, 0); - } - - /* - * Now that the ring valid bit is set we can configure E2E if - * enabled for the ring. - */ if (ring->flags & RING_FLAG_E2E) { if (!ring->is_tx) { u32 hop; hop = ring->e2e_tx_hop << REG_RX_OPTIONS_E2E_HOP_SHIFT; hop &= REG_RX_OPTIONS_E2E_HOP_MASK; - flags |= hop; + e2e_flags |= hop; dev_dbg(&ring->nhi->pdev->dev, "enabling E2E for %s %d with TX HopID %d\n", @@ -783,10 +792,16 @@ void tb_ring_start(struct tb_ring *ring) RING_TYPE(ring), ring->hop); } - flags |= RING_FLAG_E2E_FLOW_CONTROL; - ring_iowrite32options(ring, flags, 0); + e2e_flags |= RING_FLAG_E2E_FLOW_CONTROL; } + ring_iowrite64desc(ring, ring->descriptors_dma, 0); + if (ring->is_tx) + ring_iowrite32desc(ring, ring->size, 12); + else + ring_iowrite32desc(ring, (frame_size << 16) | ring->size, 12); + nhi_ring_configure(ring, flags, e2e_flags); + nhi_ring_interrupt_active(ring, true); ring->running = true; err: diff --git a/drivers/thunderbolt/nhi.h b/drivers/thunderbolt/nhi.h index 81285c631bb63a..a4570cf7a0043e 100644 --- a/drivers/thunderbolt/nhi.h +++ b/drivers/thunderbolt/nhi.h @@ -66,6 +66,9 @@ struct tb_nhi_ring_layout { * @ring_interrupt_mask: NHI specific hook to mask/unmask the interrupt of a * single ring. If not set the standard USB4 NHI * registers are used. + * @ring_configure: NHI specific hook to program the ring options registers + * and enable the ring with the given flags. If not set + * the standard USB4 NHI registers are used. * @is_present: Whether the device is currently present on the parent bus * @init_interrupts: NHI specific interrupt initialization hook */ @@ -82,6 +85,7 @@ struct tb_nhi_ops { void (*release_ring_irq)(struct tb_ring *ring); void (*ring_interrupt_active)(struct tb_ring *ring, bool active); void (*ring_interrupt_mask)(struct tb_ring *ring, bool mask); + void (*ring_configure)(struct tb_ring *ring, u32 flags, u32 e2e_flags); bool (*is_present)(struct tb_nhi *nhi); int (*init_interrupts)(struct tb_nhi *nhi); }; From 2523e7e5b89828a18bddaccb24b45c23b0380932 Mon Sep 17 00:00:00 2001 From: Sven Peter Date: Mon, 24 Aug 2026 18:07:56 +0200 Subject: [PATCH 21/33] thunderbolt: Add QUIRK_NO_DMA_PORT Apple Silicon machines load and update the host router firmware with a separate, proprietary mechanism and have no DMA port. Add a quirk to skip its initialization. Signed-off-by: Sven Peter --- drivers/thunderbolt/nhi.c | 4 ---- drivers/thunderbolt/nhi.h | 5 +++++ drivers/thunderbolt/switch.c | 10 ++++++---- drivers/thunderbolt/tb.c | 8 ++++++++ drivers/thunderbolt/tb.h | 2 ++ 5 files changed, 21 insertions(+), 8 deletions(-) diff --git a/drivers/thunderbolt/nhi.c b/drivers/thunderbolt/nhi.c index cb3ad8ad10ac55..1a7ebda5b1cacf 100644 --- a/drivers/thunderbolt/nhi.c +++ b/drivers/thunderbolt/nhi.c @@ -43,10 +43,6 @@ #define NHI_MAILBOX_TIMEOUT 500 /* ms */ -/* Host interface quirks */ -#define QUIRK_AUTO_CLEAR_INT BIT(0) -#define QUIRK_E2E BIT(1) - static bool host_reset = true; module_param(host_reset, bool, 0444); MODULE_PARM_DESC(host_reset, "reset USB4 host router (default: true)"); diff --git a/drivers/thunderbolt/nhi.h b/drivers/thunderbolt/nhi.h index a4570cf7a0043e..8a529082553e08 100644 --- a/drivers/thunderbolt/nhi.h +++ b/drivers/thunderbolt/nhi.h @@ -92,6 +92,11 @@ struct tb_nhi_ops { extern const struct tb_nhi_ops icl_nhi_ops; +/* Host interface quirks */ +#define QUIRK_AUTO_CLEAR_INT BIT(0) +#define QUIRK_E2E BIT(1) +#define QUIRK_NO_DMA_PORT BIT(2) + /* * PCI IDs used in this driver from Win Ridge forward. There is no * need for the PCI quirk anymore as we will use ICM also on Apple diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c index 4afbd87ab05937..f5f5fcd6073d61 100644 --- a/drivers/thunderbolt/switch.c +++ b/drivers/thunderbolt/switch.c @@ -3325,10 +3325,12 @@ int tb_switch_add(struct tb_switch *sw) * to the userspace. NVM can be accessed through DMA * configuration based mailbox. */ - ret = tb_switch_add_dma_port(sw); - if (ret) { - dev_err(&sw->dev, "failed to add DMA port\n"); - return ret; + if (!sw->no_dma_port) { + ret = tb_switch_add_dma_port(sw); + if (ret) { + dev_err(&sw->dev, "failed to add DMA port\n"); + return ret; + } } ret = tb_switch_nvm_init(sw); diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c index 9907966556e7b2..6d5473a2d2e736 100644 --- a/drivers/thunderbolt/tb.c +++ b/drivers/thunderbolt/tb.c @@ -15,6 +15,7 @@ #include "tb.h" #include "tb_regs.h" #include "tunnel.h" +#include "nhi.h" #define TB_TIMEOUT 100 /* ms */ #define TB_RELEASE_BW_TIMEOUT 10000 /* ms */ @@ -3005,6 +3006,13 @@ static int tb_start(struct tb *tb, bool reset) /* All USB4 routers support runtime PM */ tb->root_switch->rpm = tb_switch_is_usb4(tb->root_switch); + /* + * Apple Silicon machines have a separate, proprietary mechanism for + * loading and updating firmware. Skip the DMA port initialization on + * these machines. + */ + tb->root_switch->no_dma_port = tb->nhi->quirks & QUIRK_NO_DMA_PORT; + ret = tb_switch_configure(tb->root_switch); if (ret) { tb_switch_put(tb->root_switch); diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h index 217c3114bec8ba..8bdb696ec2e58e 100644 --- a/drivers/thunderbolt/tb.h +++ b/drivers/thunderbolt/tb.h @@ -139,6 +139,7 @@ struct tb_switch_tmu { * @drom: DROM of the switch (%NULL if not found) * @nvm: Pointer to the NVM if the switch has one (%NULL otherwise) * @no_nvm_upgrade: Prevent NVM upgrade of this switch + * @no_dma_port: Prevent adding the DMA port of this switch * @safe_mode: The switch is in safe-mode * @boot: Whether the switch was already authorized on boot or not * @rpm: The switch supports runtime PM @@ -194,6 +195,7 @@ struct tb_switch { u8 *drom; struct tb_nvm *nvm; bool no_nvm_upgrade; + bool no_dma_port; bool safe_mode; bool boot; bool rpm; From 08b4d52595cb92e002135edca5e27deffdc71636 Mon Sep 17 00:00:00 2001 From: Sven Peter Date: Mon, 24 Aug 2026 18:07:56 +0200 Subject: [PATCH 22/33] thunderbolt: Add QUIRK_NO_USB3_BW_ALLOC The host routers on Apple Silicon do not implement USB3 bandwidth allocation registers: the CMR bit set in ADP_USB3_CS_2 by usb4_usb3_port_cm_request() is never acknowledged and every USB3 tunnel bringup fails there after the timeout: [ 49.259325] thunderbolt-apple-nhi 501f00000.nhi: 1:16: available bandwidth for new USB3 tunnel 18000/18000 Mb/s [ 49.265869] thunderbolt-apple-nhi 501f00000.nhi: 1:16: maximum required bandwidth for USB3 tunnel 9000 Mb/s [ 49.267894] thunderbolt-apple-nhi 501f00000.nhi: 0:4 <-> 1:16 (USB3): activating [ 49.269410] thunderbolt-apple-nhi 501f00000.nhi: 0:4 <-> 1:16 (USB3): allocating initial bandwidth 9000/9000 Mb/s [ 50.771764] thunderbolt-apple-nhi 501f00000.nhi: 1:16: USB3 tunnel activation failed, aborting Add a quirk to skip bandwidth allocation entirely but keep tracking the initial allocation in software for the consumed bandwidth calculation. Discovered tunnels book 90% of the maximum link rate instead of reading back an allocation that does not exist. This is only done for correctness and mirrors what tb_tunnel_alloc_usb3() does since on Apple Silicon there can never be any pre-existing tunnels. Signed-off-by: Sven Peter --- drivers/thunderbolt/nhi.h | 1 + drivers/thunderbolt/tb.c | 8 +++++ drivers/thunderbolt/tb.h | 3 ++ drivers/thunderbolt/tunnel.c | 59 ++++++++++++++++++++++++------------ 4 files changed, 51 insertions(+), 20 deletions(-) diff --git a/drivers/thunderbolt/nhi.h b/drivers/thunderbolt/nhi.h index 8a529082553e08..1c11da72ca4968 100644 --- a/drivers/thunderbolt/nhi.h +++ b/drivers/thunderbolt/nhi.h @@ -96,6 +96,7 @@ extern const struct tb_nhi_ops icl_nhi_ops; #define QUIRK_AUTO_CLEAR_INT BIT(0) #define QUIRK_E2E BIT(1) #define QUIRK_NO_DMA_PORT BIT(2) +#define QUIRK_NO_USB3_BW_ALLOC BIT(3) /* * PCI IDs used in this driver from Win Ridge forward. There is no diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c index 6d5473a2d2e736..2db646a96f81ab 100644 --- a/drivers/thunderbolt/tb.c +++ b/drivers/thunderbolt/tb.c @@ -3013,6 +3013,14 @@ static int tb_start(struct tb *tb, bool reset) */ tb->root_switch->no_dma_port = tb->nhi->quirks & QUIRK_NO_DMA_PORT; + /* + * Apple Silicon host routers do not implement the USB3 bandwidth + * allocation registers: the CMR/HCA handshake is never acked and + * times out. Bandwidth for USB3 tunnels is only booked in software + * on these machines. + */ + tb->root_switch->no_usb3_bw_alloc = tb->nhi->quirks & QUIRK_NO_USB3_BW_ALLOC; + ret = tb_switch_configure(tb->root_switch); if (ret) { tb_switch_put(tb->root_switch); diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h index 8bdb696ec2e58e..23526a617b810a 100644 --- a/drivers/thunderbolt/tb.h +++ b/drivers/thunderbolt/tb.h @@ -140,6 +140,8 @@ struct tb_switch_tmu { * @nvm: Pointer to the NVM if the switch has one (%NULL otherwise) * @no_nvm_upgrade: Prevent NVM upgrade of this switch * @no_dma_port: Prevent adding the DMA port of this switch + * @no_usb3_bw_alloc: Host router does not implement the USB3 bandwidth + * allocation registers (ADP_USB3_CS_1..4) * @safe_mode: The switch is in safe-mode * @boot: Whether the switch was already authorized on boot or not * @rpm: The switch supports runtime PM @@ -196,6 +198,7 @@ struct tb_switch { struct tb_nvm *nvm; bool no_nvm_upgrade; bool no_dma_port; + bool no_usb3_bw_alloc; bool safe_mode; bool boot; bool rpm; diff --git a/drivers/thunderbolt/tunnel.c b/drivers/thunderbolt/tunnel.c index f38f7753b6e4fd..bc0a2b38cdd142 100644 --- a/drivers/thunderbolt/tunnel.c +++ b/drivers/thunderbolt/tunnel.c @@ -2226,24 +2226,40 @@ struct tb_tunnel *tb_tunnel_discover_usb3(struct tb *tb, struct tb_port *down, if (!tb_route(down->sw)) { int ret; - /* - * Read the initial bandwidth allocation for the first - * hop tunnel. - */ - ret = usb4_usb3_port_allocated_bandwidth(down, - &tunnel->allocated_up, &tunnel->allocated_down); - if (ret) - goto err_deactivate; + tunnel->consumed_bandwidth = tb_usb3_consumed_bandwidth; + + if (down->sw->no_usb3_bw_alloc) { + /* + * The host router does not implement the bandwidth + * allocation registers and nothing can be read back + * here. Book 90% of the maximum link rate in software + * instead. + */ + ret = tb_usb3_max_link_rate(tunnel->dst_port, down); + if (ret < 0) + goto err_deactivate; + + tunnel->allocated_up = ret * 90 / 100; + tunnel->allocated_down = tunnel->allocated_up; + } else { + /* + * Read the initial bandwidth allocation for the first + * hop tunnel. + */ + ret = usb4_usb3_port_allocated_bandwidth(down, + &tunnel->allocated_up, &tunnel->allocated_down); + if (ret) + goto err_deactivate; + + tunnel->pre_activate = tb_usb3_pre_activate; + tunnel->release_unused_bandwidth = + tb_usb3_release_unused_bandwidth; + tunnel->reclaim_available_bandwidth = + tb_usb3_reclaim_available_bandwidth; + } tb_tunnel_dbg(tunnel, "currently allocated bandwidth %d/%d Mb/s\n", tunnel->allocated_up, tunnel->allocated_down); - - tunnel->pre_activate = tb_usb3_pre_activate; - tunnel->consumed_bandwidth = tb_usb3_consumed_bandwidth; - tunnel->release_unused_bandwidth = - tb_usb3_release_unused_bandwidth; - tunnel->reclaim_available_bandwidth = - tb_usb3_reclaim_available_bandwidth; } tb_tunnel_dbg(tunnel, "discovered\n"); @@ -2323,12 +2339,15 @@ struct tb_tunnel *tb_tunnel_alloc_usb3(struct tb *tb, struct tb_port *up, tunnel->allocated_up = min(max_rate, max_up); tunnel->allocated_down = min(max_rate, max_down); - tunnel->pre_activate = tb_usb3_pre_activate; tunnel->consumed_bandwidth = tb_usb3_consumed_bandwidth; - tunnel->release_unused_bandwidth = - tb_usb3_release_unused_bandwidth; - tunnel->reclaim_available_bandwidth = - tb_usb3_reclaim_available_bandwidth; + + if (!down->sw->no_usb3_bw_alloc) { + tunnel->pre_activate = tb_usb3_pre_activate; + tunnel->release_unused_bandwidth = + tb_usb3_release_unused_bandwidth; + tunnel->reclaim_available_bandwidth = + tb_usb3_reclaim_available_bandwidth; + } } return tunnel; From d9fbf43a64dcf020299731a03e7235901e76e4c8 Mon Sep 17 00:00:00 2001 From: Sven Peter Date: Mon, 24 Aug 2026 18:07:56 +0200 Subject: [PATCH 23/33] thunderbolt: Export symbols required by the Apple Silicon driver The driver for the ACIO host router complex on Apple Silicon SoCs can be built as a module and needs tb_probe() and the domain functions to register its NHI with the software connection manager as well as tb_switch_find_vse_cap(), tb_cfg_write() and tb_port_unlock() to configure the cable details for its co-processor and to unlock the lane ports. Export these symbols in the USB4 namespace. Signed-off-by: Sven Peter --- drivers/thunderbolt/cap.c | 2 ++ drivers/thunderbolt/ctl.c | 2 ++ drivers/thunderbolt/domain.c | 2 ++ drivers/thunderbolt/switch.c | 1 + drivers/thunderbolt/tb.c | 2 ++ 5 files changed, 9 insertions(+) diff --git a/drivers/thunderbolt/cap.c b/drivers/thunderbolt/cap.c index 4ab22d5291acf9..901fe651fe596f 100644 --- a/drivers/thunderbolt/cap.c +++ b/drivers/thunderbolt/cap.c @@ -6,6 +6,7 @@ * Copyright (C) 2018, Intel Corporation */ +#include #include #include @@ -254,3 +255,4 @@ int tb_switch_find_vse_cap(struct tb_switch *sw, enum tb_switch_vse_cap vsec) return -ENOENT; } +EXPORT_SYMBOL_NS_GPL(tb_switch_find_vse_cap, "USB4"); diff --git a/drivers/thunderbolt/ctl.c b/drivers/thunderbolt/ctl.c index b2fd60fc7bcc78..aa7449af1ff653 100644 --- a/drivers/thunderbolt/ctl.c +++ b/drivers/thunderbolt/ctl.c @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -1159,6 +1160,7 @@ int tb_cfg_write(struct tb_ctl *ctl, const void *buffer, u64 route, u32 port, } return res.err; } +EXPORT_SYMBOL_NS_GPL(tb_cfg_write, "USB4"); /** * tb_cfg_get_upstream_port() - get upstream port number of switch at route diff --git a/drivers/thunderbolt/domain.c b/drivers/thunderbolt/domain.c index 317780b9999293..68267cee1581d4 100644 --- a/drivers/thunderbolt/domain.c +++ b/drivers/thunderbolt/domain.c @@ -489,6 +489,7 @@ int tb_domain_add(struct tb *tb, bool reset) return ret; } +EXPORT_SYMBOL_NS_GPL(tb_domain_add, "USB4"); /** * tb_domain_remove() - Removes and releases a domain @@ -513,6 +514,7 @@ void tb_domain_remove(struct tb *tb) device_unregister(&tb->dev); } +EXPORT_SYMBOL_NS_GPL(tb_domain_remove, "USB4"); /** * tb_domain_suspend_noirq() - Suspend a domain diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c index f5f5fcd6073d61..ec98c7b6e00ad2 100644 --- a/drivers/thunderbolt/switch.c +++ b/drivers/thunderbolt/switch.c @@ -649,6 +649,7 @@ int tb_port_unlock(struct tb_port *port) return usb4_port_unlock(port); return 0; } +EXPORT_SYMBOL_NS_GPL(tb_port_unlock, "USB4"); static int __tb_port_enable(struct tb_port *port, bool enable) { diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c index 2db646a96f81ab..634a34287b1221 100644 --- a/drivers/thunderbolt/tb.c +++ b/drivers/thunderbolt/tb.c @@ -6,6 +6,7 @@ * Copyright (C) 2019, Intel Corporation */ +#include #include #include #include @@ -3413,3 +3414,4 @@ struct tb *tb_probe(struct tb_nhi *nhi) return tb; } +EXPORT_SYMBOL_NS_GPL(tb_probe, "USB4"); From 713f4cdfd32770db39d97417b25b744e48386d41 Mon Sep 17 00:00:00 2001 From: Konrad Dybcio Date: Thu, 21 May 2026 12:40:00 +0200 Subject: [PATCH 24/33] thunderbolt: Move pci_device out of tb_nhi Not all USB4/TB implementations are based on a PCIe-attached controller. In order to make way for these, start off with moving the pci_device reference out of the main tb_nhi structure. Encapsulate the existing struct in a new tb_nhi_pci, that shall also house all properties that relate to the parent bus. Similarly, any other type of controller will be expected to contain tb_nhi as a member. Signed-off-by: Konrad Dybcio Signed-off-by: Mika Westerberg --- drivers/thunderbolt/acpi.c | 14 +-- drivers/thunderbolt/ctl.c | 16 ++-- drivers/thunderbolt/domain.c | 2 +- drivers/thunderbolt/eeprom.c | 2 +- drivers/thunderbolt/icm.c | 24 ++--- drivers/thunderbolt/nhi.c | 152 +++++++++++++++++++------------- drivers/thunderbolt/nhi_ops.c | 26 +++--- drivers/thunderbolt/switch.c | 6 +- drivers/thunderbolt/tb.c | 11 +-- drivers/thunderbolt/tb.h | 10 +-- drivers/thunderbolt/usb4_port.c | 2 +- include/linux/thunderbolt.h | 8 +- 12 files changed, 154 insertions(+), 119 deletions(-) diff --git a/drivers/thunderbolt/acpi.c b/drivers/thunderbolt/acpi.c index 45d1415871b405..53546bc477a5b4 100644 --- a/drivers/thunderbolt/acpi.c +++ b/drivers/thunderbolt/acpi.c @@ -28,7 +28,7 @@ static acpi_status tb_acpi_add_link(acpi_handle handle, u32 level, void *data, return AE_OK; /* It needs to reference this NHI */ - if (dev_fwnode(&nhi->pdev->dev) != fwnode) + if (dev_fwnode(nhi->dev) != fwnode) goto out_put; /* @@ -57,16 +57,16 @@ static acpi_status tb_acpi_add_link(acpi_handle handle, u32 level, void *data, */ pm_runtime_get_sync(&pdev->dev); - link = device_link_add(&pdev->dev, &nhi->pdev->dev, + link = device_link_add(&pdev->dev, nhi->dev, DL_FLAG_AUTOREMOVE_SUPPLIER | DL_FLAG_RPM_ACTIVE | DL_FLAG_PM_RUNTIME); if (link) { - dev_dbg(&nhi->pdev->dev, "created link from %s\n", + dev_dbg(nhi->dev, "created link from %s\n", dev_name(&pdev->dev)); *(bool *)ret = true; } else { - dev_warn(&nhi->pdev->dev, "device link creation from %s failed\n", + dev_warn(nhi->dev, "device link creation from %s failed\n", dev_name(&pdev->dev)); } @@ -93,7 +93,7 @@ bool tb_acpi_add_links(struct tb_nhi *nhi) acpi_status status; bool ret = false; - if (!has_acpi_companion(&nhi->pdev->dev)) + if (!has_acpi_companion(nhi->dev)) return false; /* @@ -103,7 +103,7 @@ bool tb_acpi_add_links(struct tb_nhi *nhi) status = acpi_walk_namespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT, 32, tb_acpi_add_link, NULL, nhi, (void **)&ret); if (ACPI_FAILURE(status)) { - dev_warn(&nhi->pdev->dev, "failed to enumerate tunneled ports\n"); + dev_warn(nhi->dev, "failed to enumerate tunneled ports\n"); return false; } @@ -305,7 +305,7 @@ static struct acpi_device *tb_acpi_switch_find_companion(struct tb_switch *sw) struct tb_nhi *nhi = sw->tb->nhi; struct acpi_device *parent_adev; - parent_adev = ACPI_COMPANION(&nhi->pdev->dev); + parent_adev = ACPI_COMPANION(nhi->dev); if (parent_adev) adev = acpi_find_child_device(parent_adev, 0, false); } diff --git a/drivers/thunderbolt/ctl.c b/drivers/thunderbolt/ctl.c index aa7449af1ff653..8575e7d7a1477b 100644 --- a/drivers/thunderbolt/ctl.c +++ b/drivers/thunderbolt/ctl.c @@ -57,22 +57,22 @@ struct tb_ctl { #define tb_ctl_WARN(ctl, format, arg...) \ - dev_WARN(&(ctl)->nhi->pdev->dev, format, ## arg) + dev_WARN((ctl)->nhi->dev, format, ## arg) #define tb_ctl_err(ctl, format, arg...) \ - dev_err(&(ctl)->nhi->pdev->dev, format, ## arg) + dev_err((ctl)->nhi->dev, format, ## arg) #define tb_ctl_warn(ctl, format, arg...) \ - dev_warn(&(ctl)->nhi->pdev->dev, format, ## arg) + dev_warn((ctl)->nhi->dev, format, ## arg) #define tb_ctl_info(ctl, format, arg...) \ - dev_info(&(ctl)->nhi->pdev->dev, format, ## arg) + dev_info((ctl)->nhi->dev, format, ## arg) #define tb_ctl_dbg(ctl, format, arg...) \ - dev_dbg(&(ctl)->nhi->pdev->dev, format, ## arg) + dev_dbg((ctl)->nhi->dev, format, ## arg) #define tb_ctl_dbg_once(ctl, format, arg...) \ - dev_dbg_once(&(ctl)->nhi->pdev->dev, format, ## arg) + dev_dbg_once((ctl)->nhi->dev, format, ## arg) static DECLARE_WAIT_QUEUE_HEAD(tb_cfg_request_cancel_queue); /* Serializes access to request kref_get/put */ @@ -667,8 +667,8 @@ struct tb_ctl *tb_ctl_alloc(struct tb_nhi *nhi, int index, int timeout_msec, mutex_init(&ctl->request_queue_lock); INIT_LIST_HEAD(&ctl->request_queue); - ctl->frame_pool = dma_pool_create("thunderbolt_ctl", &nhi->pdev->dev, - TB_FRAME_SIZE, 4, 0); + ctl->frame_pool = dma_pool_create("thunderbolt_ctl", nhi->dev, + TB_FRAME_SIZE, 4, 0); if (!ctl->frame_pool) goto err; diff --git a/drivers/thunderbolt/domain.c b/drivers/thunderbolt/domain.c index 68267cee1581d4..8e45fc57bc2d4e 100644 --- a/drivers/thunderbolt/domain.c +++ b/drivers/thunderbolt/domain.c @@ -402,7 +402,7 @@ struct tb *tb_domain_alloc(struct tb_nhi *nhi, int timeout_msec, size_t privsize if (!tb->ctl) goto err_destroy_wq; - tb->dev.parent = &nhi->pdev->dev; + tb->dev.parent = nhi->dev; tb->dev.bus = &tb_bus_type; tb->dev.type = &tb_domain_type; tb->dev.groups = domain_attr_groups; diff --git a/drivers/thunderbolt/eeprom.c b/drivers/thunderbolt/eeprom.c index c72092b5cb9ed9..389de7df84f65f 100644 --- a/drivers/thunderbolt/eeprom.c +++ b/drivers/thunderbolt/eeprom.c @@ -472,7 +472,7 @@ static void tb_switch_drom_free(struct tb_switch *sw) */ static int tb_drom_copy_property(struct tb_switch *sw, const char *name, u16 *size) { - struct device *dev = &sw->tb->nhi->pdev->dev; + struct device *dev = sw->tb->nhi->dev; int len, res; len = device_property_count_u8(dev, name); diff --git a/drivers/thunderbolt/icm.c b/drivers/thunderbolt/icm.c index 136516432f56d0..eadcc633a094aa 100644 --- a/drivers/thunderbolt/icm.c +++ b/drivers/thunderbolt/icm.c @@ -1455,6 +1455,7 @@ static struct pci_dev *get_upstream_port(struct pci_dev *pdev) static bool icm_ar_is_supported(struct tb *tb) { + struct pci_dev *pdev = to_pci_dev(tb->nhi->dev); struct pci_dev *upstream_port; struct icm *icm = tb_priv(tb); @@ -1472,7 +1473,7 @@ static bool icm_ar_is_supported(struct tb *tb) * Find the upstream PCIe port in case we need to do reset * through its vendor specific registers. */ - upstream_port = get_upstream_port(tb->nhi->pdev); + upstream_port = get_upstream_port(pdev); if (upstream_port) { int cap; @@ -1508,7 +1509,7 @@ static int icm_ar_get_mode(struct tb *tb) } while (--retries); if (!retries) { - dev_err(&nhi->pdev->dev, "ICM firmware not authenticated\n"); + dev_err(nhi->dev, "ICM firmware not authenticated\n"); return -ENODEV; } @@ -1674,11 +1675,11 @@ icm_icl_driver_ready(struct tb *tb, enum tb_security_level *security_level, static void icm_icl_set_uuid(struct tb *tb) { - struct tb_nhi *nhi = tb->nhi; + struct pci_dev *pdev = to_pci_dev(tb->nhi->dev); u32 uuid[4]; - pci_read_config_dword(nhi->pdev, VS_CAP_10, &uuid[0]); - pci_read_config_dword(nhi->pdev, VS_CAP_11, &uuid[1]); + pci_read_config_dword(pdev, VS_CAP_10, &uuid[0]); + pci_read_config_dword(pdev, VS_CAP_11, &uuid[1]); uuid[2] = 0xffffffff; uuid[3] = 0xffffffff; @@ -1853,7 +1854,7 @@ static int icm_firmware_start(struct tb *tb, struct tb_nhi *nhi) if (icm_firmware_running(nhi)) return 0; - dev_dbg(&nhi->pdev->dev, "starting ICM firmware\n"); + dev_dbg(nhi->dev, "starting ICM firmware\n"); ret = icm_firmware_reset(tb, nhi); if (ret) @@ -1948,7 +1949,7 @@ static int icm_firmware_init(struct tb *tb) ret = icm_firmware_start(tb, nhi); if (ret) { - dev_err(&nhi->pdev->dev, "could not start ICM firmware\n"); + dev_err(nhi->dev, "could not start ICM firmware\n"); return ret; } @@ -1980,10 +1981,10 @@ static int icm_firmware_init(struct tb *tb) */ ret = icm_reset_phy_port(tb, 0); if (ret) - dev_warn(&nhi->pdev->dev, "failed to reset links on port0\n"); + dev_warn(nhi->dev, "failed to reset links on port0\n"); ret = icm_reset_phy_port(tb, 1); if (ret) - dev_warn(&nhi->pdev->dev, "failed to reset links on port1\n"); + dev_warn(nhi->dev, "failed to reset links on port1\n"); return 0; } @@ -2462,6 +2463,7 @@ static const struct tb_cm_ops icm_icl_ops = { struct tb *icm_probe(struct tb_nhi *nhi) { + struct pci_dev *pdev = to_pci_dev(nhi->dev); struct icm *icm; struct tb *tb; @@ -2473,7 +2475,7 @@ struct tb *icm_probe(struct tb_nhi *nhi) INIT_DELAYED_WORK(&icm->rescan_work, icm_rescan_work); mutex_init(&icm->request_lock); - switch (nhi->pdev->device) { + switch (pdev->device) { case PCI_DEVICE_ID_INTEL_FALCON_RIDGE_2C_NHI: case PCI_DEVICE_ID_INTEL_FALCON_RIDGE_4C_NHI: icm->can_upgrade_nvm = true; @@ -2579,7 +2581,7 @@ struct tb *icm_probe(struct tb_nhi *nhi) } if (!icm->is_supported || !icm->is_supported(tb)) { - dev_dbg(&nhi->pdev->dev, "ICM not supported on this controller\n"); + dev_dbg(nhi->dev, "ICM not supported on this controller\n"); tb_domain_put(tb); return NULL; } diff --git a/drivers/thunderbolt/nhi.c b/drivers/thunderbolt/nhi.c index 1a7ebda5b1cacf..122f8ec651c8c4 100644 --- a/drivers/thunderbolt/nhi.c +++ b/drivers/thunderbolt/nhi.c @@ -2,7 +2,7 @@ /* * Thunderbolt driver - NHI driver * - * The NHI (native host interface) is the pci device that allows us to send and + * The NHI (native host interface) is the device that allows us to send and * receive frames from the thunderbolt bus. * * Copyright (c) 2014 Andreas Noever @@ -12,7 +12,6 @@ #include #include #include -#include #include #include #include @@ -47,6 +46,21 @@ static bool host_reset = true; module_param(host_reset, bool, 0444); MODULE_PARM_DESC(host_reset, "reset USB4 host router (default: true)"); +/** + * struct tb_nhi_pci - NHI device connected over PCIe + * @nhi: NHI device + * @msix_ida: Used to allocate MSI-X vectors for rings + */ +struct tb_nhi_pci { + struct tb_nhi nhi; + struct ida msix_ida; +}; + +static inline struct tb_nhi_pci *nhi_to_pci(struct tb_nhi *nhi) +{ + return container_of(nhi, struct tb_nhi_pci, nhi); +} + static int ring_interrupt_index(const struct tb_ring *ring) { int bit = ring->hop; @@ -135,15 +149,14 @@ static void ring_interrupt_active(struct tb_ring *ring, bool active) else new = old & ~mask; - dev_dbg(&ring->nhi->pdev->dev, + dev_dbg(ring->nhi->dev, "%s interrupt at register %#x bit %d (%#x -> %#x)\n", active ? "enabling" : "disabling", reg, interrupt_bit, old, new); if (new == old) - dev_WARN(&ring->nhi->pdev->dev, - "interrupt for %s %d is already %s\n", - RING_TYPE(ring), ring->hop, - str_enabled_disabled(active)); + dev_WARN(ring->nhi->dev, "interrupt for %s %d is already %s\n", + RING_TYPE(ring), ring->hop, + str_enabled_disabled(active)); if (active) iowrite32(new, ring->nhi->iobase + reg); @@ -514,19 +527,21 @@ static irqreturn_t ring_msix(int irq, void *data) static int ring_request_msix(struct tb_ring *ring, bool no_suspend) { struct tb_nhi *nhi = ring->nhi; + struct tb_nhi_pci *nhi_pci = nhi_to_pci(nhi); + struct pci_dev *pdev = to_pci_dev(nhi->dev); unsigned long irqflags; int ret; - if (!nhi->pdev->msix_enabled) + if (!pdev->msix_enabled) return 0; - ret = ida_alloc_max(&nhi->msix_ida, MSIX_MAX_VECS - 1, GFP_KERNEL); + ret = ida_alloc_max(&nhi_pci->msix_ida, MSIX_MAX_VECS - 1, GFP_KERNEL); if (ret < 0) return ret; ring->vector = ret; - ret = pci_irq_vector(ring->nhi->pdev, ring->vector); + ret = pci_irq_vector(pdev, ring->vector); if (ret < 0) goto err_ida_remove; @@ -540,18 +555,20 @@ static int ring_request_msix(struct tb_ring *ring, bool no_suspend) return 0; err_ida_remove: - ida_free(&nhi->msix_ida, ring->vector); + ida_free(&nhi_pci->msix_ida, ring->vector); return ret; } static void ring_release_msix(struct tb_ring *ring) { + struct tb_nhi_pci *nhi_pci = nhi_to_pci(ring->nhi); + if (ring->irq <= 0) return; free_irq(ring->irq, ring); - ida_free(&ring->nhi->msix_ida, ring->vector); + ida_free(&nhi_pci->msix_ida, ring->vector); ring->vector = 0; ring->irq = 0; } @@ -564,7 +581,7 @@ static int nhi_alloc_hop(struct tb_nhi *nhi, struct tb_ring *ring) if (nhi->quirks & QUIRK_E2E) { start_hop = RING_FIRST_USABLE_HOPID + 1; if (ring->flags & RING_FLAG_E2E && !ring->is_tx) { - dev_dbg(&nhi->pdev->dev, "quirking E2E TX HopID %u -> %u\n", + dev_dbg(nhi->dev, "quirking E2E TX HopID %u -> %u\n", ring->e2e_tx_hop, RING_E2E_RESERVED_HOPID); ring->e2e_tx_hop = RING_E2E_RESERVED_HOPID; } @@ -595,23 +612,23 @@ static int nhi_alloc_hop(struct tb_nhi *nhi, struct tb_ring *ring) } if (ring->hop > 0 && ring->hop < start_hop) { - dev_warn(&nhi->pdev->dev, "invalid hop: %d\n", ring->hop); + dev_warn(nhi->dev, "invalid hop: %d\n", ring->hop); ret = -EINVAL; goto err_unlock; } if (ring->hop < 0 || ring->hop >= nhi->hop_count) { - dev_warn(&nhi->pdev->dev, "invalid hop: %d\n", ring->hop); + dev_warn(nhi->dev, "invalid hop: %d\n", ring->hop); ret = -EINVAL; goto err_unlock; } if (ring->is_tx && nhi->tx_rings[ring->hop]) { - dev_warn(&nhi->pdev->dev, "TX hop %d already allocated\n", + dev_warn(nhi->dev, "TX hop %d already allocated\n", ring->hop); ret = -EBUSY; goto err_unlock; } if (!ring->is_tx && nhi->rx_rings[ring->hop]) { - dev_warn(&nhi->pdev->dev, "RX hop %d already allocated\n", + dev_warn(nhi->dev, "RX hop %d already allocated\n", ring->hop); ret = -EBUSY; goto err_unlock; @@ -646,7 +663,7 @@ static struct tb_ring *tb_ring_alloc(struct tb_nhi *nhi, u32 hop, int size, { struct tb_ring *ring = NULL; - dev_dbg(&nhi->pdev->dev, "allocating %s ring %d of size %d\n", + dev_dbg(nhi->dev, "allocating %s ring %d of size %d\n", transmit ? "TX" : "RX", hop, size); ring = kzalloc_obj(*ring); @@ -672,9 +689,9 @@ static struct tb_ring *tb_ring_alloc(struct tb_nhi *nhi, u32 hop, int size, ring->start_poll = start_poll; ring->poll_data = poll_data; - ring->descriptors = dma_alloc_coherent(&ring->nhi->pdev->dev, - size * sizeof(*ring->descriptors), - &ring->descriptors_dma, GFP_KERNEL | __GFP_ZERO); + ring->descriptors = dma_alloc_coherent(ring->nhi->dev, + size * sizeof(*ring->descriptors), + &ring->descriptors_dma, GFP_KERNEL | __GFP_ZERO); if (!ring->descriptors) goto err_free_ring; @@ -689,7 +706,7 @@ static struct tb_ring *tb_ring_alloc(struct tb_nhi *nhi, u32 hop, int size, err_free_hop: nhi_free_hop(nhi, ring); err_free_descs: - dma_free_coherent(&ring->nhi->pdev->dev, + dma_free_coherent(ring->nhi->dev, ring->size * sizeof(*ring->descriptors), ring->descriptors, ring->descriptors_dma); err_free_ring: @@ -757,10 +774,10 @@ void tb_ring_start(struct tb_ring *ring) if (ring->nhi->going_away) goto err; if (ring->running) { - dev_WARN(&ring->nhi->pdev->dev, "ring already started\n"); + dev_WARN(ring->nhi->dev, "ring already started\n"); goto err; } - dev_dbg(&ring->nhi->pdev->dev, "starting %s %d\n", + dev_dbg(ring->nhi->dev, "starting %s %d\n", RING_TYPE(ring), ring->hop); if (ring->flags & RING_FLAG_FRAME) { @@ -780,11 +797,11 @@ void tb_ring_start(struct tb_ring *ring) hop &= REG_RX_OPTIONS_E2E_HOP_MASK; e2e_flags |= hop; - dev_dbg(&ring->nhi->pdev->dev, + dev_dbg(ring->nhi->dev, "enabling E2E for %s %d with TX HopID %d\n", RING_TYPE(ring), ring->hop, ring->e2e_tx_hop); } else { - dev_dbg(&ring->nhi->pdev->dev, "enabling E2E for %s %d\n", + dev_dbg(ring->nhi->dev, "enabling E2E for %s %d\n", RING_TYPE(ring), ring->hop); } @@ -824,12 +841,12 @@ void tb_ring_stop(struct tb_ring *ring) { spin_lock_irq(&ring->nhi->lock); spin_lock(&ring->lock); - dev_dbg(&ring->nhi->pdev->dev, "stopping %s %d\n", + dev_dbg(ring->nhi->dev, "stopping %s %d\n", RING_TYPE(ring), ring->hop); if (ring->nhi->going_away) goto err; if (!ring->running) { - dev_WARN(&ring->nhi->pdev->dev, "%s %d already stopped\n", + dev_WARN(ring->nhi->dev, "%s %d already stopped\n", RING_TYPE(ring), ring->hop); goto err; } @@ -878,14 +895,14 @@ void tb_ring_free(struct tb_ring *ring) ring->nhi->rx_rings[ring->hop] = NULL; if (ring->running) { - dev_WARN(&ring->nhi->pdev->dev, "%s %d still running\n", + dev_WARN(ring->nhi->dev, "%s %d still running\n", RING_TYPE(ring), ring->hop); } spin_unlock_irq(&ring->nhi->lock); ring_release_msix(ring); - dma_free_coherent(&ring->nhi->pdev->dev, + dma_free_coherent(ring->nhi->dev, ring->size * sizeof(*ring->descriptors), ring->descriptors, ring->descriptors_dma); @@ -893,7 +910,7 @@ void tb_ring_free(struct tb_ring *ring) ring->descriptors_dma = 0; - dev_dbg(&ring->nhi->pdev->dev, "freeing %s %d\n", RING_TYPE(ring), + dev_dbg(ring->nhi->dev, "freeing %s %d\n", RING_TYPE(ring), ring->hop); /* @@ -992,9 +1009,7 @@ static void nhi_interrupt_work(struct work_struct *work) if ((value & (1 << (bit % 32))) == 0) continue; if (type == 2) { - dev_warn(&nhi->pdev->dev, - "RX overflow for ring %d\n", - hop); + dev_warn(nhi->dev, "RX overflow for ring %d\n", hop); continue; } if (type == 0) @@ -1002,7 +1017,7 @@ static void nhi_interrupt_work(struct work_struct *work) else ring = nhi->rx_rings[hop]; if (ring == NULL) { - dev_warn(&nhi->pdev->dev, + dev_warn(nhi->dev, "got interrupt for inactive %s ring %d\n", type ? "RX" : "TX", hop); @@ -1191,16 +1206,18 @@ static int nhi_runtime_resume(struct device *dev) static void nhi_shutdown(struct tb_nhi *nhi) { + struct tb_nhi_pci *nhi_pci = nhi_to_pci(nhi); + struct pci_dev *pdev = to_pci_dev(nhi->dev); int i; - dev_dbg(&nhi->pdev->dev, "shutdown\n"); + dev_dbg(nhi->dev, "shutdown\n"); for (i = 0; i < nhi->hop_count; i++) { if (nhi->tx_rings[i]) - dev_WARN(&nhi->pdev->dev, + dev_WARN(nhi->dev, "TX ring %d is still active\n", i); if (nhi->rx_rings[i]) - dev_WARN(&nhi->pdev->dev, + dev_WARN(nhi->dev, "RX ring %d is still active\n", i); } nhi_disable_interrupts(nhi); @@ -1208,19 +1225,22 @@ static void nhi_shutdown(struct tb_nhi *nhi) * We have to release the irq before calling flush_work. Otherwise an * already executing IRQ handler could call schedule_work again. */ - if (!nhi->pdev->msix_enabled) { - devm_free_irq(&nhi->pdev->dev, nhi->pdev->irq, nhi); + if (!pdev->msix_enabled) { + devm_free_irq(nhi->dev, pdev->irq, nhi); flush_work(&nhi->interrupt_work); } - ida_destroy(&nhi->msix_ida); + ida_destroy(&nhi_pci->msix_ida); if (nhi->ops && nhi->ops->shutdown) nhi->ops->shutdown(nhi); } -static void nhi_check_quirks(struct tb_nhi *nhi) +static void nhi_check_quirks(struct tb_nhi_pci *nhi_pci) { - if (nhi->pdev->vendor == PCI_VENDOR_ID_INTEL) { + struct tb_nhi *nhi = &nhi_pci->nhi; + struct pci_dev *pdev = to_pci_dev(nhi->dev); + + if (pdev->vendor == PCI_VENDOR_ID_INTEL) { /* * Intel hardware supports auto clear of the interrupt * status register right after interrupt is being @@ -1228,7 +1248,7 @@ static void nhi_check_quirks(struct tb_nhi *nhi) */ nhi->quirks |= QUIRK_AUTO_CLEAR_INT; - switch (nhi->pdev->device) { + switch (pdev->device) { case PCI_DEVICE_ID_INTEL_FALCON_RIDGE_2C_NHI: case PCI_DEVICE_ID_INTEL_FALCON_RIDGE_4C_NHI: /* @@ -1242,7 +1262,7 @@ static void nhi_check_quirks(struct tb_nhi *nhi) } } -static int nhi_check_iommu_pdev(struct pci_dev *pdev, void *data) +static int nhi_check_iommu_pci_dev(struct pci_dev *pdev, void *data) { if (!pdev->external_facing || !device_iommu_capable(&pdev->dev, IOMMU_CAP_PRE_BOOT_PROTECTION)) @@ -1251,9 +1271,11 @@ static int nhi_check_iommu_pdev(struct pci_dev *pdev, void *data) return 1; /* Stop walking */ } -static void nhi_check_iommu(struct tb_nhi *nhi) +static void nhi_check_iommu(struct tb_nhi_pci *nhi_pci) { - struct pci_bus *bus = nhi->pdev->bus; + struct tb_nhi *nhi = &nhi_pci->nhi; + struct pci_dev *pdev = to_pci_dev(nhi->dev); + struct pci_bus *bus = pdev->bus; bool port_ok = false; /* @@ -1276,10 +1298,10 @@ static void nhi_check_iommu(struct tb_nhi *nhi) while (bus->parent) bus = bus->parent; - pci_walk_bus(bus, nhi_check_iommu_pdev, &port_ok); + pci_walk_bus(bus, nhi_check_iommu_pci_dev, &port_ok); nhi->iommu_dma_protection = port_ok; - dev_dbg(&nhi->pdev->dev, "IOMMU DMA protection is %s\n", + dev_dbg(nhi->dev, "IOMMU DMA protection is %s\n", str_enabled_disabled(port_ok)); } @@ -1294,7 +1316,7 @@ static void nhi_reset(struct tb_nhi *nhi) return; if (!host_reset) { - dev_dbg(&nhi->pdev->dev, "skipping host router reset\n"); + dev_dbg(nhi->dev, "skipping host router reset\n"); return; } @@ -1305,18 +1327,19 @@ static void nhi_reset(struct tb_nhi *nhi) do { val = ioread32(nhi->iobase + REG_RESET); if (!(val & REG_RESET_HRR)) { - dev_warn(&nhi->pdev->dev, "host router reset successful\n"); + dev_warn(nhi->dev, "host router reset successful\n"); return; } usleep_range(10, 20); } while (ktime_before(ktime_get(), timeout)); - dev_warn(&nhi->pdev->dev, "timeout resetting host router\n"); + dev_warn(nhi->dev, "timeout resetting host router\n"); } -static int nhi_init_msi(struct tb_nhi *nhi) +static int nhi_init_msi(struct tb_nhi_pci *nhi_pci) { - struct pci_dev *pdev = nhi->pdev; + struct tb_nhi *nhi = &nhi_pci->nhi; + struct pci_dev *pdev = to_pci_dev(nhi->dev); struct device *dev = &pdev->dev; int res, irq, nvec; @@ -1325,7 +1348,7 @@ static int nhi_init_msi(struct tb_nhi *nhi) nhi_enable_int_throttling(nhi); - ida_init(&nhi->msix_ida); + ida_init(&nhi_pci->msix_ida); /* * The NHI has 16 MSI-X vectors or a single MSI. We first try to @@ -1342,7 +1365,7 @@ static int nhi_init_msi(struct tb_nhi *nhi) INIT_WORK(&nhi->interrupt_work, nhi_interrupt_work); - irq = pci_irq_vector(nhi->pdev, 0); + irq = pci_irq_vector(pdev, 0); if (irq < 0) return irq; @@ -1391,6 +1414,7 @@ static struct tb *nhi_select_cm(struct tb_nhi *nhi) static int nhi_probe(struct pci_dev *pdev, const struct pci_device_id *id) { struct device *dev = &pdev->dev; + struct tb_nhi_pci *nhi_pci; struct tb_nhi *nhi; struct tb *tb; int res; @@ -1402,11 +1426,12 @@ static int nhi_probe(struct pci_dev *pdev, const struct pci_device_id *id) if (res) return dev_err_probe(dev, res, "cannot enable PCI device, aborting\n"); - nhi = devm_kzalloc(&pdev->dev, sizeof(*nhi), GFP_KERNEL); - if (!nhi) + nhi_pci = devm_kzalloc(dev, sizeof(*nhi_pci), GFP_KERNEL); + if (!nhi_pci) return -ENOMEM; - nhi->pdev = pdev; + nhi = &nhi_pci->nhi; + nhi->dev = dev; nhi->ops = (const struct tb_nhi_ops *)id->driver_data; nhi->iobase = pcim_iomap_region(pdev, 0, "thunderbolt"); @@ -1427,11 +1452,14 @@ static int nhi_probe(struct pci_dev *pdev, const struct pci_device_id *id) if (!nhi->tx_rings || !nhi->rx_rings) return -ENOMEM; - nhi_check_quirks(nhi); - nhi_check_iommu(nhi); + nhi_check_quirks(nhi_pci); + nhi_check_iommu(nhi_pci); nhi_reset(nhi); - res = nhi_init_msi(nhi); + /* In case someone left them on. */ + nhi_disable_interrupts(nhi); + + res = nhi_init_msi(nhi_pci); if (res) return dev_err_probe(dev, res, "cannot enable MSI, aborting\n"); diff --git a/drivers/thunderbolt/nhi_ops.c b/drivers/thunderbolt/nhi_ops.c index 96da07e88c521a..8c50066f34119b 100644 --- a/drivers/thunderbolt/nhi_ops.c +++ b/drivers/thunderbolt/nhi_ops.c @@ -24,7 +24,7 @@ static int check_for_device(struct device *dev, void *data) static bool icl_nhi_is_device_connected(struct tb_nhi *nhi) { - struct tb *tb = pci_get_drvdata(nhi->pdev); + struct tb *tb = dev_get_drvdata(nhi->dev); int ret; ret = device_for_each_child(&tb->root_switch->dev, NULL, @@ -34,6 +34,7 @@ static bool icl_nhi_is_device_connected(struct tb_nhi *nhi) static int icl_nhi_force_power(struct tb_nhi *nhi, bool power) { + struct pci_dev *pdev = to_pci_dev(nhi->dev); u32 vs_cap; /* @@ -48,7 +49,7 @@ static int icl_nhi_force_power(struct tb_nhi *nhi, bool power) * The actual power management happens inside shared ACPI power * resources using standard ACPI methods. */ - pci_read_config_dword(nhi->pdev, VS_CAP_22, &vs_cap); + pci_read_config_dword(pdev, VS_CAP_22, &vs_cap); if (power) { vs_cap &= ~VS_CAP_22_DMA_DELAY_MASK; vs_cap |= 0x22 << VS_CAP_22_DMA_DELAY_SHIFT; @@ -56,7 +57,7 @@ static int icl_nhi_force_power(struct tb_nhi *nhi, bool power) } else { vs_cap &= ~VS_CAP_22_FORCE_POWER; } - pci_write_config_dword(nhi->pdev, VS_CAP_22, vs_cap); + pci_write_config_dword(pdev, VS_CAP_22, vs_cap); if (power) { unsigned int retries = 350; @@ -64,7 +65,7 @@ static int icl_nhi_force_power(struct tb_nhi *nhi, bool power) /* Wait until the firmware tells it is up and running */ do { - pci_read_config_dword(nhi->pdev, VS_CAP_9, &val); + pci_read_config_dword(pdev, VS_CAP_9, &val); if (val & VS_CAP_9_FW_READY) return 0; usleep_range(3000, 3100); @@ -78,14 +79,16 @@ static int icl_nhi_force_power(struct tb_nhi *nhi, bool power) static void icl_nhi_lc_mailbox_cmd(struct tb_nhi *nhi, enum icl_lc_mailbox_cmd cmd) { + struct pci_dev *pdev = to_pci_dev(nhi->dev); u32 data; data = (cmd << VS_CAP_19_CMD_SHIFT) & VS_CAP_19_CMD_MASK; - pci_write_config_dword(nhi->pdev, VS_CAP_19, data | VS_CAP_19_VALID); + pci_write_config_dword(pdev, VS_CAP_19, data | VS_CAP_19_VALID); } static int icl_nhi_lc_mailbox_cmd_complete(struct tb_nhi *nhi, int timeout) { + struct pci_dev *pdev = to_pci_dev(nhi->dev); unsigned long end; u32 data; @@ -94,7 +97,7 @@ static int icl_nhi_lc_mailbox_cmd_complete(struct tb_nhi *nhi, int timeout) end = jiffies + msecs_to_jiffies(timeout); do { - pci_read_config_dword(nhi->pdev, VS_CAP_18, &data); + pci_read_config_dword(pdev, VS_CAP_18, &data); if (data & VS_CAP_18_DONE) goto clear; usleep_range(1000, 1100); @@ -104,24 +107,25 @@ static int icl_nhi_lc_mailbox_cmd_complete(struct tb_nhi *nhi, int timeout) clear: /* Clear the valid bit */ - pci_write_config_dword(nhi->pdev, VS_CAP_19, 0); + pci_write_config_dword(pdev, VS_CAP_19, 0); return 0; } static void icl_nhi_set_ltr(struct tb_nhi *nhi) { + struct pci_dev *pdev = to_pci_dev(nhi->dev); u32 max_ltr, ltr; - pci_read_config_dword(nhi->pdev, VS_CAP_16, &max_ltr); + pci_read_config_dword(pdev, VS_CAP_16, &max_ltr); max_ltr &= 0xffff; /* Program the same value for both snoop and no-snoop */ ltr = max_ltr << 16 | max_ltr; - pci_write_config_dword(nhi->pdev, VS_CAP_15, ltr); + pci_write_config_dword(pdev, VS_CAP_15, ltr); } static int icl_nhi_suspend(struct tb_nhi *nhi) { - struct tb *tb = pci_get_drvdata(nhi->pdev); + struct tb *tb = dev_get_drvdata(nhi->dev); int ret; if (icl_nhi_is_device_connected(nhi)) @@ -144,7 +148,7 @@ static int icl_nhi_suspend(struct tb_nhi *nhi) static int icl_nhi_suspend_noirq(struct tb_nhi *nhi, bool wakeup) { - struct tb *tb = pci_get_drvdata(nhi->pdev); + struct tb *tb = dev_get_drvdata(nhi->dev); enum icl_lc_mailbox_cmd cmd; if (!pm_suspend_via_firmware()) diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c index ec98c7b6e00ad2..c65395ab4bedea 100644 --- a/drivers/thunderbolt/switch.c +++ b/drivers/thunderbolt/switch.c @@ -211,6 +211,7 @@ static int nvm_authenticate_device_dma_port(struct tb_switch *sw) static void nvm_authenticate_start_dma_port(struct tb_switch *sw) { + struct pci_dev *pdev = to_pci_dev(sw->tb->nhi->dev); struct pci_dev *root_port; /* @@ -219,16 +220,17 @@ static void nvm_authenticate_start_dma_port(struct tb_switch *sw) * itself. To be on the safe side keep the root port in D0 during * the whole upgrade process. */ - root_port = pcie_find_root_port(sw->tb->nhi->pdev); + root_port = pcie_find_root_port(pdev); if (root_port) pm_runtime_get_noresume(&root_port->dev); } static void nvm_authenticate_complete_dma_port(struct tb_switch *sw) { + struct pci_dev *pdev = to_pci_dev(sw->tb->nhi->dev); struct pci_dev *root_port; - root_port = pcie_find_root_port(sw->tb->nhi->pdev); + root_port = pcie_find_root_port(pdev); if (root_port) pm_runtime_put(&root_port->dev); } diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c index 634a34287b1221..a2bcd35c0d2df3 100644 --- a/drivers/thunderbolt/tb.c +++ b/drivers/thunderbolt/tb.c @@ -3321,13 +3321,14 @@ static const struct tb_cm_ops tb_cm_ops = { */ static bool tb_apple_add_links(struct tb_nhi *nhi) { + struct pci_dev *nhi_pdev = to_pci_dev(nhi->dev); struct pci_dev *upstream, *pdev; bool ret; if (!x86_apple_machine) return false; - switch (nhi->pdev->device) { + switch (nhi_pdev->device) { case PCI_DEVICE_ID_INTEL_LIGHT_RIDGE: case PCI_DEVICE_ID_INTEL_CACTUS_RIDGE_4C: case PCI_DEVICE_ID_INTEL_FALCON_RIDGE_2C_NHI: @@ -3337,7 +3338,7 @@ static bool tb_apple_add_links(struct tb_nhi *nhi) return false; } - upstream = pci_upstream_bridge(nhi->pdev); + upstream = pci_upstream_bridge(nhi_pdev); while (upstream) { if (!pci_is_pcie(upstream)) return false; @@ -3364,15 +3365,15 @@ static bool tb_apple_add_links(struct tb_nhi *nhi) !pdev->is_pciehp) continue; - link = device_link_add(&pdev->dev, &nhi->pdev->dev, + link = device_link_add(&pdev->dev, nhi->dev, DL_FLAG_AUTOREMOVE_SUPPLIER | DL_FLAG_PM_RUNTIME); if (link) { - dev_dbg(&nhi->pdev->dev, "created link from %s\n", + dev_dbg(nhi->dev, "created link from %s\n", dev_name(&pdev->dev)); ret = true; } else { - dev_warn(&nhi->pdev->dev, "device link creation from %s failed\n", + dev_warn(nhi->dev, "device link creation from %s failed\n", dev_name(&pdev->dev)); } } diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h index 23526a617b810a..222268b2fec5ca 100644 --- a/drivers/thunderbolt/tb.h +++ b/drivers/thunderbolt/tb.h @@ -730,11 +730,11 @@ static inline int tb_port_write(struct tb_port *port, const void *buffer, length); } -#define tb_err(tb, fmt, arg...) dev_err(&(tb)->nhi->pdev->dev, fmt, ## arg) -#define tb_WARN(tb, fmt, arg...) dev_WARN(&(tb)->nhi->pdev->dev, fmt, ## arg) -#define tb_warn(tb, fmt, arg...) dev_warn(&(tb)->nhi->pdev->dev, fmt, ## arg) -#define tb_info(tb, fmt, arg...) dev_info(&(tb)->nhi->pdev->dev, fmt, ## arg) -#define tb_dbg(tb, fmt, arg...) dev_dbg(&(tb)->nhi->pdev->dev, fmt, ## arg) +#define tb_err(tb, fmt, arg...) dev_err((tb)->nhi->dev, fmt, ## arg) +#define tb_WARN(tb, fmt, arg...) dev_WARN((tb)->nhi->dev, fmt, ## arg) +#define tb_warn(tb, fmt, arg...) dev_warn((tb)->nhi->dev, fmt, ## arg) +#define tb_info(tb, fmt, arg...) dev_info((tb)->nhi->dev, fmt, ## arg) +#define tb_dbg(tb, fmt, arg...) dev_dbg((tb)->nhi->dev, fmt, ## arg) #define __TB_SW_PRINT(level, sw, fmt, arg...) \ do { \ diff --git a/drivers/thunderbolt/usb4_port.c b/drivers/thunderbolt/usb4_port.c index c32d3516e78051..890de530debc1f 100644 --- a/drivers/thunderbolt/usb4_port.c +++ b/drivers/thunderbolt/usb4_port.c @@ -138,7 +138,7 @@ bool usb4_usb3_port_match(struct device *usb4_port_dev, return false; /* Check if USB3 fwnode references same NHI where USB4 port resides */ - if (!device_match_fwnode(&nhi->pdev->dev, nhi_fwnode)) + if (!device_match_fwnode(nhi->dev, nhi_fwnode)) return false; if (fwnode_property_read_u8(usb3_port_fwnode, "usb4-port-number", &usb4_port_num)) diff --git a/include/linux/thunderbolt.h b/include/linux/thunderbolt.h index b49e6bb0d097c3..27704d5e1dc378 100644 --- a/include/linux/thunderbolt.h +++ b/include/linux/thunderbolt.h @@ -483,14 +483,13 @@ static inline struct tb_xdomain *tb_service_parent(struct tb_service *svc) * struct tb_nhi - thunderbolt native host interface * @lock: Must be held during ring creation/destruction. Is acquired by * interrupt_work when dispatching interrupts to individual rings. - * @pdev: Pointer to the PCI device + * @dev: Device associated with this NHI instance * @ops: NHI specific optional ops * @ring_layout: Layout of the ring registers inside @iobase. If not set * the default layout from the USB4 specification is used. * @iobase: MMIO space of the NHI * @tx_rings: All Tx rings available on this host controller * @rx_rings: All Rx rings available on this host controller - * @msix_ida: Used to allocate MSI-X vectors for rings * @going_away: The host controller device is about to disappear so when * this flag is set, avoid touching the hardware anymore. * @iommu_dma_protection: An IOMMU will isolate external-facing ports. @@ -501,13 +500,12 @@ static inline struct tb_xdomain *tb_service_parent(struct tb_service *svc) */ struct tb_nhi { spinlock_t lock; - struct pci_dev *pdev; + struct device *dev; const struct tb_nhi_ops *ops; const struct tb_nhi_ring_layout *ring_layout; void __iomem *iobase; struct tb_ring **tx_rings; struct tb_ring **rx_rings; - struct ida msix_ida; bool going_away; bool iommu_dma_protection; struct work_struct interrupt_work; @@ -687,7 +685,7 @@ void tb_ring_poll_complete(struct tb_ring *ring); */ static inline struct device *tb_ring_dma_device(struct tb_ring *ring) { - return &ring->nhi->pdev->dev; + return ring->nhi->dev; } bool usb4_usb3_port_match(struct device *usb4_port_dev, From 3446de144b31ca5500f28f20c32a1c300111f219 Mon Sep 17 00:00:00 2001 From: Konrad Dybcio Date: Thu, 21 May 2026 12:40:01 +0200 Subject: [PATCH 25/33] thunderbolt: Separate out common NHI bits Add a new file encapsulating most of the PCI NHI specifics (intentionally leaving some odd cookies behind to make the layering simpler). Most notably, separate out nhi_probe() to make it easier to register other types of NHIs. Also, fold in Intel Icelake (nhi_ops.c) support to contain all PCIe-related bits in pci.c. Signed-off-by: Konrad Dybcio Signed-off-by: Mika Westerberg --- drivers/thunderbolt/Makefile | 2 +- drivers/thunderbolt/nhi.c | 462 +++---------------------- drivers/thunderbolt/nhi.h | 29 +- drivers/thunderbolt/nhi_ops.c | 189 ----------- drivers/thunderbolt/pci.c | 622 ++++++++++++++++++++++++++++++++++ drivers/thunderbolt/switch.c | 43 +-- 6 files changed, 702 insertions(+), 645 deletions(-) delete mode 100644 drivers/thunderbolt/nhi_ops.c create mode 100644 drivers/thunderbolt/pci.c diff --git a/drivers/thunderbolt/Makefile b/drivers/thunderbolt/Makefile index b44b32dcb83226..8f7814082df9d1 100644 --- a/drivers/thunderbolt/Makefile +++ b/drivers/thunderbolt/Makefile @@ -1,7 +1,7 @@ # SPDX-License-Identifier: GPL-2.0-only ccflags-y := -I$(src) obj-${CONFIG_USB4} := thunderbolt.o -thunderbolt-objs := nhi.o nhi_ops.o ctl.o tb.o switch.o cap.o path.o tunnel.o eeprom.o +thunderbolt-objs := nhi.o ctl.o tb.o switch.o cap.o pci.o path.o tunnel.o eeprom.o thunderbolt-objs += domain.o dma_port.o icm.o property.o xdomain.o lc.o tmu.o usb4.o thunderbolt-objs += usb4_port.o nvm.o retimer.o quirks.o clx.o diff --git a/drivers/thunderbolt/nhi.c b/drivers/thunderbolt/nhi.c index 122f8ec651c8c4..683414115590a0 100644 --- a/drivers/thunderbolt/nhi.c +++ b/drivers/thunderbolt/nhi.c @@ -33,12 +33,6 @@ * transferred. */ #define RING_E2E_RESERVED_HOPID RING_FIRST_USABLE_HOPID -/* - * Minimal number of vectors when we use MSI-X. Two for control channel - * Rx/Tx and the rest four are for cross domain DMA paths. - */ -#define MSIX_MIN_VECS 6 -#define MSIX_MAX_VECS 16 #define NHI_MAILBOX_TIMEOUT 500 /* ms */ @@ -46,21 +40,6 @@ static bool host_reset = true; module_param(host_reset, bool, 0444); MODULE_PARM_DESC(host_reset, "reset USB4 host router (default: true)"); -/** - * struct tb_nhi_pci - NHI device connected over PCIe - * @nhi: NHI device - * @msix_ida: Used to allocate MSI-X vectors for rings - */ -struct tb_nhi_pci { - struct tb_nhi nhi; - struct ida msix_ida; -}; - -static inline struct tb_nhi_pci *nhi_to_pci(struct tb_nhi *nhi) -{ - return container_of(nhi, struct tb_nhi_pci, nhi); -} - static int ring_interrupt_index(const struct tb_ring *ring) { int bit = ring->hop; @@ -177,7 +156,7 @@ static void nhi_ring_interrupt_active(struct tb_ring *ring, bool active) * * Use only during init and shutdown. */ -static void nhi_disable_interrupts(struct tb_nhi *nhi) +void nhi_disable_interrupts(struct tb_nhi *nhi) { int i = 0; /* disable interrupts */ @@ -510,7 +489,7 @@ static void ring_clear_msix(const struct tb_ring *ring) 4 * (ring->nhi->hop_count / 32)); } -static irqreturn_t ring_msix(int irq, void *data) +irqreturn_t ring_msix(int irq, void *data) { struct tb_ring *ring = data; @@ -524,55 +503,6 @@ static irqreturn_t ring_msix(int irq, void *data) return IRQ_HANDLED; } -static int ring_request_msix(struct tb_ring *ring, bool no_suspend) -{ - struct tb_nhi *nhi = ring->nhi; - struct tb_nhi_pci *nhi_pci = nhi_to_pci(nhi); - struct pci_dev *pdev = to_pci_dev(nhi->dev); - unsigned long irqflags; - int ret; - - if (!pdev->msix_enabled) - return 0; - - ret = ida_alloc_max(&nhi_pci->msix_ida, MSIX_MAX_VECS - 1, GFP_KERNEL); - if (ret < 0) - return ret; - - ring->vector = ret; - - ret = pci_irq_vector(pdev, ring->vector); - if (ret < 0) - goto err_ida_remove; - - ring->irq = ret; - - irqflags = no_suspend ? IRQF_NO_SUSPEND : 0; - ret = request_irq(ring->irq, ring_msix, irqflags, "thunderbolt", ring); - if (ret) - goto err_ida_remove; - - return 0; - -err_ida_remove: - ida_free(&nhi_pci->msix_ida, ring->vector); - - return ret; -} - -static void ring_release_msix(struct tb_ring *ring) -{ - struct tb_nhi_pci *nhi_pci = nhi_to_pci(ring->nhi); - - if (ring->irq <= 0) - return; - - free_irq(ring->irq, ring); - ida_free(&nhi_pci->msix_ida, ring->vector); - ring->vector = 0; - ring->irq = 0; -} - static int nhi_alloc_hop(struct tb_nhi *nhi, struct tb_ring *ring) { unsigned int start_hop = RING_FIRST_USABLE_HOPID; @@ -698,8 +628,10 @@ static struct tb_ring *tb_ring_alloc(struct tb_nhi *nhi, u32 hop, int size, if (nhi_alloc_hop(nhi, ring)) goto err_free_descs; - if (ring_request_msix(ring, flags & RING_FLAG_NO_SUSPEND)) - goto err_free_hop; + if (nhi->ops && nhi->ops->request_ring_irq) { + if (nhi->ops->request_ring_irq(ring, flags & RING_FLAG_NO_SUSPEND)) + goto err_free_hop; + } return ring; @@ -884,6 +816,8 @@ EXPORT_SYMBOL_GPL(tb_ring_stop); */ void tb_ring_free(struct tb_ring *ring) { + struct tb_nhi *nhi = ring->nhi; + spin_lock_irq(&ring->nhi->lock); /* * Dissociate the ring from the NHI. This also ensures that @@ -900,7 +834,8 @@ void tb_ring_free(struct tb_ring *ring) } spin_unlock_irq(&ring->nhi->lock); - ring_release_msix(ring); + if (nhi->ops && nhi->ops->release_ring_irq) + nhi->ops->release_ring_irq(ring); dma_free_coherent(ring->nhi->dev, ring->size * sizeof(*ring->descriptors), @@ -981,7 +916,7 @@ enum nhi_fw_mode nhi_mailbox_mode(struct tb_nhi *nhi) return (enum nhi_fw_mode)val; } -static void nhi_interrupt_work(struct work_struct *work) +void nhi_interrupt_work(struct work_struct *work) { struct tb_nhi *nhi = container_of(work, typeof(*nhi), interrupt_work); int value = 0; /* Suppress uninitialized usage warning. */ @@ -1031,7 +966,7 @@ static void nhi_interrupt_work(struct work_struct *work) spin_unlock_irq(&nhi->lock); } -static irqreturn_t nhi_msi(int irq, void *data) +irqreturn_t nhi_msi(int irq, void *data) { struct tb_nhi *nhi = data; schedule_work(&nhi->interrupt_work); @@ -1040,8 +975,7 @@ static irqreturn_t nhi_msi(int irq, void *data) static int __nhi_suspend_noirq(struct device *dev, bool wakeup) { - struct pci_dev *pdev = to_pci_dev(dev); - struct tb *tb = pci_get_drvdata(pdev); + struct tb *tb = dev_get_drvdata(dev); struct tb_nhi *nhi = tb->nhi; int ret; @@ -1065,21 +999,19 @@ static int nhi_suspend_noirq(struct device *dev) static int nhi_freeze_noirq(struct device *dev) { - struct pci_dev *pdev = to_pci_dev(dev); - struct tb *tb = pci_get_drvdata(pdev); + struct tb *tb = dev_get_drvdata(dev); return tb_domain_freeze_noirq(tb); } static int nhi_thaw_noirq(struct device *dev) { - struct pci_dev *pdev = to_pci_dev(dev); - struct tb *tb = pci_get_drvdata(pdev); + struct tb *tb = dev_get_drvdata(dev); return tb_domain_thaw_noirq(tb); } -static bool nhi_wake_supported(struct pci_dev *pdev) +static bool nhi_wake_supported(struct device *dev) { u8 val; @@ -1087,7 +1019,7 @@ static bool nhi_wake_supported(struct pci_dev *pdev) * If power rails are sustainable for wakeup from S4 this * property is set by the BIOS. */ - if (!device_property_read_u8(&pdev->dev, "WAKE_SUPPORTED", &val)) + if (!device_property_read_u8(dev, "WAKE_SUPPORTED", &val)) return !!val; return true; @@ -1095,14 +1027,13 @@ static bool nhi_wake_supported(struct pci_dev *pdev) static int nhi_poweroff_noirq(struct device *dev) { - struct pci_dev *pdev = to_pci_dev(dev); bool wakeup; - wakeup = device_may_wakeup(dev) && nhi_wake_supported(pdev); + wakeup = device_may_wakeup(dev) && nhi_wake_supported(dev); return __nhi_suspend_noirq(dev, wakeup); } -static void nhi_enable_int_throttling(struct tb_nhi *nhi) +void nhi_enable_int_throttling(struct tb_nhi *nhi) { /* Throttling is specified in 256ns increments */ u32 throttle = DIV_ROUND_UP(128 * NSEC_PER_USEC, 256); @@ -1120,8 +1051,7 @@ static void nhi_enable_int_throttling(struct tb_nhi *nhi) static int nhi_resume_noirq(struct device *dev) { - struct pci_dev *pdev = to_pci_dev(dev); - struct tb *tb = pci_get_drvdata(pdev); + struct tb *tb = dev_get_drvdata(dev); struct tb_nhi *nhi = tb->nhi; int ret; @@ -1130,7 +1060,7 @@ static int nhi_resume_noirq(struct device *dev) * unplugged last device which causes the host controller to go * away on PCs. */ - if (!pci_device_is_present(pdev)) { + if ((nhi->ops->is_present && !nhi->ops->is_present(nhi))) { nhi->going_away = true; } else { if (nhi->ops && nhi->ops->resume_noirq) { @@ -1146,32 +1076,29 @@ static int nhi_resume_noirq(struct device *dev) static int nhi_suspend(struct device *dev) { - struct pci_dev *pdev = to_pci_dev(dev); - struct tb *tb = pci_get_drvdata(pdev); + struct tb *tb = dev_get_drvdata(dev); return tb_domain_suspend(tb); } static void nhi_complete(struct device *dev) { - struct pci_dev *pdev = to_pci_dev(dev); - struct tb *tb = pci_get_drvdata(pdev); + struct tb *tb = dev_get_drvdata(dev); /* * If we were runtime suspended when system suspend started, * schedule runtime resume now. It should bring the domain back * to functional state. */ - if (pm_runtime_suspended(&pdev->dev)) - pm_runtime_resume(&pdev->dev); + if (pm_runtime_suspended(dev)) + pm_runtime_resume(dev); else tb_domain_complete(tb); } static int nhi_runtime_suspend(struct device *dev) { - struct pci_dev *pdev = to_pci_dev(dev); - struct tb *tb = pci_get_drvdata(pdev); + struct tb *tb = dev_get_drvdata(dev); struct tb_nhi *nhi = tb->nhi; int ret; @@ -1189,8 +1116,7 @@ static int nhi_runtime_suspend(struct device *dev) static int nhi_runtime_resume(struct device *dev) { - struct pci_dev *pdev = to_pci_dev(dev); - struct tb *tb = pci_get_drvdata(pdev); + struct tb *tb = dev_get_drvdata(dev); struct tb_nhi *nhi = tb->nhi; int ret; @@ -1204,10 +1130,8 @@ static int nhi_runtime_resume(struct device *dev) return tb_domain_runtime_resume(tb); } -static void nhi_shutdown(struct tb_nhi *nhi) +void nhi_shutdown(struct tb_nhi *nhi) { - struct tb_nhi_pci *nhi_pci = nhi_to_pci(nhi); - struct pci_dev *pdev = to_pci_dev(nhi->dev); int i; dev_dbg(nhi->dev, "shutdown\n"); @@ -1221,90 +1145,11 @@ static void nhi_shutdown(struct tb_nhi *nhi) "RX ring %d is still active\n", i); } nhi_disable_interrupts(nhi); - /* - * We have to release the irq before calling flush_work. Otherwise an - * already executing IRQ handler could call schedule_work again. - */ - if (!pdev->msix_enabled) { - devm_free_irq(nhi->dev, pdev->irq, nhi); - flush_work(&nhi->interrupt_work); - } - ida_destroy(&nhi_pci->msix_ida); if (nhi->ops && nhi->ops->shutdown) nhi->ops->shutdown(nhi); } -static void nhi_check_quirks(struct tb_nhi_pci *nhi_pci) -{ - struct tb_nhi *nhi = &nhi_pci->nhi; - struct pci_dev *pdev = to_pci_dev(nhi->dev); - - if (pdev->vendor == PCI_VENDOR_ID_INTEL) { - /* - * Intel hardware supports auto clear of the interrupt - * status register right after interrupt is being - * issued. - */ - nhi->quirks |= QUIRK_AUTO_CLEAR_INT; - - switch (pdev->device) { - case PCI_DEVICE_ID_INTEL_FALCON_RIDGE_2C_NHI: - case PCI_DEVICE_ID_INTEL_FALCON_RIDGE_4C_NHI: - /* - * Falcon Ridge controller needs the end-to-end - * flow control workaround to avoid losing Rx - * packets when RING_FLAG_E2E is set. - */ - nhi->quirks |= QUIRK_E2E; - break; - } - } -} - -static int nhi_check_iommu_pci_dev(struct pci_dev *pdev, void *data) -{ - if (!pdev->external_facing || - !device_iommu_capable(&pdev->dev, IOMMU_CAP_PRE_BOOT_PROTECTION)) - return 0; - *(bool *)data = true; - return 1; /* Stop walking */ -} - -static void nhi_check_iommu(struct tb_nhi_pci *nhi_pci) -{ - struct tb_nhi *nhi = &nhi_pci->nhi; - struct pci_dev *pdev = to_pci_dev(nhi->dev); - struct pci_bus *bus = pdev->bus; - bool port_ok = false; - - /* - * Ideally what we'd do here is grab every PCI device that - * represents a tunnelling adapter for this NHI and check their - * status directly, but unfortunately USB4 seems to make it - * obnoxiously difficult to reliably make any correlation. - * - * So for now we'll have to bodge it... Hoping that the system - * is at least sane enough that an adapter is in the same PCI - * segment as its NHI, if we can find *something* on that segment - * which meets the requirements for Kernel DMA Protection, we'll - * take that to imply that firmware is aware and has (hopefully) - * done the right thing in general. We need to know that the PCI - * layer has seen the ExternalFacingPort property which will then - * inform the IOMMU layer to enforce the complete "untrusted DMA" - * flow, but also that the IOMMU driver itself can be trusted not - * to have been subverted by a pre-boot DMA attack. - */ - while (bus->parent) - bus = bus->parent; - - pci_walk_bus(bus, nhi_check_iommu_pci_dev, &port_ok); - - nhi->iommu_dma_protection = port_ok; - dev_dbg(nhi->dev, "IOMMU DMA protection is %s\n", - str_enabled_disabled(port_ok)); -} - static void nhi_reset(struct tb_nhi *nhi) { ktime_t timeout; @@ -1336,58 +1181,6 @@ static void nhi_reset(struct tb_nhi *nhi) dev_warn(nhi->dev, "timeout resetting host router\n"); } -static int nhi_init_msi(struct tb_nhi_pci *nhi_pci) -{ - struct tb_nhi *nhi = &nhi_pci->nhi; - struct pci_dev *pdev = to_pci_dev(nhi->dev); - struct device *dev = &pdev->dev; - int res, irq, nvec; - - /* In case someone left them on. */ - nhi_disable_interrupts(nhi); - - nhi_enable_int_throttling(nhi); - - ida_init(&nhi_pci->msix_ida); - - /* - * The NHI has 16 MSI-X vectors or a single MSI. We first try to - * get all MSI-X vectors and if we succeed, each ring will have - * one MSI-X. If for some reason that does not work out, we - * fallback to a single MSI. - */ - nvec = pci_alloc_irq_vectors(pdev, MSIX_MIN_VECS, MSIX_MAX_VECS, - PCI_IRQ_MSIX); - if (nvec < 0) { - nvec = pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_MSI); - if (nvec < 0) - return nvec; - - INIT_WORK(&nhi->interrupt_work, nhi_interrupt_work); - - irq = pci_irq_vector(pdev, 0); - if (irq < 0) - return irq; - - res = devm_request_irq(&pdev->dev, irq, nhi_msi, - IRQF_NO_SUSPEND, "thunderbolt", nhi); - if (res) - return dev_err_probe(dev, res, "request_irq failed, aborting\n"); - } - - return 0; -} - -static bool nhi_imr_valid(struct pci_dev *pdev) -{ - u8 val; - - if (!device_property_read_u8(&pdev->dev, "IMR_VALID", &val)) - return !!val; - - return true; -} - static struct tb *nhi_select_cm(struct tb_nhi *nhi) { struct tb *tb; @@ -1411,66 +1204,42 @@ static struct tb *nhi_select_cm(struct tb_nhi *nhi) return tb; } -static int nhi_probe(struct pci_dev *pdev, const struct pci_device_id *id) +int nhi_probe(struct tb_nhi *nhi) { - struct device *dev = &pdev->dev; - struct tb_nhi_pci *nhi_pci; - struct tb_nhi *nhi; + struct device *dev = nhi->dev; struct tb *tb; int res; - if (!nhi_imr_valid(pdev)) - return dev_err_probe(dev, -ENODEV, "firmware image not valid, aborting\n"); - - res = pcim_enable_device(pdev); - if (res) - return dev_err_probe(dev, res, "cannot enable PCI device, aborting\n"); - - nhi_pci = devm_kzalloc(dev, sizeof(*nhi_pci), GFP_KERNEL); - if (!nhi_pci) - return -ENOMEM; - - nhi = &nhi_pci->nhi; - nhi->dev = dev; - nhi->ops = (const struct tb_nhi_ops *)id->driver_data; - - nhi->iobase = pcim_iomap_region(pdev, 0, "thunderbolt"); - res = PTR_ERR_OR_ZERO(nhi->iobase); - if (res) - return dev_err_probe(dev, res, "cannot obtain PCI resources, aborting\n"); - if (!nhi->ring_layout) nhi->ring_layout = &nhi_default_ring_layout; - nhi->hop_count = ioread32(nhi->iobase + REG_CAPS) & 0x3ff; dev_dbg(dev, "total paths: %d\n", nhi->hop_count); - nhi->tx_rings = devm_kcalloc(&pdev->dev, nhi->hop_count, + nhi->tx_rings = devm_kcalloc(dev, nhi->hop_count, sizeof(*nhi->tx_rings), GFP_KERNEL); - nhi->rx_rings = devm_kcalloc(&pdev->dev, nhi->hop_count, + nhi->rx_rings = devm_kcalloc(dev, nhi->hop_count, sizeof(*nhi->rx_rings), GFP_KERNEL); if (!nhi->tx_rings || !nhi->rx_rings) return -ENOMEM; - nhi_check_quirks(nhi_pci); - nhi_check_iommu(nhi_pci); nhi_reset(nhi); /* In case someone left them on. */ nhi_disable_interrupts(nhi); + nhi_enable_int_throttling(nhi); - res = nhi_init_msi(nhi_pci); - if (res) - return dev_err_probe(dev, res, "cannot enable MSI, aborting\n"); + if (nhi->ops && nhi->ops->init_interrupts) { + res = nhi->ops->init_interrupts(nhi); + if (res) + return dev_err_probe(dev, res, "cannot enable interrupts, aborting\n"); + } spin_lock_init(&nhi->lock); - res = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64)); + res = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64)); if (res) return dev_err_probe(dev, res, "failed to set DMA mask\n"); - pci_set_master(pdev); - if (nhi->ops && nhi->ops->init) { res = nhi->ops->init(nhi); if (res) @@ -1494,37 +1263,24 @@ static int nhi_probe(struct pci_dev *pdev, const struct pci_device_id *id) nhi_shutdown(nhi); return res; } - pci_set_drvdata(pdev, tb); + dev_set_drvdata(dev, tb); - device_wakeup_enable(&pdev->dev); + device_wakeup_enable(dev); - pm_runtime_allow(&pdev->dev); - pm_runtime_set_autosuspend_delay(&pdev->dev, TB_AUTOSUSPEND_DELAY); - pm_runtime_use_autosuspend(&pdev->dev); - pm_runtime_put_autosuspend(&pdev->dev); + pm_runtime_allow(dev); + pm_runtime_set_autosuspend_delay(dev, TB_AUTOSUSPEND_DELAY); + pm_runtime_use_autosuspend(dev); + pm_runtime_put_autosuspend(dev); return 0; } -static void nhi_remove(struct pci_dev *pdev) -{ - struct tb *tb = pci_get_drvdata(pdev); - struct tb_nhi *nhi = tb->nhi; - - pm_runtime_get_sync(&pdev->dev); - pm_runtime_dont_use_autosuspend(&pdev->dev); - pm_runtime_forbid(&pdev->dev); - - tb_domain_remove(tb); - nhi_shutdown(nhi); -} - /* * The tunneled pci bridges are siblings of us. Use resume_noirq to reenable * the tunnels asap. A corresponding pci quirk blocks the downstream bridges * resume_noirq until we are done. */ -static const struct dev_pm_ops nhi_pm_ops = { +const struct dev_pm_ops nhi_pm_ops = { .suspend_noirq = nhi_suspend_noirq, .resume_noirq = nhi_resume_noirq, .freeze_noirq = nhi_freeze_noirq, /* @@ -1540,129 +1296,3 @@ static const struct dev_pm_ops nhi_pm_ops = { .runtime_suspend = nhi_runtime_suspend, .runtime_resume = nhi_runtime_resume, }; - -static struct pci_device_id nhi_ids[] = { - /* - * We have to specify class, the TB bridges use the same device and - * vendor (sub)id on gen 1 and gen 2 controllers. - */ - { - .class = PCI_CLASS_SYSTEM_OTHER << 8, .class_mask = ~0, - .vendor = PCI_VENDOR_ID_INTEL, - .device = PCI_DEVICE_ID_INTEL_LIGHT_RIDGE, - .subvendor = 0x2222, .subdevice = 0x1111, - }, - { - .class = PCI_CLASS_SYSTEM_OTHER << 8, .class_mask = ~0, - .vendor = PCI_VENDOR_ID_INTEL, - .device = PCI_DEVICE_ID_INTEL_CACTUS_RIDGE_4C, - .subvendor = 0x2222, .subdevice = 0x1111, - }, - { - .class = PCI_CLASS_SYSTEM_OTHER << 8, .class_mask = ~0, - .vendor = PCI_VENDOR_ID_INTEL, - .device = PCI_DEVICE_ID_INTEL_FALCON_RIDGE_2C_NHI, - .subvendor = PCI_ANY_ID, .subdevice = PCI_ANY_ID, - }, - { - .class = PCI_CLASS_SYSTEM_OTHER << 8, .class_mask = ~0, - .vendor = PCI_VENDOR_ID_INTEL, - .device = PCI_DEVICE_ID_INTEL_FALCON_RIDGE_4C_NHI, - .subvendor = PCI_ANY_ID, .subdevice = PCI_ANY_ID, - }, - - /* Thunderbolt 3 */ - { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_2C_NHI) }, - { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_4C_NHI) }, - { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_USBONLY_NHI) }, - { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_LP_NHI) }, - { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_LP_USBONLY_NHI) }, - { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_C_2C_NHI) }, - { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_C_4C_NHI) }, - { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_C_USBONLY_NHI) }, - { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_TITAN_RIDGE_2C_NHI) }, - { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_TITAN_RIDGE_4C_NHI) }, - { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ICL_NHI0), - .driver_data = (kernel_ulong_t)&icl_nhi_ops }, - { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ICL_NHI1), - .driver_data = (kernel_ulong_t)&icl_nhi_ops }, - /* Thunderbolt 4 */ - { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_TGL_NHI0), - .driver_data = (kernel_ulong_t)&icl_nhi_ops }, - { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_TGL_NHI1), - .driver_data = (kernel_ulong_t)&icl_nhi_ops }, - { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_TGL_H_NHI0), - .driver_data = (kernel_ulong_t)&icl_nhi_ops }, - { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_TGL_H_NHI1), - .driver_data = (kernel_ulong_t)&icl_nhi_ops }, - { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ADL_NHI0), - .driver_data = (kernel_ulong_t)&icl_nhi_ops }, - { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ADL_NHI1), - .driver_data = (kernel_ulong_t)&icl_nhi_ops }, - { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_RPL_NHI0), - .driver_data = (kernel_ulong_t)&icl_nhi_ops }, - { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_RPL_NHI1), - .driver_data = (kernel_ulong_t)&icl_nhi_ops }, - { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_MTL_M_NHI0), - .driver_data = (kernel_ulong_t)&icl_nhi_ops }, - { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_MTL_P_NHI0), - .driver_data = (kernel_ulong_t)&icl_nhi_ops }, - { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_MTL_P_NHI1), - .driver_data = (kernel_ulong_t)&icl_nhi_ops }, - { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_LNL_NHI0), - .driver_data = (kernel_ulong_t)&icl_nhi_ops }, - { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_LNL_NHI1), - .driver_data = (kernel_ulong_t)&icl_nhi_ops }, - { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_PTL_M_NHI0), - .driver_data = (kernel_ulong_t)&icl_nhi_ops }, - { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_PTL_M_NHI1), - .driver_data = (kernel_ulong_t)&icl_nhi_ops }, - { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_PTL_P_NHI0), - .driver_data = (kernel_ulong_t)&icl_nhi_ops }, - { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_PTL_P_NHI1), - .driver_data = (kernel_ulong_t)&icl_nhi_ops }, - { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_WCL_NHI0), - .driver_data = (kernel_ulong_t)&icl_nhi_ops }, - { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_BARLOW_RIDGE_HOST_80G_NHI) }, - { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_BARLOW_RIDGE_HOST_40G_NHI) }, - - /* Any USB4 compliant host */ - { PCI_DEVICE_CLASS(PCI_CLASS_SERIAL_USB_USB4, ~0) }, - - { 0,} -}; - -MODULE_DEVICE_TABLE(pci, nhi_ids); -MODULE_DESCRIPTION("Thunderbolt/USB4 core driver"); -MODULE_LICENSE("GPL"); - -static struct pci_driver nhi_driver = { - .name = "thunderbolt", - .id_table = nhi_ids, - .probe = nhi_probe, - .remove = nhi_remove, - .shutdown = nhi_remove, - .driver.pm = &nhi_pm_ops, -}; - -static int __init nhi_init(void) -{ - int ret; - - ret = tb_domain_init(); - if (ret) - return ret; - ret = pci_register_driver(&nhi_driver); - if (ret) - tb_domain_exit(); - return ret; -} - -static void __exit nhi_unload(void) -{ - pci_unregister_driver(&nhi_driver); - tb_domain_exit(); -} - -rootfs_initcall(nhi_init); -module_exit(nhi_unload); diff --git a/drivers/thunderbolt/nhi.h b/drivers/thunderbolt/nhi.h index 1c11da72ca4968..50fcf330eb6f32 100644 --- a/drivers/thunderbolt/nhi.h +++ b/drivers/thunderbolt/nhi.h @@ -29,6 +29,14 @@ enum nhi_mailbox_cmd { int nhi_mailbox_cmd(struct tb_nhi *nhi, enum nhi_mailbox_cmd cmd, u32 data); enum nhi_fw_mode nhi_mailbox_mode(struct tb_nhi *nhi); +void nhi_enable_int_throttling(struct tb_nhi *nhi); +void nhi_disable_interrupts(struct tb_nhi *nhi); +void nhi_interrupt_work(struct work_struct *work); +irqreturn_t nhi_msi(int irq, void *data); +irqreturn_t ring_msix(int irq, void *data); +int nhi_probe(struct tb_nhi *nhi); +void nhi_shutdown(struct tb_nhi *nhi); +extern const struct dev_pm_ops nhi_pm_ops; /** * struct tb_nhi_ring_layout - Layout of the ring registers in the NHI MMIO space @@ -90,14 +98,6 @@ struct tb_nhi_ops { int (*init_interrupts)(struct tb_nhi *nhi); }; -extern const struct tb_nhi_ops icl_nhi_ops; - -/* Host interface quirks */ -#define QUIRK_AUTO_CLEAR_INT BIT(0) -#define QUIRK_E2E BIT(1) -#define QUIRK_NO_DMA_PORT BIT(2) -#define QUIRK_NO_USB3_BW_ALLOC BIT(3) - /* * PCI IDs used in this driver from Win Ridge forward. There is no * need for the PCI quirk anymore as we will use ICM also on Apple @@ -148,4 +148,17 @@ extern const struct tb_nhi_ops icl_nhi_ops; #define PCI_CLASS_SERIAL_USB_USB4 0x0c0340 +/* Host interface quirks */ +#define QUIRK_AUTO_CLEAR_INT BIT(0) +#define QUIRK_E2E BIT(1) +#define QUIRK_NO_DMA_PORT BIT(2) +#define QUIRK_NO_USB3_BW_ALLOC BIT(3) + +/* + * Minimal number of vectors when we use MSI-X. Two for control channel + * Rx/Tx and the rest four are for cross domain DMA paths. + */ +#define MSIX_MIN_VECS 6 +#define MSIX_MAX_VECS 16 + #endif diff --git a/drivers/thunderbolt/nhi_ops.c b/drivers/thunderbolt/nhi_ops.c deleted file mode 100644 index 8c50066f34119b..00000000000000 --- a/drivers/thunderbolt/nhi_ops.c +++ /dev/null @@ -1,189 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * NHI specific operations - * - * Copyright (C) 2019, Intel Corporation - * Author: Mika Westerberg - */ - -#include -#include - -#include "nhi.h" -#include "nhi_regs.h" -#include "tb.h" - -/* Ice Lake specific NHI operations */ - -#define ICL_LC_MAILBOX_TIMEOUT 500 /* ms */ - -static int check_for_device(struct device *dev, void *data) -{ - return tb_is_switch(dev); -} - -static bool icl_nhi_is_device_connected(struct tb_nhi *nhi) -{ - struct tb *tb = dev_get_drvdata(nhi->dev); - int ret; - - ret = device_for_each_child(&tb->root_switch->dev, NULL, - check_for_device); - return ret > 0; -} - -static int icl_nhi_force_power(struct tb_nhi *nhi, bool power) -{ - struct pci_dev *pdev = to_pci_dev(nhi->dev); - u32 vs_cap; - - /* - * The Thunderbolt host controller is present always in Ice Lake - * but the firmware may not be loaded and running (depending - * whether there is device connected and so on). Each time the - * controller is used we need to "Force Power" it first and wait - * for the firmware to indicate it is up and running. This "Force - * Power" is really not about actually powering on/off the - * controller so it is accessible even if "Force Power" is off. - * - * The actual power management happens inside shared ACPI power - * resources using standard ACPI methods. - */ - pci_read_config_dword(pdev, VS_CAP_22, &vs_cap); - if (power) { - vs_cap &= ~VS_CAP_22_DMA_DELAY_MASK; - vs_cap |= 0x22 << VS_CAP_22_DMA_DELAY_SHIFT; - vs_cap |= VS_CAP_22_FORCE_POWER; - } else { - vs_cap &= ~VS_CAP_22_FORCE_POWER; - } - pci_write_config_dword(pdev, VS_CAP_22, vs_cap); - - if (power) { - unsigned int retries = 350; - u32 val; - - /* Wait until the firmware tells it is up and running */ - do { - pci_read_config_dword(pdev, VS_CAP_9, &val); - if (val & VS_CAP_9_FW_READY) - return 0; - usleep_range(3000, 3100); - } while (--retries); - - return -ETIMEDOUT; - } - - return 0; -} - -static void icl_nhi_lc_mailbox_cmd(struct tb_nhi *nhi, enum icl_lc_mailbox_cmd cmd) -{ - struct pci_dev *pdev = to_pci_dev(nhi->dev); - u32 data; - - data = (cmd << VS_CAP_19_CMD_SHIFT) & VS_CAP_19_CMD_MASK; - pci_write_config_dword(pdev, VS_CAP_19, data | VS_CAP_19_VALID); -} - -static int icl_nhi_lc_mailbox_cmd_complete(struct tb_nhi *nhi, int timeout) -{ - struct pci_dev *pdev = to_pci_dev(nhi->dev); - unsigned long end; - u32 data; - - if (!timeout) - goto clear; - - end = jiffies + msecs_to_jiffies(timeout); - do { - pci_read_config_dword(pdev, VS_CAP_18, &data); - if (data & VS_CAP_18_DONE) - goto clear; - usleep_range(1000, 1100); - } while (time_before(jiffies, end)); - - return -ETIMEDOUT; - -clear: - /* Clear the valid bit */ - pci_write_config_dword(pdev, VS_CAP_19, 0); - return 0; -} - -static void icl_nhi_set_ltr(struct tb_nhi *nhi) -{ - struct pci_dev *pdev = to_pci_dev(nhi->dev); - u32 max_ltr, ltr; - - pci_read_config_dword(pdev, VS_CAP_16, &max_ltr); - max_ltr &= 0xffff; - /* Program the same value for both snoop and no-snoop */ - ltr = max_ltr << 16 | max_ltr; - pci_write_config_dword(pdev, VS_CAP_15, ltr); -} - -static int icl_nhi_suspend(struct tb_nhi *nhi) -{ - struct tb *tb = dev_get_drvdata(nhi->dev); - int ret; - - if (icl_nhi_is_device_connected(nhi)) - return 0; - - if (tb_switch_is_icm(tb->root_switch)) { - /* - * If there is no device connected we need to perform - * both: a handshake through LC mailbox and force power - * down before entering D3. - */ - icl_nhi_lc_mailbox_cmd(nhi, ICL_LC_PREPARE_FOR_RESET); - ret = icl_nhi_lc_mailbox_cmd_complete(nhi, ICL_LC_MAILBOX_TIMEOUT); - if (ret) - return ret; - } - - return icl_nhi_force_power(nhi, false); -} - -static int icl_nhi_suspend_noirq(struct tb_nhi *nhi, bool wakeup) -{ - struct tb *tb = dev_get_drvdata(nhi->dev); - enum icl_lc_mailbox_cmd cmd; - - if (!pm_suspend_via_firmware()) - return icl_nhi_suspend(nhi); - - if (!tb_switch_is_icm(tb->root_switch)) - return 0; - - cmd = wakeup ? ICL_LC_GO2SX : ICL_LC_GO2SX_NO_WAKE; - icl_nhi_lc_mailbox_cmd(nhi, cmd); - return icl_nhi_lc_mailbox_cmd_complete(nhi, ICL_LC_MAILBOX_TIMEOUT); -} - -static int icl_nhi_resume(struct tb_nhi *nhi) -{ - int ret; - - ret = icl_nhi_force_power(nhi, true); - if (ret) - return ret; - - icl_nhi_set_ltr(nhi); - return 0; -} - -static void icl_nhi_shutdown(struct tb_nhi *nhi) -{ - icl_nhi_force_power(nhi, false); -} - -const struct tb_nhi_ops icl_nhi_ops = { - .init = icl_nhi_resume, - .suspend_noirq = icl_nhi_suspend_noirq, - .resume_noirq = icl_nhi_resume, - .runtime_suspend = icl_nhi_suspend, - .runtime_resume = icl_nhi_resume, - .shutdown = icl_nhi_shutdown, -}; diff --git a/drivers/thunderbolt/pci.c b/drivers/thunderbolt/pci.c new file mode 100644 index 00000000000000..bbd186c29ef723 --- /dev/null +++ b/drivers/thunderbolt/pci.c @@ -0,0 +1,622 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Thunderbolt driver - PCI NHI driver + * + * Copyright (c) 2014 Andreas Noever + * Copyright (C) 2018, Intel Corporation + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "nhi.h" +#include "nhi_regs.h" +#include "tb.h" + +/** + * struct tb_nhi_pci - NHI device connected over PCIe + * @nhi: NHI device + * @msix_ida: Used to allocate MSI-X vectors for rings + */ +struct tb_nhi_pci { + struct tb_nhi nhi; + struct ida msix_ida; +}; + +static inline struct tb_nhi_pci *nhi_to_pci(struct tb_nhi *nhi) +{ + return container_of(nhi, struct tb_nhi_pci, nhi); +} + +static void nhi_pci_check_quirks(struct tb_nhi_pci *nhi_pci) +{ + struct tb_nhi *nhi = &nhi_pci->nhi; + struct pci_dev *pdev = to_pci_dev(nhi->dev); + + if (pdev->vendor == PCI_VENDOR_ID_INTEL) { + /* + * Intel hardware supports auto clear of the interrupt + * status register right after interrupt is being + * issued. + */ + nhi->quirks |= QUIRK_AUTO_CLEAR_INT; + + switch (pdev->device) { + case PCI_DEVICE_ID_INTEL_FALCON_RIDGE_2C_NHI: + case PCI_DEVICE_ID_INTEL_FALCON_RIDGE_4C_NHI: + /* + * Falcon Ridge controller needs the end-to-end + * flow control workaround to avoid losing Rx + * packets when RING_FLAG_E2E is set. + */ + nhi->quirks |= QUIRK_E2E; + break; + } + } +} + +static int nhi_pci_check_iommu_pdev(struct pci_dev *pdev, void *data) +{ + if (!pdev->external_facing || + !device_iommu_capable(&pdev->dev, IOMMU_CAP_PRE_BOOT_PROTECTION)) + return 0; + *(bool *)data = true; + return 1; /* Stop walking */ +} + +static void nhi_pci_check_iommu(struct tb_nhi_pci *nhi_pci) +{ + struct tb_nhi *nhi = &nhi_pci->nhi; + struct pci_dev *pdev = to_pci_dev(nhi->dev); + struct pci_bus *bus = pdev->bus; + bool port_ok = false; + + /* + * Ideally what we'd do here is grab every PCI device that + * represents a tunnelling adapter for this NHI and check their + * status directly, but unfortunately USB4 seems to make it + * obnoxiously difficult to reliably make any correlation. + * + * So for now we'll have to bodge it... Hoping that the system + * is at least sane enough that an adapter is in the same PCI + * segment as its NHI, if we can find *something* on that segment + * which meets the requirements for Kernel DMA Protection, we'll + * take that to imply that firmware is aware and has (hopefully) + * done the right thing in general. We need to know that the PCI + * layer has seen the ExternalFacingPort property which will then + * inform the IOMMU layer to enforce the complete "untrusted DMA" + * flow, but also that the IOMMU driver itself can be trusted not + * to have been subverted by a pre-boot DMA attack. + */ + while (bus->parent) + bus = bus->parent; + + pci_walk_bus(bus, nhi_pci_check_iommu_pdev, &port_ok); + + nhi->iommu_dma_protection = port_ok; + dev_dbg(nhi->dev, "IOMMU DMA protection is %s\n", + str_enabled_disabled(port_ok)); +} + +static int nhi_pci_init_msi(struct tb_nhi *nhi) +{ + struct tb_nhi_pci *nhi_pci = nhi_to_pci(nhi); + struct pci_dev *pdev = to_pci_dev(nhi->dev); + struct device *dev = &pdev->dev; + int res, irq, nvec; + + ida_init(&nhi_pci->msix_ida); + + /* + * The NHI has 16 MSI-X vectors or a single MSI. We first try to + * get all MSI-X vectors and if we succeed, each ring will have + * one MSI-X. If for some reason that does not work out, we + * fallback to a single MSI. + */ + nvec = pci_alloc_irq_vectors(pdev, MSIX_MIN_VECS, MSIX_MAX_VECS, + PCI_IRQ_MSIX); + if (nvec < 0) { + nvec = pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_MSI); + if (nvec < 0) + return nvec; + + INIT_WORK(&nhi->interrupt_work, nhi_interrupt_work); + + irq = pci_irq_vector(pdev, 0); + if (irq < 0) + return irq; + + res = devm_request_irq(&pdev->dev, irq, nhi_msi, + IRQF_NO_SUSPEND, "thunderbolt", nhi); + if (res) + return dev_err_probe(dev, res, "request_irq failed, aborting\n"); + } + + return 0; +} + +static bool nhi_pci_imr_valid(struct pci_dev *pdev) +{ + u8 val; + + if (!device_property_read_u8(&pdev->dev, "IMR_VALID", &val)) + return !!val; + + return true; +} + +static void nhi_pci_start_dma_port(struct tb_nhi *nhi) +{ + struct pci_dev *pdev = to_pci_dev(nhi->dev); + struct pci_dev *root_port; + + /* + * During host router NVM upgrade we should not allow root port to + * go into D3cold because some root ports cannot trigger PME + * itself. To be on the safe side keep the root port in D0 during + * the whole upgrade process. + */ + root_port = pcie_find_root_port(pdev); + if (root_port) + pm_runtime_get_noresume(&root_port->dev); +} + +static void nhi_pci_complete_dma_port(struct tb_nhi *nhi) +{ + struct pci_dev *pdev = to_pci_dev(nhi->dev); + struct pci_dev *root_port; + + root_port = pcie_find_root_port(pdev); + if (root_port) + pm_runtime_put(&root_port->dev); +} + +static int nhi_pci_ring_request_msix(struct tb_ring *ring, bool no_suspend) +{ + struct tb_nhi *nhi = ring->nhi; + struct tb_nhi_pci *nhi_pci = nhi_to_pci(nhi); + struct pci_dev *pdev = to_pci_dev(nhi->dev); + unsigned long irqflags; + int ret; + + if (!pdev->msix_enabled) + return 0; + + ret = ida_alloc_max(&nhi_pci->msix_ida, MSIX_MAX_VECS - 1, GFP_KERNEL); + if (ret < 0) + return ret; + + ring->vector = ret; + + ret = pci_irq_vector(pdev, ring->vector); + if (ret < 0) + goto err_ida_remove; + + ring->irq = ret; + + irqflags = no_suspend ? IRQF_NO_SUSPEND : 0; + ret = request_irq(ring->irq, ring_msix, irqflags, "thunderbolt", ring); + if (ret) + goto err_ida_remove; + + return 0; + +err_ida_remove: + ida_free(&nhi_pci->msix_ida, ring->vector); + + return ret; +} + +static void nhi_pci_ring_release_msix(struct tb_ring *ring) +{ + struct tb_nhi_pci *nhi_pci = nhi_to_pci(ring->nhi); + + if (ring->irq <= 0) + return; + + free_irq(ring->irq, ring); + ida_free(&nhi_pci->msix_ida, ring->vector); + ring->vector = 0; + ring->irq = 0; +} + +static void nhi_pci_shutdown(struct tb_nhi *nhi) +{ + struct tb_nhi_pci *nhi_pci = nhi_to_pci(nhi); + struct pci_dev *pdev = to_pci_dev(nhi->dev); + + /* + * We have to release the irq before calling flush_work. Otherwise an + * already executing IRQ handler could call schedule_work again. + */ + if (!pdev->msix_enabled) { + devm_free_irq(nhi->dev, pdev->irq, nhi); + flush_work(&nhi->interrupt_work); + } + ida_destroy(&nhi_pci->msix_ida); +} + +static bool nhi_pci_is_present(struct tb_nhi *nhi) +{ + return pci_device_is_present(to_pci_dev(nhi->dev)); +} + +static const struct tb_nhi_ops pci_nhi_default_ops = { + .pre_nvm_auth = nhi_pci_start_dma_port, + .post_nvm_auth = nhi_pci_complete_dma_port, + .request_ring_irq = nhi_pci_ring_request_msix, + .release_ring_irq = nhi_pci_ring_release_msix, + .shutdown = nhi_pci_shutdown, + .is_present = nhi_pci_is_present, + .init_interrupts = nhi_pci_init_msi, +}; + +/* Ice Lake specific NHI operations */ + +#define ICL_LC_MAILBOX_TIMEOUT 500 /* ms */ + +static int check_for_device(struct device *dev, void *data) +{ + return tb_is_switch(dev); +} + +static bool icl_nhi_is_device_connected(struct tb_nhi *nhi) +{ + struct tb *tb = dev_get_drvdata(nhi->dev); + int ret; + + ret = device_for_each_child(&tb->root_switch->dev, NULL, + check_for_device); + return ret > 0; +} + +static int icl_nhi_force_power(struct tb_nhi *nhi, bool power) +{ + struct pci_dev *pdev = to_pci_dev(nhi->dev); + u32 vs_cap; + + /* + * The Thunderbolt host controller is present always in Ice Lake + * but the firmware may not be loaded and running (depending + * whether there is device connected and so on). Each time the + * controller is used we need to "Force Power" it first and wait + * for the firmware to indicate it is up and running. This "Force + * Power" is really not about actually powering on/off the + * controller so it is accessible even if "Force Power" is off. + * + * The actual power management happens inside shared ACPI power + * resources using standard ACPI methods. + */ + pci_read_config_dword(pdev, VS_CAP_22, &vs_cap); + if (power) { + vs_cap &= ~VS_CAP_22_DMA_DELAY_MASK; + vs_cap |= 0x22 << VS_CAP_22_DMA_DELAY_SHIFT; + vs_cap |= VS_CAP_22_FORCE_POWER; + } else { + vs_cap &= ~VS_CAP_22_FORCE_POWER; + } + pci_write_config_dword(pdev, VS_CAP_22, vs_cap); + + if (power) { + unsigned int retries = 350; + u32 val; + + /* Wait until the firmware tells it is up and running */ + do { + pci_read_config_dword(pdev, VS_CAP_9, &val); + if (val & VS_CAP_9_FW_READY) + return 0; + usleep_range(3000, 3100); + } while (--retries); + + return -ETIMEDOUT; + } + + return 0; +} + +static void icl_nhi_lc_mailbox_cmd(struct tb_nhi *nhi, enum icl_lc_mailbox_cmd cmd) +{ + struct pci_dev *pdev = to_pci_dev(nhi->dev); + u32 data; + + data = (cmd << VS_CAP_19_CMD_SHIFT) & VS_CAP_19_CMD_MASK; + pci_write_config_dword(pdev, VS_CAP_19, data | VS_CAP_19_VALID); +} + +static int icl_nhi_lc_mailbox_cmd_complete(struct tb_nhi *nhi, int timeout) +{ + struct pci_dev *pdev = to_pci_dev(nhi->dev); + unsigned long end; + u32 data; + + if (!timeout) + goto clear; + + end = jiffies + msecs_to_jiffies(timeout); + do { + pci_read_config_dword(pdev, VS_CAP_18, &data); + if (data & VS_CAP_18_DONE) + goto clear; + usleep_range(1000, 1100); + } while (time_before(jiffies, end)); + + return -ETIMEDOUT; + +clear: + /* Clear the valid bit */ + pci_write_config_dword(pdev, VS_CAP_19, 0); + return 0; +} + +static void icl_nhi_set_ltr(struct tb_nhi *nhi) +{ + struct pci_dev *pdev = to_pci_dev(nhi->dev); + u32 max_ltr, ltr; + + pci_read_config_dword(pdev, VS_CAP_16, &max_ltr); + max_ltr &= 0xffff; + /* Program the same value for both snoop and no-snoop */ + ltr = max_ltr << 16 | max_ltr; + pci_write_config_dword(pdev, VS_CAP_15, ltr); +} + +static int icl_nhi_suspend(struct tb_nhi *nhi) +{ + struct tb *tb = dev_get_drvdata(nhi->dev); + int ret; + + if (icl_nhi_is_device_connected(nhi)) + return 0; + + if (tb_switch_is_icm(tb->root_switch)) { + /* + * If there is no device connected we need to perform + * both: a handshake through LC mailbox and force power + * down before entering D3. + */ + icl_nhi_lc_mailbox_cmd(nhi, ICL_LC_PREPARE_FOR_RESET); + ret = icl_nhi_lc_mailbox_cmd_complete(nhi, ICL_LC_MAILBOX_TIMEOUT); + if (ret) + return ret; + } + + return icl_nhi_force_power(nhi, false); +} + +static int icl_nhi_suspend_noirq(struct tb_nhi *nhi, bool wakeup) +{ + struct tb *tb = dev_get_drvdata(nhi->dev); + enum icl_lc_mailbox_cmd cmd; + + if (!pm_suspend_via_firmware()) + return icl_nhi_suspend(nhi); + + if (!tb_switch_is_icm(tb->root_switch)) + return 0; + + cmd = wakeup ? ICL_LC_GO2SX : ICL_LC_GO2SX_NO_WAKE; + icl_nhi_lc_mailbox_cmd(nhi, cmd); + return icl_nhi_lc_mailbox_cmd_complete(nhi, ICL_LC_MAILBOX_TIMEOUT); +} + +static int icl_nhi_resume(struct tb_nhi *nhi) +{ + int ret; + + ret = icl_nhi_force_power(nhi, true); + if (ret) + return ret; + + icl_nhi_set_ltr(nhi); + return 0; +} + +static void icl_nhi_shutdown(struct tb_nhi *nhi) +{ + nhi_pci_shutdown(nhi); + + icl_nhi_force_power(nhi, false); +} + +static const struct tb_nhi_ops icl_nhi_ops = { + .init = icl_nhi_resume, + .suspend_noirq = icl_nhi_suspend_noirq, + .resume_noirq = icl_nhi_resume, + .runtime_suspend = icl_nhi_suspend, + .runtime_resume = icl_nhi_resume, + .shutdown = icl_nhi_shutdown, + .pre_nvm_auth = nhi_pci_start_dma_port, + .post_nvm_auth = nhi_pci_complete_dma_port, + .request_ring_irq = nhi_pci_ring_request_msix, + .release_ring_irq = nhi_pci_ring_release_msix, + .is_present = nhi_pci_is_present, + .init_interrupts = nhi_pci_init_msi, +}; + +static int nhi_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) +{ + struct device *dev = &pdev->dev; + struct tb_nhi_pci *nhi_pci; + struct tb_nhi *nhi; + int res; + + if (!nhi_pci_imr_valid(pdev)) + return dev_err_probe(dev, -ENODEV, "firmware image not valid, aborting\n"); + + res = pcim_enable_device(pdev); + if (res) + return dev_err_probe(dev, res, "cannot enable PCI device, aborting\n"); + + nhi_pci = devm_kzalloc(dev, sizeof(*nhi_pci), GFP_KERNEL); + if (!nhi_pci) + return -ENOMEM; + + nhi = &nhi_pci->nhi; + nhi->dev = dev; + nhi->ops = (const struct tb_nhi_ops *)id->driver_data ?: &pci_nhi_default_ops; + + nhi->iobase = pcim_iomap_region(pdev, 0, "thunderbolt"); + res = PTR_ERR_OR_ZERO(nhi->iobase); + if (res) + return dev_err_probe(dev, res, "cannot obtain PCI resources, aborting\n"); + + nhi_pci_check_quirks(nhi_pci); + nhi_pci_check_iommu(nhi_pci); + + pci_set_master(pdev); + + return nhi_probe(&nhi_pci->nhi); +} + +static void nhi_pci_remove(struct pci_dev *pdev) +{ + struct tb *tb = pci_get_drvdata(pdev); + struct tb_nhi *nhi = tb->nhi; + + pm_runtime_get_sync(&pdev->dev); + pm_runtime_dont_use_autosuspend(&pdev->dev); + pm_runtime_forbid(&pdev->dev); + + tb_domain_remove(tb); + wait_for_completion(&nhi->domain_released); + nhi_shutdown(nhi); +} + +static struct pci_device_id nhi_ids[] = { + /* + * We have to specify class, the TB bridges use the same device and + * vendor (sub)id on gen 1 and gen 2 controllers. + */ + { + .class = PCI_CLASS_SYSTEM_OTHER << 8, .class_mask = ~0, + .vendor = PCI_VENDOR_ID_INTEL, + .device = PCI_DEVICE_ID_INTEL_LIGHT_RIDGE, + .subvendor = 0x2222, .subdevice = 0x1111, + }, + { + .class = PCI_CLASS_SYSTEM_OTHER << 8, .class_mask = ~0, + .vendor = PCI_VENDOR_ID_INTEL, + .device = PCI_DEVICE_ID_INTEL_CACTUS_RIDGE_4C, + .subvendor = 0x2222, .subdevice = 0x1111, + }, + { + .class = PCI_CLASS_SYSTEM_OTHER << 8, .class_mask = ~0, + .vendor = PCI_VENDOR_ID_INTEL, + .device = PCI_DEVICE_ID_INTEL_FALCON_RIDGE_2C_NHI, + .subvendor = PCI_ANY_ID, .subdevice = PCI_ANY_ID, + }, + { + .class = PCI_CLASS_SYSTEM_OTHER << 8, .class_mask = ~0, + .vendor = PCI_VENDOR_ID_INTEL, + .device = PCI_DEVICE_ID_INTEL_FALCON_RIDGE_4C_NHI, + .subvendor = PCI_ANY_ID, .subdevice = PCI_ANY_ID, + }, + + /* Thunderbolt 3 */ + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_2C_NHI) }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_4C_NHI) }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_USBONLY_NHI) }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_LP_NHI) }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_LP_USBONLY_NHI) }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_C_2C_NHI) }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_C_4C_NHI) }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ALPINE_RIDGE_C_USBONLY_NHI) }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_TITAN_RIDGE_2C_NHI) }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_TITAN_RIDGE_4C_NHI) }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ICL_NHI0), + .driver_data = (kernel_ulong_t)&icl_nhi_ops }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ICL_NHI1), + .driver_data = (kernel_ulong_t)&icl_nhi_ops }, + /* Thunderbolt 4 */ + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_TGL_NHI0), + .driver_data = (kernel_ulong_t)&icl_nhi_ops }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_TGL_NHI1), + .driver_data = (kernel_ulong_t)&icl_nhi_ops }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_TGL_H_NHI0), + .driver_data = (kernel_ulong_t)&icl_nhi_ops }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_TGL_H_NHI1), + .driver_data = (kernel_ulong_t)&icl_nhi_ops }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ADL_NHI0), + .driver_data = (kernel_ulong_t)&icl_nhi_ops }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ADL_NHI1), + .driver_data = (kernel_ulong_t)&icl_nhi_ops }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_RPL_NHI0), + .driver_data = (kernel_ulong_t)&icl_nhi_ops }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_RPL_NHI1), + .driver_data = (kernel_ulong_t)&icl_nhi_ops }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_MTL_M_NHI0), + .driver_data = (kernel_ulong_t)&icl_nhi_ops }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_MTL_P_NHI0), + .driver_data = (kernel_ulong_t)&icl_nhi_ops }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_MTL_P_NHI1), + .driver_data = (kernel_ulong_t)&icl_nhi_ops }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_LNL_NHI0), + .driver_data = (kernel_ulong_t)&icl_nhi_ops }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_LNL_NHI1), + .driver_data = (kernel_ulong_t)&icl_nhi_ops }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_PTL_M_NHI0), + .driver_data = (kernel_ulong_t)&icl_nhi_ops }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_PTL_M_NHI1), + .driver_data = (kernel_ulong_t)&icl_nhi_ops }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_PTL_P_NHI0), + .driver_data = (kernel_ulong_t)&icl_nhi_ops }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_PTL_P_NHI1), + .driver_data = (kernel_ulong_t)&icl_nhi_ops }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_WCL_NHI0), + .driver_data = (kernel_ulong_t)&icl_nhi_ops }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_BARLOW_RIDGE_HOST_80G_NHI) }, + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_BARLOW_RIDGE_HOST_40G_NHI) }, + + /* Any USB4 compliant host */ + { PCI_DEVICE_CLASS(PCI_CLASS_SERIAL_USB_USB4, ~0) }, + + { 0,} +}; + +MODULE_DEVICE_TABLE(pci, nhi_ids); +MODULE_DESCRIPTION("Thunderbolt/USB4 core driver"); +MODULE_LICENSE("GPL"); + +static struct pci_driver nhi_driver = { + .name = "thunderbolt", + .id_table = nhi_ids, + .probe = nhi_pci_probe, + .remove = nhi_pci_remove, + .shutdown = nhi_pci_remove, + .driver.pm = &nhi_pm_ops, +}; + +static int __init nhi_init(void) +{ + int ret; + + ret = tb_domain_init(); + if (ret) + return ret; + + ret = pci_register_driver(&nhi_driver); + if (ret) + tb_domain_exit(); + + return ret; +} + +static void __exit nhi_unload(void) +{ + pci_unregister_driver(&nhi_driver); + tb_domain_exit(); +} + +rootfs_initcall(nhi_init); +module_exit(nhi_unload); diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c index c65395ab4bedea..94e5d6bcead2cb 100644 --- a/drivers/thunderbolt/switch.c +++ b/drivers/thunderbolt/switch.c @@ -209,32 +209,6 @@ static int nvm_authenticate_device_dma_port(struct tb_switch *sw) return -ETIMEDOUT; } -static void nvm_authenticate_start_dma_port(struct tb_switch *sw) -{ - struct pci_dev *pdev = to_pci_dev(sw->tb->nhi->dev); - struct pci_dev *root_port; - - /* - * During host router NVM upgrade we should not allow root port to - * go into D3cold because some root ports cannot trigger PME - * itself. To be on the safe side keep the root port in D0 during - * the whole upgrade process. - */ - root_port = pcie_find_root_port(pdev); - if (root_port) - pm_runtime_get_noresume(&root_port->dev); -} - -static void nvm_authenticate_complete_dma_port(struct tb_switch *sw) -{ - struct pci_dev *pdev = to_pci_dev(sw->tb->nhi->dev); - struct pci_dev *root_port; - - root_port = pcie_find_root_port(pdev); - if (root_port) - pm_runtime_put(&root_port->dev); -} - static inline bool nvm_readable(struct tb_switch *sw) { if (tb_switch_is_usb4(sw)) { @@ -260,6 +234,7 @@ static inline bool nvm_upgradeable(struct tb_switch *sw) static int nvm_authenticate(struct tb_switch *sw, bool auth_only) { + struct tb_nhi *nhi = sw->tb->nhi; int ret; if (tb_switch_is_usb4(sw)) { @@ -276,7 +251,8 @@ static int nvm_authenticate(struct tb_switch *sw, bool auth_only) sw->nvm->authenticating = true; if (!tb_route(sw)) { - nvm_authenticate_start_dma_port(sw); + if (nhi->ops && nhi->ops->pre_nvm_auth) + nhi->ops->pre_nvm_auth(nhi); ret = nvm_authenticate_host_dma_port(sw); } else { ret = nvm_authenticate_device_dma_port(sw); @@ -2748,6 +2724,7 @@ static int tb_switch_set_uuid(struct tb_switch *sw) static int tb_switch_add_dma_port(struct tb_switch *sw) { + struct tb_nhi *nhi = sw->tb->nhi; u32 status; int ret; @@ -2807,8 +2784,10 @@ static int tb_switch_add_dma_port(struct tb_switch *sw) */ nvm_get_auth_status(sw, &status); if (status) { - if (!tb_route(sw)) - nvm_authenticate_complete_dma_port(sw); + if (!tb_route(sw)) { + if (nhi->ops && nhi->ops->post_nvm_auth) + nhi->ops->post_nvm_auth(nhi); + } return 0; } @@ -2822,8 +2801,10 @@ static int tb_switch_add_dma_port(struct tb_switch *sw) return ret; /* Now we can allow root port to suspend again */ - if (!tb_route(sw)) - nvm_authenticate_complete_dma_port(sw); + if (!tb_route(sw)) { + if (nhi->ops && nhi->ops->post_nvm_auth) + nhi->ops->post_nvm_auth(nhi); + } if (status) { tb_sw_info(sw, "switch flash authentication failed\n"); From 4f54744e32b38a1f2b7ddfdae1a76cab1a153442 Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Tue, 28 Jul 2026 09:15:39 +0300 Subject: [PATCH 26/33] thunderbolt: Initialize ->domain_released completion before it is being used MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both Woody and Marek reported following crash: BUG: unable to handle page fault for address: fffffffffffffff8 Call Trace: device_release+0x43/0x90 kobject_cleanup+0x3c/0x180 icm_probe+0x19c/0x550 [thunderbolt] nhi_probe+0x1a4/0x370 [thunderbolt] local_pci_probe+0x41/0x90 pci_call_probe+0x5b/0x1a0 ... This only triggers on the error path when icm_probe() fails and the domain structure is released, it tries to complete() uninitialized completion. Fix this by initializing the completion earlier. Reported-by: Marek Marczykowski-Górecki Closes: https://lore.kernel.org/linux-usb/amdezCBiW4fd_DuB@mail-itl/ Reported-by: Woody Suwalski Tested_by: Woody Suwalski Closes: https://lore.kernel.org/linux-usb/62caf7f8-b403-d0dd-15bc-b31b56f71c28@gmail.com/ Fixes: f5cc545f5969 ("thunderbolt: Wait for tb_domain_release() to complete when driver is removed") Signed-off-by: Mika Westerberg --- drivers/thunderbolt/nhi.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/thunderbolt/nhi.c b/drivers/thunderbolt/nhi.c index 683414115590a0..9ecfdfee23d050 100644 --- a/drivers/thunderbolt/nhi.c +++ b/drivers/thunderbolt/nhi.c @@ -1246,6 +1246,8 @@ int nhi_probe(struct tb_nhi *nhi) return res; } + init_completion(&nhi->domain_released); + tb = nhi_select_cm(nhi); if (!tb) return dev_err_probe(dev, -ENODEV, From 6110014ccc8a747565eed3bafc8430625c24ddc2 Mon Sep 17 00:00:00 2001 From: Sven Peter Date: Mon, 24 Aug 2026 18:07:56 +0200 Subject: [PATCH 27/33] thunderbolt: Add Apple Silicon support Add a driver for the ACIO host complex and USB Native Host Interface found on Apple Silicon SoCs. Signed-off-by: Sven Peter --- MAINTAINERS | 1 + drivers/thunderbolt/Kconfig | 13 + drivers/thunderbolt/Makefile | 2 + drivers/thunderbolt/apple.c | 900 +++++++++++++++++++++++++++++++++++ 4 files changed, 916 insertions(+) create mode 100644 drivers/thunderbolt/apple.c diff --git a/MAINTAINERS b/MAINTAINERS index b2535bb35b5441..b089da8e32b5f7 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2590,6 +2590,7 @@ F: drivers/rtc/rtc-macsmc.c F: drivers/soc/apple/* F: drivers/spi/spi-apple.c F: drivers/spmi/spmi-apple-controller.c +F: drivers/thunderbolt/apple.c F: drivers/usb/dwc3/dwc3-apple.c F: drivers/usb/typec/tipd/spmi.c F: drivers/video/backlight/apple_dwi_bl.c diff --git a/drivers/thunderbolt/Kconfig b/drivers/thunderbolt/Kconfig index db3b0bef48f4c3..69f266371c3e47 100644 --- a/drivers/thunderbolt/Kconfig +++ b/drivers/thunderbolt/Kconfig @@ -60,4 +60,17 @@ config USB4_DMA_TEST To compile this driver a module, choose M here. The module will be called thunderbolt_dma_test. +config USB4_APPLE_SOC + tristate "Apple Silicon USB4/Thunderbolt Support" + depends on ARCH_APPLE || COMPILE_TEST + depends on OF && APPLE_RTKIT + depends on TYPEC + select APPLE_TUNABLE + help + Say Y here to add support for the USB4 and Thunderbolt host + routers found on Apple Silicon machines starting with the M1. + + To compile the driver as a module, choose M here. The module + will be called thunderbolt_apple. + endif # USB4 diff --git a/drivers/thunderbolt/Makefile b/drivers/thunderbolt/Makefile index 8f7814082df9d1..99672b8776eb4e 100644 --- a/drivers/thunderbolt/Makefile +++ b/drivers/thunderbolt/Makefile @@ -12,3 +12,5 @@ CFLAGS_test.o += $(DISABLE_STRUCTLEAK_PLUGIN) thunderbolt_dma_test-${CONFIG_USB4_DMA_TEST} += dma_test.o obj-$(CONFIG_USB4_DMA_TEST) += thunderbolt_dma_test.o +obj-$(CONFIG_USB4_APPLE_SOC) += thunderbolt_apple.o +thunderbolt_apple-y := apple.o diff --git a/drivers/thunderbolt/apple.c b/drivers/thunderbolt/apple.c new file mode 100644 index 00000000000000..8786a9c26678d5 --- /dev/null +++ b/drivers/thunderbolt/apple.c @@ -0,0 +1,900 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Apple Silicon USB4 and Thunderbolt driver + * + * This driver implements the Host Router / ACIO coprocessor as well as the + * Native Host Interface (NHI) as shown in the diagram below. + * The ACIO is a Cortex-M3 coprocessor which handles the Thunderbolt + * protocol and exposes its own hardware blocks like the USB4 Native Host + * Interface (NHI) and its IOMMU to the main SoC bus. The entire block + * can only be brought up after the unified Type-C PHY has been initialized + * to Thunderbolt or USB4 mode and needs an out-of-band notification from + * the Type-C PD driver. + * + * +--------------------+ + * | | +--------------------------------------------+ + * | Display Controller | | Host Router / ACIO | + * | dcpext0 | | +----------------+ | + * | | | | Native | | + * +--------+-----------+ | | Host | | + * | | | Interface | | + * | +---------+ +----------------+ +---------+ +--------+ + * | +--------->| DP IN | | PCIE DN |<--->| APCIEC | + * v | | Adapter | +----------------+ | Adapter | +--------+ + * +---------+-+ +---------+ | | +---------+ + * | Display | | | IOMMU / DART | | + * | Crossbar | | | | +---------+ +------+ + * +---------+-+ +---------+ +----------------+ | USB3 |<--->| DWC3 | + * ^ | | DP IN | | Adapter | +------+ + * | +--------->| Adapter | +---------+ + * | +---------+ | + * | | | + * | | +----------+ | + * +--------+-----------+ | | Type C | | + * | | | | Adapter | | + * | Display Controller | +-----------------+----------+---------------+ + * | dcpext1 | ^ ^ + * | | | | + * +--------------------+ | | + * v | + * +--------------+ | SBRX/TX + * | Apple Type-C | | + * | PHY | | + * +--------------+ | + * ^ | + * | v + * | +---------+ + * +---->| Type C | + * SSRX/TX | Port | + * +---------+ + * + * Copyright (c) Sven Peter + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "nhi.h" +#include "tb.h" + +#define APPLE_CIO_M3_CTRL 0x0c +#define APPLE_CIO_M3_CTRL_START BIT(1) +#define APPLE_CIO_M3_STAT 0xa8 +#define APPLE_CIO_M3_STAT_STATE GENMASK(30, 24) + +#define APPLE_CIO_NHI_HOP_COUNT 0x0 +#define APPLE_CIO_NHI_HOP_COUNT_MASK GENMASK(9, 0) + +#define APPLE_CIO_NHI_TXRING_DESC_BASE 0x10000 +#define APPLE_CIO_NHI_RXRING_DESC_BASE 0x80000 +#define APPLE_CIO_NHI_RING_STRIDE 0x4000 + +#define APPLE_CIO_NHI_PDF_STRIDE 0x4000 + +#define APPLE_CIO_NHI_IRQ_STATUS 0xd0000 +#define APPLE_CIO_NHI_IRQ_ENABLE 0xd0010 +#define APPLE_CIO_NHI_IRQ_THROTTLE 0xd004c +#define APPLE_CIO_NHI_IRQ_THROTTLE_INTERVAL_MASK GENMASK(15, 0) +#define APPLE_CIO_NHI_IRQ_THROTTLE_GRANULARITY_NSEC 256 + +#define APPLE_CIO_SRAM_IOVA_BASE 0x10000000 + +#define APPLE_CIO_NHI_BOOT_TIMEOUT_MS 10000 + +#define TB_VSE_CAP_APPLE 0x00 +#define TB_VSE_CAP_APPLE_CABLE_INFO 0x01 +#define TB_VSE_CAP_APPLE_CABLE_INFO_PRESENT BIT(0) +#define TB_VSE_CAP_APPLE_CABLE_INFO_ORIENTATION_REVERSE BIT(1) +#define TB_VSE_CAP_APPLE_CABLE_INFO_ACTIVE_CABLE BIT(2) +#define TB_VSE_CAP_APPLE_CABLE_INFO_BIDIR_LSRX BIT(3) +#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) + +struct apple_cio { + struct device *dev; + struct device_node *np; + struct apple_rtkit *rtk; + + void __iomem *rc_base; + struct resource *rc_res; + struct apple_tunable *rc_tunable; + + struct resource *sram_res; + void __iomem *sram_base; + + struct reset_control *reset; + + struct dev_pm_domain_list *pd_list; + + struct mutex lock; /* serializes cable transitions and ACIO power up/down */ + + u32 current_cable_info; + u32 target_cable_info; + struct completion nhi_boot_completion; + int nhi_boot_status; + + struct typec_thunderbolt_switch_dev *tbt_switch; +}; + +struct apple_nhi { + struct device *dev; + struct platform_device *pdev; + struct device_node *np; + + struct apple_cio *acio; + + struct tb *tb; + struct tb_nhi nhi; + + void __iomem *nhi_base; + void __iomem *pdf_base; + + int *tx_irqs; + int *rx_irqs; + const char **tx_irq_names; + const char **rx_irq_names; + size_t n_rings; +}; + +#define nhi_to_anhi(nhi_) container_of((nhi_), struct apple_nhi, nhi) + +static int apple_cio_rtkit_shmem_setup(void *cookie, struct apple_rtkit_shmem *bfr) +{ + struct apple_cio *acio = cookie; + struct resource res = { + .name = "acio_rtkit_buffer", + .flags = acio->sram_res->flags, + }; + + if (!bfr->iova) + return -EIO; + + if (bfr->iova < APPLE_CIO_SRAM_IOVA_BASE) { + dev_err(acio->dev, "firmware requested invalid buffer before SRAM IOVA base 0x%llx\n", + bfr->iova); + return -EFAULT; + } + + res.start = bfr->iova - APPLE_CIO_SRAM_IOVA_BASE + acio->sram_res->start; + res.end = res.start + bfr->size - 1; + + if (res.end < res.start) { + dev_err(acio->dev, "firmware requested invalid buffer %pR\n", &res); + return -EFAULT; + } + + if (!resource_contains(acio->sram_res, &res)) { + dev_err(acio->dev, "firmware requested buffer %pR outside SRAM %pR\n", &res, + acio->sram_res); + return -EFAULT; + } + + bfr->iomem = acio->sram_base + (res.start - acio->sram_res->start); + bfr->is_mapped = true; + return 0; +} + +static const struct apple_rtkit_ops apple_cio_rtkit_ops = { + .shmem_setup = apple_cio_rtkit_shmem_setup, +}; + +static int apple_nhi_probe_irqs(struct apple_nhi *anhi) +{ + int n_irqs; + char name[64]; + + n_irqs = platform_irq_count(anhi->pdev); + if (n_irqs < 0) + return dev_err_probe(anhi->dev, n_irqs, "platform_irq_count failed\n"); + if (n_irqs == 0) { + dev_err(anhi->dev, "No interrupts found\n"); + return -EINVAL; + } + if (n_irqs % 2) { + dev_err(anhi->dev, "Invalid number of interrupts: %d must be even\n", n_irqs); + return -EINVAL; + } + anhi->n_rings = n_irqs / 2; + + anhi->rx_irqs = devm_kcalloc(anhi->dev, anhi->n_rings, sizeof(*anhi->rx_irqs), GFP_KERNEL); + if (!anhi->rx_irqs) + return -ENOMEM; + anhi->tx_irqs = devm_kcalloc(anhi->dev, anhi->n_rings, sizeof(*anhi->tx_irqs), GFP_KERNEL); + if (!anhi->tx_irqs) + return -ENOMEM; + anhi->rx_irq_names = devm_kcalloc(anhi->dev, anhi->n_rings, + sizeof(*anhi->rx_irq_names), GFP_KERNEL); + if (!anhi->rx_irq_names) + return -ENOMEM; + anhi->tx_irq_names = devm_kcalloc(anhi->dev, anhi->n_rings, + sizeof(*anhi->tx_irq_names), GFP_KERNEL); + if (!anhi->tx_irq_names) + return -ENOMEM; + + for (int i = 0; i < anhi->n_rings; ++i) { + snprintf(name, sizeof(name), "rxring%d", i); + anhi->rx_irqs[i] = platform_get_irq_byname(anhi->pdev, name); + if (anhi->rx_irqs[i] < 0) + return anhi->rx_irqs[i]; + anhi->rx_irq_names[i] = devm_kasprintf(anhi->dev, GFP_KERNEL, "%s-%s", + dev_name(anhi->dev), name); + if (!anhi->rx_irq_names[i]) + return -ENOMEM; + + snprintf(name, sizeof(name), "txring%d", i); + anhi->tx_irqs[i] = platform_get_irq_byname(anhi->pdev, name); + if (anhi->tx_irqs[i] < 0) + return anhi->tx_irqs[i]; + anhi->tx_irq_names[i] = devm_kasprintf(anhi->dev, GFP_KERNEL, "%s-%s", + dev_name(anhi->dev), name); + if (!anhi->tx_irq_names[i]) + return -ENOMEM; + } + + return 0; +} + +static unsigned int apple_cio_ring_index(struct tb_ring *ring) +{ + struct apple_nhi *anhi = nhi_to_anhi(ring->nhi); + + if (ring->is_tx) + return ring->hop; + else + return ring->hop + anhi->n_rings; +} + +static void apple_nhi_ring_interrupt_active(struct tb_ring *ring, bool active) +{ + struct apple_nhi *anhi = nhi_to_anhi(ring->nhi); + unsigned int idx = apple_cio_ring_index(ring); + u32 reg, interval; + + lockdep_assert_held(&ring->nhi->lock); + + if (active && ring->interval_nsec) { + interval = DIV_ROUND_UP(ring->interval_nsec, + APPLE_CIO_NHI_IRQ_THROTTLE_GRANULARITY_NSEC); + interval &= APPLE_CIO_NHI_IRQ_THROTTLE_INTERVAL_MASK; + writel(interval, anhi->nhi_base + APPLE_CIO_NHI_IRQ_THROTTLE + + 4 * idx); + } + + reg = readl(anhi->nhi_base + APPLE_CIO_NHI_IRQ_ENABLE); + + if (active) + reg |= BIT(idx); + else + reg &= ~BIT(idx); + + writel(reg, anhi->nhi_base + APPLE_CIO_NHI_IRQ_ENABLE); +} + +static void apple_nhi_ring_interrupt_mask(struct tb_ring *ring, bool mask) +{ + apple_nhi_ring_interrupt_active(ring, !mask); +} + +static irqreturn_t apple_cio_ring_irq(int irq, void *data) +{ + struct tb_ring *ring = data; + struct apple_nhi *anhi = nhi_to_anhi(ring->nhi); + unsigned int idx = apple_cio_ring_index(ring); + + guard(spinlock)(&ring->nhi->lock); + guard(spinlock)(&ring->lock); + + writel(BIT(idx), anhi->nhi_base + APPLE_CIO_NHI_IRQ_STATUS); + if (!ring->running) + return IRQ_NONE; + + if (ring->start_poll) { + apple_nhi_ring_interrupt_mask(ring, true); + ring->start_poll(ring->poll_data); + } else { + schedule_work(&ring->work); + } + + return IRQ_HANDLED; +} + + +static int apple_nhi_request_irq(struct tb_ring *ring, bool no_suspend) +{ + struct apple_nhi *anhi = nhi_to_anhi(ring->nhi); + const char *name; + + if (ring->is_tx) { + ring->irq = anhi->tx_irqs[ring->hop]; + name = anhi->tx_irq_names[ring->hop]; + } else { + ring->irq = anhi->rx_irqs[ring->hop]; + name = anhi->rx_irq_names[ring->hop]; + } + + return devm_request_irq(anhi->dev, ring->irq, apple_cio_ring_irq, + no_suspend ? IRQF_NO_SUSPEND : 0, name, ring); +} + +static void apple_nhi_release_irq(struct tb_ring *ring) +{ + if (ring->irq <= 0) + return; + + devm_free_irq(ring->nhi->dev, ring->irq, ring); + ring->irq = 0; +} + +static void apple_nhi_ring_configure(struct tb_ring *ring, u32 flags, u32 e2e_flags) +{ + struct apple_nhi *anhi = nhi_to_anhi(ring->nhi); + void __iomem *options; + u32 sof_eof_mask; + + lockdep_assert_held(&ring->lock); + + options = anhi->nhi_base + ring->hop * APPLE_CIO_NHI_RING_STRIDE + 0x10; + + if (ring->is_tx) { + options += APPLE_CIO_NHI_TXRING_DESC_BASE; + + /* + * All TX rings share what macOS calls a shared buffer with 232 entries. This is how + * macOS splits it up, ring 0 only carries control packets and gets the minimum. + */ + if (ring->hop == 0) + writel(2, options + 4); + else if (ring->hop <= 5) + writel(40, options + 4); + else + writel(5, options + 4); + } else { + options += APPLE_CIO_NHI_RXRING_DESC_BASE; + + sof_eof_mask = ring->sof_mask << 16 | ring->eof_mask; + writel(sof_eof_mask, options + 4); + writel(sof_eof_mask, anhi->pdf_base + ring->hop * APPLE_CIO_NHI_PDF_STRIDE); + } + + /* + * The firmware samples the ring configuration when the valid bit is set and E2E flow + * control never engages when configured afterwards. Write everything at once like macOS. + */ + writel(flags | e2e_flags, options); +} + +static const struct tb_nhi_ops apple_nhi_ops = { + .request_ring_irq = apple_nhi_request_irq, + .release_ring_irq = apple_nhi_release_irq, + .ring_interrupt_active = apple_nhi_ring_interrupt_active, + .ring_interrupt_mask = apple_nhi_ring_interrupt_mask, + .ring_configure = apple_nhi_ring_configure, +}; + +static const struct tb_nhi_ring_layout apple_nhi_ring_layout = { + .tx_desc_base = APPLE_CIO_NHI_TXRING_DESC_BASE, + .rx_desc_base = APPLE_CIO_NHI_RXRING_DESC_BASE, + .desc_stride = APPLE_CIO_NHI_RING_STRIDE, + .tx_options_base = APPLE_CIO_NHI_TXRING_DESC_BASE + 0x10, + .rx_options_base = APPLE_CIO_NHI_RXRING_DESC_BASE + 0x10, + .options_stride = APPLE_CIO_NHI_RING_STRIDE, +}; + +static int apple_nhi_probe(struct platform_device *pdev) +{ + struct apple_cio *acio = dev_get_drvdata(pdev->dev.parent); + struct apple_nhi *anhi; + struct apple_tunable *tunable; + struct resource *res; + struct tb_port *port; + int cap_apple; + int ret = 0; + + anhi = devm_kzalloc(&pdev->dev, sizeof(*anhi), GFP_KERNEL); + if (!anhi) { + ret = -ENOMEM; + goto err; + } + + dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(42)); + + anhi->pdev = pdev; + anhi->dev = &pdev->dev; + anhi->np = pdev->dev.of_node; + anhi->acio = acio; + platform_set_drvdata(pdev, anhi); + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "nhi"); + anhi->nhi_base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(anhi->nhi_base)) { + ret = dev_err_probe(&pdev->dev, PTR_ERR(anhi->nhi_base), + "Unable to map NHI regs\n"); + goto err; + } + tunable = devm_apple_tunable_parse(&pdev->dev, anhi->np, "apple,tunable-nhi", res); + if (IS_ERR(tunable)) { + ret = dev_err_probe(&pdev->dev, PTR_ERR(tunable), "Unable to load NHI tunable\n"); + goto err; + } + apple_tunable_apply(anhi->nhi_base, tunable); + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "pdf"); + anhi->pdf_base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(anhi->pdf_base)) { + ret = dev_err_probe(&pdev->dev, PTR_ERR(anhi->pdf_base), + "Unable to map PDF regs\n"); + goto err; + } + + ret = apple_nhi_probe_irqs(anhi); + if (ret) + goto err; + + spin_lock_init(&anhi->nhi.lock); + anhi->nhi.iommu_dma_protection = true; + anhi->nhi.ops = &apple_nhi_ops; + anhi->nhi.ring_layout = &apple_nhi_ring_layout; + anhi->nhi.iobase = anhi->nhi_base; + anhi->nhi.quirks = QUIRK_NO_DMA_PORT | QUIRK_NO_USB3_BW_ALLOC; + + anhi->nhi.hop_count = readl(anhi->nhi_base + APPLE_CIO_NHI_HOP_COUNT) & + APPLE_CIO_NHI_HOP_COUNT_MASK; + if (anhi->nhi.hop_count != anhi->n_rings) { + ret = dev_err_probe(anhi->dev, -EINVAL, "Ring IRQs (%zd) != HOP_COUNT (%d)\n", + anhi->n_rings, anhi->nhi.hop_count); + goto err; + } + + anhi->nhi.tx_rings = devm_kcalloc(&pdev->dev, anhi->nhi.hop_count, + sizeof(*anhi->nhi.tx_rings), GFP_KERNEL); + anhi->nhi.rx_rings = devm_kcalloc(&pdev->dev, anhi->nhi.hop_count, + sizeof(*anhi->nhi.rx_rings), GFP_KERNEL); + if (!anhi->nhi.tx_rings || !anhi->nhi.rx_rings) { + ret = -ENOMEM; + goto err; + } + + anhi->nhi.dev = &pdev->dev; + init_completion(&anhi->nhi.domain_released); + anhi->tb = tb_probe(&anhi->nhi); + if (!anhi->tb) { + ret = dev_err_probe(anhi->dev, -ENODEV, + "Failed to init software connection manager\n"); + goto err; + } + + ret = tb_domain_add(anhi->tb, false); + if (ret) { + dev_err_probe(anhi->dev, ret, "failed to add TB domain\n"); + tb_domain_put(anhi->tb); + wait_for_completion(&anhi->nhi.domain_released); + goto err; + } + + mutex_lock(&anhi->tb->lock); + + if (!anhi->tb->root_switch->drom) { + dev_err(anhi->dev, "No valid host DROM in the device tree\n"); + mutex_unlock(&anhi->tb->lock); + ret = -EINVAL; + goto err_remove_tb_domain; + } + + cap_apple = tb_switch_find_vse_cap(anhi->tb->root_switch, TB_VSE_CAP_APPLE); + + if (cap_apple < 0) { + dev_err(anhi->dev, "Unable to find VSE Apple capability: %d\n", + cap_apple); + mutex_unlock(&anhi->tb->lock); + ret = cap_apple; + goto err_remove_tb_domain; + } + + /* + * The ports are locked after reset. Unlock them before writing the cable information which + * will bring up the link and start the first scan such that XDomain responses are not + * rejected by our own router. + */ + tb_switch_for_each_port(anhi->tb->root_switch, port) { + if (!tb_port_is_null(port)) + continue; + ret = tb_port_unlock(port); + if (ret) + dev_warn(anhi->dev, "Failed to unlock port %d: %d\n", + port->port, ret); + } + + ret = tb_sw_write(anhi->tb->root_switch, &acio->target_cable_info, TB_CFG_SWITCH, + cap_apple + TB_VSE_CAP_APPLE_CABLE_INFO, 1); + if (ret) { + dev_warn(anhi->dev, "Setting VSE Apple cable info failed: %d\n", ret); + mutex_unlock(&anhi->tb->lock); + goto err_remove_tb_domain; + } + + mutex_unlock(&anhi->tb->lock); + + acio->nhi_boot_status = 0; + complete(&acio->nhi_boot_completion); + return 0; + +err_remove_tb_domain: + tb_domain_remove(anhi->tb); + wait_for_completion(&anhi->nhi.domain_released); +err: + acio->nhi_boot_status = ret; + complete(&acio->nhi_boot_completion); + return ret; +} + +static void apple_nhi_remove(struct platform_device *pdev) +{ + struct apple_nhi *anhi = platform_get_drvdata(pdev); + + tb_domain_remove(anhi->tb); + wait_for_completion(&anhi->nhi.domain_released); +} + +static const struct of_device_id apple_nhi_match[] = { + { + .compatible = "apple,t8103-usb4-nhi", + }, + {}, +}; +MODULE_DEVICE_TABLE(of, apple_nhi_match); + +static struct platform_driver apple_nhi_driver = { + .driver = { + .name = "thunderbolt-apple-nhi", + .of_match_table = apple_nhi_match, + }, + .probe = apple_nhi_probe, + .remove = apple_nhi_remove, +}; + +static void apple_cio_stop(struct apple_cio *acio) +{ + int ret, i; + + lockdep_assert_held(&acio->lock); + + /* + * First, shutdown the blocks inside the ACIO complex, like the NHI and the IOMMU. + * After we shut down the ACIO co-processor we will no longer be able to access + * the MMIO space of these so make sure nothing tries to do just that. + */ + of_platform_depopulate(acio->dev); + + /* Try to shut down and power off the co-processor gracefully */ + ret = apple_rtkit_poweroff(acio->rtk); + if (ret) + dev_warn(acio->dev, + "Failed to shutdown M3 RTKit, continuing ACIO shutdown anyway\n"); + apple_rtkit_free(acio->rtk); + + /* Finally, remove the links to the PD domains to power everything off */ + for (i = 0; i < acio->pd_list->num_pds; i++) { + if (acio->pd_list->pd_links[i]) + device_link_del(acio->pd_list->pd_links[i]); + acio->pd_list->pd_links[i] = NULL; + } + + acio->current_cable_info = 0; +} + +static int apple_cio_start(struct apple_cio *acio) +{ + struct device_link *link; + int i, ret; + u32 state; + + lockdep_assert_held(&acio->lock); + + /* Create device links to the power domains in order to power them on */ + for (i = 0; i < acio->pd_list->num_pds; i++) { + link = device_link_add(acio->dev, acio->pd_list->pd_devs[i], + DL_FLAG_STATELESS | DL_FLAG_PM_RUNTIME | DL_FLAG_RPM_ACTIVE); + if (!link) { + ret = -ENODEV; + goto remove_links; + } + acio->pd_list->pd_links[i] = link; + } + + /* + * After the power domains are on we need to signal and wait for the ACIO block + * to actually start before we can bring up the co-processor. + */ + ret = reset_control_deassert(acio->reset); + if (ret) { + dev_err(acio->dev, "ACIO block failed to start: %d\n", ret); + goto remove_links; + } + + /* Start and wait for the co-processor to boot */ + writel(APPLE_CIO_M3_CTRL_START, acio->rc_base + APPLE_CIO_M3_CTRL); + acio->rtk = apple_rtkit_init(acio->dev, acio, NULL, 0, &apple_cio_rtkit_ops); + if (IS_ERR(acio->rtk)) { + ret = PTR_ERR(acio->rtk); + dev_err(acio->dev, "Failed to initialize RTKit: %d\n", ret); + goto remove_links; + } + + ret = apple_rtkit_boot(acio->rtk); + if (ret) { + dev_err(acio->dev, "M3 RTKit failed to boot: %d\n", ret); + goto err_free_rtkit; + } + + ret = readl_poll_timeout(acio->rc_base + APPLE_CIO_M3_STAT, state, + state & APPLE_CIO_M3_STAT_STATE, 100, 500000); + if (ret < 0) { + dev_err(acio->dev, "M3 firmware failed to get ready: %d\n", ret); + goto err_shutdown_rtkit; + } + + apple_tunable_apply(acio->rc_base, acio->rc_tunable); + + /* + * Bring up devices which are part of ACIO and are now accessible by the main SoC + * and specifically wait for the NHI to be up to prevent concurrent shutdowns. + */ + reinit_completion(&acio->nhi_boot_completion); + ret = of_platform_populate(acio->np, NULL, NULL, acio->dev); + if (ret) { + dev_err(acio->dev, "failed to populate children: %d\n", ret); + goto err_depopulate; + } + + if (!wait_for_completion_timeout(&acio->nhi_boot_completion, + msecs_to_jiffies(APPLE_CIO_NHI_BOOT_TIMEOUT_MS))) { + dev_err(acio->dev, "Timed out waiting for the NHI to come up\n"); + ret = -ETIMEDOUT; + goto err_depopulate; + } + if (acio->nhi_boot_status) { + ret = acio->nhi_boot_status; + goto err_depopulate; + } + + acio->current_cable_info = acio->target_cable_info; + return 0; + +err_depopulate: + of_platform_depopulate(acio->dev); +err_shutdown_rtkit: + /* Ignore errors here since we're about to cut power to the entire block anyway */ + apple_rtkit_poweroff(acio->rtk); +err_free_rtkit: + apple_rtkit_free(acio->rtk); +remove_links: + /* Cut power to reset the entire block */ + for (i = 0; i < acio->pd_list->num_pds; i++) { + if (acio->pd_list->pd_links[i]) + device_link_del(acio->pd_list->pd_links[i]); + acio->pd_list->pd_links[i] = NULL; + } + return ret; +} + +static int apple_cio_tbt_switch_set(struct typec_thunderbolt_switch_dev *sw, + const struct typec_thunderbolt_switch_data *data) +{ + struct apple_cio *acio = typec_thunderbolt_switch_get_drvdata(sw); + + guard(mutex)(&acio->lock); + + dev_dbg(acio->dev, "set cable state: %d\n", data->state); + + switch (data->state) { + case TYPEC_THUNDERBOLT_SWITCH_OFF: + acio->target_cable_info = 0; + break; + case TYPEC_THUNDERBOLT_SWITCH_TBT: + acio->target_cable_info = TB_VSE_CAP_APPLE_CABLE_INFO_PRESENT; + acio->target_cable_info |= TB_VSE_CAP_APPLE_CABLE_INFO_TBT2_3; + if (data->tbt.cable_mode & TBT_CABLE_ACTIVE_PASSIVE) { + acio->target_cable_info |= TB_VSE_CAP_APPLE_CABLE_INFO_ACTIVE_CABLE; + if (!(data->tbt.cable_mode & TBT_CABLE_LINK_TRAINING)) + acio->target_cable_info |= TB_VSE_CAP_APPLE_CABLE_INFO_BIDIR_LSRX; + } + /* bit 16 of the Device Discover Mode VDO is 1 for a legacy TBT2 adapter */ + if (TBT_ADAPTER(data->tbt.device_mode)) + acio->target_cable_info |= TB_VSE_CAP_APPLE_CABLE_INFO_LEGACY_ADAPTER; + if (TBT_CABLE_SPEED(data->tbt.cable_mode) == TBT_CABLE_10_AND_20GBPS) + 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; + dev_dbg(acio->dev, + "TBT cable: cable_mode 0x%x, device_mode 0x%x, enter_vdo 0x%x, orientation %d -> cable info 0x%x\n", + data->tbt.cable_mode, data->tbt.device_mode, + data->tbt.enter_vdo, data->orientation, + acio->target_cable_info); + break; + case TYPEC_THUNDERBOLT_SWITCH_USB4: + 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) + 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; + dev_dbg(acio->dev, "USB4 cable: eudo 0x%x, orientation %d -> cable info 0x%x\n", + data->usb4.eudo, data->orientation, acio->target_cable_info); + break; + } + + if (acio->target_cable_info == acio->current_cable_info) + return 0; + + /* + * Transitions between different cables without a shutdown inbetween are invalid and can + * only happen when there's a bug inside the Type-C PD driver. If we tried such a + * transition, ACIO would crash and then trigger some watchdog that would reset the entire + * SoC a few seconds later. Shutting down instead only makes the connected device not work + * but we should be able to recover once the next cable is plugged in. + */ + if (acio->current_cable_info && acio->target_cable_info) { + dev_err(acio->dev, + "Invalid cable transition from 0x%x to 0x%x, shutting down instead\n", + acio->current_cable_info, acio->target_cable_info); + acio->target_cable_info = 0; + } + + /* + * Bring up or power down the ACIO complex + * current_cable_info will be updated in the start/stop functions + */ + if (acio->target_cable_info) + return apple_cio_start(acio); + + apple_cio_stop(acio); + return 0; +} + +static int apple_cio_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct apple_cio *acio; + int ret; + + acio = devm_kzalloc(dev, sizeof(*acio), GFP_KERNEL); + if (!acio) + return -ENOMEM; + platform_set_drvdata(pdev, acio); + + ret = devm_mutex_init(dev, &acio->lock); + if (ret) + return ret; + init_completion(&acio->nhi_boot_completion); + acio->dev = &pdev->dev; + acio->np = dev->of_node; + + acio->sram_res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "sram"); + if (!acio->sram_res) + return dev_err_probe(dev, -EIO, "Failed to get SRAM resource\n"); + acio->sram_base = devm_ioremap_resource(dev, acio->sram_res); + if (IS_ERR(acio->sram_base)) + return dev_err_probe(dev, PTR_ERR(acio->sram_base), "Failed to map SRAM\n"); + + acio->rc_res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "rc"); + acio->rc_base = devm_ioremap_resource(&pdev->dev, acio->rc_res); + if (IS_ERR(acio->rc_base)) + return dev_err_probe(dev, PTR_ERR(acio->rc_base), "Unable to map rc regs\n"); + acio->rc_tunable = + devm_apple_tunable_parse(dev, acio->np, "apple,tunable-rc", acio->rc_res); + if (IS_ERR(acio->rc_tunable)) + return dev_err_probe(dev, PTR_ERR(acio->rc_tunable), "Unable to load rc tunable\n"); + + 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"); + + /* + * If there is only a single domain listed in the device tree the platform driver + * framework will already attach it. Thus, if we find an already attached domain + * here something's wrong in the device tree because we expect at least three separate + * domains that we have to control manually. + */ + if (dev->pm_domain) { + dev_err(dev, "PM domain already attached, check if the DT lists three domains\n"); + return -EINVAL; + } + + /* + * Find and attach the PM domains but don't power them on yet since we must only + * do that after the PHY has already been configured into USB4/Thunderbolt mode. + */ + struct dev_pm_domain_attach_data pd_data = { + .pd_flags = PD_FLAG_NO_DEV_LINK, + }; + ret = devm_pm_domain_attach_list(dev, &pd_data, &acio->pd_list); + if (ret < 0) + return dev_err_probe(dev, ret, "Unable to attach PM domains\n"); + else if (ret < 3) + return dev_err_probe(dev, -EINVAL, "Not enough PM domains\n"); + + /* And finally register the OOB notification for Thunderbolt/USB4 cables */ + struct typec_thunderbolt_switch_desc desc = { + .fwnode = pdev->dev.fwnode, + .set = apple_cio_tbt_switch_set, + .drvdata = acio, + }; + acio->tbt_switch = typec_thunderbolt_switch_register(dev, &desc); + if (IS_ERR(acio->tbt_switch)) + return dev_err_probe(dev, PTR_ERR(acio->tbt_switch), + "Unable to register thunderbolt switch\n"); + + return 0; +} + +static void apple_cio_remove(struct platform_device *pdev) +{ + struct apple_cio *acio = platform_get_drvdata(pdev); + + typec_thunderbolt_switch_unregister(acio->tbt_switch); + + guard(mutex)(&acio->lock); + if (acio->current_cable_info) + apple_cio_stop(acio); +} + +static const struct of_device_id apple_acio_match[] = { + { + .compatible = "apple,t8103-usb4-acio", + }, + {}, +}; +MODULE_DEVICE_TABLE(of, apple_acio_match); + +static struct platform_driver apple_cio_driver = { + .driver = { + .name = "thunderbolt-apple-acio", + .of_match_table = apple_acio_match, + }, + .probe = apple_cio_probe, + .remove = apple_cio_remove, +}; + +static struct platform_driver * const apple_cio_drivers[] = { + &apple_nhi_driver, + &apple_cio_driver, +}; + +static int __init apple_cio_init(void) +{ + return platform_register_drivers(apple_cio_drivers, + ARRAY_SIZE(apple_cio_drivers)); +} + +static void __exit apple_cio_exit(void) +{ + platform_unregister_drivers(apple_cio_drivers, + ARRAY_SIZE(apple_cio_drivers)); +} + +module_init(apple_cio_init); +module_exit(apple_cio_exit); + +MODULE_IMPORT_NS("USB4"); +MODULE_AUTHOR("Sven Peter "); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Apple Silicon USB4/Thunderbolt driver"); From 2f2c0fbaa9162ff20069d35ade23ebe31264c5f8 Mon Sep 17 00:00:00 2001 From: Sven Peter Date: Mon, 24 Aug 2026 18:07:56 +0200 Subject: [PATCH 28/33] arm64: dts: apple: t8103: Add USB4 ACIO and NHI Add the ACIO host router, NHI, DART and M3 mailbox nodes for both Type-C ports on t8103 and connect the host routers to the Type-C connectors. Signed-off-by: Sven Peter --- arch/arm64/boot/dts/apple/t8103-jxxx.dtsi | 61 +++++++ arch/arm64/boot/dts/apple/t8103.dtsi | 188 ++++++++++++++++++++++ 2 files changed, 249 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t8103-jxxx.dtsi b/arch/arm64/boot/dts/apple/t8103-jxxx.dtsi index bf92d3e2c7753e..df919d4889d248 100644 --- a/arch/arm64/boot/dts/apple/t8103-jxxx.dtsi +++ b/arch/arm64/boot/dts/apple/t8103-jxxx.dtsi @@ -25,6 +25,10 @@ wifi0 = &wifi0; atcphy0 = &atcphy0; atcphy1 = &atcphy1; + usb4-0-acio = &usb4_0_acio; + usb4-0-nhi = &usb4_0_nhi; + usb4-1-acio = &usb4_1_acio; + usb4-1-nhi = &usb4_1_nhi; }; chosen { @@ -92,6 +96,13 @@ remote-endpoint = <&atcphy0_typec_lanes>; }; }; + + port@2 { + reg = <2>; + typec0_con_tbt: endpoint { + remote-endpoint = <&usb4_0_acio_tbt>; + }; + }; }; }; }; @@ -128,6 +139,13 @@ remote-endpoint = <&atcphy1_typec_lanes>; }; }; + + port@2 { + reg = <2>; + typec1_con_tbt: endpoint { + remote-endpoint = <&usb4_1_acio_tbt>; + }; + }; }; }; }; @@ -258,6 +276,49 @@ }; }; +/* USB4 */ +&usb4_0_acio { + ports { + #address-cells = <1>; + #size-cells = <0>; + + /* 1: USB4 port */ + port@1 { + reg = <1>; + usb4_0_acio_tbt: endpoint { + remote-endpoint = <&typec0_con_tbt>; + }; + }; + + /* 2: unused(?) USB4 port */ + /* 3: PCIe */ + /* 4: USB3 */ + /* 5: DP0 */ + /* 6: DP1 */ + }; +}; + +&usb4_1_acio { + ports { + #address-cells = <1>; + #size-cells = <0>; + + /* 1: USB4 port */ + port@1 { + reg = <1>; + usb4_1_acio_tbt: endpoint { + remote-endpoint = <&typec1_con_tbt>; + }; + }; + + /* 2: unused(?) USB4 port */ + /* 3: PCIe */ + /* 4: USB3 */ + /* 5: DP0 */ + /* 6: DP1 */ + }; +}; + /* * Force the bus number assignments so that we can declare some of the * on-board devices and properties that are populated by the bootloader diff --git a/arch/arm64/boot/dts/apple/t8103.dtsi b/arch/arm64/boot/dts/apple/t8103.dtsi index b6f7f1417b04f4..d54ec8e5290c58 100644 --- a/arch/arm64/boot/dts/apple/t8103.dtsi +++ b/arch/arm64/boot/dts/apple/t8103.dtsi @@ -1349,6 +1349,14 @@ , , ; + + acio0_pins: acio0-pins { + pinmux = ; + }; + + acio1_pins: acio1-pins { + pinmux = ; + }; }; aop_mbox: mbox@24a408000 { @@ -1551,6 +1559,96 @@ resets = <&ps_ans2>; }; + usb4_0_mbox: mailbox@381100000 { + compatible = "apple,t8103-m3-mailbox", + "apple,m3-mailbox-v2"; + reg = <0x3 0x81100000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = , + , + , + ; + interrupt-names = "send-empty", "send-not-empty", + "recv-empty", "recv-not-empty"; + #mbox-cells = <0>; + }; + + usb4_0_acio: cio@381ac0000 { + compatible = "apple,t8103-usb4-acio"; + reg = <0x3 0x81ac0000 0x0 0xc000>, + <0x3 0x81080000 0x0 0x20000>; + reg-names = "rc", "sram"; + + mboxes = <&usb4_0_mbox>; + resets = <&cio_reset 0>; + + power-domains = <&ps_atc0_cio>, + <&ps_atc0_cio_pcie>, + <&ps_atc0_cio_usb>; + + thunderbolt-switch; + + pinctrl-0 = <&acio0_pins>; + pinctrl-names = "default"; + + #address-cells = <1>; + #size-cells = <1>; + ranges = <0x0 0x3 0x81000000 0x1000000>; + nonposted-mmio; + + usb4_0_dart: iommu@a80000 { + compatible = "apple,t8103-dart"; + reg = <0xa80000 0x4000>; + interrupt-parent = <&aic>; + interrupts = ; + #iommu-cells = <1>; + }; + + usb4_0_nhi: nhi@f00000 { + reg = <0xf00000 0x100000>, + <0xdb0000 0x30004>; + reg-names = "nhi", "pdf"; + compatible = "apple,t8103-usb4-nhi"; + interrupt-parent = <&aic>; + 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"; + iommus = <&usb4_0_dart 1>; + /* To be filled by the loader */ + apple,thunderbolt-drom = [00]; + }; + }; + dwc3_0: usb@382280000 { compatible = "apple,t8103-dwc3"; reg = <0x3 0x82280000 0x0 0xcd00>, <0x3 0x8228cd00 0x0 0x3200>; @@ -1610,6 +1708,96 @@ power-domains = <&ps_atc0_usb>; }; + usb4_1_mbox: mailbox@501100000 { + compatible = "apple,t8103-m3-mailbox", + "apple,m3-mailbox-v2"; + reg = <0x5 0x01100000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = , + , + , + ; + interrupt-names = "send-empty", "send-not-empty", + "recv-empty", "recv-not-empty"; + #mbox-cells = <0>; + }; + + usb4_1_acio: cio@501ac0000 { + compatible = "apple,t8103-usb4-acio"; + reg = <0x5 0x01ac0000 0x0 0xc000>, + <0x5 0x01080000 0x0 0x20000>; + reg-names = "rc", "sram"; + + mboxes = <&usb4_1_mbox>; + resets = <&cio_reset 1>; + + power-domains = <&ps_atc1_cio>, + <&ps_atc1_cio_pcie>, + <&ps_atc1_cio_usb>; + + thunderbolt-switch; + + pinctrl-0 = <&acio1_pins>; + pinctrl-names = "default"; + + #address-cells = <1>; + #size-cells = <1>; + ranges = <0x0 0x5 0x01000000 0x1000000>; + nonposted-mmio; + + usb4_1_dart: iommu@a80000 { + compatible = "apple,t8103-dart"; + reg = <0xa80000 0x4000>; + interrupt-parent = <&aic>; + interrupts = ; + #iommu-cells = <1>; + }; + + usb4_1_nhi: nhi@f00000 { + reg = <0xf00000 0x100000>, + <0xdb0000 0x30004>; + reg-names = "nhi", "pdf"; + compatible = "apple,t8103-usb4-nhi"; + interrupt-parent = <&aic>; + 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"; + iommus = <&usb4_1_dart 1>; + /* To be filled by the loader */ + apple,thunderbolt-drom = [00]; + }; + }; + dwc3_1: usb@502280000 { compatible = "apple,t8103-dwc3"; reg = <0x5 0x02280000 0x0 0xcd00>, <0x5 0x0228cd00 0x0 0x3200>; From eafc48ddb385147dacd713649fdb938750c11f21 Mon Sep 17 00:00:00 2001 From: Sven Peter Date: Mon, 24 Aug 2026 18:07:56 +0200 Subject: [PATCH 29/33] arm64: dts: apple: t8112: Add USB4 ACIO and NHI Add the ACIO host router, NHI, DART and mailbox nodes for both Type-C ports on t8112 and connect the host routers to the Type-C connectors. Signed-off-by: Sven Peter --- arch/arm64/boot/dts/apple/t8112-jxxx.dtsi | 61 +++++++ arch/arm64/boot/dts/apple/t8112.dtsi | 190 ++++++++++++++++++++++ 2 files changed, 251 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t8112-jxxx.dtsi b/arch/arm64/boot/dts/apple/t8112-jxxx.dtsi index 7976360ebf1281..a9088c17b76197 100644 --- a/arch/arm64/boot/dts/apple/t8112-jxxx.dtsi +++ b/arch/arm64/boot/dts/apple/t8112-jxxx.dtsi @@ -16,6 +16,10 @@ dcp = &dcp; disp0 = &display; disp0_piodma = &disp0_piodma; + usb4-0-acio = &usb4_0_acio; + usb4-0-nhi = &usb4_0_nhi; + usb4-1-acio = &usb4_1_acio; + usb4-1-nhi = &usb4_1_nhi; serial0 = &serial0; serial2 = &serial2; sio = &sio; @@ -81,6 +85,13 @@ remote-endpoint = <&atcphy0_typec_lanes>; }; }; + + port@2 { + reg = <2>; + typec0_con_tbt: endpoint { + remote-endpoint = <&usb4_0_acio_tbt>; + }; + }; }; }; }; @@ -117,6 +128,13 @@ remote-endpoint = <&atcphy1_typec_lanes>; }; }; + + port@2 { + reg = <2>; + typec1_con_tbt: endpoint { + remote-endpoint = <&usb4_1_acio_tbt>; + }; + }; }; }; }; @@ -258,6 +276,49 @@ }; }; +/* USB4 */ +&usb4_0_acio { + ports { + #address-cells = <1>; + #size-cells = <0>; + + /* 1: USB4 port */ + port@1 { + reg = <1>; + usb4_0_acio_tbt: endpoint { + remote-endpoint = <&typec0_con_tbt>; + }; + }; + + /* 2: unused(?) USB4 port */ + /* 3: PCIe */ + /* 4: USB3 */ + /* 5: DP0 */ + /* 6: DP1 */ + }; +}; + +&usb4_1_acio { + ports { + #address-cells = <1>; + #size-cells = <0>; + + /* 1: USB4 port */ + port@1 { + reg = <1>; + usb4_1_acio_tbt: endpoint { + remote-endpoint = <&typec1_con_tbt>; + }; + }; + + /* 2: unused(?) USB4 port */ + /* 3: PCIe */ + /* 4: USB3 */ + /* 5: DP0 */ + /* 6: DP1 */ + }; +}; + &i2c1 { status = "okay"; }; diff --git a/arch/arm64/boot/dts/apple/t8112.dtsi b/arch/arm64/boot/dts/apple/t8112.dtsi index 8a30d51795d76f..90e6bc2aa5148e 100644 --- a/arch/arm64/boot/dts/apple/t8112.dtsi +++ b/arch/arm64/boot/dts/apple/t8112.dtsi @@ -1503,6 +1503,14 @@ , , ; + + acio0_pins: acio0-pins { + pinmux = ; + }; + + acio1_pins: acio1-pins { + pinmux = ; + }; }; aop_mbox: mbox@24a408000 { @@ -1816,6 +1824,97 @@ resets = <&ps_ans>; }; + usb4_0_mbox: mailbox@381108000 { + compatible = "apple,t8112-asc-mailbox", + "apple,asc-mailbox-v4"; + reg = <0x3 0x81108000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = , + , + , + ; + interrupt-names = "send-empty", "send-not-empty", + "recv-empty", "recv-not-empty"; + #mbox-cells = <0>; + }; + + usb4_0_acio: cio@381ac0000 { + compatible = "apple,t8112-usb4-acio", "apple,t8103-usb4-acio"; + reg = <0x3 0x81ac0000 0x0 0xc000>, + <0x3 0x81080000 0x0 0x20000>; + reg-names = "rc", "sram"; + + mboxes = <&usb4_0_mbox>; + resets = <&cio_reset 0>; + + power-domains = <&ps_atc0_cio>, + <&ps_atc0_cio_pcie>, + <&ps_atc0_cio_usb>; + + thunderbolt-switch; + + pinctrl-0 = <&acio0_pins>; + pinctrl-names = "default"; + + #address-cells = <1>; + #size-cells = <1>; + ranges = <0x0 0x3 0x81000000 0x1000000>; + nonposted-mmio; + + usb4_0_dart: iommu@a80000 { + compatible = "apple,t8110-dart"; + reg = <0xa80000 0x4000>; + interrupt-parent = <&aic>; + interrupts = ; + #iommu-cells = <1>; + }; + + usb4_0_nhi: nhi@f00000 { + reg = <0xf00000 0x100000>, + <0xdb0000 0x30004>; + reg-names = "nhi", "pdf"; + compatible = "apple,t8112-usb4-nhi", + "apple,t8103-usb4-nhi"; + interrupt-parent = <&aic>; + 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"; + iommus = <&usb4_0_dart 1>; + /* To be filled by the loader */ + apple,thunderbolt-drom = [00]; + }; + }; + dwc3_0: usb@382280000 { compatible = "apple,t8112-dwc3", "apple,t8103-dwc3"; reg = <0x3 0x82280000 0x0 0xcd00>, <0x3 0x8228cd00 0x0 0x3200>; @@ -1875,6 +1974,97 @@ power-domains = <&ps_atc0_usb>; }; + usb4_1_mbox: mailbox@501108000 { + compatible = "apple,t8112-asc-mailbox", + "apple,asc-mailbox-v4"; + reg = <0x5 0x01108000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = , + , + , + ; + interrupt-names = "send-empty", "send-not-empty", + "recv-empty", "recv-not-empty"; + #mbox-cells = <0>; + }; + + usb4_1_acio: cio@501ac0000 { + compatible = "apple,t8112-usb4-acio", "apple,t8103-usb4-acio"; + reg = <0x5 0x01ac0000 0x0 0xc000>, + <0x5 0x01080000 0x0 0x20000>; + reg-names = "rc", "sram"; + + mboxes = <&usb4_1_mbox>; + resets = <&cio_reset 1>; + + power-domains = <&ps_atc1_cio>, + <&ps_atc1_cio_pcie>, + <&ps_atc1_cio_usb>; + + thunderbolt-switch; + + pinctrl-0 = <&acio1_pins>; + pinctrl-names = "default"; + + #address-cells = <1>; + #size-cells = <1>; + ranges = <0x0 0x5 0x01000000 0x1000000>; + nonposted-mmio; + + usb4_1_dart: iommu@a80000 { + compatible = "apple,t8110-dart"; + reg = <0xa80000 0x4000>; + interrupt-parent = <&aic>; + interrupts = ; + #iommu-cells = <1>; + }; + + usb4_1_nhi: nhi@f00000 { + reg = <0xf00000 0x100000>, + <0xdb0000 0x30004>; + reg-names = "nhi", "pdf"; + compatible = "apple,t8112-usb4-nhi", + "apple,t8103-usb4-nhi"; + interrupt-parent = <&aic>; + 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"; + iommus = <&usb4_1_dart 1>; + /* To be filled by the loader */ + apple,thunderbolt-drom = [00]; + }; + }; + dwc3_1: usb@502280000 { compatible = "apple,t8112-dwc3", "apple,t8103-dwc3"; reg = <0x5 0x02280000 0x0 0xcd00>, <0x5 0x0228cd00 0x0 0x3200>; From 80ee643ada0a2ab73bdef09e94a662be867a2914 Mon Sep 17 00:00:00 2001 From: Sven Peter Date: Mon, 24 Aug 2026 18:07:56 +0200 Subject: [PATCH 30/33] arm64: dts: apple: t60xx: Add USB4 ACIO and NHI Add the ACIO host router, NHI, DART and mailbox nodes for the four ATC instances on each t600x and t602x die and connect the host routers to the Type-C connectors on the MacBook Pro (j314/j316 and j414/j416), Mac Studio (j375 and j475), Mac mini (j474) and Mac Pro (j180d) boards. Signed-off-by: Sven Peter --- arch/arm64/boot/dts/apple/t600x-dieX.dtsi | 384 ++++++++++++++++++ .../arm64/boot/dts/apple/t600x-j314-j316.dtsi | 107 +++++ arch/arm64/boot/dts/apple/t600x-j375.dtsi | 137 +++++++ arch/arm64/boot/dts/apple/t602x-dieX.dtsi | 384 ++++++++++++++++++ 4 files changed, 1012 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t600x-dieX.dtsi b/arch/arm64/boot/dts/apple/t600x-dieX.dtsi index 845f172929f3b2..c5ae1fe877b4e3 100644 --- a/arch/arm64/boot/dts/apple/t600x-dieX.dtsi +++ b/arch/arm64/boot/dts/apple/t600x-dieX.dtsi @@ -291,6 +291,22 @@ , , ; + + DIE_NODE(acio0_pins): acio0-pins { + pinmux = ; + }; + + DIE_NODE(acio1_pins): acio1-pins { + pinmux = ; + }; + + DIE_NODE(acio2_pins): acio2-pins { + pinmux = ; + }; + + DIE_NODE(acio3_pins): acio3-pins { + pinmux = ; + }; }; DIE_NODE(sio_dart_0): iommu@39b004000 { @@ -450,6 +466,98 @@ }; */ + DIE_NODE(usb4_0_mbox): mailbox@701100000 { + compatible = "apple,t6000-m3-mailbox", + "apple,m3-mailbox-v2"; + reg = <0x7 0x01100000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = , + , + , + ; + interrupt-names = "send-empty", "send-not-empty", + "recv-empty", "recv-not-empty"; + #mbox-cells = <0>; + }; + + DIE_NODE(usb4_0_acio): cio@701ac0000 { + compatible = "apple,t6000-usb4-acio", "apple,t8103-usb4-acio"; + reg = <0x7 0x01ac0000 0x0 0xc000>, + <0x7 0x01080000 0x0 0x20000>; + reg-names = "rc", "sram"; + + mboxes = <&DIE_NODE(usb4_0_mbox)>; + resets = <&DIE_NODE(cio_reset) 0>; + + power-domains = <&DIE_NODE(ps_atc0_cio)>, + <&DIE_NODE(ps_atc0_cio_pcie)>, + <&DIE_NODE(ps_atc0_cio_usb)>; + + thunderbolt-switch; + + pinctrl-0 = <&DIE_NODE(acio0_pins)>; + pinctrl-names = "default"; + + #address-cells = <1>; + #size-cells = <1>; + ranges = <0x0 0x7 0x01000000 0x1000000>; + nonposted-mmio; + + DIE_NODE(usb4_0_dart): iommu@a80000 { + compatible = "apple,t6000-dart"; + reg = <0xa80000 0x4000>; + interrupt-parent = <&aic>; + interrupts = ; + #iommu-cells = <1>; + }; + + DIE_NODE(usb4_0_nhi): nhi@f00000 { + reg = <0xf00000 0x100000>, + <0xdb0000 0x30004>; + reg-names = "nhi", "pdf"; + compatible = "apple,t6000-usb4-nhi", + "apple,t8103-usb4-nhi"; + interrupt-parent = <&aic>; + 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"; + iommus = <&DIE_NODE(usb4_0_dart) 1>, + <&DIE_NODE(usb4_0_dart) 2>; + /* To be filled by the loader */ + apple,thunderbolt-drom = [00]; + }; + }; + DIE_NODE(dwc3_0): usb@702280000 { compatible = "apple,t6000-dwc3", "apple,t8103-dwc3"; reg = <0x7 0x02280000 0x0 0xcd00>, <0x7 0x0228cd00 0x0 0x3200>; @@ -511,6 +619,98 @@ status = "disabled"; }; + DIE_NODE(usb4_1_mbox): mailbox@b01100000 { + compatible = "apple,t6000-m3-mailbox", + "apple,m3-mailbox-v2"; + reg = <0xb 0x01100000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = , + , + , + ; + interrupt-names = "send-empty", "send-not-empty", + "recv-empty", "recv-not-empty"; + #mbox-cells = <0>; + }; + + DIE_NODE(usb4_1_acio): cio@b01ac0000 { + compatible = "apple,t6000-usb4-acio", "apple,t8103-usb4-acio"; + reg = <0xb 0x01ac0000 0x0 0xc000>, + <0xb 0x01080000 0x0 0x20000>; + reg-names = "rc", "sram"; + + mboxes = <&DIE_NODE(usb4_1_mbox)>; + resets = <&DIE_NODE(cio_reset) 1>; + + power-domains = <&DIE_NODE(ps_atc1_cio)>, + <&DIE_NODE(ps_atc1_cio_pcie)>, + <&DIE_NODE(ps_atc1_cio_usb)>; + + thunderbolt-switch; + + pinctrl-0 = <&DIE_NODE(acio1_pins)>; + pinctrl-names = "default"; + + #address-cells = <1>; + #size-cells = <1>; + ranges = <0x0 0xb 0x01000000 0x1000000>; + nonposted-mmio; + + DIE_NODE(usb4_1_dart): iommu@a80000 { + compatible = "apple,t6000-dart"; + reg = <0xa80000 0x4000>; + interrupt-parent = <&aic>; + interrupts = ; + #iommu-cells = <1>; + }; + + DIE_NODE(usb4_1_nhi): nhi@f00000 { + reg = <0xf00000 0x100000>, + <0xdb0000 0x30004>; + reg-names = "nhi", "pdf"; + compatible = "apple,t6000-usb4-nhi", + "apple,t8103-usb4-nhi"; + interrupt-parent = <&aic>; + 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"; + iommus = <&DIE_NODE(usb4_1_dart) 1>, + <&DIE_NODE(usb4_1_dart) 2>; + /* To be filled by the loader */ + apple,thunderbolt-drom = [00]; + }; + }; + DIE_NODE(dwc3_1): usb@b02280000 { compatible = "apple,t6000-dwc3", "apple,t8103-dwc3"; reg = <0xb 0x02280000 0x0 0xcd00>, <0xb 0x0228cd00 0x0 0x3200>; @@ -572,6 +772,98 @@ status = "disabled"; }; + DIE_NODE(usb4_2_mbox): mailbox@f01100000 { + compatible = "apple,t6000-m3-mailbox", + "apple,m3-mailbox-v2"; + reg = <0xf 0x01100000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = , + , + , + ; + interrupt-names = "send-empty", "send-not-empty", + "recv-empty", "recv-not-empty"; + #mbox-cells = <0>; + }; + + DIE_NODE(usb4_2_acio): cio@f01ac0000 { + compatible = "apple,t6000-usb4-acio", "apple,t8103-usb4-acio"; + reg = <0xf 0x01ac0000 0x0 0xc000>, + <0xf 0x01080000 0x0 0x20000>; + reg-names = "rc", "sram"; + + mboxes = <&DIE_NODE(usb4_2_mbox)>; + resets = <&DIE_NODE(cio_reset) 2>; + + power-domains = <&DIE_NODE(ps_atc2_cio)>, + <&DIE_NODE(ps_atc2_cio_pcie)>, + <&DIE_NODE(ps_atc2_cio_usb)>; + + thunderbolt-switch; + + pinctrl-0 = <&DIE_NODE(acio2_pins)>; + pinctrl-names = "default"; + + #address-cells = <1>; + #size-cells = <1>; + ranges = <0x0 0xf 0x01000000 0x1000000>; + nonposted-mmio; + + DIE_NODE(usb4_2_dart): iommu@a80000 { + compatible = "apple,t6000-dart"; + reg = <0xa80000 0x4000>; + interrupt-parent = <&aic>; + interrupts = ; + #iommu-cells = <1>; + }; + + DIE_NODE(usb4_2_nhi): nhi@f00000 { + reg = <0xf00000 0x100000>, + <0xdb0000 0x30004>; + reg-names = "nhi", "pdf"; + compatible = "apple,t6000-usb4-nhi", + "apple,t8103-usb4-nhi"; + interrupt-parent = <&aic>; + 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"; + iommus = <&DIE_NODE(usb4_2_dart) 1>, + <&DIE_NODE(usb4_2_dart) 2>; + /* To be filled by the loader */ + apple,thunderbolt-drom = [00]; + }; + }; + DIE_NODE(dwc3_2): usb@f02280000 { compatible = "apple,t6000-dwc3", "apple,t8103-dwc3"; reg = <0xf 0x02280000 0x0 0xcd00>, <0xf 0x0228cd00 0x0 0x3200>; @@ -633,6 +925,98 @@ status = "disabled"; }; + DIE_NODE(usb4_3_mbox): mailbox@1301100000 { + compatible = "apple,t6000-m3-mailbox", + "apple,m3-mailbox-v2"; + reg = <0x13 0x01100000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = , + , + , + ; + interrupt-names = "send-empty", "send-not-empty", + "recv-empty", "recv-not-empty"; + #mbox-cells = <0>; + }; + + DIE_NODE(usb4_3_acio): cio@1301ac0000 { + compatible = "apple,t6000-usb4-acio", "apple,t8103-usb4-acio"; + reg = <0x13 0x01ac0000 0x0 0xc000>, + <0x13 0x01080000 0x0 0x20000>; + reg-names = "rc", "sram"; + + mboxes = <&DIE_NODE(usb4_3_mbox)>; + resets = <&DIE_NODE(cio_reset) 3>; + + power-domains = <&DIE_NODE(ps_atc3_cio)>, + <&DIE_NODE(ps_atc3_cio_pcie)>, + <&DIE_NODE(ps_atc3_cio_usb)>; + + thunderbolt-switch; + + pinctrl-0 = <&DIE_NODE(acio3_pins)>; + pinctrl-names = "default"; + + #address-cells = <1>; + #size-cells = <1>; + ranges = <0x0 0x13 0x01000000 0x1000000>; + nonposted-mmio; + + DIE_NODE(usb4_3_dart): iommu@a80000 { + compatible = "apple,t6000-dart"; + reg = <0xa80000 0x4000>; + interrupt-parent = <&aic>; + interrupts = ; + #iommu-cells = <1>; + }; + + DIE_NODE(usb4_3_nhi): nhi@f00000 { + reg = <0xf00000 0x100000>, + <0xdb0000 0x30004>; + reg-names = "nhi", "pdf"; + compatible = "apple,t6000-usb4-nhi", + "apple,t8103-usb4-nhi"; + interrupt-parent = <&aic>; + 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"; + iommus = <&DIE_NODE(usb4_3_dart) 1>, + <&DIE_NODE(usb4_3_dart) 2>; + /* To be filled by the loader */ + apple,thunderbolt-drom = [00]; + }; + }; + DIE_NODE(dwc3_3): usb@1302280000 { compatible = "apple,t6000-dwc3", "apple,t8103-dwc3"; reg = <0x13 0x02280000 0x0 0xcd00>, <0x13 0x0228cd00 0x0 0x3200>; diff --git a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi index 2284cb70ec5d4d..19010c3f654633 100644 --- a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi +++ b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi @@ -31,6 +31,12 @@ disp0_piodma = &disp0_piodma; serial0 = &serial0; sio = &sio; + usb4-0-acio = &usb4_0_acio; + usb4-0-nhi = &usb4_0_nhi; + usb4-1-acio = &usb4_1_acio; + usb4-1-nhi = &usb4_1_nhi; + usb4-2-acio = &usb4_2_acio; + usb4-2-nhi = &usb4_2_nhi; wifi0 = &wifi0; }; @@ -679,6 +685,107 @@ }; }; +&typec0 { + ports { + port@2 { + reg = <2>; + typec0_con_tbt: endpoint { + remote-endpoint = <&usb4_0_acio_tbt>; + }; + }; + }; +}; + +&typec1 { + ports { + port@2 { + reg = <2>; + typec1_con_tbt: endpoint { + remote-endpoint = <&usb4_1_acio_tbt>; + }; + }; + }; +}; + +&typec2 { + ports { + port@2 { + reg = <2>; + typec2_con_tbt: endpoint { + remote-endpoint = <&usb4_2_acio_tbt>; + }; + }; + }; +}; + +/* USB4 */ +&usb4_0_acio { + ports { + #address-cells = <1>; + #size-cells = <0>; + + /* 1: USB4 port */ + port@1 { + reg = <1>; + usb4_0_acio_tbt: endpoint { + remote-endpoint = <&typec0_con_tbt>; + }; + }; + + /* 2: unused(?) USB4 port */ + /* 3: PCIe */ + /* 4: USB3 */ + /* 5: DP0 */ + /* 6: DP1 */ + }; +}; + +&usb4_1_acio { + ports { + #address-cells = <1>; + #size-cells = <0>; + + /* 1: USB4 port */ + port@1 { + reg = <1>; + usb4_1_acio_tbt: endpoint { + remote-endpoint = <&typec1_con_tbt>; + }; + }; + + /* 2: unused(?) USB4 port */ + /* 3: PCIe */ + /* 4: USB3 */ + /* 5: DP0 */ + /* 6: DP1 */ + }; +}; + +&usb4_2_acio { + ports { + #address-cells = <1>; + #size-cells = <0>; + + /* 1: USB4 port */ + port@1 { + reg = <1>; + usb4_2_acio_tbt: endpoint { + remote-endpoint = <&typec2_con_tbt>; + }; + }; + + /* 2: unused(?) USB4 port */ + /* 3: PCIe */ + /* 4: USB3 */ + /* 5: DP0 */ + /* 6: DP1 */ + }; +}; + +/* ATC3 is used for DP-to-HDMI only and has no USB4 host router */ +/delete-node/ &usb4_3_acio; +/delete-node/ &usb4_3_mbox; + #include "spi1-nvram.dtsi" #include "isp-imx558.dtsi" diff --git a/arch/arm64/boot/dts/apple/t600x-j375.dtsi b/arch/arm64/boot/dts/apple/t600x-j375.dtsi index 03b335d0cc53b2..a42983d9786fda 100644 --- a/arch/arm64/boot/dts/apple/t600x-j375.dtsi +++ b/arch/arm64/boot/dts/apple/t600x-j375.dtsi @@ -29,6 +29,14 @@ ethernet0 = ðernet0; serial0 = &serial0; sio = &sio; + usb4-0-acio = &usb4_0_acio; + usb4-0-nhi = &usb4_0_nhi; + usb4-1-acio = &usb4_1_acio; + usb4-1-nhi = &usb4_1_nhi; + usb4-2-acio = &usb4_2_acio; + usb4-2-nhi = &usb4_2_nhi; + usb4-3-acio = &usb4_3_acio; + usb4-3-nhi = &usb4_3_nhi; wifi0 = &wifi0; }; @@ -536,6 +544,135 @@ status = "okay"; }; +&typec0 { + ports { + port@2 { + reg = <2>; + typec0_con_tbt: endpoint { + remote-endpoint = <&usb4_0_acio_tbt>; + }; + }; + }; +}; + +&typec1 { + ports { + port@2 { + reg = <2>; + typec1_con_tbt: endpoint { + remote-endpoint = <&usb4_1_acio_tbt>; + }; + }; + }; +}; + +&typec2 { + ports { + port@2 { + reg = <2>; + typec2_con_tbt: endpoint { + remote-endpoint = <&usb4_2_acio_tbt>; + }; + }; + }; +}; + +&typec3 { + ports { + port@2 { + reg = <2>; + typec3_con_tbt: endpoint { + remote-endpoint = <&usb4_3_acio_tbt>; + }; + }; + }; +}; + +/* USB4 */ +&usb4_0_acio { + ports { + #address-cells = <1>; + #size-cells = <0>; + + /* 1: USB4 port */ + port@1 { + reg = <1>; + usb4_0_acio_tbt: endpoint { + remote-endpoint = <&typec0_con_tbt>; + }; + }; + + /* 2: unused(?) USB4 port */ + /* 3: PCIe */ + /* 4: USB3 */ + /* 5: DP0 */ + /* 6: DP1 */ + }; +}; + +&usb4_1_acio { + ports { + #address-cells = <1>; + #size-cells = <0>; + + /* 1: USB4 port */ + port@1 { + reg = <1>; + usb4_1_acio_tbt: endpoint { + remote-endpoint = <&typec1_con_tbt>; + }; + }; + + /* 2: unused(?) USB4 port */ + /* 3: PCIe */ + /* 4: USB3 */ + /* 5: DP0 */ + /* 6: DP1 */ + }; +}; + +&usb4_2_acio { + ports { + #address-cells = <1>; + #size-cells = <0>; + + /* 1: USB4 port */ + port@1 { + reg = <1>; + usb4_2_acio_tbt: endpoint { + remote-endpoint = <&typec2_con_tbt>; + }; + }; + + /* 2: unused(?) USB4 port */ + /* 3: PCIe */ + /* 4: USB3 */ + /* 5: DP0 */ + /* 6: DP1 */ + }; +}; + +&usb4_3_acio { + ports { + #address-cells = <1>; + #size-cells = <0>; + + /* 1: USB4 port */ + port@1 { + reg = <1>; + usb4_3_acio_tbt: endpoint { + remote-endpoint = <&typec3_con_tbt>; + }; + }; + + /* 2: unused(?) USB4 port */ + /* 3: PCIe */ + /* 4: USB3 */ + /* 5: DP0 */ + /* 6: DP1 */ + }; +}; + #include "spi1-nvram.dtsi" #include "hwmon-common.dtsi" diff --git a/arch/arm64/boot/dts/apple/t602x-dieX.dtsi b/arch/arm64/boot/dts/apple/t602x-dieX.dtsi index 06cc15fa80da36..68adb70733807b 100644 --- a/arch/arm64/boot/dts/apple/t602x-dieX.dtsi +++ b/arch/arm64/boot/dts/apple/t602x-dieX.dtsi @@ -208,6 +208,22 @@ , , ; + + DIE_NODE(acio0_pins): acio0-pins { + pinmux = ; + }; + + DIE_NODE(acio1_pins): acio1-pins { + pinmux = ; + }; + + DIE_NODE(acio2_pins): acio2-pins { + pinmux = ; + }; + + DIE_NODE(acio3_pins): acio3-pins { + pinmux = ; + }; }; DIE_NODE(dispext1_dart): iommu@315304000 { @@ -460,6 +476,98 @@ reg = <0x4 0x4e80000 0 0x4000>; }; + DIE_NODE(usb4_0_mbox): mailbox@701108000 { + compatible = "apple,t6020-asc-mailbox", + "apple,asc-mailbox-v4"; + reg = <0x7 0x01108000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = , + , + , + ; + interrupt-names = "send-empty", "send-not-empty", + "recv-empty", "recv-not-empty"; + #mbox-cells = <0>; + }; + + DIE_NODE(usb4_0_acio): cio@701ac0000 { + compatible = "apple,t6020-usb4-acio", "apple,t8103-usb4-acio"; + reg = <0x7 0x01ac0000 0x0 0xc000>, + <0x7 0x01080000 0x0 0x20000>; + reg-names = "rc", "sram"; + + mboxes = <&DIE_NODE(usb4_0_mbox)>; + resets = <&DIE_NODE(cio_reset) 0>; + + power-domains = <&DIE_NODE(ps_atc0_cio)>, + <&DIE_NODE(ps_atc0_cio_pcie)>, + <&DIE_NODE(ps_atc0_cio_usb)>; + + thunderbolt-switch; + + pinctrl-0 = <&DIE_NODE(acio0_pins)>; + pinctrl-names = "default"; + + #address-cells = <1>; + #size-cells = <1>; + ranges = <0x0 0x7 0x01000000 0x1000000>; + nonposted-mmio; + + DIE_NODE(usb4_0_dart): iommu@a80000 { + compatible = "apple,t6020-dart", "apple,t8110-dart"; + reg = <0xa80000 0x4000>; + interrupt-parent = <&aic>; + interrupts = ; + #iommu-cells = <1>; + }; + + DIE_NODE(usb4_0_nhi): nhi@f00000 { + reg = <0xf00000 0x100000>, + <0xdb0000 0x30004>; + reg-names = "nhi", "pdf"; + compatible = "apple,t6020-usb4-nhi", + "apple,t8103-usb4-nhi"; + interrupt-parent = <&aic>; + 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"; + iommus = <&DIE_NODE(usb4_0_dart) 1>, + <&DIE_NODE(usb4_0_dart) 2>; + /* To be filled by the loader */ + apple,thunderbolt-drom = [00]; + }; + }; + DIE_NODE(dwc3_0): usb@702280000 { compatible = "apple,t6020-dwc3", "apple,t8103-dwc3"; reg = <0x7 0x02280000 0x0 0xcd00>, <0x7 0x0228cd00 0x0 0x3200>; @@ -521,6 +629,98 @@ status = "disabled"; }; + DIE_NODE(usb4_1_mbox): mailbox@b01108000 { + compatible = "apple,t6020-asc-mailbox", + "apple,asc-mailbox-v4"; + reg = <0xb 0x01108000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = , + , + , + ; + interrupt-names = "send-empty", "send-not-empty", + "recv-empty", "recv-not-empty"; + #mbox-cells = <0>; + }; + + DIE_NODE(usb4_1_acio): cio@b01ac0000 { + compatible = "apple,t6020-usb4-acio", "apple,t8103-usb4-acio"; + reg = <0xb 0x01ac0000 0x0 0xc000>, + <0xb 0x01080000 0x0 0x20000>; + reg-names = "rc", "sram"; + + mboxes = <&DIE_NODE(usb4_1_mbox)>; + resets = <&DIE_NODE(cio_reset) 1>; + + power-domains = <&DIE_NODE(ps_atc1_cio)>, + <&DIE_NODE(ps_atc1_cio_pcie)>, + <&DIE_NODE(ps_atc1_cio_usb)>; + + thunderbolt-switch; + + pinctrl-0 = <&DIE_NODE(acio1_pins)>; + pinctrl-names = "default"; + + #address-cells = <1>; + #size-cells = <1>; + ranges = <0x0 0xb 0x01000000 0x1000000>; + nonposted-mmio; + + DIE_NODE(usb4_1_dart): iommu@a80000 { + compatible = "apple,t6020-dart", "apple,t8110-dart"; + reg = <0xa80000 0x4000>; + interrupt-parent = <&aic>; + interrupts = ; + #iommu-cells = <1>; + }; + + DIE_NODE(usb4_1_nhi): nhi@f00000 { + reg = <0xf00000 0x100000>, + <0xdb0000 0x30004>; + reg-names = "nhi", "pdf"; + compatible = "apple,t6020-usb4-nhi", + "apple,t8103-usb4-nhi"; + interrupt-parent = <&aic>; + 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"; + iommus = <&DIE_NODE(usb4_1_dart) 1>, + <&DIE_NODE(usb4_1_dart) 2>; + /* To be filled by the loader */ + apple,thunderbolt-drom = [00]; + }; + }; + DIE_NODE(dwc3_1): usb@b02280000 { compatible = "apple,t6020-dwc3", "apple,t8103-dwc3"; reg = <0xb 0x02280000 0x0 0xcd00>, <0xb 0x0228cd00 0x0 0x3200>; @@ -582,6 +782,98 @@ status = "disabled"; }; + DIE_NODE(usb4_2_mbox): mailbox@f01108000 { + compatible = "apple,t6020-asc-mailbox", + "apple,asc-mailbox-v4"; + reg = <0xf 0x01108000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = , + , + , + ; + interrupt-names = "send-empty", "send-not-empty", + "recv-empty", "recv-not-empty"; + #mbox-cells = <0>; + }; + + DIE_NODE(usb4_2_acio): cio@f01ac0000 { + compatible = "apple,t6020-usb4-acio", "apple,t8103-usb4-acio"; + reg = <0xf 0x01ac0000 0x0 0xc000>, + <0xf 0x01080000 0x0 0x20000>; + reg-names = "rc", "sram"; + + mboxes = <&DIE_NODE(usb4_2_mbox)>; + resets = <&DIE_NODE(cio_reset) 2>; + + power-domains = <&DIE_NODE(ps_atc2_cio)>, + <&DIE_NODE(ps_atc2_cio_pcie)>, + <&DIE_NODE(ps_atc2_cio_usb)>; + + thunderbolt-switch; + + pinctrl-0 = <&DIE_NODE(acio2_pins)>; + pinctrl-names = "default"; + + #address-cells = <1>; + #size-cells = <1>; + ranges = <0x0 0xf 0x01000000 0x1000000>; + nonposted-mmio; + + DIE_NODE(usb4_2_dart): iommu@a80000 { + compatible = "apple,t6020-dart", "apple,t8110-dart"; + reg = <0xa80000 0x4000>; + interrupt-parent = <&aic>; + interrupts = ; + #iommu-cells = <1>; + }; + + DIE_NODE(usb4_2_nhi): nhi@f00000 { + reg = <0xf00000 0x100000>, + <0xdb0000 0x30004>; + reg-names = "nhi", "pdf"; + compatible = "apple,t6020-usb4-nhi", + "apple,t8103-usb4-nhi"; + interrupt-parent = <&aic>; + 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"; + iommus = <&DIE_NODE(usb4_2_dart) 1>, + <&DIE_NODE(usb4_2_dart) 2>; + /* To be filled by the loader */ + apple,thunderbolt-drom = [00]; + }; + }; + DIE_NODE(dwc3_2): usb@f02280000 { compatible = "apple,t6020-dwc3", "apple,t8103-dwc3"; reg = <0xf 0x02280000 0x0 0xcd00>, <0xf 0x0228cd00 0x0 0x3200>; @@ -643,6 +935,98 @@ status = "disabled"; }; + DIE_NODE(usb4_3_mbox): mailbox@1301108000 { + compatible = "apple,t6020-asc-mailbox", + "apple,asc-mailbox-v4"; + reg = <0x13 0x01108000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = , + , + , + ; + interrupt-names = "send-empty", "send-not-empty", + "recv-empty", "recv-not-empty"; + #mbox-cells = <0>; + }; + + DIE_NODE(usb4_3_acio): cio@1301ac0000 { + compatible = "apple,t6020-usb4-acio", "apple,t8103-usb4-acio"; + reg = <0x13 0x01ac0000 0x0 0xc000>, + <0x13 0x01080000 0x0 0x20000>; + reg-names = "rc", "sram"; + + mboxes = <&DIE_NODE(usb4_3_mbox)>; + resets = <&DIE_NODE(cio_reset) 3>; + + power-domains = <&DIE_NODE(ps_atc3_cio)>, + <&DIE_NODE(ps_atc3_cio_pcie)>, + <&DIE_NODE(ps_atc3_cio_usb)>; + + thunderbolt-switch; + + pinctrl-0 = <&DIE_NODE(acio3_pins)>; + pinctrl-names = "default"; + + #address-cells = <1>; + #size-cells = <1>; + ranges = <0x0 0x13 0x01000000 0x1000000>; + nonposted-mmio; + + DIE_NODE(usb4_3_dart): iommu@a80000 { + compatible = "apple,t6020-dart", "apple,t8110-dart"; + reg = <0xa80000 0x4000>; + interrupt-parent = <&aic>; + interrupts = ; + #iommu-cells = <1>; + }; + + DIE_NODE(usb4_3_nhi): nhi@f00000 { + reg = <0xf00000 0x100000>, + <0xdb0000 0x30004>; + reg-names = "nhi", "pdf"; + compatible = "apple,t6020-usb4-nhi", + "apple,t8103-usb4-nhi"; + interrupt-parent = <&aic>; + 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"; + iommus = <&DIE_NODE(usb4_3_dart) 1>, + <&DIE_NODE(usb4_3_dart) 2>; + /* To be filled by the loader */ + apple,thunderbolt-drom = [00]; + }; + }; + DIE_NODE(dwc3_3): usb@1302280000 { compatible = "apple,t6020-dwc3", "apple,t8103-dwc3"; reg = <0x13 0x02280000 0x0 0xcd00>, <0x13 0x0228cd00 0x0 0x3200>; From 504f94b0f448c7e96deffd78b4eb1bd51b41ae43 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Mon, 24 Aug 2026 18:07:56 +0200 Subject: [PATCH 31/33] arm64: dts: apple: t602x: Specify aperture for USB4 NHIs depends on "iommu: apple-dart: Support specifying the DMA aperture in the DT" series. Signed-off-by: Janne Grunau --- arch/arm64/boot/dts/apple/t602x-dieX.dtsi | 24 +++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/arch/arm64/boot/dts/apple/t602x-dieX.dtsi b/arch/arm64/boot/dts/apple/t602x-dieX.dtsi index 68adb70733807b..6cd6a58d1e7594 100644 --- a/arch/arm64/boot/dts/apple/t602x-dieX.dtsi +++ b/arch/arm64/boot/dts/apple/t602x-dieX.dtsi @@ -518,7 +518,7 @@ reg = <0xa80000 0x4000>; interrupt-parent = <&aic>; interrupts = ; - #iommu-cells = <1>; + #iommu-cells = <5>; }; DIE_NODE(usb4_0_nhi): nhi@f00000 { @@ -561,8 +561,8 @@ "rxring3", "rxring4", "rxring5", "rxring6", "rxring7", "rxring8", "rxring9", "rxring10", "rxring11"; - iommus = <&DIE_NODE(usb4_0_dart) 1>, - <&DIE_NODE(usb4_0_dart) 2>; + iommus = <&DIE_NODE(usb4_0_dart) 1 0x100 0x0 0x10 0x0>, + <&DIE_NODE(usb4_0_dart) 2 0x100 0x0 0x10 0x0>; /* To be filled by the loader */ apple,thunderbolt-drom = [00]; }; @@ -671,7 +671,7 @@ reg = <0xa80000 0x4000>; interrupt-parent = <&aic>; interrupts = ; - #iommu-cells = <1>; + #iommu-cells = <5>; }; DIE_NODE(usb4_1_nhi): nhi@f00000 { @@ -714,8 +714,8 @@ "rxring3", "rxring4", "rxring5", "rxring6", "rxring7", "rxring8", "rxring9", "rxring10", "rxring11"; - iommus = <&DIE_NODE(usb4_1_dart) 1>, - <&DIE_NODE(usb4_1_dart) 2>; + iommus = <&DIE_NODE(usb4_1_dart) 1 0x100 0x0 0x10 0x0>, + <&DIE_NODE(usb4_1_dart) 2 0x100 0x0 0x10 0x0>; /* To be filled by the loader */ apple,thunderbolt-drom = [00]; }; @@ -824,7 +824,7 @@ reg = <0xa80000 0x4000>; interrupt-parent = <&aic>; interrupts = ; - #iommu-cells = <1>; + #iommu-cells = <5>; }; DIE_NODE(usb4_2_nhi): nhi@f00000 { @@ -867,8 +867,8 @@ "rxring3", "rxring4", "rxring5", "rxring6", "rxring7", "rxring8", "rxring9", "rxring10", "rxring11"; - iommus = <&DIE_NODE(usb4_2_dart) 1>, - <&DIE_NODE(usb4_2_dart) 2>; + iommus = <&DIE_NODE(usb4_2_dart) 1 0x100 0x0 0x10 0x0>, + <&DIE_NODE(usb4_2_dart) 2 0x100 0x0 0x10 0x0>; /* To be filled by the loader */ apple,thunderbolt-drom = [00]; }; @@ -977,7 +977,7 @@ reg = <0xa80000 0x4000>; interrupt-parent = <&aic>; interrupts = ; - #iommu-cells = <1>; + #iommu-cells = <5>; }; DIE_NODE(usb4_3_nhi): nhi@f00000 { @@ -1020,8 +1020,8 @@ "rxring3", "rxring4", "rxring5", "rxring6", "rxring7", "rxring8", "rxring9", "rxring10", "rxring11"; - iommus = <&DIE_NODE(usb4_3_dart) 1>, - <&DIE_NODE(usb4_3_dart) 2>; + iommus = <&DIE_NODE(usb4_3_dart) 1 0x100 0x0 0x10 0x0>, + <&DIE_NODE(usb4_3_dart) 2 0x100 0x0 0x10 0x0>; /* To be filled by the loader */ apple,thunderbolt-drom = [00]; }; From 27526f216207989fd5509b7cde6975ba0cf72626 Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Wed, 19 Nov 2025 12:53:58 +0200 Subject: [PATCH 32/33] thunderbolt: Wait for tb_domain_release() to complete when driver is removed We should not call nhi_shutdown() before the domain structure and the control channel rings are completely released. Otherwise we might release resources like the nhi->msix_ida that are still referenced in tb_domain_release(). For this reason wait for the tb_domain_release() to be completed before continuing to nhi_shutdown() and eventually releasing of the rest of the data structures. Signed-off-by: Mika Westerberg --- drivers/thunderbolt/domain.c | 3 +++ drivers/thunderbolt/nhi.c | 1 + include/linux/thunderbolt.h | 2 ++ 3 files changed, 6 insertions(+) diff --git a/drivers/thunderbolt/domain.c b/drivers/thunderbolt/domain.c index 8e45fc57bc2d4e..162cbb0b22a1b1 100644 --- a/drivers/thunderbolt/domain.c +++ b/drivers/thunderbolt/domain.c @@ -319,12 +319,15 @@ const struct bus_type tb_bus_type = { static void tb_domain_release(struct device *dev) { struct tb *tb = container_of(dev, struct tb, dev); + struct tb_nhi *nhi = tb->nhi; tb_ctl_free(tb->ctl); destroy_workqueue(tb->wq); ida_free(&tb_domain_ida, tb->index); mutex_destroy(&tb->lock); kfree(tb); + + complete(&nhi->domain_released); } const struct device_type tb_domain_type = { diff --git a/drivers/thunderbolt/nhi.c b/drivers/thunderbolt/nhi.c index 9ecfdfee23d050..64b9191d16a532 100644 --- a/drivers/thunderbolt/nhi.c +++ b/drivers/thunderbolt/nhi.c @@ -1262,6 +1262,7 @@ int nhi_probe(struct tb_nhi *nhi) * activated. Do a proper shutdown. */ tb_domain_put(tb); + wait_for_completion(&nhi->domain_released); nhi_shutdown(nhi); return res; } diff --git a/include/linux/thunderbolt.h b/include/linux/thunderbolt.h index 27704d5e1dc378..2b549c006e3334 100644 --- a/include/linux/thunderbolt.h +++ b/include/linux/thunderbolt.h @@ -497,6 +497,7 @@ static inline struct tb_xdomain *tb_service_parent(struct tb_service *svc) * MSI-X is used. * @hop_count: Number of rings (end point hops) supported by NHI. * @quirks: NHI specific quirks if any + * @domain_released: Completed when domain has been fully released */ struct tb_nhi { spinlock_t lock; @@ -511,6 +512,7 @@ struct tb_nhi { struct work_struct interrupt_work; u32 hop_count; unsigned long quirks; + struct completion domain_released; }; /** From 2daeae4fb0a91701c30cff53e23d42e23a9ae3ec Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Fri, 27 Feb 2026 19:51:45 +0200 Subject: [PATCH 33/33] thunderbolt / net: Let the service drivers configure interrupt throttling Instead of the core driver programming fixed value for throttling let the service drivers to specify the interval if they need this. Signed-off-by: Mika Westerberg --- drivers/net/thunderbolt/main.c | 4 +++ drivers/thunderbolt/dma_test.c | 5 +++ drivers/thunderbolt/nhi.c | 57 ++++++++++++++++++---------------- drivers/thunderbolt/nhi.h | 1 - drivers/thunderbolt/nhi_regs.h | 3 +- include/linux/thunderbolt.h | 5 +++ 6 files changed, 47 insertions(+), 28 deletions(-) diff --git a/drivers/net/thunderbolt/main.c b/drivers/net/thunderbolt/main.c index fb569c0abf8e62..95f6de69bc2ede 100644 --- a/drivers/net/thunderbolt/main.c +++ b/drivers/net/thunderbolt/main.c @@ -34,6 +34,7 @@ #define TBNET_RING_SIZE 256 #define TBNET_LOGIN_RETRIES 60 #define TBNET_LOGOUT_RETRIES 10 +#define TBNET_THROTTLING 128000 #define TBNET_E2E BIT(0) #define TBNET_MATCH_FRAGS_ID BIT(1) #define TBNET_64K_FRAMES BIT(2) @@ -976,6 +977,9 @@ static int tbnet_open(struct net_device *dev) } net->rx_ring.ring = ring; + tb_ring_throttling(net->tx_ring.ring, TBNET_THROTTLING); + tb_ring_throttling(net->rx_ring.ring, TBNET_THROTTLING); + napi_enable(&net->napi); start_login(net); diff --git a/drivers/thunderbolt/dma_test.c b/drivers/thunderbolt/dma_test.c index b4aa79d482a0b9..8cfe4569997fe5 100644 --- a/drivers/thunderbolt/dma_test.c +++ b/drivers/thunderbolt/dma_test.c @@ -157,6 +157,8 @@ static int dma_test_start_rings(struct dma_test *dt) dt->tx_ring = ring; e2e_tx_hop = ring->hop; + tb_ring_throttling(ring, 128000); + ret = tb_xdomain_alloc_out_hopid(xd, -1); if (ret < 0) { dma_test_free_rings(dt); @@ -164,6 +166,7 @@ static int dma_test_start_rings(struct dma_test *dt) } dt->tx_hopid = ret; + } if (dt->packets_to_receive) { @@ -182,6 +185,8 @@ static int dma_test_start_rings(struct dma_test *dt) dt->rx_ring = ring; + tb_ring_throttling(ring, 128000); + ret = tb_xdomain_alloc_in_hopid(xd, -1); if (ret < 0) { dma_test_free_rings(dt); diff --git a/drivers/thunderbolt/nhi.c b/drivers/thunderbolt/nhi.c index 64b9191d16a532..8d786e5a904d0b 100644 --- a/drivers/thunderbolt/nhi.c +++ b/drivers/thunderbolt/nhi.c @@ -82,7 +82,7 @@ static void ring_interrupt_active(struct tb_ring *ring, bool active) u32 old, new; if (ring->irq > 0) { - u32 step, shift, ivr, misc; + u32 step, shift, ivr, misc, itr; void __iomem *ivr_base; int auto_clear_bit; int index; @@ -120,6 +120,12 @@ static void ring_interrupt_active(struct tb_ring *ring, bool active) if (active) ivr |= ring->vector << shift; iowrite32(ivr, ivr_base + step); + + /* Throttling is specified in 256ns increments */ + itr = DIV_ROUND_UP(ring->interval_nsec, 256); + itr &= REG_INT_THROTTLING_RATE_INTERVAL_MASK; + iowrite32(itr, ring->nhi->iobase + REG_INT_THROTTLING_RATE + + ring->vector * 4); } old = ioread32(ring->nhi->iobase + reg); @@ -858,6 +864,26 @@ void tb_ring_free(struct tb_ring *ring) } EXPORT_SYMBOL_GPL(tb_ring_free); +/** + * tb_ring_throttling() - Configure throttling for ring interrupt + * @ring: Ring to configure + * @interval_nsec: Interval counter for moderation (in ns), %0 disables + * + * Enables or disables ring interrupt throttling. The ring must be + * stopped for this to be called. Granularity is 256 ns. + * + * Return: %0 on success, negative errno otherwise. + */ +int tb_ring_throttling(struct tb_ring *ring, unsigned int interval_nsec) +{ + guard(spinlock_irqsave)(&ring->lock); + if (WARN_ON_ONCE(ring->running)) + return -EBUSY; + ring->interval_nsec = interval_nsec; + return 0; +} +EXPORT_SYMBOL_GPL(tb_ring_throttling); + /** * nhi_mailbox_cmd() - Send a command through NHI mailbox * @nhi: Pointer to the NHI structure @@ -1033,22 +1059,6 @@ static int nhi_poweroff_noirq(struct device *dev) return __nhi_suspend_noirq(dev, wakeup); } -void nhi_enable_int_throttling(struct tb_nhi *nhi) -{ - /* Throttling is specified in 256ns increments */ - u32 throttle = DIV_ROUND_UP(128 * NSEC_PER_USEC, 256); - unsigned int i; - - /* - * Configure interrupt throttling for all vectors even if we - * only use few. - */ - for (i = 0; i < MSIX_MAX_VECS; i++) { - u32 reg = REG_INT_THROTTLING_RATE + i * 4; - iowrite32(throttle, nhi->iobase + reg); - } -} - static int nhi_resume_noirq(struct device *dev) { struct tb *tb = dev_get_drvdata(dev); @@ -1062,13 +1072,10 @@ static int nhi_resume_noirq(struct device *dev) */ if ((nhi->ops->is_present && !nhi->ops->is_present(nhi))) { nhi->going_away = true; - } else { - if (nhi->ops && nhi->ops->resume_noirq) { - ret = nhi->ops->resume_noirq(nhi); - if (ret) - return ret; - } - nhi_enable_int_throttling(tb->nhi); + } else if (nhi->ops && nhi->ops->resume_noirq) { + ret = nhi->ops->resume_noirq(nhi); + if (ret) + return ret; } return tb_domain_resume_noirq(tb); @@ -1126,7 +1133,6 @@ static int nhi_runtime_resume(struct device *dev) return ret; } - nhi_enable_int_throttling(nhi); return tb_domain_runtime_resume(tb); } @@ -1226,7 +1232,6 @@ int nhi_probe(struct tb_nhi *nhi) /* In case someone left them on. */ nhi_disable_interrupts(nhi); - nhi_enable_int_throttling(nhi); if (nhi->ops && nhi->ops->init_interrupts) { res = nhi->ops->init_interrupts(nhi); diff --git a/drivers/thunderbolt/nhi.h b/drivers/thunderbolt/nhi.h index 50fcf330eb6f32..6c1c35362fe975 100644 --- a/drivers/thunderbolt/nhi.h +++ b/drivers/thunderbolt/nhi.h @@ -29,7 +29,6 @@ enum nhi_mailbox_cmd { int nhi_mailbox_cmd(struct tb_nhi *nhi, enum nhi_mailbox_cmd cmd, u32 data); enum nhi_fw_mode nhi_mailbox_mode(struct tb_nhi *nhi); -void nhi_enable_int_throttling(struct tb_nhi *nhi); void nhi_disable_interrupts(struct tb_nhi *nhi); void nhi_interrupt_work(struct work_struct *work); irqreturn_t nhi_msi(int irq, void *data); diff --git a/drivers/thunderbolt/nhi_regs.h b/drivers/thunderbolt/nhi_regs.h index cf5222bee97131..d6a197fabc74a2 100644 --- a/drivers/thunderbolt/nhi_regs.h +++ b/drivers/thunderbolt/nhi_regs.h @@ -101,7 +101,8 @@ struct ring_desc { #define REG_RING_INTERRUPT_MASK_CLEAR_BASE 0x38208 -#define REG_INT_THROTTLING_RATE 0x38c00 +#define REG_INT_THROTTLING_RATE 0x38c00 +#define REG_INT_THROTTLING_RATE_INTERVAL_MASK GENMASK(15, 0) /* Interrupt Vector Allocation */ #define REG_INT_VEC_ALLOC_BASE 0x38c40 diff --git a/include/linux/thunderbolt.h b/include/linux/thunderbolt.h index 2b549c006e3334..56ed4a55c95aff 100644 --- a/include/linux/thunderbolt.h +++ b/include/linux/thunderbolt.h @@ -541,6 +541,8 @@ struct tb_nhi { * @start_poll: Called when ring interrupt is triggered to start * polling. Passing %NULL keeps the ring in interrupt mode. * @poll_data: Data passed to @start_poll + * @interval_nsec: Interval counter if interrupt throttling is to be + * used with this ring (in ns) */ struct tb_ring { spinlock_t lock; @@ -564,6 +566,7 @@ struct tb_ring { u16 eof_mask; void (*start_poll)(void *data); void *poll_data; + unsigned int interval_nsec; }; /* Leave ring interrupt enabled on suspend */ @@ -676,6 +679,8 @@ static inline int tb_ring_tx(struct tb_ring *ring, struct ring_frame *frame) struct ring_frame *tb_ring_poll(struct tb_ring *ring); void tb_ring_poll_complete(struct tb_ring *ring); +int tb_ring_throttling(struct tb_ring *ring, unsigned int interval_nsec); + /** * tb_ring_dma_device() - Return device used for DMA mapping * @ring: Ring whose DMA device is retrieved