* [PATCH 01/19] dt-bindings: usb: Add thunderbolt-switch property
2026-08-30 20:19 [PATCH 00/19] Initial USB4/Thunderbolt support for Apple M1/M2/M3 SoCs Sven Peter
@ 2026-08-30 20:19 ` Sven Peter
2026-08-30 20:19 ` [PATCH 02/19] usb: typec: Add thunderbolt switch Sven Peter
` (18 subsequent siblings)
19 siblings, 0 replies; 32+ messages in thread
From: Sven Peter @ 2026-08-30 20:19 UTC (permalink / raw)
To: Greg Kroah-Hartman, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Heikki Krogerus, Janne Grunau, Neal Gompa,
Andreas Noever, Mika Westerberg, Yehezkel Bernat, Philipp Zabel
Cc: Konrad Dybcio, linux-usb, devicetree, linux-kernel, asahi,
linux-arm-kernel, Sven Peter
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 <sven@kernel.org>
---
Documentation/devicetree/bindings/usb/usb-switch.yaml | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/Documentation/devicetree/bindings/usb/usb-switch.yaml b/Documentation/devicetree/bindings/usb/usb-switch.yaml
index f77731493dc4..5d125fbb252d 100644
--- a/Documentation/devicetree/bindings/usb/usb-switch.yaml
+++ b/Documentation/devicetree/bindings/usb/usb-switch.yaml
@@ -25,4 +25,10 @@ properties:
description: Possible handler of SuperSpeed signals retiming
type: boolean
+ thunderbolt-switch:
+ description:
+ Possible handler of USB4/Thunderbolt host router activation and cable
+ information updates
+ type: boolean
+
additionalProperties: true
--
2.55.0
^ permalink raw reply related [flat|nested] 32+ messages in thread* [PATCH 02/19] usb: typec: Add thunderbolt switch
2026-08-30 20:19 [PATCH 00/19] Initial USB4/Thunderbolt support for Apple M1/M2/M3 SoCs Sven Peter
2026-08-30 20:19 ` [PATCH 01/19] dt-bindings: usb: Add thunderbolt-switch property Sven Peter
@ 2026-08-30 20:19 ` Sven Peter
2026-09-01 11:17 ` Heikki Krogerus
2026-08-30 20:19 ` [PATCH 03/19] usb: typec: tipd: Hook up Thunderbolt switch for CD321x Sven Peter
` (17 subsequent siblings)
19 siblings, 1 reply; 32+ messages in thread
From: Sven Peter @ 2026-08-30 20:19 UTC (permalink / raw)
To: Greg Kroah-Hartman, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Heikki Krogerus, Janne Grunau, Neal Gompa,
Andreas Noever, Mika Westerberg, Yehezkel Bernat, Philipp Zabel
Cc: Konrad Dybcio, linux-usb, devicetree, linux-kernel, asahi,
linux-arm-kernel, Sven Peter
On Apple Silicon the USB4/Thunderbolt host router can only be brought
up after the Type-C PHY has been configured and requires cable details
known only to the Type-C port controller.
The existing orientation switch only forwards the cable orientation.
The mode switch also forwards most of the required details but
represents devices which configure or route the Type-C signal path. The
ACIO host router is a consumer of that path and has additional ordering
requirements: it has to be started after the PHY has been configured
and stopped again before the PHY is disabled.
The mode switch code calls all handlers in the same order both when
entering and leaving a mode and cannot express this reverse teardown
order. Treating ACIO as another mode switch could therefore leave the
NHI active after its PHY has been disabled, which can result in an
asynchronous SError or a SoC watchdog reset.
Add a separate Thunderbolt switch which Type-C port drivers can use to
forward the negotiated Thunderbolt or USB4 cable details and explicitly
order host router setup and teardown around the mode switch. This is
similar to a USB role switch: each connection has exactly one host
router and the callback controls the lifetime of that functional
controller instead of configuring a signal mux.
Signed-off-by: Sven Peter <sven@kernel.org>
---
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 9b908c46bd7d..8339846ba355 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 58f0f28b6dc8..a2c92cab1211 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 aa9ebb7e2fe0..0726407bb624 100644
--- a/include/linux/usb/typec_mux.h
+++ b/include/linux/usb/typec_mux.h
@@ -6,6 +6,7 @@
#include <linux/err.h>
#include <linux/property.h>
#include <linux/usb/typec.h>
+#include <linux/usb/typec_tbt.h>
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 */
--
2.55.0
^ permalink raw reply related [flat|nested] 32+ messages in thread* Re: [PATCH 02/19] usb: typec: Add thunderbolt switch
2026-08-30 20:19 ` [PATCH 02/19] usb: typec: Add thunderbolt switch Sven Peter
@ 2026-09-01 11:17 ` Heikki Krogerus
2026-09-01 18:53 ` Sven Peter
0 siblings, 1 reply; 32+ messages in thread
From: Heikki Krogerus @ 2026-09-01 11:17 UTC (permalink / raw)
To: Sven Peter
Cc: Greg Kroah-Hartman, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Janne Grunau, Neal Gompa, Andreas Noever,
Mika Westerberg, Yehezkel Bernat, Philipp Zabel, Konrad Dybcio,
linux-usb, devicetree, linux-kernel, asahi, linux-arm-kernel
Hi Sven,
On Sun, Aug 30, 2026 at 10:19:20PM +0200, Sven Peter wrote:
> On Apple Silicon the USB4/Thunderbolt host router can only be brought
> up after the Type-C PHY has been configured and requires cable details
> known only to the Type-C port controller.
>
> The existing orientation switch only forwards the cable orientation.
> The mode switch also forwards most of the required details but
> represents devices which configure or route the Type-C signal path. The
> ACIO host router is a consumer of that path and has additional ordering
> requirements: it has to be started after the PHY has been configured
> and stopped again before the PHY is disabled.
>
> The mode switch code calls all handlers in the same order both when
> entering and leaving a mode and cannot express this reverse teardown
> order. Treating ACIO as another mode switch could therefore leave the
> NHI active after its PHY has been disabled, which can result in an
> asynchronous SError or a SoC watchdog reset.
>
> Add a separate Thunderbolt switch which Type-C port drivers can use to
> forward the negotiated Thunderbolt or USB4 cable details and explicitly
> order host router setup and teardown around the mode switch. This is
> similar to a USB role switch: each connection has exactly one host
> router and the callback controls the lifetime of that functional
> controller instead of configuring a signal mux.
You are bypassing and duplicating things that are already supported in
the typec bus.
Your typec port driver probable does not register the partner altmode
that you need, so you just need to fix that. After that you should be
able to get the details you need simply by registering a typec bus
notifier in the thunderbolt driver. We can add a helper for that if
needed - the DRM subsystem will use the same notifier with the DP
altmode soon.
USB4 is meant to be handled as an altmode too, but it looks like it is
not supported yet - I need to check the status with that. This may
still need to be fixed, but that should not be a problem.
Thanks,
--
heikki
^ permalink raw reply [flat|nested] 32+ messages in thread
* Re: [PATCH 02/19] usb: typec: Add thunderbolt switch
2026-09-01 11:17 ` Heikki Krogerus
@ 2026-09-01 18:53 ` Sven Peter
0 siblings, 0 replies; 32+ messages in thread
From: Sven Peter @ 2026-09-01 18:53 UTC (permalink / raw)
To: Heikki Krogerus
Cc: Greg Kroah-Hartman, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Janne Grunau, Neal Gompa, Andreas Noever,
Mika Westerberg, Yehezkel Bernat, Philipp Zabel, Konrad Dybcio,
linux-usb, devicetree, linux-kernel, asahi, linux-arm-kernel
Hi Heikki,
On 9/1/26 13:17, Heikki Krogerus wrote:
> Hi Sven,
>
> On Sun, Aug 30, 2026 at 10:19:20PM +0200, Sven Peter wrote:
>> On Apple Silicon the USB4/Thunderbolt host router can only be brought
>> up after the Type-C PHY has been configured and requires cable details
>> known only to the Type-C port controller.
>>
>> The existing orientation switch only forwards the cable orientation.
>> The mode switch also forwards most of the required details but
>> represents devices which configure or route the Type-C signal path. The
>> ACIO host router is a consumer of that path and has additional ordering
>> requirements: it has to be started after the PHY has been configured
>> and stopped again before the PHY is disabled.
>>
>> The mode switch code calls all handlers in the same order both when
>> entering and leaving a mode and cannot express this reverse teardown
>> order. Treating ACIO as another mode switch could therefore leave the
>> NHI active after its PHY has been disabled, which can result in an
>> asynchronous SError or a SoC watchdog reset.
>>
>> Add a separate Thunderbolt switch which Type-C port drivers can use to
>> forward the negotiated Thunderbolt or USB4 cable details and explicitly
>> order host router setup and teardown around the mode switch. This is
>> similar to a USB role switch: each connection has exactly one host
>> router and the callback controls the lifetime of that functional
>> controller instead of configuring a signal mux.
> You are bypassing and duplicating things that are already supported in
> the typec bus.
>
> Your typec port driver probable does not register the partner altmode
> that you need, so you just need to fix that. After that you should be
> able to get the details you need simply by registering a typec bus
> notifier in the thunderbolt driver. We can add a helper for that if
> needed - the DRM subsystem will use the same notifier with the DP
> altmode soon.
Thanks for the pointer, I think that can indeed work out just fine. The
bus notifier isn't enough though, I need a notification specifically
when typec_altmode_update_active() happens (and not when the partner is
registered as a possible altmode) and that only triggers a sysfs_notify
and a kobject_uevent unless I'm missing something. Adding a simple
notifier call chain should be possible there though.
>
> USB4 is meant to be handled as an altmode too, but it looks like it is
> not supported yet - I need to check the status with that. This may
> still need to be fixed, but that should not be a problem.
It isn't, but that indeed should not be hard to add.
Best,
Sven
^ permalink raw reply [flat|nested] 32+ messages in thread
* [PATCH 03/19] usb: typec: tipd: Hook up Thunderbolt switch for CD321x
2026-08-30 20:19 [PATCH 00/19] Initial USB4/Thunderbolt support for Apple M1/M2/M3 SoCs Sven Peter
2026-08-30 20:19 ` [PATCH 01/19] dt-bindings: usb: Add thunderbolt-switch property Sven Peter
2026-08-30 20:19 ` [PATCH 02/19] usb: typec: Add thunderbolt switch Sven Peter
@ 2026-08-30 20:19 ` Sven Peter
2026-08-30 20:19 ` [PATCH 04/19] dt-bindings: thunderbolt: Add Apple USB4/Thunderbolt NHI Sven Peter
` (16 subsequent siblings)
19 siblings, 0 replies; 32+ messages in thread
From: Sven Peter @ 2026-08-30 20:19 UTC (permalink / raw)
To: Greg Kroah-Hartman, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Heikki Krogerus, Janne Grunau, Neal Gompa,
Andreas Noever, Mika Westerberg, Yehezkel Bernat, Philipp Zabel
Cc: Konrad Dybcio, linux-usb, devicetree, linux-kernel, asahi,
linux-arm-kernel, Sven Peter
Look up the Thunderbolt switch for CD321x ports and forward cable details
whenever a Thunderbolt or USB4 connection comes up or goes away.
Signed-off-by: Sven Peter <sven@kernel.org>
---
drivers/usb/typec/tipd/core.c | 37 +++++++++++++++++++++++++++++++++++++
1 file changed, 37 insertions(+)
diff --git a/drivers/usb/typec/tipd/core.c b/drivers/usb/typec/tipd/core.c
index 522f56742aa9..5016f9dba956 100644
--- a/drivers/usb/typec/tipd/core.c
+++ b/drivers/usb/typec/tipd/core.c
@@ -216,6 +216,8 @@ 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;
struct usb_pd_identity cur_partner_identity;
@@ -677,6 +679,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)
@@ -684,6 +689,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;
@@ -723,6 +729,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;
@@ -738,6 +745,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;
@@ -752,12 +766,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);
}
@@ -771,6 +793,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);
@@ -821,6 +846,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;
@@ -1328,6 +1354,12 @@ cd321x_register_port(struct tps6598x *tps, struct fwnode_handle *fwnode)
goto err_unregister_altmodes;
}
+ 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;
@@ -1335,6 +1367,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);
@@ -1356,6 +1391,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);
--
2.55.0
^ permalink raw reply related [flat|nested] 32+ messages in thread* [PATCH 04/19] dt-bindings: thunderbolt: Add Apple USB4/Thunderbolt NHI
2026-08-30 20:19 [PATCH 00/19] Initial USB4/Thunderbolt support for Apple M1/M2/M3 SoCs Sven Peter
` (2 preceding siblings ...)
2026-08-30 20:19 ` [PATCH 03/19] usb: typec: tipd: Hook up Thunderbolt switch for CD321x Sven Peter
@ 2026-08-30 20:19 ` Sven Peter
2026-08-30 20:19 ` [PATCH 05/19] dt-bindings: thunderbolt: Add Apple USB4/Thunderbolt ACIO block Sven Peter
` (15 subsequent siblings)
19 siblings, 0 replies; 32+ messages in thread
From: Sven Peter @ 2026-08-30 20:19 UTC (permalink / raw)
To: Greg Kroah-Hartman, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Heikki Krogerus, Janne Grunau, Neal Gompa,
Andreas Noever, Mika Westerberg, Yehezkel Bernat, Philipp Zabel
Cc: Konrad Dybcio, linux-usb, devicetree, linux-kernel, asahi,
linux-arm-kernel, Sven Peter
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 <sven@kernel.org>
---
.../bindings/thunderbolt/apple,t8103-usb4-nhi.yaml | 151 +++++++++++++++++++++
MAINTAINERS | 2 +
2 files changed, 153 insertions(+)
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 000000000000..a80c8db3ef51
--- /dev/null
+++ b/Documentation/devicetree/bindings/thunderbolt/apple,t8103-usb4-nhi.yaml
@@ -0,0 +1,151 @@
+# 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 <sven@kernel.org>
+
+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: |
+ T8103 and T8112 use a single IOMMU for all DMA transactions.
+
+ 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 IOMMUs. 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 3a19da74d00c..bb26036a043f 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2658,6 +2658,7 @@ F: Documentation/devicetree/bindings/rtc/apple,smc-rtc.yaml
F: Documentation/devicetree/bindings/soc/apple/apple,t6000-pmgr-misc.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/watchdog/apple,wdt.yaml
F: Documentation/hwmon/macsmc-hwmon.rst
@@ -27215,6 +27216,7 @@ L: linux-usb@vger.kernel.org
S: Maintained
T: git git://git.kernel.org/pub/scm/linux/kernel/git/westeri/thunderbolt.git
F: Documentation/admin-guide/thunderbolt.rst
+F: Documentation/devicetree/bindings/thunderbolt/
F: drivers/thunderbolt/
F: include/linux/thunderbolt.h
--
2.55.0
^ permalink raw reply related [flat|nested] 32+ messages in thread* [PATCH 05/19] dt-bindings: thunderbolt: Add Apple USB4/Thunderbolt ACIO block
2026-08-30 20:19 [PATCH 00/19] Initial USB4/Thunderbolt support for Apple M1/M2/M3 SoCs Sven Peter
` (3 preceding siblings ...)
2026-08-30 20:19 ` [PATCH 04/19] dt-bindings: thunderbolt: Add Apple USB4/Thunderbolt NHI Sven Peter
@ 2026-08-30 20:19 ` Sven Peter
2026-08-30 20:19 ` [PATCH 06/19] thunderbolt: Try reading host DROM from device tree first Sven Peter
` (14 subsequent siblings)
19 siblings, 0 replies; 32+ messages in thread
From: Sven Peter @ 2026-08-30 20:19 UTC (permalink / raw)
To: Greg Kroah-Hartman, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Heikki Krogerus, Janne Grunau, Neal Gompa,
Andreas Noever, Mika Westerberg, Yehezkel Bernat, Philipp Zabel
Cc: Konrad Dybcio, linux-usb, devicetree, linux-kernel, asahi,
linux-arm-kernel, Sven Peter
Each Type-C port with USB4/Thunderbolt support on Apple Silicon SoCs
comes with block called ACIO ("Apple Converged I/O"). It contains a
Cortex-M3 co-processor, the hardware blocks implementing the USB4 host
router, the Native Host Interface (NHI) and a DART IOMMU. 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 <sven@kernel.org>
---
.../thunderbolt/apple,t8103-usb4-acio.yaml | 218 +++++++++++++++++++++
MAINTAINERS | 1 +
2 files changed, 219 insertions(+)
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 000000000000..38496ce31d20
--- /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 <sven@kernel.org>
+
+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:
+ 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";
+ ranges = <0x0 0x5 0x01000000 0x1000000>;
+ #address-cells = <1>;
+ mboxes = <&usb4_1_mbox>;
+ nonposted-mmio;
+ pinctrl-0 = <&acio1_pins>;
+ pinctrl-names = "default";
+ power-domains = <&ps_atc1_cio>,
+ <&ps_atc1_cio_pcie>,
+ <&ps_atc1_cio_usb>;
+ resets = <&cio_reset 1>;
+ #size-cells = <1>;
+ thunderbolt-switch;
+
+ 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 bb26036a043f..c2c6e4541da7 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2658,6 +2658,7 @@ F: Documentation/devicetree/bindings/rtc/apple,smc-rtc.yaml
F: Documentation/devicetree/bindings/soc/apple/apple,t6000-pmgr-misc.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/watchdog/apple,wdt.yaml
--
2.55.0
^ permalink raw reply related [flat|nested] 32+ messages in thread* [PATCH 06/19] thunderbolt: Try reading host DROM from device tree first
2026-08-30 20:19 [PATCH 00/19] Initial USB4/Thunderbolt support for Apple M1/M2/M3 SoCs Sven Peter
` (4 preceding siblings ...)
2026-08-30 20:19 ` [PATCH 05/19] dt-bindings: thunderbolt: Add Apple USB4/Thunderbolt ACIO block Sven Peter
@ 2026-08-30 20:19 ` Sven Peter
2026-09-01 8:48 ` Mika Westerberg
2026-08-30 20:19 ` [PATCH 07/19] thunderbolt: Don't read the UID if we already know it Sven Peter
` (13 subsequent siblings)
19 siblings, 1 reply; 32+ messages in thread
From: Sven Peter @ 2026-08-30 20:19 UTC (permalink / raw)
To: Greg Kroah-Hartman, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Heikki Krogerus, Janne Grunau, Neal Gompa,
Andreas Noever, Mika Westerberg, Yehezkel Bernat, Philipp Zabel
Cc: Konrad Dybcio, linux-usb, devicetree, linux-kernel, asahi,
linux-arm-kernel, Sven Peter
On Apple Silicon SoCs the DROM is provided by a device tree property.
Try reading from that property first.
Signed-off-by: Sven Peter <sven@kernel.org>
---
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 2a13fa6888ba..389de7df84f6 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->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))
--
2.55.0
^ permalink raw reply related [flat|nested] 32+ messages in thread* Re: [PATCH 06/19] thunderbolt: Try reading host DROM from device tree first
2026-08-30 20:19 ` [PATCH 06/19] thunderbolt: Try reading host DROM from device tree first Sven Peter
@ 2026-09-01 8:48 ` Mika Westerberg
0 siblings, 0 replies; 32+ messages in thread
From: Mika Westerberg @ 2026-09-01 8:48 UTC (permalink / raw)
To: Sven Peter
Cc: Greg Kroah-Hartman, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Heikki Krogerus, Janne Grunau, Neal Gompa,
Andreas Noever, Mika Westerberg, Yehezkel Bernat, Philipp Zabel,
Konrad Dybcio, linux-usb, devicetree, linux-kernel, asahi,
linux-arm-kernel
Hi,
On Sun, Aug 30, 2026 at 10:19:24PM +0200, Sven Peter wrote:
> On Apple Silicon SoCs the DROM is provided by a device tree property.
> Try reading from that property first.
>
> Signed-off-by: Sven Peter <sven@kernel.org>
> ---
> 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 2a13fa6888ba..389de7df84f6 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
tb_drom_copy_property() - Copy DROM from ..
Ditto for the other kernel-doc comments. If it is static trivial function
you don't need to add them.
> */
> -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)
I think this should be split into two lines.
> {
> struct device *dev = sw->tb->nhi->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.
Like these, not really useful comments you can drop.
> + */
> +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))
>
> --
> 2.55.0
>
^ permalink raw reply [flat|nested] 32+ messages in thread
* [PATCH 07/19] thunderbolt: Don't read the UID if we already know it
2026-08-30 20:19 [PATCH 00/19] Initial USB4/Thunderbolt support for Apple M1/M2/M3 SoCs Sven Peter
` (5 preceding siblings ...)
2026-08-30 20:19 ` [PATCH 06/19] thunderbolt: Try reading host DROM from device tree first Sven Peter
@ 2026-08-30 20:19 ` Sven Peter
2026-08-30 20:19 ` [PATCH 08/19] thunderbolt: Allocate ring HopID before requesting the ring interrupt Sven Peter
` (12 subsequent siblings)
19 siblings, 0 replies; 32+ messages in thread
From: Sven Peter @ 2026-08-30 20:19 UTC (permalink / raw)
To: Greg Kroah-Hartman, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Heikki Krogerus, Janne Grunau, Neal Gompa,
Andreas Noever, Mika Westerberg, Yehezkel Bernat, Philipp Zabel
Cc: Konrad Dybcio, linux-usb, devicetree, linux-kernel, asahi,
linux-arm-kernel, Sven Peter
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 <sven@kernel.org>
---
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 404c0693df50..3f54a5ae56b0 100644
--- a/drivers/thunderbolt/switch.c
+++ b/drivers/thunderbolt/switch.c
@@ -2692,9 +2692,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 {
/*
--
2.55.0
^ permalink raw reply related [flat|nested] 32+ messages in thread* [PATCH 08/19] thunderbolt: Allocate ring HopID before requesting the ring interrupt
2026-08-30 20:19 [PATCH 00/19] Initial USB4/Thunderbolt support for Apple M1/M2/M3 SoCs Sven Peter
` (6 preceding siblings ...)
2026-08-30 20:19 ` [PATCH 07/19] thunderbolt: Don't read the UID if we already know it Sven Peter
@ 2026-08-30 20:19 ` Sven Peter
2026-08-30 20:19 ` [PATCH 09/19] thunderbolt: Add ring_interrupt_active to tb_nhi_ops Sven Peter
` (11 subsequent siblings)
19 siblings, 0 replies; 32+ messages in thread
From: Sven Peter @ 2026-08-30 20:19 UTC (permalink / raw)
To: Greg Kroah-Hartman, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Heikki Krogerus, Janne Grunau, Neal Gompa,
Andreas Noever, Mika Westerberg, Yehezkel Bernat, Philipp Zabel
Cc: Konrad Dybcio, linux-usb, devicetree, linux-kernel, asahi,
linux-arm-kernel, Sven Peter
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 <sven@kernel.org>
---
drivers/thunderbolt/nhi.c | 23 ++++++++++++++++-------
1 file changed, 16 insertions(+), 7 deletions(-)
diff --git a/drivers/thunderbolt/nhi.c b/drivers/thunderbolt/nhi.c
index 5809809f64d4..6a97dcd57deb 100644
--- a/drivers/thunderbolt/nhi.c
+++ b/drivers/thunderbolt/nhi.c
@@ -534,6 +534,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,
@@ -581,19 +591,18 @@ static struct tb_ring *tb_ring_alloc(struct tb_nhi *nhi, u32 hop, int size,
if (!ring->descriptors)
goto err_free_ring;
+ if (nhi_alloc_hop(nhi, ring))
+ goto err_free_descs;
+
if (!(flags & RING_FLAG_NO_INTERRUPT) && nhi->ops->request_ring_irq) {
if (nhi->ops->request_ring_irq(ring, flags & RING_FLAG_NO_SUSPEND))
- goto err_free_descs;
+ goto err_free_hop;
}
- if (nhi_alloc_hop(nhi, ring))
- goto err_release_msix;
-
return ring;
-err_release_msix:
- if (nhi->ops->release_ring_irq)
- nhi->ops->release_ring_irq(ring);
+err_free_hop:
+ nhi_free_hop(nhi, ring);
err_free_descs:
dma_free_coherent(ring->nhi->dev,
ring->size * sizeof(*ring->descriptors),
--
2.55.0
^ permalink raw reply related [flat|nested] 32+ messages in thread* [PATCH 09/19] thunderbolt: Add ring_interrupt_active to tb_nhi_ops
2026-08-30 20:19 [PATCH 00/19] Initial USB4/Thunderbolt support for Apple M1/M2/M3 SoCs Sven Peter
` (7 preceding siblings ...)
2026-08-30 20:19 ` [PATCH 08/19] thunderbolt: Allocate ring HopID before requesting the ring interrupt Sven Peter
@ 2026-08-30 20:19 ` Sven Peter
2026-08-30 20:19 ` [PATCH 10/19] thunderbolt: Make the ring register layout configurable Sven Peter
` (10 subsequent siblings)
19 siblings, 0 replies; 32+ messages in thread
From: Sven Peter @ 2026-08-30 20:19 UTC (permalink / raw)
To: Greg Kroah-Hartman, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Heikki Krogerus, Janne Grunau, Neal Gompa,
Andreas Noever, Mika Westerberg, Yehezkel Bernat, Philipp Zabel
Cc: Konrad Dybcio, linux-usb, devicetree, linux-kernel, asahi,
linux-arm-kernel, Sven Peter
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 <sven@kernel.org>
---
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 6a97dcd57deb..fc54ff7edfb9 100644
--- a/drivers/thunderbolt/nhi.c
+++ b/drivers/thunderbolt/nhi.c
@@ -149,6 +149,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
*
@@ -724,7 +732,7 @@ void tb_ring_start(struct tb_ring *ring)
}
if (!(ring->flags & RING_FLAG_NO_INTERRUPT))
- ring_interrupt_active(ring, true);
+ nhi_ring_interrupt_active(ring, true);
ring->running = true;
err:
spin_unlock(&ring->lock);
@@ -785,7 +793,7 @@ void tb_ring_stop(struct tb_ring *ring)
goto err;
}
if (!(ring->flags & RING_FLAG_NO_INTERRUPT))
- 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 f72d6b274501..4884c3f5a2b2 100644
--- a/drivers/thunderbolt/nhi.h
+++ b/drivers/thunderbolt/nhi.h
@@ -52,6 +52,9 @@ extern const struct dev_pm_ops nhi_pm_ops;
* @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
* @reset_interface: Resets the host interface
@@ -67,6 +70,7 @@ struct tb_nhi_ops {
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);
void (*reset_interface)(struct tb_nhi *nhi);
--
2.55.0
^ permalink raw reply related [flat|nested] 32+ messages in thread* [PATCH 10/19] thunderbolt: Make the ring register layout configurable
2026-08-30 20:19 [PATCH 00/19] Initial USB4/Thunderbolt support for Apple M1/M2/M3 SoCs Sven Peter
` (8 preceding siblings ...)
2026-08-30 20:19 ` [PATCH 09/19] thunderbolt: Add ring_interrupt_active to tb_nhi_ops Sven Peter
@ 2026-08-30 20:19 ` Sven Peter
2026-09-01 8:58 ` Mika Westerberg
2026-08-30 20:19 ` [PATCH 11/19] thunderbolt: Add ring_interrupt_mask to tb_nhi_ops Sven Peter
` (9 subsequent siblings)
19 siblings, 1 reply; 32+ messages in thread
From: Sven Peter @ 2026-08-30 20:19 UTC (permalink / raw)
To: Greg Kroah-Hartman, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Heikki Krogerus, Janne Grunau, Neal Gompa,
Andreas Noever, Mika Westerberg, Yehezkel Bernat, Philipp Zabel
Cc: Konrad Dybcio, linux-usb, devicetree, linux-kernel, asahi,
linux-arm-kernel, Sven Peter
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 <sven@kernel.org>
---
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 fc54ff7edfb9..3a3d334e69fd 100644
--- a/drivers/thunderbolt/nhi.c
+++ b/drivers/thunderbolt/nhi.c
@@ -176,19 +176,32 @@ 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;
}
@@ -215,8 +228,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)
@@ -1253,6 +1268,9 @@ int nhi_probe(struct tb_nhi *nhi)
if (!nhi->ops->init_interrupts)
return dev_err_probe(dev, -EINVAL, "missing required NHI ops\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 4884c3f5a2b2..035cee433a3c 100644
--- a/drivers/thunderbolt/nhi.h
+++ b/drivers/thunderbolt/nhi.h
@@ -40,6 +40,24 @@ void nhi_reset_interface(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
+ * @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 d48623fda79b..7c2717bd2945 100644
--- a/include/linux/thunderbolt.h
+++ b/include/linux/thunderbolt.h
@@ -503,6 +503,8 @@ void tb_service_properties_changed(struct tb_service *svc);
* interrupt_work when dispatching interrupts to individual rings.
* @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
@@ -524,6 +526,7 @@ struct tb_nhi {
spinlock_t lock;
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;
--
2.55.0
^ permalink raw reply related [flat|nested] 32+ messages in thread* Re: [PATCH 10/19] thunderbolt: Make the ring register layout configurable
2026-08-30 20:19 ` [PATCH 10/19] thunderbolt: Make the ring register layout configurable Sven Peter
@ 2026-09-01 8:58 ` Mika Westerberg
2026-09-01 18:56 ` Sven Peter
0 siblings, 1 reply; 32+ messages in thread
From: Mika Westerberg @ 2026-09-01 8:58 UTC (permalink / raw)
To: Sven Peter
Cc: Greg Kroah-Hartman, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Heikki Krogerus, Janne Grunau, Neal Gompa,
Andreas Noever, Mika Westerberg, Yehezkel Bernat, Philipp Zabel,
Konrad Dybcio, linux-usb, devicetree, linux-kernel, asahi,
linux-arm-kernel
Hi,
On Sun, Aug 30, 2026 at 10:19:28PM +0200, Sven Peter wrote:
> 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.
This is odd because Apple is sitting in USB-IF and they are behind the
original register layout so why they do not follow it themselves?
> Signed-off-by: Sven Peter <sven@kernel.org>
> ---
> 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 fc54ff7edfb9..3a3d334e69fd 100644
> --- a/drivers/thunderbolt/nhi.c
> +++ b/drivers/thunderbolt/nhi.c
> @@ -176,19 +176,32 @@ 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,
> +};
I'm not fan of these to be honest.
I think either adding ring_ops that includes hooks that can be overridden
by non-standard HI or not sure if we can take advantage of regmap here?
> +
> 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;
> }
>
> @@ -215,8 +228,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)
> @@ -1253,6 +1268,9 @@ int nhi_probe(struct tb_nhi *nhi)
> if (!nhi->ops->init_interrupts)
> return dev_err_probe(dev, -EINVAL, "missing required NHI ops\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 4884c3f5a2b2..035cee433a3c 100644
> --- a/drivers/thunderbolt/nhi.h
> +++ b/drivers/thunderbolt/nhi.h
> @@ -40,6 +40,24 @@ void nhi_reset_interface(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
> + * @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 d48623fda79b..7c2717bd2945 100644
> --- a/include/linux/thunderbolt.h
> +++ b/include/linux/thunderbolt.h
> @@ -503,6 +503,8 @@ void tb_service_properties_changed(struct tb_service *svc);
> * interrupt_work when dispatching interrupts to individual rings.
> * @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
> @@ -524,6 +526,7 @@ struct tb_nhi {
> spinlock_t lock;
> 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;
>
> --
> 2.55.0
>
^ permalink raw reply [flat|nested] 32+ messages in thread* Re: [PATCH 10/19] thunderbolt: Make the ring register layout configurable
2026-09-01 8:58 ` Mika Westerberg
@ 2026-09-01 18:56 ` Sven Peter
0 siblings, 0 replies; 32+ messages in thread
From: Sven Peter @ 2026-09-01 18:56 UTC (permalink / raw)
To: Mika Westerberg
Cc: Greg Kroah-Hartman, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Heikki Krogerus, Janne Grunau, Neal Gompa,
Andreas Noever, Mika Westerberg, Yehezkel Bernat, Philipp Zabel,
Konrad Dybcio, linux-usb, devicetree, linux-kernel, asahi,
linux-arm-kernel
Hi,
On 9/1/26 10:58, Mika Westerberg wrote:
> Hi,
>
> On Sun, Aug 30, 2026 at 10:19:28PM +0200, Sven Peter wrote:
>> 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.
> This is odd because Apple is sitting in USB-IF and they are behind the
> original register layout so why they do not follow it themselves?
My guess is as good as yours but knowing Apple's culture it's probably
separate teams not being allowed to talk to each other...
>
>> Signed-off-by: Sven Peter <sven@kernel.org>
>> ---
>> 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 fc54ff7edfb9..3a3d334e69fd 100644
>> --- a/drivers/thunderbolt/nhi.c
>> +++ b/drivers/thunderbolt/nhi.c
>> @@ -176,19 +176,32 @@ 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,
>> +};
> I'm not fan of these to be honest.
>
> I think either adding ring_ops that includes hooks that can be overridden
> by non-standard HI or not sure if we can take advantage of regmap here?
Sure! I don't think regmap buys us anything but I think I can get away
with just adding nhi_ring_desc_base() and nhi_ring_options_base() to the
ops.
Sven
^ permalink raw reply [flat|nested] 32+ messages in thread
* [PATCH 11/19] thunderbolt: Add ring_interrupt_mask to tb_nhi_ops
2026-08-30 20:19 [PATCH 00/19] Initial USB4/Thunderbolt support for Apple M1/M2/M3 SoCs Sven Peter
` (9 preceding siblings ...)
2026-08-30 20:19 ` [PATCH 10/19] thunderbolt: Make the ring register layout configurable Sven Peter
@ 2026-08-30 20:19 ` Sven Peter
2026-08-30 20:19 ` [PATCH 12/19] thunderbolt: Add ring_configure " Sven Peter
` (8 subsequent siblings)
19 siblings, 0 replies; 32+ messages in thread
From: Sven Peter @ 2026-08-30 20:19 UTC (permalink / raw)
To: Greg Kroah-Hartman, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Heikki Krogerus, Janne Grunau, Neal Gompa,
Andreas Noever, Mika Westerberg, Yehezkel Bernat, Philipp Zabel
Cc: Konrad Dybcio, linux-usb, devicetree, linux-kernel, asahi,
linux-arm-kernel, Sven Peter
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 <sven@kernel.org>
---
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 3a3d334e69fd..3cb69deec63e 100644
--- a/drivers/thunderbolt/nhi.c
+++ b/drivers/thunderbolt/nhi.c
@@ -422,6 +422,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)
{
@@ -429,7 +437,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);
@@ -450,7 +458,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 035cee433a3c..5f70d7ddc2e2 100644
--- a/drivers/thunderbolt/nhi.h
+++ b/drivers/thunderbolt/nhi.h
@@ -73,6 +73,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
* @reset_interface: Resets the host interface
@@ -89,6 +92,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);
void (*reset_interface)(struct tb_nhi *nhi);
--
2.55.0
^ permalink raw reply related [flat|nested] 32+ messages in thread* [PATCH 12/19] thunderbolt: Add ring_configure to tb_nhi_ops
2026-08-30 20:19 [PATCH 00/19] Initial USB4/Thunderbolt support for Apple M1/M2/M3 SoCs Sven Peter
` (10 preceding siblings ...)
2026-08-30 20:19 ` [PATCH 11/19] thunderbolt: Add ring_interrupt_mask to tb_nhi_ops Sven Peter
@ 2026-08-30 20:19 ` Sven Peter
2026-08-30 20:19 ` [PATCH 13/19] thunderbolt: Add QUIRK_NO_DMA_PORT Sven Peter
` (7 subsequent siblings)
19 siblings, 0 replies; 32+ messages in thread
From: Sven Peter @ 2026-08-30 20:19 UTC (permalink / raw)
To: Greg Kroah-Hartman, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Heikki Krogerus, Janne Grunau, Neal Gompa,
Andreas Noever, Mika Westerberg, Yehezkel Bernat, Philipp Zabel
Cc: Konrad Dybcio, linux-usb, devicetree, linux-kernel, asahi,
linux-arm-kernel, Sven Peter
The Apple NHI has to configure and enable each ring with a single write
and uses a separate register range for the receive ring PDF masks. 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 <sven@kernel.org>
---
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 3cb69deec63e..696da52abff6 100644
--- a/drivers/thunderbolt/nhi.c
+++ b/drivers/thunderbolt/nhi.c
@@ -239,6 +239,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;
@@ -694,6 +719,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;
@@ -717,30 +743,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->dev,
"enabling E2E for %s %d with TX HopID %d\n",
@@ -750,10 +759,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);
+
if (!(ring->flags & RING_FLAG_NO_INTERRUPT))
nhi_ring_interrupt_active(ring, true);
ring->running = true;
diff --git a/drivers/thunderbolt/nhi.h b/drivers/thunderbolt/nhi.h
index 5f70d7ddc2e2..670b48efeec5 100644
--- a/drivers/thunderbolt/nhi.h
+++ b/drivers/thunderbolt/nhi.h
@@ -76,6 +76,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
* @reset_interface: Resets the host interface
@@ -93,6 +96,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);
void (*reset_interface)(struct tb_nhi *nhi);
--
2.55.0
^ permalink raw reply related [flat|nested] 32+ messages in thread* [PATCH 13/19] thunderbolt: Add QUIRK_NO_DMA_PORT
2026-08-30 20:19 [PATCH 00/19] Initial USB4/Thunderbolt support for Apple M1/M2/M3 SoCs Sven Peter
` (11 preceding siblings ...)
2026-08-30 20:19 ` [PATCH 12/19] thunderbolt: Add ring_configure " Sven Peter
@ 2026-08-30 20:19 ` Sven Peter
2026-09-01 9:04 ` Mika Westerberg
2026-08-30 20:19 ` [PATCH 14/19] thunderbolt: Add QUIRK_NO_USB3_BW_ALLOC Sven Peter
` (6 subsequent siblings)
19 siblings, 1 reply; 32+ messages in thread
From: Sven Peter @ 2026-08-30 20:19 UTC (permalink / raw)
To: Greg Kroah-Hartman, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Heikki Krogerus, Janne Grunau, Neal Gompa,
Andreas Noever, Mika Westerberg, Yehezkel Bernat, Philipp Zabel
Cc: Konrad Dybcio, linux-usb, devicetree, linux-kernel, asahi,
linux-arm-kernel, Sven Peter
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 <sven@kernel.org>
---
drivers/thunderbolt/nhi.h | 1 +
drivers/thunderbolt/switch.c | 10 ++++++----
drivers/thunderbolt/tb.c | 8 ++++++++
drivers/thunderbolt/tb.h | 2 ++
4 files changed, 17 insertions(+), 4 deletions(-)
diff --git a/drivers/thunderbolt/nhi.h b/drivers/thunderbolt/nhi.h
index 670b48efeec5..c2d16ab6994e 100644
--- a/drivers/thunderbolt/nhi.h
+++ b/drivers/thunderbolt/nhi.h
@@ -168,6 +168,7 @@ struct tb_nhi_ops {
#define QUIRK_AUTO_CLEAR_INT BIT(0)
#define QUIRK_E2E BIT(1)
#define QUIRK_RESET_DMA_ON_TEARDOWN BIT(2)
+#define QUIRK_NO_DMA_PORT BIT(3)
/*
* Minimal number of vectors when we use MSI-X. Two for control channel
diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c
index 3f54a5ae56b0..ef267f40c103 100644
--- a/drivers/thunderbolt/switch.c
+++ b/drivers/thunderbolt/switch.c
@@ -3317,10 +3317,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 47753a5c0f2e..c43cdd2c2e2e 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 */
@@ -3036,6 +3037,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 4373336d9425..0977cd690d7e 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;
--
2.55.0
^ permalink raw reply related [flat|nested] 32+ messages in thread* Re: [PATCH 13/19] thunderbolt: Add QUIRK_NO_DMA_PORT
2026-08-30 20:19 ` [PATCH 13/19] thunderbolt: Add QUIRK_NO_DMA_PORT Sven Peter
@ 2026-09-01 9:04 ` Mika Westerberg
2026-09-01 17:06 ` Sven Peter
0 siblings, 1 reply; 32+ messages in thread
From: Mika Westerberg @ 2026-09-01 9:04 UTC (permalink / raw)
To: Sven Peter
Cc: Greg Kroah-Hartman, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Heikki Krogerus, Janne Grunau, Neal Gompa,
Andreas Noever, Mika Westerberg, Yehezkel Bernat, Philipp Zabel,
Konrad Dybcio, linux-usb, devicetree, linux-kernel, asahi,
linux-arm-kernel
Hi,
On Sun, Aug 30, 2026 at 10:19:31PM +0200, Sven Peter wrote:
> 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.
DMA port is Intel Thunderbolt 1-3 thing and not expected to found on USB4
routers. The current code should not even be trying to find it, if it does
then that's a bug that should be fixed.
USB4 router firmware upgrades use the standard operations and if those are
not supported then we just don't expose the corresponding NVMem nodes.
^ permalink raw reply [flat|nested] 32+ messages in thread
* Re: [PATCH 13/19] thunderbolt: Add QUIRK_NO_DMA_PORT
2026-09-01 9:04 ` Mika Westerberg
@ 2026-09-01 17:06 ` Sven Peter
0 siblings, 0 replies; 32+ messages in thread
From: Sven Peter @ 2026-09-01 17:06 UTC (permalink / raw)
To: Mika Westerberg
Cc: Greg Kroah-Hartman, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Heikki Krogerus, Janne Grunau, Neal Gompa,
Andreas Noever, Mika Westerberg, Yehezkel Bernat, Philipp Zabel,
Konrad Dybcio, linux-usb, devicetree, linux-kernel, asahi,
linux-arm-kernel
On 9/1/26 11:04, Mika Westerberg wrote:
> Hi,
>
> On Sun, Aug 30, 2026 at 10:19:31PM +0200, Sven Peter wrote:
>> 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.
> DMA port is Intel Thunderbolt 1-3 thing and not expected to found on USB4
> routers. The current code should not even be trying to find it, if it does
> then that's a bug that should be fixed.
>
> USB4 router firmware upgrades use the standard operations and if those are
> not supported then we just don't expose the corresponding NVMem nodes.
Yeah, you're right, I can just drop this and everything still works.
I think this may have been broken back in 2023 when I brought this all
up for the first time (and still had trouble with the Type-C PHY and PD
controller which is why I didn't try to upstream any of this back then)
Sven
^ permalink raw reply [flat|nested] 32+ messages in thread
* [PATCH 14/19] thunderbolt: Add QUIRK_NO_USB3_BW_ALLOC
2026-08-30 20:19 [PATCH 00/19] Initial USB4/Thunderbolt support for Apple M1/M2/M3 SoCs Sven Peter
` (12 preceding siblings ...)
2026-08-30 20:19 ` [PATCH 13/19] thunderbolt: Add QUIRK_NO_DMA_PORT Sven Peter
@ 2026-08-30 20:19 ` Sven Peter
2026-09-01 9:12 ` Mika Westerberg
2026-08-30 20:19 ` [PATCH 15/19] thunderbolt: Export symbols required by the Apple Silicon driver Sven Peter
` (5 subsequent siblings)
19 siblings, 1 reply; 32+ messages in thread
From: Sven Peter @ 2026-08-30 20:19 UTC (permalink / raw)
To: Greg Kroah-Hartman, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Heikki Krogerus, Janne Grunau, Neal Gompa,
Andreas Noever, Mika Westerberg, Yehezkel Bernat, Philipp Zabel
Cc: Konrad Dybcio, linux-usb, devicetree, linux-kernel, asahi,
linux-arm-kernel, Sven Peter
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. For completeness, discovered tunnels book 90% of the
maximum link rate in software, matching tb_tunnel_alloc_usb3(), although
Apple Silicon cannot have any pre-existing tunnels.
Signed-off-by: Sven Peter <sven@kernel.org>
---
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 c2d16ab6994e..afd9a11489a9 100644
--- a/drivers/thunderbolt/nhi.h
+++ b/drivers/thunderbolt/nhi.h
@@ -169,6 +169,7 @@ struct tb_nhi_ops {
#define QUIRK_E2E BIT(1)
#define QUIRK_RESET_DMA_ON_TEARDOWN BIT(2)
#define QUIRK_NO_DMA_PORT BIT(3)
+#define QUIRK_NO_USB3_BW_ALLOC BIT(4)
/*
* Minimal number of vectors when we use MSI-X. Two for control channel
diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c
index c43cdd2c2e2e..7ab53a30fe30 100644
--- a/drivers/thunderbolt/tb.c
+++ b/drivers/thunderbolt/tb.c
@@ -3044,6 +3044,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 0977cd690d7e..530b0e6c3a11 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 7e8284575dff..9178120a6b66 100644
--- a/drivers/thunderbolt/tunnel.c
+++ b/drivers/thunderbolt/tunnel.c
@@ -2268,24 +2268,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");
@@ -2365,12 +2381,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;
--
2.55.0
^ permalink raw reply related [flat|nested] 32+ messages in thread* Re: [PATCH 14/19] thunderbolt: Add QUIRK_NO_USB3_BW_ALLOC
2026-08-30 20:19 ` [PATCH 14/19] thunderbolt: Add QUIRK_NO_USB3_BW_ALLOC Sven Peter
@ 2026-09-01 9:12 ` Mika Westerberg
0 siblings, 0 replies; 32+ messages in thread
From: Mika Westerberg @ 2026-09-01 9:12 UTC (permalink / raw)
To: Sven Peter
Cc: Greg Kroah-Hartman, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Heikki Krogerus, Janne Grunau, Neal Gompa,
Andreas Noever, Mika Westerberg, Yehezkel Bernat, Philipp Zabel,
Konrad Dybcio, linux-usb, devicetree, linux-kernel, asahi,
linux-arm-kernel
Hi,
On Sun, Aug 30, 2026 at 10:19:32PM +0200, Sven Peter wrote:
> 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
Okay this kind of makes sense. Apple is doing this only via software and
this is also what Linux needs to do with USB3 Gen T tunneling so we should
prepare for this.
> Add a quirk to skip bandwidth allocation entirely but keep tracking
> the initial allocation in software for the consumed bandwidth
> calculation. For completeness, discovered tunnels book 90% of the
> maximum link rate in software, matching tb_tunnel_alloc_usb3(), although
> Apple Silicon cannot have any pre-existing tunnels.
>
> Signed-off-by: Sven Peter <sven@kernel.org>
> ---
> 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 c2d16ab6994e..afd9a11489a9 100644
> --- a/drivers/thunderbolt/nhi.h
> +++ b/drivers/thunderbolt/nhi.h
> @@ -169,6 +169,7 @@ struct tb_nhi_ops {
> #define QUIRK_E2E BIT(1)
> #define QUIRK_RESET_DMA_ON_TEARDOWN BIT(2)
> #define QUIRK_NO_DMA_PORT BIT(3)
> +#define QUIRK_NO_USB3_BW_ALLOC BIT(4)
This is not NHI thing but lives inside the USB4 domain so the quirk should
end up in drivers/thunderbolt/quirks.c.
For now I think that's enough but in the future we should provide software
mechanism that takes over the hardware if present, and then get rid of the
quirk alltogether.
> /*
> * Minimal number of vectors when we use MSI-X. Two for control channel
> diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c
> index c43cdd2c2e2e..7ab53a30fe30 100644
> --- a/drivers/thunderbolt/tb.c
> +++ b/drivers/thunderbolt/tb.c
> @@ -3044,6 +3044,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 0977cd690d7e..530b0e6c3a11 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 7e8284575dff..9178120a6b66 100644
> --- a/drivers/thunderbolt/tunnel.c
> +++ b/drivers/thunderbolt/tunnel.c
> @@ -2268,24 +2268,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");
> @@ -2365,12 +2381,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;
>
> --
> 2.55.0
>
^ permalink raw reply [flat|nested] 32+ messages in thread
* [PATCH 15/19] thunderbolt: Export symbols required by the Apple Silicon driver
2026-08-30 20:19 [PATCH 00/19] Initial USB4/Thunderbolt support for Apple M1/M2/M3 SoCs Sven Peter
` (13 preceding siblings ...)
2026-08-30 20:19 ` [PATCH 14/19] thunderbolt: Add QUIRK_NO_USB3_BW_ALLOC Sven Peter
@ 2026-08-30 20:19 ` Sven Peter
2026-08-30 20:19 ` [PATCH 16/19] thunderbolt: Add Apple Silicon support Sven Peter
` (4 subsequent siblings)
19 siblings, 0 replies; 32+ messages in thread
From: Sven Peter @ 2026-08-30 20:19 UTC (permalink / raw)
To: Greg Kroah-Hartman, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Heikki Krogerus, Janne Grunau, Neal Gompa,
Andreas Noever, Mika Westerberg, Yehezkel Bernat, Philipp Zabel
Cc: Konrad Dybcio, linux-usb, devicetree, linux-kernel, asahi,
linux-arm-kernel, Sven Peter
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. Restrict these exports to the thunderbolt_apple module to
keep them internal to the Thunderbolt subsystem.
Signed-off-by: Sven Peter <sven@kernel.org>
---
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 4ab22d5291ac..e000a53e5e75 100644
--- a/drivers/thunderbolt/cap.c
+++ b/drivers/thunderbolt/cap.c
@@ -6,6 +6,7 @@
* Copyright (C) 2018, Intel Corporation
*/
+#include <linux/export.h>
#include <linux/slab.h>
#include <linux/errno.h>
@@ -254,3 +255,4 @@ int tb_switch_find_vse_cap(struct tb_switch *sw, enum tb_switch_vse_cap vsec)
return -ENOENT;
}
+EXPORT_SYMBOL_FOR_MODULES(tb_switch_find_vse_cap, "thunderbolt_apple");
diff --git a/drivers/thunderbolt/ctl.c b/drivers/thunderbolt/ctl.c
index cd47b627f97b..b2a6b300e934 100644
--- a/drivers/thunderbolt/ctl.c
+++ b/drivers/thunderbolt/ctl.c
@@ -8,6 +8,7 @@
#include <linux/crc32.h>
#include <linux/delay.h>
+#include <linux/export.h>
#include <linux/slab.h>
#include <linux/pci.h>
#include <linux/dmapool.h>
@@ -1159,6 +1160,7 @@ int tb_cfg_write(struct tb_ctl *ctl, const void *buffer, u64 route, u32 port,
}
return res.err;
}
+EXPORT_SYMBOL_FOR_MODULES(tb_cfg_write, "thunderbolt_apple");
/**
* 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 12c88509a54f..477a64258b87 100644
--- a/drivers/thunderbolt/domain.c
+++ b/drivers/thunderbolt/domain.c
@@ -490,6 +490,7 @@ int tb_domain_add(struct tb *tb, bool reset)
return ret;
}
+EXPORT_SYMBOL_FOR_MODULES(tb_domain_add, "thunderbolt_apple");
/**
* tb_domain_remove() - Removes and releases a domain
@@ -514,6 +515,7 @@ void tb_domain_remove(struct tb *tb)
device_unregister(&tb->dev);
}
+EXPORT_SYMBOL_FOR_MODULES(tb_domain_remove, "thunderbolt_apple");
/**
* tb_domain_suspend_noirq() - Suspend a domain
diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c
index ef267f40c103..db675e4f0bc2 100644
--- a/drivers/thunderbolt/switch.c
+++ b/drivers/thunderbolt/switch.c
@@ -627,6 +627,7 @@ int tb_port_unlock(struct tb_port *port)
return usb4_port_unlock(port);
return 0;
}
+EXPORT_SYMBOL_FOR_MODULES(tb_port_unlock, "thunderbolt_apple");
static int __tb_port_enable(struct tb_port *port, bool enable)
{
diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c
index 7ab53a30fe30..6a1f49c473eb 100644
--- a/drivers/thunderbolt/tb.c
+++ b/drivers/thunderbolt/tb.c
@@ -6,6 +6,7 @@
* Copyright (C) 2019, Intel Corporation
*/
+#include <linux/export.h>
#include <linux/slab.h>
#include <linux/errno.h>
#include <linux/delay.h>
@@ -3442,3 +3443,4 @@ struct tb *tb_probe(struct tb_nhi *nhi)
return tb;
}
+EXPORT_SYMBOL_FOR_MODULES(tb_probe, "thunderbolt_apple");
--
2.55.0
^ permalink raw reply related [flat|nested] 32+ messages in thread* [PATCH 16/19] thunderbolt: Add Apple Silicon support
2026-08-30 20:19 [PATCH 00/19] Initial USB4/Thunderbolt support for Apple M1/M2/M3 SoCs Sven Peter
` (14 preceding siblings ...)
2026-08-30 20:19 ` [PATCH 15/19] thunderbolt: Export symbols required by the Apple Silicon driver Sven Peter
@ 2026-08-30 20:19 ` Sven Peter
2026-09-01 10:09 ` Mika Westerberg
2026-08-30 20:19 ` [PATCH 17/19] arm64: dts: apple: t8103: Add USB4 ACIO and NHI Sven Peter
` (3 subsequent siblings)
19 siblings, 1 reply; 32+ messages in thread
From: Sven Peter @ 2026-08-30 20:19 UTC (permalink / raw)
To: Greg Kroah-Hartman, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Heikki Krogerus, Janne Grunau, Neal Gompa,
Andreas Noever, Mika Westerberg, Yehezkel Bernat, Philipp Zabel
Cc: Konrad Dybcio, linux-usb, devicetree, linux-kernel, asahi,
linux-arm-kernel, Sven Peter
Add a platform driver for the ACIO host router complex and Native Host
Interface (NHI) found on Apple Silicon SoCs.
The driver powers ACIO in response to Type-C notifications, boots its
RTKit co-processor and populates the DART and NHI child devices once
their MMIO regions become accessible. It then registers the NHI with the
software connection manager and forwards the cable details to the host
router.
The Apple NHI uses one interrupt per ring and overrides the generic ring
register layout, interrupt handling and ring configuration. As of this
commit only XDomain connections and USB3-via-USB4 tunnels are supported.
Signed-off-by: Sven Peter <sven@kernel.org>
---
MAINTAINERS | 1 +
drivers/thunderbolt/Kconfig | 13 +
drivers/thunderbolt/Makefile | 3 +
drivers/thunderbolt/apple.c | 1028 ++++++++++++++++++++++++++++++++++++++++++
4 files changed, 1045 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index c2c6e4541da7..28a32729af8c 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2690,6 +2690,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/video/backlight/apple_dwi_bl.c
F: drivers/watchdog/apple_wdt.c
diff --git a/drivers/thunderbolt/Kconfig b/drivers/thunderbolt/Kconfig
index 294b3227a545..d9fbe3a0dc3f 100644
--- a/drivers/thunderbolt/Kconfig
+++ b/drivers/thunderbolt/Kconfig
@@ -75,4 +75,17 @@ config USB4_STREAM
To compile this driver a module, choose M here. The module will be
called thunderbolt_stream.
+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 beb054c3126b..edcfe58bc8de 100644
--- a/drivers/thunderbolt/Makefile
+++ b/drivers/thunderbolt/Makefile
@@ -16,3 +16,6 @@ obj-$(CONFIG_USB4_DMA_TEST) += thunderbolt_dma_test.o
thunderbolt_stream-${CONFIG_USB4_STREAM} += stream.o
obj-$(CONFIG_USB4_STREAM) += thunderbolt_stream.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 000000000000..530008cc8ec5
--- /dev/null
+++ b/drivers/thunderbolt/apple.c
@@ -0,0 +1,1028 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Apple Silicon USB4 and Thunderbolt driver
+ * Copyright (c) Sven Peter <sven@kernel.org>
+ *
+ * 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 |
+ * +---------+
+ *
+ * ACIO and ATCPHY have strict ordering requirements. When a USB4/Thunderbolt
+ * connection is requested after a device has been connected the Type-C PD
+ * driver performs the following sequence:
+ *
+ * 1) Configure the PHY for the negotiated mode and orientation through
+ * typec_mux_set()
+ * 2) Pass the cable details to this driver through typec_thunderbolt_switch_set()
+ * 3) Power up ACIO, boot its RTKit co-processor and apply the tunables
+ * 4) Probe the DART and NHI children now that their MMIO is accessible from
+ * the main SoC bus
+ * 5) Register the USB4 domain and write the cable details to the host router
+ * which brings up the link and starts discovery
+ *
+ * The reverse order is required when the cable is disconnected then:
+ *
+ * 1) typec_thunderbolt_switch_set() removes the USB4 domain and the ACIO
+ * children, shuts down the co-processor and powers everything off.
+ * 2) typec_mux_set() puts the PHY into safe mode and powers it off.
+ *
+ * ACIO must not be started before the PHY is configured and the PHY must
+ * remain active until ACIO has been powered down again. The child devices
+ * cannot remain populated while ACIO is powered off because they are part
+ * of the ACIO block and only exposed to the main SoC bus and will SError
+ * once ACIO is off.
+ * Violating this ordering can panic the kernel with an async SError at best
+ * and trigger some internal watchdog that will reset the entire SoC at worst.
+ */
+
+#include <linux/bitfield.h>
+#include <linux/completion.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/pm_domain.h>
+#include <linux/reset.h>
+#include <linux/soc/apple/rtkit.h>
+#include <linux/soc/apple/tunable.h>
+#include <linux/spinlock.h>
+#include <linux/types.h>
+#include <linux/usb/pd.h>
+#include <linux/usb/typec_mux.h>
+#include <linux/usb/typec_tbt.h>
+
+#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
+
+/*
+ * The Apple vendor-specific extended capability contains a cable-information
+ * word which has to be programmed from the USB4/Thunderbolt details reported
+ * out-of-band by the Type-C PD controller. It must be programmed into the
+ * host router before link discovery starts.
+ */
+#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 - Apple Converged I/O host router complex
+ * @dev: ACIO device
+ * @np: ACIO device tree node
+ * @rtk: RTKit instance for the ACIO co-processor
+ * @rc_base: ACIO root controller registers
+ * @rc_res: ACIO root controller MMIO resource
+ * @rc_tunable: Tunable sequence for the ACIO root controller
+ * @sram_res: ACIO co-processor SRAM resource
+ * @sram_base: ACIO co-processor SRAM
+ * @reset: ACIO reset controller
+ * @pd_list: Power domains used by the ACIO complex
+ * @lock: Serializes cable transitions and ACIO power changes
+ * @current_cable_info: Cable information currently programmed into ACIO
+ * @target_cable_info: Cable information requested by the Type-C PD driver
+ * @nhi_boot_completion: Signals completion of the NHI bringup
+ * @nhi_boot_status: Status returned by the NHI bringup
+ * @tbt_switch: USB4/Thunderbolt switch triggered by the Type-C PD driver
+ */
+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;
+
+ 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 - Apple Native Host Interface
+ * @dev: NHI device
+ * @pdev: NHI platform device
+ * @np: NHI device tree node
+ * @acio: Parent ACIO complex
+ * @tb: USB4 domain and software connection manager
+ * @nhi: NHI struct
+ * @nhi_base: NHI registers
+ * @pdf_base: PDF (Protocol Defined Field) configuration registers
+ * @tx_irqs: Transmit ring interrupts indexed by HopID
+ * @rx_irqs: Receive ring interrupts indexed by HopID
+ * @tx_irq_names: Names of the transmit ring interrupts
+ * @rx_irq_names: Names of the receive ring interrupts
+ * @n_rings: Number of rings in each direction
+ */
+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)
+
+/**
+ * apple_cio_rtkit_shmem_setup - Map an RTKit shared memory buffer
+ * @cookie: ACIO instance passed to RTKit
+ * @bfr: RTKit shared memory buffer descriptor
+ *
+ * Translate the firmware-provided IOVA into the ACIO SRAM mapping and reject
+ * buffers that fall outside the reserved SRAM resource.
+ *
+ * Return: 0 on success or a negative error code on failure.
+ */
+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 = min_t(u32, ring->interval_nsec,
+ FIELD_MAX(APPLE_CIO_NHI_IRQ_THROTTLE_INTERVAL_MASK) *
+ APPLE_CIO_NHI_IRQ_THROTTLE_GRANULARITY_NSEC);
+ interval = DIV_ROUND_UP(interval,
+ APPLE_CIO_NHI_IRQ_THROTTLE_GRANULARITY_NSEC);
+ 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_HANDLED;
+
+ 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;
+ }
+
+ anhi->pdev = pdev;
+ anhi->dev = &pdev->dev;
+ anhi->np = pdev->dev.of_node;
+ anhi->acio = acio;
+ platform_set_drvdata(pdev, anhi);
+
+ ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(42));
+ if (ret)
+ goto err;
+
+ 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");
+ ret = -EINVAL;
+ goto err_unlock_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);
+ ret = cap_apple;
+ goto err_unlock_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);
+ goto err_unlock_tb_domain;
+ }
+
+ mutex_unlock(&anhi->tb->lock);
+
+ acio->nhi_boot_status = 0;
+ complete(&acio->nhi_boot_completion);
+ return 0;
+
+err_unlock_tb_domain:
+ mutex_unlock(&anhi->tb->lock);
+ 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,
+};
+
+/**
+ * apple_cio_stop - Stop and power down the ACIO complex
+ * @acio: ACIO complex to stop
+ *
+ * Remove the NHI and DART children before shutting down the co-processor and
+ * dropping the power-domain links. The Type-C PHY must remain configured
+ * until this function returns. The caller must hold @acio->lock.
+ */
+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;
+}
+
+/**
+ * apple_cio_start - Power up and start the ACIO complex
+ * @acio: ACIO complex to start
+ *
+ * The Type-C PHY must already be configured for USB4 or Thunderbolt. Power up
+ * ACIO, boot its RTKit co-processor, populate its child devices and wait for
+ * the NHI to register the USB4 domain. All completed steps are unwound on
+ * failure. The caller must hold @acio->lock.
+ *
+ * Return: 0 on success or a negative error code on failure.
+ */
+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;
+}
+
+/**
+ * apple_cio_tbt_switch_set - Handle a USB4/Thunderbolt cable transition
+ * @sw: USB4/Thunderbolt switch
+ * @data: New cable state and negotiated cable details
+ *
+ * Translate the cable details into the Apple host-router representation and
+ * synchronously power ACIO up or down. The Type-C PD driver must configure
+ * the PHY before an active transition and must request the inactive state
+ * before shutting the PHY down.
+ *
+ * Return: 0 on success or a negative error code on failure.
+ */
+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 int apple_cio_prepare(struct device *dev)
+{
+ struct apple_cio *acio = dev_get_drvdata(dev);
+
+ guard(mutex)(&acio->lock);
+
+ if (acio->current_cable_info) {
+ dev_err(dev, "unable to suspend while a USB4/Thunderbolt connection is active\n");
+ return -EBUSY;
+ }
+
+ return 0;
+}
+
+static const struct dev_pm_ops apple_cio_pm_ops = {
+ .prepare = apple_cio_prepare,
+};
+
+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,
+ .pm = pm_sleep_ptr(&apple_cio_pm_ops),
+ },
+ .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_AUTHOR("Sven Peter <sven@kernel.org>");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Apple Silicon USB4/Thunderbolt driver");
--
2.55.0
^ permalink raw reply related [flat|nested] 32+ messages in thread* Re: [PATCH 16/19] thunderbolt: Add Apple Silicon support
2026-08-30 20:19 ` [PATCH 16/19] thunderbolt: Add Apple Silicon support Sven Peter
@ 2026-09-01 10:09 ` Mika Westerberg
2026-09-01 19:06 ` Sven Peter
0 siblings, 1 reply; 32+ messages in thread
From: Mika Westerberg @ 2026-09-01 10:09 UTC (permalink / raw)
To: Sven Peter
Cc: Greg Kroah-Hartman, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Heikki Krogerus, Janne Grunau, Neal Gompa,
Andreas Noever, Mika Westerberg, Yehezkel Bernat, Philipp Zabel,
Konrad Dybcio, linux-usb, devicetree, linux-kernel, asahi,
linux-arm-kernel
Hi,
On Sun, Aug 30, 2026 at 10:19:34PM +0200, Sven Peter wrote:
> Add a platform driver for the ACIO host router complex and Native Host
> Interface (NHI) found on Apple Silicon SoCs.
>
> The driver powers ACIO in response to Type-C notifications, boots its
> RTKit co-processor and populates the DART and NHI child devices once
> their MMIO regions become accessible. It then registers the NHI with the
> software connection manager and forwards the cable details to the host
> router.
>
> The Apple NHI uses one interrupt per ring and overrides the generic ring
> register layout, interrupt handling and ring configuration. As of this
> commit only XDomain connections and USB3-via-USB4 tunnels are supported.
>
> Signed-off-by: Sven Peter <sven@kernel.org>
> ---
> MAINTAINERS | 1 +
> drivers/thunderbolt/Kconfig | 13 +
> drivers/thunderbolt/Makefile | 3 +
> drivers/thunderbolt/apple.c | 1028 ++++++++++++++++++++++++++++++++++++++++++
> 4 files changed, 1045 insertions(+)
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index c2c6e4541da7..28a32729af8c 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -2690,6 +2690,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/video/backlight/apple_dwi_bl.c
> F: drivers/watchdog/apple_wdt.c
> diff --git a/drivers/thunderbolt/Kconfig b/drivers/thunderbolt/Kconfig
> index 294b3227a545..d9fbe3a0dc3f 100644
> --- a/drivers/thunderbolt/Kconfig
> +++ b/drivers/thunderbolt/Kconfig
> @@ -75,4 +75,17 @@ config USB4_STREAM
> To compile this driver a module, choose M here. The module will be
> called thunderbolt_stream.
>
> +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 beb054c3126b..edcfe58bc8de 100644
> --- a/drivers/thunderbolt/Makefile
> +++ b/drivers/thunderbolt/Makefile
> @@ -16,3 +16,6 @@ obj-$(CONFIG_USB4_DMA_TEST) += thunderbolt_dma_test.o
>
> thunderbolt_stream-${CONFIG_USB4_STREAM} += stream.o
> obj-$(CONFIG_USB4_STREAM) += thunderbolt_stream.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 000000000000..530008cc8ec5
> --- /dev/null
> +++ b/drivers/thunderbolt/apple.c
> @@ -0,0 +1,1028 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Apple Silicon USB4 and Thunderbolt driver
> + * Copyright (c) Sven Peter <sven@kernel.org>
> + *
> + * 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 |
> + * +---------+
> + *
> + * ACIO and ATCPHY have strict ordering requirements. When a USB4/Thunderbolt
> + * connection is requested after a device has been connected the Type-C PD
> + * driver performs the following sequence:
> + *
> + * 1) Configure the PHY for the negotiated mode and orientation through
> + * typec_mux_set()
> + * 2) Pass the cable details to this driver through typec_thunderbolt_switch_set()
> + * 3) Power up ACIO, boot its RTKit co-processor and apply the tunables
> + * 4) Probe the DART and NHI children now that their MMIO is accessible from
> + * the main SoC bus
> + * 5) Register the USB4 domain and write the cable details to the host router
> + * which brings up the link and starts discovery
> + *
> + * The reverse order is required when the cable is disconnected then:
> + *
> + * 1) typec_thunderbolt_switch_set() removes the USB4 domain and the ACIO
> + * children, shuts down the co-processor and powers everything off.
> + * 2) typec_mux_set() puts the PHY into safe mode and powers it off.
> + *
> + * ACIO must not be started before the PHY is configured and the PHY must
> + * remain active until ACIO has been powered down again. The child devices
> + * cannot remain populated while ACIO is powered off because they are part
> + * of the ACIO block and only exposed to the main SoC bus and will SError
> + * once ACIO is off.
> + * Violating this ordering can panic the kernel with an async SError at best
> + * and trigger some internal watchdog that will reset the entire SoC at worst.
> + */
> +
> +#include <linux/bitfield.h>
> +#include <linux/completion.h>
> +#include <linux/interrupt.h>
> +#include <linux/io.h>
> +#include <linux/iopoll.h>
> +#include <linux/module.h>
> +#include <linux/mutex.h>
> +#include <linux/of.h>
> +#include <linux/of_platform.h>
> +#include <linux/platform_device.h>
> +#include <linux/pm_domain.h>
> +#include <linux/reset.h>
> +#include <linux/soc/apple/rtkit.h>
> +#include <linux/soc/apple/tunable.h>
> +#include <linux/spinlock.h>
> +#include <linux/types.h>
> +#include <linux/usb/pd.h>
> +#include <linux/usb/typec_mux.h>
> +#include <linux/usb/typec_tbt.h>
> +
> +#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)
Can you tab align the values so that it is consistent with the rest of the
driver?
#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
Also the style used is:
#define APPLE_CIO_NHI_BOOT_TIMEOUT 10000 /* ms */
for parameters/variables it's foo_timeout_msec and so on.
> +
> +/*
> + * The Apple vendor-specific extended capability contains a cable-information
> + * word which has to be programmed from the USB4/Thunderbolt details reported
> + * out-of-band by the Type-C PD controller. It must be programmed into the
> + * host router before link discovery starts.
> + */
> +#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 - Apple Converged I/O host router complex
> + * @dev: ACIO device
> + * @np: ACIO device tree node
> + * @rtk: RTKit instance for the ACIO co-processor
> + * @rc_base: ACIO root controller registers
> + * @rc_res: ACIO root controller MMIO resource
> + * @rc_tunable: Tunable sequence for the ACIO root controller
> + * @sram_res: ACIO co-processor SRAM resource
> + * @sram_base: ACIO co-processor SRAM
> + * @reset: ACIO reset controller
> + * @pd_list: Power domains used by the ACIO complex
> + * @lock: Serializes cable transitions and ACIO power changes
> + * @current_cable_info: Cable information currently programmed into ACIO
> + * @target_cable_info: Cable information requested by the Type-C PD driver
> + * @nhi_boot_completion: Signals completion of the NHI bringup
> + * @nhi_boot_status: Status returned by the NHI bringup
> + * @tbt_switch: USB4/Thunderbolt switch triggered by the Type-C PD driver
> + */
> +struct apple_cio {
> + struct device *dev;
> + struct device_node *np;
> + struct apple_rtkit *rtk;
> +
No need for empty lines here.
> + 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;
> +
> + u32 current_cable_info;
> + u32 target_cable_info;
> + struct completion nhi_boot_completion;
> + int nhi_boot_status;
> +
> + struct typec_thunderbolt_switch_dev *tbt_switch;
Call it "tb_switch" instead.
> +};
> +
> +/**
> + * struct apple_nhi - Apple Native Host Interface
> + * @dev: NHI device
> + * @pdev: NHI platform device
> + * @np: NHI device tree node
> + * @acio: Parent ACIO complex
> + * @tb: USB4 domain and software connection manager
> + * @nhi: NHI struct
> + * @nhi_base: NHI registers
> + * @pdf_base: PDF (Protocol Defined Field) configuration registers
> + * @tx_irqs: Transmit ring interrupts indexed by HopID
> + * @rx_irqs: Receive ring interrupts indexed by HopID
> + * @tx_irq_names: Names of the transmit ring interrupts
> + * @rx_irq_names: Names of the receive ring interrupts
> + * @n_rings: Number of rings in each direction
@nrings
That's the convention too.
> + */
> +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)
> +
> +/**
> + * apple_cio_rtkit_shmem_setup - Map an RTKit shared memory buffer
apple_cio_rtkit_shmem_setup() - Map an RTKit shared memory buffer
Ditto everywhere.
> + * @cookie: ACIO instance passed to RTKit
> + * @bfr: RTKit shared memory buffer descriptor
> + *
> + * Translate the firmware-provided IOVA into the ACIO SRAM mapping and reject
> + * buffers that fall outside the reserved SRAM resource.
> + *
> + * Return: 0 on success or a negative error code on failure.
> + */
> +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;
nirqs
> + 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 = min_t(u32, ring->interval_nsec,
> + FIELD_MAX(APPLE_CIO_NHI_IRQ_THROTTLE_INTERVAL_MASK) *
> + APPLE_CIO_NHI_IRQ_THROTTLE_GRANULARITY_NSEC);
> + interval = DIV_ROUND_UP(interval,
> + APPLE_CIO_NHI_IRQ_THROTTLE_GRANULARITY_NSEC);
> + 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_HANDLED;
> +
> + 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);
I think we can do this flow in the generic parts too. It makes the driver
follow the CM guide more closely.
> +}
> +
> +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;
Please use "reverse christmas tree" order where possible.
> + 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;
> + }
> +
> + anhi->pdev = pdev;
> + anhi->dev = &pdev->dev;
> + anhi->np = pdev->dev.of_node;
> + anhi->acio = acio;
> + platform_set_drvdata(pdev, anhi);
> +
> + ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(42));
42?
> + if (ret)
> + goto err;
> +
> + 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");
"unable to map.. "
So it's consistent with the rest. Ditto everywhere.
Also the typical pattern is
return dev_err_probe(..)
I think it's fine to use it with goto but better to check.
> + 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;
Okay so essentially it's always on and can't be disabled? Maybe comment here.
> + 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);
You do need to setup device links for the tunneled protocols as well. Have
you checked if they describe these in DT? I would expect so.
> + 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");
"failed to add domain"
> + 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");
> + ret = -EINVAL;
> + goto err_unlock_tb_domain;
> + }
> +
> + cap_apple = tb_switch_find_vse_cap(anhi->tb->root_switch, TB_VSE_CAP_APPLE);
This should not be done here. It belongs to the CM.
We can do it in tb_start() for example, because only Apple silicon has the
cap.
> +
> + if (cap_apple < 0) {
> + dev_err(anhi->dev, "Unable to find VSE Apple capability: %d\n",
> + cap_apple);
> + ret = cap_apple;
> + goto err_unlock_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);
This we can do in tb.c for the host router unconditionally, I think also in
tb_start().
> + }
> +
> + 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);
> + goto err_unlock_tb_domain;
> + }
> +
> + mutex_unlock(&anhi->tb->lock);
> +
> + acio->nhi_boot_status = 0;
> + complete(&acio->nhi_boot_completion);
> + return 0;
> +
> +err_unlock_tb_domain:
> + mutex_unlock(&anhi->tb->lock);
> + tb_domain_remove(anhi->tb);
> + wait_for_completion(&anhi->nhi.domain_released);
> +err:
> + acio->nhi_boot_status = ret;
> + complete(&acio->nhi_boot_completion);
Empty line here.
> + 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,
> +};
> +
> +/**
> + * apple_cio_stop - Stop and power down the ACIO complex
> + * @acio: ACIO complex to stop
> + *
> + * Remove the NHI and DART children before shutting down the co-processor and
> + * dropping the power-domain links. The Type-C PHY must remain configured
> + * until this function returns. The caller must hold @acio->lock.
> + */
> +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;
> +}
> +
> +/**
> + * apple_cio_start - Power up and start the ACIO complex
> + * @acio: ACIO complex to start
> + *
> + * The Type-C PHY must already be configured for USB4 or Thunderbolt. Power up
> + * ACIO, boot its RTKit co-processor, populate its child devices and wait for
> + * the NHI to register the USB4 domain. All completed steps are unwound on
> + * failure. The caller must hold @acio->lock.
> + *
> + * Return: 0 on success or a negative error code on failure.
> + */
> +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++) {
You can declare link here.
> + 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;
> + }
Empty line.
> + return ret;
> +}
> +
> +/**
> + * apple_cio_tbt_switch_set - Handle a USB4/Thunderbolt cable transition
apple_cio_tb_switch_set() -
> + * @sw: USB4/Thunderbolt switch
> + * @data: New cable state and negotiated cable details
> + *
> + * Translate the cable details into the Apple host-router representation and
> + * synchronously power ACIO up or down. The Type-C PD driver must configure
> + * the PHY before an active transition and must request the inactive state
> + * before shutting the PHY down.
> + *
> + * Return: 0 on success or a negative error code on failure.
> + */
> +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;
Empty line here.
> + case TYPEC_THUNDERBOLT_SWITCH_TBT:
Probably cannot affect these anymore but if can then _TB instead.
> + 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 */
/* Bit 16 .. */
> + 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;
Empty line.
These also may look better with helper functions.
> + 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;
Empty line.
> + 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
This is PM domain I think not to be confused with USB4 domain.
> + * 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,
> + };
Variable declarations to the top of the block.
> + 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,
> + };
Ditto.
> + 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 int apple_cio_prepare(struct device *dev)
> +{
> + struct apple_cio *acio = dev_get_drvdata(dev);
> +
> + guard(mutex)(&acio->lock);
> +
> + if (acio->current_cable_info) {
> + dev_err(dev, "unable to suspend while a USB4/Thunderbolt connection is active\n");
> + return -EBUSY;
> + }
> +
> + return 0;
> +}
> +
> +static const struct dev_pm_ops apple_cio_pm_ops = {
> + .prepare = apple_cio_prepare,
> +};
> +
> +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,
> + .pm = pm_sleep_ptr(&apple_cio_pm_ops),
> + },
> + .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);
Put these right below the functions.
> +
> +MODULE_AUTHOR("Sven Peter <sven@kernel.org>");
> +MODULE_LICENSE("GPL");
> +MODULE_DESCRIPTION("Apple Silicon USB4/Thunderbolt driver");
>
> --
> 2.55.0
>
^ permalink raw reply [flat|nested] 32+ messages in thread* Re: [PATCH 16/19] thunderbolt: Add Apple Silicon support
2026-09-01 10:09 ` Mika Westerberg
@ 2026-09-01 19:06 ` Sven Peter
0 siblings, 0 replies; 32+ messages in thread
From: Sven Peter @ 2026-09-01 19:06 UTC (permalink / raw)
To: Mika Westerberg
Cc: Greg Kroah-Hartman, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Heikki Krogerus, Janne Grunau, Neal Gompa,
Andreas Noever, Mika Westerberg, Yehezkel Bernat, Philipp Zabel,
Konrad Dybcio, linux-usb, devicetree, linux-kernel, asahi,
linux-arm-kernel
Hi,
thanks for the very fast and detailed review! Will address those points
for v2, some comments below:
On 9/1/26 12:09, Mika Westerberg wrote:
> Hi,
>
> On Sun, Aug 30, 2026 at 10:19:34PM +0200, Sven Peter wrote:
>> Add a platform driver for the ACIO host router complex and Native Host
>> Interface (NHI) found on Apple Silicon SoCs.
[...]
>> +
>> + /*
>> + * 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);
> I think we can do this flow in the generic parts too. It makes the driver
> follow the CM guide more closely.
sure, I can adjust that as well.
>
>> +}
>> +
>> +
>> +
>> + ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(42));
> 42?
42! don't ask me why but that's the number of address lines they hooked
up. I'll add a comment.
[...]
>> +
>> + 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);
> You do need to setup device links for the tunneled protocols as well. Have
> you checked if they describe these in DT? I would expect so.
I'll look into that, the DT already has the ports which I'll need for
notifications to PCIe and DP later anyway and I should be able to add
the device links then as well. Right now only USB3 tunnels work (by
accident: I'm not following what macOS does and will probably need a
notification once I get to suspend/resume as well) but I'll see if I can
already add device links. Either way, the dt-binding already has enough
to describe the connections to dwc3/pcie/dp through the graph
ports/endpoints.
>
>> + 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");
>> + ret = -EINVAL;
>> + goto err_unlock_tb_domain;
>> + }
>> +
>> + cap_apple = tb_switch_find_vse_cap(anhi->tb->root_switch, TB_VSE_CAP_APPLE);
> This should not be done here. It belongs to the CM.
>
> We can do it in tb_start() for example, because only Apple silicon has the
> cap.
Ok, I'll find it in there and just expose it to apple.c somehow then to
be able to write the cable info from here.
[...]
>
>> + case TYPEC_THUNDERBOLT_SWITCH_TBT:
> Probably cannot affect these anymore but if can then _TB instead.
That was only introduced in the first patches but after Heikki's review
it'll go away anyway. I'll make sure to TB instead of TBT anywhere else
though!
Thanks,
Sven
^ permalink raw reply [flat|nested] 32+ messages in thread
* [PATCH 17/19] arm64: dts: apple: t8103: Add USB4 ACIO and NHI
2026-08-30 20:19 [PATCH 00/19] Initial USB4/Thunderbolt support for Apple M1/M2/M3 SoCs Sven Peter
` (15 preceding siblings ...)
2026-08-30 20:19 ` [PATCH 16/19] thunderbolt: Add Apple Silicon support Sven Peter
@ 2026-08-30 20:19 ` Sven Peter
2026-09-01 10:20 ` Mika Westerberg
2026-08-30 20:19 ` [PATCH 18/19] arm64: dts: apple: t8112: " Sven Peter
` (2 subsequent siblings)
19 siblings, 1 reply; 32+ messages in thread
From: Sven Peter @ 2026-08-30 20:19 UTC (permalink / raw)
To: Greg Kroah-Hartman, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Heikki Krogerus, Janne Grunau, Neal Gompa,
Andreas Noever, Mika Westerberg, Yehezkel Bernat, Philipp Zabel
Cc: Konrad Dybcio, linux-usb, devicetree, linux-kernel, asahi,
linux-arm-kernel, Sven Peter
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 <sven@kernel.org>
---
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 686fb1dd215d..acdefa2a0084 100644
--- a/arch/arm64/boot/dts/apple/t8103-jxxx.dtsi
+++ b/arch/arm64/boot/dts/apple/t8103-jxxx.dtsi
@@ -17,6 +17,10 @@ aliases {
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 {
@@ -76,6 +80,13 @@ typec0_connector_ss: endpoint {
remote-endpoint = <&atcphy0_typec_lanes>;
};
};
+
+ port@2 {
+ reg = <2>;
+ typec0_con_tbt: endpoint {
+ remote-endpoint = <&usb4_0_acio_tbt>;
+ };
+ };
};
};
};
@@ -107,6 +118,13 @@ typec1_connector_ss: endpoint {
remote-endpoint = <&atcphy1_typec_lanes>;
};
};
+
+ port@2 {
+ reg = <2>;
+ typec1_con_tbt: endpoint {
+ remote-endpoint = <&usb4_1_acio_tbt>;
+ };
+ };
};
};
};
@@ -198,6 +216,49 @@ atcphy1_usb3: endpoint {
};
};
+/* 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 5b68bf0df4e6..f83ed8da9a42 100644
--- a/arch/arm64/boot/dts/apple/t8103.dtsi
+++ b/arch/arm64/boot/dts/apple/t8103.dtsi
@@ -982,6 +982,14 @@ pinctrl_aop: pinctrl@24a820000 {
<AIC_IRQ 272 IRQ_TYPE_LEVEL_HIGH>,
<AIC_IRQ 273 IRQ_TYPE_LEVEL_HIGH>,
<AIC_IRQ 274 IRQ_TYPE_LEVEL_HIGH>;
+
+ acio0_pins: acio0-pins {
+ pinmux = <APPLE_PINMUX(39, 1)>;
+ };
+
+ acio1_pins: acio1-pins {
+ pinmux = <APPLE_PINMUX(41, 1)>;
+ };
};
ans_mbox: mbox@277408000 {
@@ -1018,6 +1026,96 @@ nvme@27bcc0000 {
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 = <AIC_IRQ 812 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 813 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 814 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 815 IRQ_TYPE_LEVEL_HIGH>;
+ 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 = <AIC_IRQ 810 IRQ_TYPE_LEVEL_HIGH>;
+ #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 = <AIC_IRQ 783 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 784 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 785 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 786 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 787 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 788 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 789 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 790 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 791 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 792 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 793 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 794 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 795 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 796 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 797 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 798 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 799 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 800 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 801 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 802 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 803 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 804 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 805 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 806 IRQ_TYPE_LEVEL_HIGH>;
+ 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>;
@@ -1070,6 +1168,96 @@ atcphy0: phy@383000000 {
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 = <AIC_IRQ 892 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 893 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 894 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 895 IRQ_TYPE_LEVEL_HIGH>;
+ 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 = <AIC_IRQ 890 IRQ_TYPE_LEVEL_HIGH>;
+ #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 = <AIC_IRQ 863 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 864 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 865 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 866 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 867 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 868 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 869 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 870 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 871 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 872 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 873 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 874 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 875 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 876 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 877 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 878 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 879 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 880 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 881 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 882 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 883 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 884 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 885 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 886 IRQ_TYPE_LEVEL_HIGH>;
+ 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>;
--
2.55.0
^ permalink raw reply related [flat|nested] 32+ messages in thread* Re: [PATCH 17/19] arm64: dts: apple: t8103: Add USB4 ACIO and NHI
2026-08-30 20:19 ` [PATCH 17/19] arm64: dts: apple: t8103: Add USB4 ACIO and NHI Sven Peter
@ 2026-09-01 10:20 ` Mika Westerberg
0 siblings, 0 replies; 32+ messages in thread
From: Mika Westerberg @ 2026-09-01 10:20 UTC (permalink / raw)
To: Sven Peter
Cc: Greg Kroah-Hartman, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Heikki Krogerus, Janne Grunau, Neal Gompa,
Andreas Noever, Mika Westerberg, Yehezkel Bernat, Philipp Zabel,
Konrad Dybcio, linux-usb, devicetree, linux-kernel, asahi,
linux-arm-kernel
On Sun, Aug 30, 2026 at 10:19:35PM +0200, Sven Peter wrote:
> 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 <sven@kernel.org>
> ---
> 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 686fb1dd215d..acdefa2a0084 100644
> --- a/arch/arm64/boot/dts/apple/t8103-jxxx.dtsi
> +++ b/arch/arm64/boot/dts/apple/t8103-jxxx.dtsi
> @@ -17,6 +17,10 @@ aliases {
> 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 {
> @@ -76,6 +80,13 @@ typec0_connector_ss: endpoint {
> remote-endpoint = <&atcphy0_typec_lanes>;
> };
> };
> +
> + port@2 {
> + reg = <2>;
> + typec0_con_tbt: endpoint {
> + remote-endpoint = <&usb4_0_acio_tbt>;
> + };
> + };
> };
> };
> };
> @@ -107,6 +118,13 @@ typec1_connector_ss: endpoint {
> remote-endpoint = <&atcphy1_typec_lanes>;
> };
> };
> +
> + port@2 {
> + reg = <2>;
> + typec1_con_tbt: endpoint {
> + remote-endpoint = <&usb4_1_acio_tbt>;
> + };
> + };
> };
> };
> };
> @@ -198,6 +216,49 @@ atcphy1_usb3: endpoint {
> };
> };
>
> +/* 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 */
I'm assuming these are all adapters in a router so the 2 here is the lane 1
adapter. One USB4 port has always 2 lane adapters: lane 0 and lane 1.
> + /* 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 */
Same here.
> + /* 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 5b68bf0df4e6..f83ed8da9a42 100644
> --- a/arch/arm64/boot/dts/apple/t8103.dtsi
> +++ b/arch/arm64/boot/dts/apple/t8103.dtsi
> @@ -982,6 +982,14 @@ pinctrl_aop: pinctrl@24a820000 {
> <AIC_IRQ 272 IRQ_TYPE_LEVEL_HIGH>,
> <AIC_IRQ 273 IRQ_TYPE_LEVEL_HIGH>,
> <AIC_IRQ 274 IRQ_TYPE_LEVEL_HIGH>;
> +
> + acio0_pins: acio0-pins {
> + pinmux = <APPLE_PINMUX(39, 1)>;
> + };
> +
> + acio1_pins: acio1-pins {
> + pinmux = <APPLE_PINMUX(41, 1)>;
> + };
> };
>
> ans_mbox: mbox@277408000 {
> @@ -1018,6 +1026,96 @@ nvme@27bcc0000 {
> 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 = <AIC_IRQ 812 IRQ_TYPE_LEVEL_HIGH>,
> + <AIC_IRQ 813 IRQ_TYPE_LEVEL_HIGH>,
> + <AIC_IRQ 814 IRQ_TYPE_LEVEL_HIGH>,
> + <AIC_IRQ 815 IRQ_TYPE_LEVEL_HIGH>;
> + 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 = <AIC_IRQ 810 IRQ_TYPE_LEVEL_HIGH>;
> + #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 = <AIC_IRQ 783 IRQ_TYPE_LEVEL_HIGH>,
> + <AIC_IRQ 784 IRQ_TYPE_LEVEL_HIGH>,
> + <AIC_IRQ 785 IRQ_TYPE_LEVEL_HIGH>,
> + <AIC_IRQ 786 IRQ_TYPE_LEVEL_HIGH>,
> + <AIC_IRQ 787 IRQ_TYPE_LEVEL_HIGH>,
> + <AIC_IRQ 788 IRQ_TYPE_LEVEL_HIGH>,
> + <AIC_IRQ 789 IRQ_TYPE_LEVEL_HIGH>,
> + <AIC_IRQ 790 IRQ_TYPE_LEVEL_HIGH>,
> + <AIC_IRQ 791 IRQ_TYPE_LEVEL_HIGH>,
> + <AIC_IRQ 792 IRQ_TYPE_LEVEL_HIGH>,
> + <AIC_IRQ 793 IRQ_TYPE_LEVEL_HIGH>,
> + <AIC_IRQ 794 IRQ_TYPE_LEVEL_HIGH>,
> + <AIC_IRQ 795 IRQ_TYPE_LEVEL_HIGH>,
> + <AIC_IRQ 796 IRQ_TYPE_LEVEL_HIGH>,
> + <AIC_IRQ 797 IRQ_TYPE_LEVEL_HIGH>,
> + <AIC_IRQ 798 IRQ_TYPE_LEVEL_HIGH>,
> + <AIC_IRQ 799 IRQ_TYPE_LEVEL_HIGH>,
> + <AIC_IRQ 800 IRQ_TYPE_LEVEL_HIGH>,
> + <AIC_IRQ 801 IRQ_TYPE_LEVEL_HIGH>,
> + <AIC_IRQ 802 IRQ_TYPE_LEVEL_HIGH>,
> + <AIC_IRQ 803 IRQ_TYPE_LEVEL_HIGH>,
> + <AIC_IRQ 804 IRQ_TYPE_LEVEL_HIGH>,
> + <AIC_IRQ 805 IRQ_TYPE_LEVEL_HIGH>,
> + <AIC_IRQ 806 IRQ_TYPE_LEVEL_HIGH>;
> + 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>;
> @@ -1070,6 +1168,96 @@ atcphy0: phy@383000000 {
> 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 = <AIC_IRQ 892 IRQ_TYPE_LEVEL_HIGH>,
> + <AIC_IRQ 893 IRQ_TYPE_LEVEL_HIGH>,
> + <AIC_IRQ 894 IRQ_TYPE_LEVEL_HIGH>,
> + <AIC_IRQ 895 IRQ_TYPE_LEVEL_HIGH>;
> + 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 = <AIC_IRQ 890 IRQ_TYPE_LEVEL_HIGH>;
> + #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 = <AIC_IRQ 863 IRQ_TYPE_LEVEL_HIGH>,
> + <AIC_IRQ 864 IRQ_TYPE_LEVEL_HIGH>,
> + <AIC_IRQ 865 IRQ_TYPE_LEVEL_HIGH>,
> + <AIC_IRQ 866 IRQ_TYPE_LEVEL_HIGH>,
> + <AIC_IRQ 867 IRQ_TYPE_LEVEL_HIGH>,
> + <AIC_IRQ 868 IRQ_TYPE_LEVEL_HIGH>,
> + <AIC_IRQ 869 IRQ_TYPE_LEVEL_HIGH>,
> + <AIC_IRQ 870 IRQ_TYPE_LEVEL_HIGH>,
> + <AIC_IRQ 871 IRQ_TYPE_LEVEL_HIGH>,
> + <AIC_IRQ 872 IRQ_TYPE_LEVEL_HIGH>,
> + <AIC_IRQ 873 IRQ_TYPE_LEVEL_HIGH>,
> + <AIC_IRQ 874 IRQ_TYPE_LEVEL_HIGH>,
> + <AIC_IRQ 875 IRQ_TYPE_LEVEL_HIGH>,
> + <AIC_IRQ 876 IRQ_TYPE_LEVEL_HIGH>,
> + <AIC_IRQ 877 IRQ_TYPE_LEVEL_HIGH>,
> + <AIC_IRQ 878 IRQ_TYPE_LEVEL_HIGH>,
> + <AIC_IRQ 879 IRQ_TYPE_LEVEL_HIGH>,
> + <AIC_IRQ 880 IRQ_TYPE_LEVEL_HIGH>,
> + <AIC_IRQ 881 IRQ_TYPE_LEVEL_HIGH>,
> + <AIC_IRQ 882 IRQ_TYPE_LEVEL_HIGH>,
> + <AIC_IRQ 883 IRQ_TYPE_LEVEL_HIGH>,
> + <AIC_IRQ 884 IRQ_TYPE_LEVEL_HIGH>,
> + <AIC_IRQ 885 IRQ_TYPE_LEVEL_HIGH>,
> + <AIC_IRQ 886 IRQ_TYPE_LEVEL_HIGH>;
> + 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>;
>
> --
> 2.55.0
>
^ permalink raw reply [flat|nested] 32+ messages in thread
* [PATCH 18/19] arm64: dts: apple: t8112: Add USB4 ACIO and NHI
2026-08-30 20:19 [PATCH 00/19] Initial USB4/Thunderbolt support for Apple M1/M2/M3 SoCs Sven Peter
` (16 preceding siblings ...)
2026-08-30 20:19 ` [PATCH 17/19] arm64: dts: apple: t8103: Add USB4 ACIO and NHI Sven Peter
@ 2026-08-30 20:19 ` Sven Peter
2026-08-30 20:19 ` [PATCH 19/19] arm64: dts: apple: t60xx: " Sven Peter
2026-08-31 17:44 ` [PATCH 00/19] Initial USB4/Thunderbolt support for Apple M1/M2/M3 SoCs Joshua Peisach
19 siblings, 0 replies; 32+ messages in thread
From: Sven Peter @ 2026-08-30 20:19 UTC (permalink / raw)
To: Greg Kroah-Hartman, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Heikki Krogerus, Janne Grunau, Neal Gompa,
Andreas Noever, Mika Westerberg, Yehezkel Bernat, Philipp Zabel
Cc: Konrad Dybcio, linux-usb, devicetree, linux-kernel, asahi,
linux-arm-kernel, Sven Peter
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 <sven@kernel.org>
---
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 562e7a25a1e8..4c9654f169f4 100644
--- a/arch/arm64/boot/dts/apple/t8112-jxxx.dtsi
+++ b/arch/arm64/boot/dts/apple/t8112-jxxx.dtsi
@@ -13,6 +13,10 @@ / {
aliases {
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;
serial0 = &serial0;
serial2 = &serial2;
};
@@ -76,6 +80,13 @@ typec0_connector_ss: endpoint {
remote-endpoint = <&atcphy0_typec_lanes>;
};
};
+
+ port@2 {
+ reg = <2>;
+ typec0_con_tbt: endpoint {
+ remote-endpoint = <&usb4_0_acio_tbt>;
+ };
+ };
};
};
};
@@ -107,6 +118,13 @@ typec1_connector_ss: endpoint {
remote-endpoint = <&atcphy1_typec_lanes>;
};
};
+
+ port@2 {
+ reg = <2>;
+ typec1_con_tbt: endpoint {
+ remote-endpoint = <&usb4_1_acio_tbt>;
+ };
+ };
};
};
};
@@ -198,6 +216,49 @@ atcphy1_usb3: endpoint {
};
};
+/* 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 ec248ca052cb..735ca3b8b716 100644
--- a/arch/arm64/boot/dts/apple/t8112.dtsi
+++ b/arch/arm64/boot/dts/apple/t8112.dtsi
@@ -985,6 +985,14 @@ pinctrl_aop: pinctrl@24a820000 {
<AIC_IRQ 305 IRQ_TYPE_LEVEL_HIGH>,
<AIC_IRQ 306 IRQ_TYPE_LEVEL_HIGH>,
<AIC_IRQ 307 IRQ_TYPE_LEVEL_HIGH>;
+
+ acio0_pins: acio0-pins {
+ pinmux = <APPLE_PINMUX(28, 1)>;
+ };
+
+ acio1_pins: acio1-pins {
+ pinmux = <APPLE_PINMUX(30, 1)>;
+ };
};
ans_mbox: mbox@277408000 {
@@ -1021,6 +1029,97 @@ nvme@27bcc0000 {
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 = <AIC_IRQ 1067 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 1068 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 1069 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 1070 IRQ_TYPE_LEVEL_HIGH>;
+ 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 = <AIC_IRQ 1065 IRQ_TYPE_LEVEL_HIGH>;
+ #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 = <AIC_IRQ 1038 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 1039 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 1040 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 1041 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 1042 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 1043 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 1044 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 1045 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 1046 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 1047 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 1048 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 1049 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 1050 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 1051 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 1052 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 1053 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 1054 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 1055 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 1056 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 1057 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 1058 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 1059 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 1060 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 1061 IRQ_TYPE_LEVEL_HIGH>;
+ 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>;
@@ -1073,6 +1172,97 @@ atcphy0: phy@383000000 {
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 = <AIC_IRQ 1148 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 1149 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 1150 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 1151 IRQ_TYPE_LEVEL_HIGH>;
+ 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 = <AIC_IRQ 1146 IRQ_TYPE_LEVEL_HIGH>;
+ #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 = <AIC_IRQ 1119 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 1120 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 1121 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 1122 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 1123 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 1124 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 1125 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 1126 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 1127 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 1128 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 1129 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 1130 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 1131 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 1132 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 1133 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 1134 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 1135 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 1136 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 1137 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 1138 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 1139 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 1140 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 1141 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 1142 IRQ_TYPE_LEVEL_HIGH>;
+ 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>;
--
2.55.0
^ permalink raw reply related [flat|nested] 32+ messages in thread* [PATCH 19/19] arm64: dts: apple: t60xx: Add USB4 ACIO and NHI
2026-08-30 20:19 [PATCH 00/19] Initial USB4/Thunderbolt support for Apple M1/M2/M3 SoCs Sven Peter
` (17 preceding siblings ...)
2026-08-30 20:19 ` [PATCH 18/19] arm64: dts: apple: t8112: " Sven Peter
@ 2026-08-30 20:19 ` Sven Peter
2026-08-31 17:44 ` [PATCH 00/19] Initial USB4/Thunderbolt support for Apple M1/M2/M3 SoCs Joshua Peisach
19 siblings, 0 replies; 32+ messages in thread
From: Sven Peter @ 2026-08-30 20:19 UTC (permalink / raw)
To: Greg Kroah-Hartman, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Heikki Krogerus, Janne Grunau, Neal Gompa,
Andreas Noever, Mika Westerberg, Yehezkel Bernat, Philipp Zabel
Cc: Konrad Dybcio, linux-usb, devicetree, linux-kernel, asahi,
linux-arm-kernel, Sven Peter
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 <sven@kernel.org>
---
arch/arm64/boot/dts/apple/t6002-j375d.dts | 67 +++++
arch/arm64/boot/dts/apple/t600x-dieX.dtsi | 384 +++++++++++++++++++++++++
arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi | 107 +++++++
arch/arm64/boot/dts/apple/t600x-j375.dtsi | 137 +++++++++
arch/arm64/boot/dts/apple/t6022-j180d.dts | 202 +++++++++++++
arch/arm64/boot/dts/apple/t6022-j475d.dts | 10 +
arch/arm64/boot/dts/apple/t6022-jxxxd.dtsi | 57 ++++
arch/arm64/boot/dts/apple/t602x-dieX.dtsi | 384 +++++++++++++++++++++++++
8 files changed, 1348 insertions(+)
diff --git a/arch/arm64/boot/dts/apple/t6002-j375d.dts b/arch/arm64/boot/dts/apple/t6002-j375d.dts
index e6eb6b6c386d..c28f5c604550 100644
--- a/arch/arm64/boot/dts/apple/t6002-j375d.dts
+++ b/arch/arm64/boot/dts/apple/t6002-j375d.dts
@@ -18,6 +18,10 @@ / {
aliases {
atcphy4 = &atcphy0_die1;
atcphy5 = &atcphy1_die1;
+ usb4-4-acio = &usb4_0_acio_die1;
+ usb4-4-nhi = &usb4_0_nhi_die1;
+ usb4-5-acio = &usb4_1_acio_die1;
+ usb4-5-nhi = &usb4_1_nhi_die1;
};
};
@@ -52,6 +56,13 @@ typec4_connector_ss: endpoint {
remote-endpoint = <&atcphy4_typec_lanes>;
};
};
+
+ port@2 {
+ reg = <2>;
+ typec4_con_tbt: endpoint {
+ remote-endpoint = <&usb4_4_acio_tbt>;
+ };
+ };
};
};
};
@@ -85,6 +96,13 @@ typec5_connector_ss: endpoint {
remote-endpoint = <&atcphy5_typec_lanes>;
};
};
+
+ port@2 {
+ reg = <2>;
+ typec5_con_tbt: endpoint {
+ remote-endpoint = <&usb4_5_acio_tbt>;
+ };
+ };
};
};
};
@@ -186,8 +204,57 @@ atcphy5_usb3: endpoint {
#include "hwmon-fan-dual.dtsi"
+/* USB4 on die 1 */
+&usb4_0_acio_die1 {
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ /* 1: USB4 port */
+ port@1 {
+ reg = <1>;
+ usb4_4_acio_tbt: endpoint {
+ remote-endpoint = <&typec4_con_tbt>;
+ };
+ };
+
+ /* 2: unused(?) USB4 port */
+ /* 3: PCIe */
+ /* 4: USB3 */
+ /* 5: DP0 */
+ /* 6: DP1 */
+ };
+};
+
+&usb4_1_acio_die1 {
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ /* 1: USB4 port */
+ port@1 {
+ reg = <1>;
+ usb4_5_acio_tbt: endpoint {
+ remote-endpoint = <&typec5_con_tbt>;
+ };
+ };
+
+ /* 2: unused(?) USB4 port */
+ /* 3: PCIe */
+ /* 4: USB3 */
+ /* 5: DP0 */
+ /* 6: DP1 */
+ };
+};
+
/* delete unused USB nodes on die 1 */
+/delete-node/ &usb4_2_acio_die1;
+/delete-node/ &usb4_2_mbox_die1;
+
+/delete-node/ &usb4_3_acio_die1;
+/delete-node/ &usb4_3_mbox_die1;
+
/delete-node/ &dwc3_2_dart_0_die1;
/delete-node/ &dwc3_2_dart_1_die1;
/delete-node/ &dwc3_2_die1;
diff --git a/arch/arm64/boot/dts/apple/t600x-dieX.dtsi b/arch/arm64/boot/dts/apple/t600x-dieX.dtsi
index 9676d5127039..98867855454d 100644
--- a/arch/arm64/boot/dts/apple/t600x-dieX.dtsi
+++ b/arch/arm64/boot/dts/apple/t600x-dieX.dtsi
@@ -93,6 +93,22 @@ DIE_NODE(pinctrl_aop): pinctrl@293820000 {
<AIC_IRQ DIE_NO 571 IRQ_TYPE_LEVEL_HIGH>,
<AIC_IRQ DIE_NO 572 IRQ_TYPE_LEVEL_HIGH>,
<AIC_IRQ DIE_NO 573 IRQ_TYPE_LEVEL_HIGH>;
+
+ DIE_NODE(acio0_pins): acio0-pins {
+ pinmux = <APPLE_PINMUX(39, 1)>;
+ };
+
+ DIE_NODE(acio1_pins): acio1-pins {
+ pinmux = <APPLE_PINMUX(41, 1)>;
+ };
+
+ DIE_NODE(acio2_pins): acio2-pins {
+ pinmux = <APPLE_PINMUX(43, 1)>;
+ };
+
+ DIE_NODE(acio3_pins): acio3-pins {
+ pinmux = <APPLE_PINMUX(45, 1)>;
+ };
};
DIE_NODE(pinctrl_ap): pinctrl@39b028000 {
@@ -120,6 +136,98 @@ DIE_NODE(pinctrl_ap): pinctrl@39b028000 {
#interrupt-cells = <2>;
};
+ 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 = <AIC_IRQ DIE_NO 1311 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1312 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1313 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1314 IRQ_TYPE_LEVEL_HIGH>;
+ 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 = <AIC_IRQ DIE_NO 1309 IRQ_TYPE_LEVEL_HIGH>;
+ #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 = <AIC_IRQ DIE_NO 1282 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1283 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1284 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1285 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1286 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1287 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1288 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1289 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1290 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1291 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1292 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1293 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1294 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1295 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1296 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1297 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1298 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1299 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1300 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1301 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1302 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1303 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1304 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1305 IRQ_TYPE_LEVEL_HIGH>;
+ 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>;
@@ -173,6 +281,98 @@ DIE_NODE(atcphy0): phy@703000000 {
power-domains = <&DIE_NODE(ps_atc0_usb)>;
};
+ 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 = <AIC_IRQ DIE_NO 1359 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1360 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1361 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1362 IRQ_TYPE_LEVEL_HIGH>;
+ 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 = <AIC_IRQ DIE_NO 1357 IRQ_TYPE_LEVEL_HIGH>;
+ #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 = <AIC_IRQ DIE_NO 1330 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1331 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1332 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1333 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1334 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1335 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1336 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1337 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1338 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1339 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1340 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1341 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1342 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1343 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1344 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1345 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1346 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1347 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1348 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1349 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1350 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1351 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1352 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1353 IRQ_TYPE_LEVEL_HIGH>;
+ 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>;
@@ -226,6 +426,98 @@ DIE_NODE(atcphy1): phy@b03000000 {
power-domains = <&DIE_NODE(ps_atc1_usb)>;
};
+ 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 = <AIC_IRQ DIE_NO 1407 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1408 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1409 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1410 IRQ_TYPE_LEVEL_HIGH>;
+ 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 = <AIC_IRQ DIE_NO 1405 IRQ_TYPE_LEVEL_HIGH>;
+ #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 = <AIC_IRQ DIE_NO 1378 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1379 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1380 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1381 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1382 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1383 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1384 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1385 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1386 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1387 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1388 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1389 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1390 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1391 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1392 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1393 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1394 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1395 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1396 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1397 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1398 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1399 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1400 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1401 IRQ_TYPE_LEVEL_HIGH>;
+ 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>;
@@ -279,6 +571,98 @@ DIE_NODE(atcphy2): phy@f03000000 {
power-domains = <&DIE_NODE(ps_atc2_usb)>;
};
+ 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 = <AIC_IRQ DIE_NO 1455 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1456 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1457 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1458 IRQ_TYPE_LEVEL_HIGH>;
+ 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 = <AIC_IRQ DIE_NO 1453 IRQ_TYPE_LEVEL_HIGH>;
+ #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 = <AIC_IRQ DIE_NO 1426 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1427 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1428 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1429 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1430 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1431 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1432 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1433 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1434 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1435 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1436 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1437 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1438 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1439 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1440 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1441 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1442 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1443 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1444 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1445 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1446 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1447 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1448 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1449 IRQ_TYPE_LEVEL_HIGH>;
+ 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 caded1636f3d..38b7d0c3a585 100644
--- a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi
+++ b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi
@@ -21,6 +21,12 @@ aliases {
atcphy3 = &atcphy3;
bluetooth0 = &bluetooth0;
serial0 = &serial0;
+ 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;
};
@@ -366,6 +372,107 @@ &atcphy3 {
status = "disabled";
};
+&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 "hwmon-laptop.dtsi"
#include "hwmon-fan-dual.dtsi"
diff --git a/arch/arm64/boot/dts/apple/t600x-j375.dtsi b/arch/arm64/boot/dts/apple/t600x-j375.dtsi
index 8a1494949e4c..d081cb03b03b 100644
--- a/arch/arm64/boot/dts/apple/t600x-j375.dtsi
+++ b/arch/arm64/boot/dts/apple/t600x-j375.dtsi
@@ -20,6 +20,14 @@ aliases {
bluetooth0 = &bluetooth0;
ethernet0 = ðernet0;
serial0 = &serial0;
+ 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;
};
@@ -410,4 +418,133 @@ &pcie0_dart_3 {
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"
diff --git a/arch/arm64/boot/dts/apple/t6022-j180d.dts b/arch/arm64/boot/dts/apple/t6022-j180d.dts
index f76b887429dd..27323fb51dd1 100644
--- a/arch/arm64/boot/dts/apple/t6022-j180d.dts
+++ b/arch/arm64/boot/dts/apple/t6022-j180d.dts
@@ -26,6 +26,22 @@ aliases {
atcphy5 = &atcphy1_die1;
atcphy6 = &atcphy2_die1;
atcphy7 = &atcphy3_die1;
+ 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;
+ usb4-4-acio = &usb4_0_acio_die1;
+ usb4-4-nhi = &usb4_0_nhi_die1;
+ usb4-5-acio = &usb4_1_acio_die1;
+ usb4-5-nhi = &usb4_1_nhi_die1;
+ usb4-6-acio = &usb4_2_acio_die1;
+ usb4-6-nhi = &usb4_2_nhi_die1;
+ usb4-7-acio = &usb4_3_acio_die1;
+ usb4-7-nhi = &usb4_3_nhi_die1;
nvram = &nvram;
serial0 = &serial0;
};
@@ -152,6 +168,13 @@ typec6_connector_ss: endpoint {
remote-endpoint = <&atcphy6_typec_lanes>;
};
};
+
+ port@2 {
+ reg = <2>;
+ typec6_con_tbt: endpoint {
+ remote-endpoint = <&usb4_6_acio_tbt>;
+ };
+ };
};
};
};
@@ -184,6 +207,13 @@ typec7_connector_ss: endpoint {
remote-endpoint = <&atcphy7_typec_lanes>;
};
};
+
+ port@2 {
+ reg = <2>;
+ typec7_con_tbt: endpoint {
+ remote-endpoint = <&usb4_7_acio_tbt>;
+ };
+ };
};
};
};
@@ -535,4 +565,176 @@ &nco_clkref {
clock-frequency = <1068000000>;
};
+/* USB4 on die 1 */
+&usb4_2_acio_die1 {
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ /* 1: USB4 port */
+ port@1 {
+ reg = <1>;
+ usb4_6_acio_tbt: endpoint {
+ remote-endpoint = <&typec6_con_tbt>;
+ };
+ };
+
+ /* 2: unused(?) USB4 port */
+ /* 3: PCIe */
+ /* 4: USB3 */
+ /* 5: DP0 */
+ /* 6: DP1 */
+ };
+};
+
+&usb4_3_acio_die1 {
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ /* 1: USB4 port */
+ port@1 {
+ reg = <1>;
+ usb4_7_acio_tbt: endpoint {
+ remote-endpoint = <&typec7_con_tbt>;
+ };
+ };
+
+ /* 2: unused(?) USB4 port */
+ /* 3: PCIe */
+ /* 4: USB3 */
+ /* 5: DP0 */
+ /* 6: DP1 */
+ };
+};
+
+&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"
diff --git a/arch/arm64/boot/dts/apple/t6022-j475d.dts b/arch/arm64/boot/dts/apple/t6022-j475d.dts
index 32938a3bf96a..d99633160beb 100644
--- a/arch/arm64/boot/dts/apple/t6022-j475d.dts
+++ b/arch/arm64/boot/dts/apple/t6022-j475d.dts
@@ -20,6 +20,10 @@ / {
aliases {
atcphy4 = &atcphy0_die1;
atcphy5 = &atcphy1_die1;
+ usb4-4-acio = &usb4_0_acio_die1;
+ usb4-4-nhi = &usb4_0_nhi_die1;
+ usb4-5-acio = &usb4_1_acio_die1;
+ usb4-5-nhi = &usb4_1_nhi_die1;
};
};
@@ -57,6 +61,12 @@ &typec5 {
/delete-node/ &dwc3_3_die1;
/delete-node/ &atcphy3_die1;
+/delete-node/ &usb4_2_acio_die1;
+/delete-node/ &usb4_2_mbox_die1;
+
+/delete-node/ &usb4_3_acio_die1;
+/delete-node/ &usb4_3_mbox_die1;
+
/* delete unused always-on power-domains on die 1 */
/delete-node/ &ps_atc2_usb_aon_die1;
/delete-node/ &ps_atc2_usb_die1;
diff --git a/arch/arm64/boot/dts/apple/t6022-jxxxd.dtsi b/arch/arm64/boot/dts/apple/t6022-jxxxd.dtsi
index dc877bd604f8..944a79d30fd7 100644
--- a/arch/arm64/boot/dts/apple/t6022-jxxxd.dtsi
+++ b/arch/arm64/boot/dts/apple/t6022-jxxxd.dtsi
@@ -46,6 +46,13 @@ typec4_connector_ss: endpoint {
remote-endpoint = <&atcphy4_typec_lanes>;
};
};
+
+ port@2 {
+ reg = <2>;
+ typec4_con_tbt: endpoint {
+ remote-endpoint = <&usb4_4_acio_tbt>;
+ };
+ };
};
};
};
@@ -78,6 +85,13 @@ typec5_connector_ss: endpoint {
remote-endpoint = <&atcphy5_typec_lanes>;
};
};
+
+ port@2 {
+ reg = <2>;
+ typec5_con_tbt: endpoint {
+ remote-endpoint = <&usb4_5_acio_tbt>;
+ };
+ };
};
};
};
@@ -169,3 +183,46 @@ atcphy5_usb3: endpoint {
};
};
};
+
+/* USB4 on die 1 */
+&usb4_0_acio_die1 {
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ /* 1: USB4 port */
+ port@1 {
+ reg = <1>;
+ usb4_4_acio_tbt: endpoint {
+ remote-endpoint = <&typec4_con_tbt>;
+ };
+ };
+
+ /* 2: unused(?) USB4 port */
+ /* 3: PCIe */
+ /* 4: USB3 */
+ /* 5: DP0 */
+ /* 6: DP1 */
+ };
+};
+
+&usb4_1_acio_die1 {
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ /* 1: USB4 port */
+ port@1 {
+ reg = <1>;
+ usb4_5_acio_tbt: endpoint {
+ remote-endpoint = <&typec5_con_tbt>;
+ };
+ };
+
+ /* 2: unused(?) USB4 port */
+ /* 3: PCIe */
+ /* 4: USB3 */
+ /* 5: DP0 */
+ /* 6: DP1 */
+ };
+};
diff --git a/arch/arm64/boot/dts/apple/t602x-dieX.dtsi b/arch/arm64/boot/dts/apple/t602x-dieX.dtsi
index ae3d535c5acb..dae7b81c13d0 100644
--- a/arch/arm64/boot/dts/apple/t602x-dieX.dtsi
+++ b/arch/arm64/boot/dts/apple/t602x-dieX.dtsi
@@ -92,6 +92,22 @@ DIE_NODE(pinctrl_aop): pinctrl@2a6820000 {
<AIC_IRQ DIE_NO 602 IRQ_TYPE_LEVEL_HIGH>,
<AIC_IRQ DIE_NO 603 IRQ_TYPE_LEVEL_HIGH>,
<AIC_IRQ DIE_NO 604 IRQ_TYPE_LEVEL_HIGH>;
+
+ DIE_NODE(acio0_pins): acio0-pins {
+ pinmux = <APPLE_PINMUX(65, 1)>;
+ };
+
+ DIE_NODE(acio1_pins): acio1-pins {
+ pinmux = <APPLE_PINMUX(67, 1)>;
+ };
+
+ DIE_NODE(acio2_pins): acio2-pins {
+ pinmux = <APPLE_PINMUX(69, 1)>;
+ };
+
+ DIE_NODE(acio3_pins): acio3-pins {
+ pinmux = <APPLE_PINMUX(71, 1)>;
+ };
};
DIE_NODE(pinctrl_ap): pinctrl@39b028000 {
@@ -127,6 +143,98 @@ DIE_NODE(pmgr_gfx): power-management@404e80000 {
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 = <AIC_IRQ DIE_NO 822 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 823 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 824 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 825 IRQ_TYPE_LEVEL_HIGH>;
+ 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 = <AIC_IRQ DIE_NO 1387 IRQ_TYPE_LEVEL_HIGH>;
+ #iommu-cells = <5>;
+ };
+
+ 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 = <AIC_IRQ DIE_NO 1360 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1361 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1362 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1363 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1364 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1365 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1366 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1367 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1368 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1369 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1370 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1371 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1372 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1373 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1374 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1375 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1376 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1377 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1378 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1379 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1380 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1381 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1382 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1383 IRQ_TYPE_LEVEL_HIGH>;
+ 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 0x100 0x0 0x10 0x0>,
+ <&DIE_NODE(usb4_0_dart) 2 0x100 0x0 0x10 0x0>;
+ /* 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>;
@@ -180,6 +288,98 @@ DIE_NODE(atcphy0): phy@703000000 {
power-domains = <&DIE_NODE(ps_atc0_usb)>;
};
+ 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 = <AIC_IRQ DIE_NO 826 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 827 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 828 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 829 IRQ_TYPE_LEVEL_HIGH>;
+ 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 = <AIC_IRQ DIE_NO 1435 IRQ_TYPE_LEVEL_HIGH>;
+ #iommu-cells = <5>;
+ };
+
+ 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 = <AIC_IRQ DIE_NO 1408 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1409 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1410 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1411 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1412 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1413 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1414 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1415 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1416 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1417 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1418 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1419 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1420 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1421 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1422 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1423 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1424 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1425 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1426 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1427 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1428 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1429 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1430 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1431 IRQ_TYPE_LEVEL_HIGH>;
+ 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 0x100 0x0 0x10 0x0>,
+ <&DIE_NODE(usb4_1_dart) 2 0x100 0x0 0x10 0x0>;
+ /* 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>;
@@ -233,6 +433,98 @@ DIE_NODE(atcphy1): phy@b03000000 {
power-domains = <&DIE_NODE(ps_atc1_usb)>;
};
+ 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 = <AIC_IRQ DIE_NO 830 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 831 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 832 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 833 IRQ_TYPE_LEVEL_HIGH>;
+ 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 = <AIC_IRQ DIE_NO 1483 IRQ_TYPE_LEVEL_HIGH>;
+ #iommu-cells = <5>;
+ };
+
+ 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 = <AIC_IRQ DIE_NO 1456 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1457 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1458 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1459 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1460 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1461 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1462 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1463 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1464 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1465 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1466 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1467 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1468 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1469 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1470 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1471 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1472 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1473 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1474 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1475 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1476 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1477 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1478 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1479 IRQ_TYPE_LEVEL_HIGH>;
+ 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 0x100 0x0 0x10 0x0>,
+ <&DIE_NODE(usb4_2_dart) 2 0x100 0x0 0x10 0x0>;
+ /* 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>;
@@ -286,6 +578,98 @@ DIE_NODE(atcphy2): phy@f03000000 {
power-domains = <&DIE_NODE(ps_atc2_usb)>;
};
+ 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 = <AIC_IRQ DIE_NO 834 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 835 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 836 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 837 IRQ_TYPE_LEVEL_HIGH>;
+ 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 = <AIC_IRQ DIE_NO 1531 IRQ_TYPE_LEVEL_HIGH>;
+ #iommu-cells = <5>;
+ };
+
+ 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 = <AIC_IRQ DIE_NO 1504 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1505 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1506 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1507 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1508 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1509 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1510 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1511 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1512 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1513 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1514 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1515 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1516 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1517 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1518 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1519 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1520 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1521 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1522 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1523 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1524 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1525 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1526 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ DIE_NO 1527 IRQ_TYPE_LEVEL_HIGH>;
+ 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 0x100 0x0 0x10 0x0>,
+ <&DIE_NODE(usb4_3_dart) 2 0x100 0x0 0x10 0x0>;
+ /* 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>;
--
2.55.0
^ permalink raw reply related [flat|nested] 32+ messages in thread* Re: [PATCH 00/19] Initial USB4/Thunderbolt support for Apple M1/M2/M3 SoCs
2026-08-30 20:19 [PATCH 00/19] Initial USB4/Thunderbolt support for Apple M1/M2/M3 SoCs Sven Peter
` (18 preceding siblings ...)
2026-08-30 20:19 ` [PATCH 19/19] arm64: dts: apple: t60xx: " Sven Peter
@ 2026-08-31 17:44 ` Joshua Peisach
19 siblings, 0 replies; 32+ messages in thread
From: Joshua Peisach @ 2026-08-31 17:44 UTC (permalink / raw)
To: Sven Peter, Greg Kroah-Hartman, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Heikki Krogerus, Janne Grunau, Neal Gompa,
Andreas Noever, Mika Westerberg, Yehezkel Bernat, Philipp Zabel
Cc: Konrad Dybcio, linux-usb, devicetree, linux-kernel, asahi,
linux-arm-kernel
On Sun Aug 30, 2026 at 4:19 PM EDT, Sven Peter wrote:
> Hi,
>
> This series adds initial USB4/Thunderbolt support for Apple Silicon
> M1, M2 and M3 SoCs. Each USB4 capable Type-C port comes with an ACIO
> ("Apple Converged I/O") block which contains a Cortex-M3 co-processor
> and the hardware implementing the USB4 router.
>
> The NHI and DART IOMMU are part of this ACIO block as well. Instead of
> being exposed as independent always-accessible devices the main SoC bus
> only gets a 16 MiB window into ACIO's MMIO space while its co-processor
> is running. It is not entirely clear whether this is ACIO's own address
> space or a window into the co-processor's address space.
>
> This is why the device tree represents the NHI and DART as children of
> ACIO with addresses relative to that window. The driver only populates
> these children after booting the co-processor and removes them again
> before powering ACIO down. This setup is slightly unusual but matches
> both the MMIO layout and the lifetime of the devices.
>
> ACIO also has rather strict ordering requirements: it can only be
> started after the Type-C PHY has been configured for USB4/Thunderbolt
> and has to be stopped again before the PHY is powered down. It also
> needs cable details which are only known to the Type-C port controller.
> The existing Type-C mode switch cannot express the required reverse
> teardown order, so the first three patches add a separate Thunderbolt
> switch and hook it up to the CD321x port controller.
>
> The next two patches add the bindings for the NHI and ACIO. The
> following patches then prepare the Thunderbolt core for the slightly
> different NHI register layout and interrupt handling, read the USB4
> router DROM from the device tree and add quirks for functionality not
> implemented by the Apple hardware.
>
> The main driver powers up ACIO, boots the co-processor, populates
> the DART and NHI children once their MMIO regions are accessible and
> registers the NHI with the software connection manager. The last three
> patches add the device tree nodes for the M1 and M2. M3 nodes will follow
> once the rest of the stack is upstream, but the current Thunderbolt/USB4
> code has already been successfully tested on those.
>
> Right now only XDomain connections and USB3-via-USB4 tunnels are
> supported. Both PCIe and DisplayPort tunnels will come later since those
> require additional work and reverse engineering that is not done yet.
> Suspend is also not supported yet and we actually have to prevent
> suspending whenever an active connection is present because we would
> otherwise resume into an SError.
>
> This series depends on the Apple CIO reset controller [1], the ATCPHY
> USB3-via-4 pipehandler support [2] and support for specifying the DART DMA
> aperture in the device tree [3], a fix for CD321x Thunderbolt
> altmode VDOs [4], and fixes for making the DPTX capabilities read fail
> gracefully [5]. I couldn't split it any further since the remaining
> patches now directly depend upon each other.
> Except for the dts changes the series applies cleanly without these
> dependencies though.
>
> For merging it probably makes most sense if all of this (except for the
> DTS changes) is taken through the thunderbolt tree once everything's been
> reviewed.
>
> Best,
>
> Sven
>
> [1] https://patch.msgid.link/20260821-b4-cio-reset-v2-0-2d045f80a424@kernel.org
> [2] https://patch.msgid.link/20260821-b4-atcphy-usb4-v1-0-45c0b741e0c0@kernel.org
> [3] https://patch.msgid.link/20260819-iommu-apple-dart-aperture-v1-0-252703f381aa@jannau.net
> [4] https://patch.msgid.link/20260813-b4-tipd-vdo-fix-v1-1-70317f2cd554@kernel.org
> [5] https://patch.msgid.link/20260829-b4-tbt-fixes-v3-0-e1fab6ac54fe@kernel.org
>
> Signed-off-by: Sven Peter <sven@kernel.org>
> ---
> Sven Peter (19):
> dt-bindings: usb: Add thunderbolt-switch property
> usb: typec: Add thunderbolt switch
> usb: typec: tipd: Hook up Thunderbolt switch for CD321x
> dt-bindings: thunderbolt: Add Apple USB4/Thunderbolt NHI
> dt-bindings: thunderbolt: Add Apple USB4/Thunderbolt ACIO block
> thunderbolt: Try reading host DROM from device tree first
> thunderbolt: Don't read the UID if we already know it
> thunderbolt: Allocate ring HopID before requesting the ring interrupt
> thunderbolt: Add ring_interrupt_active to tb_nhi_ops
> thunderbolt: Make the ring register layout configurable
> thunderbolt: Add ring_interrupt_mask to tb_nhi_ops
> thunderbolt: Add ring_configure to tb_nhi_ops
> thunderbolt: Add QUIRK_NO_DMA_PORT
> thunderbolt: Add QUIRK_NO_USB3_BW_ALLOC
> thunderbolt: Export symbols required by the Apple Silicon driver
> thunderbolt: Add Apple Silicon support
> arm64: dts: apple: t8103: Add USB4 ACIO and NHI
> arm64: dts: apple: t8112: Add USB4 ACIO and NHI
> arm64: dts: apple: t60xx: Add USB4 ACIO and NHI
>
> .../thunderbolt/apple,t8103-usb4-acio.yaml | 218 +++++
> .../bindings/thunderbolt/apple,t8103-usb4-nhi.yaml | 151 +++
> .../devicetree/bindings/usb/usb-switch.yaml | 6 +
> MAINTAINERS | 4 +
> arch/arm64/boot/dts/apple/t6002-j375d.dts | 67 ++
> arch/arm64/boot/dts/apple/t600x-dieX.dtsi | 384 ++++++++
> arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi | 107 ++
> arch/arm64/boot/dts/apple/t600x-j375.dtsi | 137 +++
> arch/arm64/boot/dts/apple/t6022-j180d.dts | 202 ++++
> arch/arm64/boot/dts/apple/t6022-j475d.dts | 10 +
> arch/arm64/boot/dts/apple/t6022-jxxxd.dtsi | 57 ++
> arch/arm64/boot/dts/apple/t602x-dieX.dtsi | 384 ++++++++
> arch/arm64/boot/dts/apple/t8103-jxxx.dtsi | 61 ++
> arch/arm64/boot/dts/apple/t8103.dtsi | 188 ++++
> arch/arm64/boot/dts/apple/t8112-jxxx.dtsi | 61 ++
> arch/arm64/boot/dts/apple/t8112.dtsi | 190 ++++
> drivers/thunderbolt/Kconfig | 13 +
> drivers/thunderbolt/Makefile | 3 +
> drivers/thunderbolt/apple.c | 1028 ++++++++++++++++++++
> drivers/thunderbolt/cap.c | 2 +
> drivers/thunderbolt/ctl.c | 2 +
> drivers/thunderbolt/domain.c | 2 +
> drivers/thunderbolt/eeprom.c | 32 +-
> drivers/thunderbolt/nhi.c | 132 ++-
> drivers/thunderbolt/nhi.h | 32 +
> drivers/thunderbolt/switch.c | 19 +-
> drivers/thunderbolt/tb.c | 18 +
> drivers/thunderbolt/tb.h | 5 +
> drivers/thunderbolt/tunnel.c | 59 +-
> drivers/usb/typec/mux.c | 201 ++++
> drivers/usb/typec/mux.h | 12 +
> drivers/usb/typec/tipd/core.c | 37 +
> include/linux/thunderbolt.h | 3 +
> include/linux/usb/typec_mux.h | 114 +++
> 34 files changed, 3872 insertions(+), 69 deletions(-)
> ---
> base-commit: 78bb208b99e7d3314f670710fa9ee0a793682bca
> change-id: 20260829-b4-apple-soc-tbt-a00e873a52db
>
> Best regards,
> --
> Sven Peter <sven@kernel.org>
I unfortunately cannot test (afaik I do not have the devices) but a
glance over everything seems reasonable :)
Reviewed-by: Joshua Peisach <jpeisach@ubuntu.com>
^ permalink raw reply [flat|nested] 32+ messages in thread