* [PATCH v2 00/22] Initial USB4/Thunderbolt support for Apple M1/M2/M3 SoCs
@ 2026-09-06 18:36 Sven Peter
2026-09-06 18:36 ` [PATCH v2 01/22] usb: typec: Add alternate mode state notifiers Sven Peter
` (22 more replies)
0 siblings, 23 replies; 41+ messages in thread
From: Sven Peter @ 2026-09-06 18:36 UTC (permalink / raw)
To: Heikki Krogerus, Greg Kroah-Hartman, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, 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, Joshua Peisach
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 ideally has to be stopped again before the PHY is powered down but
this doesn't break anything unlike getting startup wrong). It also
needs cable details which are only known to the Type-C port controller.
The first two patches add synchronous Type-C alternate mode state
notifiers and support for representing USB4 connections on the Type-C
bus. USB4 uses Enter_USB instead of Enter Mode and has no SVID, so it
gets its own mode kind and stores the negotiated EUDO directly.
The next two patches register a USB4 port mode and publish
the negotiated Thunderbolt and USB4 partner modes on the Type-C bus,
along with the Thunderbolt cable mode. These modes are marked active
after the mux has been configured and deactivated before it is switched
back to its safe state.
The following two patches add the bindings for the NHI and ACIO. The
Thunderbolt core patches prepare for the different NHI register layout
and interrupt handling, read the USB4 router DROM from the device tree,
unlock the host router ports and find the Apple VSE capability. They
also add a hook for device links to tunneled native ports, a quirk for
USB3 bandwidth allocation not implemented by the Apple hardware and
the symbol exports needed by the driver.
The main driver registers the newly introduced notifications for the
negotiated Type-C modes, 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], 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 we'll probably need an immutable brach with the first two
commits after they've been reviewed which is then merged in both the usb
and thunderbolt tree sincethere are other tipd changes in flight as well.
Happy to split this series in two as well if you prefer that.
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
---
Changes in v2:
- Dropped thunderbolt switch in favor of the typec bus and an additional
notifer
- Add USB4 to the Type-C bus with its own mode kind instead of an SVID
- Register port/partner altmodes for CD321x
- Replaced the struct with ring MMIO offsets with two callbacks insid
tb_nhi_ops
- Moved port unlock sequence and finding the Apple VSE capability into
core tb code
- Added device links for USB3 tunnels
- Move the USB3 BW quirk from an NHI quirk to a router quirk
- Added Joshua's rb to patches which didn't change
- Link to v1: https://patch.msgid.link/20260830-b4-apple-soc-tbt-v1-0-44bc9348683c@kernel.org
To: Heikki Krogerus <heikki.krogerus@linux.intel.com>
To: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
To: Rob Herring <robh@kernel.org>
To: Krzysztof Kozlowski <krzk+dt@kernel.org>
To: Conor Dooley <conor+dt@kernel.org>
To: Janne Grunau <j@jannau.net>
To: Neal Gompa <neal@gompa.dev>
To: Andreas Noever <andreas.noever@gmail.com>
To: Mika Westerberg <westeri@kernel.org>
To: Yehezkel Bernat <YehezkelShB@gmail.com>
To: Philipp Zabel <p.zabel@pengutronix.de>
Cc: Konrad Dybcio <konradybcio@kernel.org>
Cc: linux-usb@vger.kernel.org
Cc: devicetree@vger.kernel.org
Cc: linux-kernel@vger.kernel.org
Cc: asahi@lists.linux.dev
Cc: linux-arm-kernel@lists.infradead.org
Signed-off-by: Sven Peter <sven@kernel.org>
---
Sven Peter (22):
usb: typec: Add alternate mode state notifiers
usb: typec: Represent USB4 on the Type-C bus
usb: typec: tipd: Register a USB4 port mode for CD321x
usb: typec: tipd: Publish CD321x partner alternate modes
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: Unlock host router ports during startup
thunderbolt: Find Apple VSE capability during startup
thunderbolt: Add ring_interrupt_active to tb_nhi_ops
thunderbolt: Add ring register accessors to tb_nhi_ops
thunderbolt: Add ring_interrupt_mask to tb_nhi_ops
thunderbolt: Add ring_configure to tb_nhi_ops
thunderbolt: Add add_links to tb_nhi_ops
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
Documentation/ABI/testing/sysfs-bus-typec | 36 +
.../thunderbolt/apple,t8103-usb4-acio.yaml | 213 ++++
.../bindings/thunderbolt/apple,t8103-usb4-nhi.yaml | 151 +++
MAINTAINERS | 4 +
arch/arm64/boot/dts/apple/t6002-j375d.dts | 101 +-
arch/arm64/boot/dts/apple/t600x-dieX.dtsi | 376 ++++++
arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi | 158 ++-
arch/arm64/boot/dts/apple/t600x-j375.dtsi | 205 +++-
arch/arm64/boot/dts/apple/t6022-j180d.dts | 304 ++++-
arch/arm64/boot/dts/apple/t6022-j475d.dts | 10 +
arch/arm64/boot/dts/apple/t6022-jxxxd.dtsi | 91 +-
arch/arm64/boot/dts/apple/t602x-dieX.dtsi | 376 ++++++
arch/arm64/boot/dts/apple/t8103-jxxx.dtsi | 95 +-
arch/arm64/boot/dts/apple/t8103.dtsi | 184 +++
arch/arm64/boot/dts/apple/t8112-jxxx.dtsi | 95 +-
arch/arm64/boot/dts/apple/t8112.dtsi | 186 +++
drivers/thunderbolt/Kconfig | 13 +
drivers/thunderbolt/Makefile | 3 +
drivers/thunderbolt/apple.c | 1277 ++++++++++++++++++++
drivers/thunderbolt/ctl.c | 2 +
drivers/thunderbolt/domain.c | 2 +
drivers/thunderbolt/eeprom.c | 27 +-
drivers/thunderbolt/nhi.c | 127 +-
drivers/thunderbolt/nhi.h | 20 +
drivers/thunderbolt/quirks.c | 9 +
drivers/thunderbolt/switch.c | 8 +-
drivers/thunderbolt/tb.c | 18 +-
drivers/thunderbolt/tb.h | 4 +
drivers/thunderbolt/tb_regs.h | 1 +
drivers/thunderbolt/tunnel.c | 60 +-
drivers/usb/typec/bus.c | 67 +-
drivers/usb/typec/bus.h | 6 +
drivers/usb/typec/class.c | 142 ++-
drivers/usb/typec/mode_selection.c | 6 +-
drivers/usb/typec/tipd/core.c | 241 +++-
include/linux/usb/typec.h | 36 +-
include/linux/usb/typec_altmode.h | 10 +-
37 files changed, 4533 insertions(+), 131 deletions(-)
---
base-commit: 19998fc5177866e77a3be488644438a2227ff370
change-id: 20260829-b4-apple-soc-tbt-a00e873a52db
Best regards,
--
Sven Peter <sven@kernel.org>
^ permalink raw reply [flat|nested] 41+ messages in thread
* [PATCH v2 01/22] usb: typec: Add alternate mode state notifiers
2026-09-06 18:36 [PATCH v2 00/22] Initial USB4/Thunderbolt support for Apple M1/M2/M3 SoCs Sven Peter
@ 2026-09-06 18:36 ` Sven Peter
2026-09-07 13:27 ` Joshua Peisach
2026-09-08 12:00 ` Heikki Krogerus
2026-09-06 18:36 ` [PATCH v2 02/22] usb: typec: Represent USB4 on the Type-C bus Sven Peter
` (21 subsequent siblings)
22 siblings, 2 replies; 41+ messages in thread
From: Sven Peter @ 2026-09-06 18:36 UTC (permalink / raw)
To: Heikki Krogerus, Greg Kroah-Hartman, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, 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 Silicon USB4/Thunderbolt driver needs to be made aware of
cable details and whenever USB4 or Thunderbolt is entered. The cable
details are already available as part of the typec subsystem but there
is no way to get a notification on typec_altmode_update_active() so
far.
Add a blocking notifier chain to each alternate mode and call it
whenever the mode is activated or deactivated right next to the already
existing sysfs and kobject_uevent notifications. Replay activations when a
notifier is registered.
Signed-off-by: Sven Peter <sven@kernel.org>
---
drivers/usb/typec/bus.h | 6 +++++
drivers/usb/typec/class.c | 59 +++++++++++++++++++++++++++++++++++++++++++++++
include/linux/usb/typec.h | 16 +++++++++++++
3 files changed, 81 insertions(+)
diff --git a/drivers/usb/typec/bus.h b/drivers/usb/typec/bus.h
index 7df5deb1dd3a..4dc60c48c91e 100644
--- a/drivers/usb/typec/bus.h
+++ b/drivers/usb/typec/bus.h
@@ -3,6 +3,8 @@
#ifndef __USB_TYPEC_ALTMODE_H__
#define __USB_TYPEC_ALTMODE_H__
+#include <linux/mutex.h>
+#include <linux/notifier.h>
#include <linux/usb/typec_altmode.h>
struct typec_mux;
@@ -23,6 +25,10 @@ struct altmode {
struct altmode *partner;
struct altmode *plug[2];
+
+ /* Serializes active state changes and notifier registration. */
+ struct mutex state_lock;
+ struct blocking_notifier_head state_notifier;
};
#define to_altmode(d) container_of(d, struct altmode, adev)
diff --git a/drivers/usb/typec/class.c b/drivers/usb/typec/class.c
index 54c7810b563b..5808a3ef0d6c 100644
--- a/drivers/usb/typec/class.c
+++ b/drivers/usb/typec/class.c
@@ -299,8 +299,11 @@ static void typec_altmode_put_partner(struct altmode *altmode)
*/
void typec_altmode_update_active(struct typec_altmode *adev, bool active)
{
+ struct altmode *alt = to_altmode(adev);
char dir[6];
+ guard(mutex)(&alt->state_lock);
+
if (adev->active == active)
return;
@@ -312,6 +315,10 @@ void typec_altmode_update_active(struct typec_altmode *adev, bool active)
}
adev->active = active;
+ blocking_notifier_call_chain(&alt->state_notifier,
+ active ? TYPEC_ALTMODE_ENTERED :
+ TYPEC_ALTMODE_EXITED,
+ adev);
snprintf(dir, sizeof(dir), "mode%d", adev->mode);
sysfs_notify(&adev->dev.kobj, dir, "active");
sysfs_notify(&adev->dev.kobj, NULL, "active");
@@ -319,6 +326,56 @@ void typec_altmode_update_active(struct typec_altmode *adev, bool active)
}
EXPORT_SYMBOL_GPL(typec_altmode_update_active);
+/**
+ * typec_altmode_register_notifier - Register an alternate mode state notifier
+ * @adev: Handle to the alternate mode
+ * @nb: Notifier block
+ *
+ * The notifier is called synchronously when the mode represented by @adev is
+ * entered or exited. If @adev is already active %TYPEC_ALTMODE_ENTERED will be
+ * emitted before this function returns. Callbacks must not update the state of
+ * @adev or register or unregister another notifier for @adev. They must also
+ * handle failures locally and return %NOTIFY_OK or %NOTIFY_DONE so that other
+ * subscribers also receive the event. Callback return values do not affect the
+ * active state or the result of notifier registration.
+ *
+ * Return: 0 on success or a negative error code on failure.
+ */
+int typec_altmode_register_notifier(struct typec_altmode *adev,
+ struct notifier_block *nb)
+{
+ struct altmode *alt = to_altmode(adev);
+ int ret;
+
+ guard(mutex)(&alt->state_lock);
+ ret = blocking_notifier_chain_register(&alt->state_notifier, nb);
+ if (!ret && adev->active)
+ nb->notifier_call(nb, TYPEC_ALTMODE_ENTERED, adev);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(typec_altmode_register_notifier);
+
+/**
+ * typec_altmode_unregister_notifier - Unregister an alternate mode notifier
+ * @adev: Handle to the alternate mode
+ * @nb: Notifier block
+ *
+ * Return: 0 on success or a negative error code on failure.
+ */
+int typec_altmode_unregister_notifier(struct typec_altmode *adev,
+ struct notifier_block *nb)
+{
+ struct altmode *alt = to_altmode(adev);
+ int ret;
+
+ guard(mutex)(&alt->state_lock);
+ ret = blocking_notifier_chain_unregister(&alt->state_notifier, nb);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(typec_altmode_unregister_notifier);
+
/**
* typec_altmode2port - Alternate Mode to USB Type-C port
* @alt: The Alternate Mode
@@ -658,6 +715,8 @@ typec_register_altmode(struct device *parent,
alt->adev.mode_selection = desc->mode_selection;
alt->roles = desc->roles;
alt->id = id;
+ mutex_init(&alt->state_lock);
+ BLOCKING_INIT_NOTIFIER_HEAD(&alt->state_notifier);
alt->attrs[0] = &dev_attr_vdo.attr;
alt->attrs[1] = &dev_attr_description.attr;
diff --git a/include/linux/usb/typec.h b/include/linux/usb/typec.h
index 10a783b738ef..914a6f0844c5 100644
--- a/include/linux/usb/typec.h
+++ b/include/linux/usb/typec.h
@@ -19,6 +19,7 @@ struct typec_plug;
struct typec_port;
struct typec_altmode_ops;
struct typec_cable_ops;
+struct notifier_block;
struct bus_type;
struct fwnode_handle;
@@ -184,6 +185,21 @@ struct typec_port *typec_altmode2port(struct typec_altmode *alt);
void typec_altmode_update_active(struct typec_altmode *alt, bool active);
+/**
+ * enum typec_altmode_event - USB Type-C alternate mode state event
+ * @TYPEC_ALTMODE_ENTERED: The alternate mode has been entered
+ * @TYPEC_ALTMODE_EXITED: The alternate mode has been exited
+ */
+enum typec_altmode_event {
+ TYPEC_ALTMODE_ENTERED,
+ TYPEC_ALTMODE_EXITED,
+};
+
+int typec_altmode_register_notifier(struct typec_altmode *alt,
+ struct notifier_block *nb);
+int typec_altmode_unregister_notifier(struct typec_altmode *alt,
+ struct notifier_block *nb);
+
void typec_altmode_set_ops(struct typec_altmode *alt,
const struct typec_altmode_ops *ops);
--
2.55.0
^ permalink raw reply [flat|nested] 41+ messages in thread
* [PATCH v2 02/22] usb: typec: Represent USB4 on the Type-C bus
2026-09-06 18:36 [PATCH v2 00/22] Initial USB4/Thunderbolt support for Apple M1/M2/M3 SoCs Sven Peter
2026-09-06 18:36 ` [PATCH v2 01/22] usb: typec: Add alternate mode state notifiers Sven Peter
@ 2026-09-06 18:36 ` Sven Peter
2026-09-07 13:31 ` Joshua Peisach
2026-09-08 12:07 ` Heikki Krogerus
2026-09-06 18:36 ` [PATCH v2 03/22] usb: typec: tipd: Register a USB4 port mode for CD321x Sven Peter
` (20 subsequent siblings)
22 siblings, 2 replies; 41+ messages in thread
From: Sven Peter @ 2026-09-06 18:36 UTC (permalink / raw)
To: Heikki Krogerus, Greg Kroah-Hartman, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, 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
USB4 technically isn't alternate mode and entered using the EUDO and has
no SVID or Discover Modes Response. We can still represent it like an
altmode on the Type-C bus though by adding a mode kind. Exclude the USB4
modes from any SVID-specific operations and driver binding, but allow
consumers to observe their state through notifications.
Like for other altmodes, expose the USB4 mode to sysfs: Add a mode_kind
to be able to differentiate and then expose the EUDO, make the USB4
active state read-only since we only represent firmware-negotiated USB4
modes and omit anything that only applies to SVID-specific altmodes.
Signed-off-by: Sven Peter <sven@kernel.org>
---
Hope it's fine to add you as the contact for the sysfs documentation,
happy to add myself otherwise though.
---
Documentation/ABI/testing/sysfs-bus-typec | 36 ++++++++++++++
drivers/usb/typec/bus.c | 67 ++++++++++++++++++++-----
drivers/usb/typec/class.c | 83 +++++++++++++++++++++++++++----
drivers/usb/typec/mode_selection.c | 6 ++-
include/linux/usb/typec.h | 20 +++++++-
include/linux/usb/typec_altmode.h | 10 +++-
6 files changed, 193 insertions(+), 29 deletions(-)
diff --git a/Documentation/ABI/testing/sysfs-bus-typec b/Documentation/ABI/testing/sysfs-bus-typec
index 205d9c91e2e1..524bbcbadf64 100644
--- a/Documentation/ABI/testing/sysfs-bus-typec
+++ b/Documentation/ABI/testing/sysfs-bus-typec
@@ -12,6 +12,35 @@ Description:
Valid values are boolean.
+ For USB4 mode devices this attribute is read-only. On a port it
+ indicates whether USB4 support is enabled; on a partner it reports
+ whether USB4 is active. USB4 entry and exit are controlled by the
+ port controller. Partner state changes notify this attribute and
+ generate KOBJ_CHANGE uevents.
+
+What: /sys/bus/typec/devices/.../mode_kind
+Date: September 2026
+Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com>
+Description:
+ Read-only protocol kind represented by this mode device:
+ "altmode" for an SVID-specific alternate mode, or "usb4" for
+ USB4 negotiated using Enter_USB.
+
+ USB4 devices do not expose svid, mode, vdo, or priority
+ attributes, or the legacy modeN attribute group. USB4 partner
+ uevents contain MODE_KIND=usb4 instead of SVID, MODE, and
+ MODALIAS variables.
+
+What: /sys/bus/typec/devices/.../eudo
+Date: September 2026
+Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com>
+Description:
+ Read-only Enter_USB Data Object (EUDO), formatted as
+ 0x followed by eight hexadecimal digits. Present only on USB4
+ partner mode devices, not on ports or SVID-specific alternate
+ modes. USB4 cable information is encoded in the EUDO; there are
+ no USB4 cable plug mode devices.
+
What: /sys/bus/typec/devices/.../description
Date: July 2018
Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com>
@@ -36,6 +65,8 @@ Description:
the mode index is not assigned, identifying the alternate mode
must be done with either mode VDO or the description.
+ This attribute is not present on USB4 mode devices.
+
What: /sys/bus/typec/devices/.../svid
Date: July 2018
Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com>
@@ -43,9 +74,14 @@ Description:
The Standard or Vendor ID (SVID) assigned by USB-IF for this
alternate mode.
+ This attribute is not present on USB4 mode devices.
+
What: /sys/bus/typec/devices/.../vdo
Date: July 2018
Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com>
Description:
Shows the VDO in hexadecimal returned by Discover Modes command
for this mode.
+
+ This attribute is not present on USB4 mode devices. USB4 partner
+ mode devices expose the EUDO in the eudo attribute.
diff --git a/drivers/usb/typec/bus.c b/drivers/usb/typec/bus.c
index e84b134a3381..8e6d7769fac3 100644
--- a/drivers/usb/typec/bus.c
+++ b/drivers/usb/typec/bus.c
@@ -93,6 +93,8 @@ int typec_altmode_notify(struct typec_altmode *adev,
if (!adev)
return 0;
+ if (adev->mode_kind != TYPEC_MODE_KIND_ALTMODE)
+ return -EOPNOTSUPP;
altmode = to_altmode(adev);
@@ -125,12 +127,19 @@ EXPORT_SYMBOL_GPL(typec_altmode_notify);
*/
int typec_altmode_enter(struct typec_altmode *adev, u32 *vdo)
{
- struct altmode *partner = to_altmode(adev)->partner;
- struct typec_altmode *pdev = &partner->adev;
+ struct typec_altmode *pdev;
+ struct altmode *partner;
int ret;
- if (!adev || adev->active)
+ if (!adev)
return 0;
+ if (adev->mode_kind != TYPEC_MODE_KIND_ALTMODE)
+ return -EOPNOTSUPP;
+ if (adev->active)
+ return 0;
+
+ partner = to_altmode(adev)->partner;
+ pdev = &partner->adev;
if (!pdev->ops || !pdev->ops->enter)
return -EOPNOTSUPP;
@@ -156,12 +165,19 @@ EXPORT_SYMBOL_GPL(typec_altmode_enter);
*/
int typec_altmode_exit(struct typec_altmode *adev)
{
- struct altmode *partner = to_altmode(adev)->partner;
- struct typec_altmode *pdev = &partner->adev;
+ struct typec_altmode *pdev;
+ struct altmode *partner;
int ret;
- if (!adev || !adev->active)
+ if (!adev)
return 0;
+ if (adev->mode_kind != TYPEC_MODE_KIND_ALTMODE)
+ return -EOPNOTSUPP;
+ if (!adev->active)
+ return 0;
+
+ partner = to_altmode(adev)->partner;
+ pdev = &partner->adev;
if (!pdev->ops || !pdev->ops->exit)
return -EOPNOTSUPP;
@@ -185,8 +201,13 @@ EXPORT_SYMBOL_GPL(typec_altmode_exit);
*/
int typec_altmode_attention(struct typec_altmode *adev, u32 vdo)
{
- struct altmode *partner = to_altmode(adev)->partner;
struct typec_altmode *pdev;
+ struct altmode *partner;
+
+ if (adev->mode_kind != TYPEC_MODE_KIND_ALTMODE)
+ return -EOPNOTSUPP;
+
+ partner = to_altmode(adev)->partner;
if (!partner)
return -ENODEV;
@@ -219,6 +240,8 @@ int typec_altmode_vdm(struct typec_altmode *adev,
if (!adev)
return 0;
+ if (adev->mode_kind != TYPEC_MODE_KIND_ALTMODE)
+ return -EOPNOTSUPP;
altmode = to_altmode(adev);
@@ -258,12 +281,17 @@ EXPORT_SYMBOL_GPL(typec_altmode_get_partner);
*/
int typec_cable_altmode_enter(struct typec_altmode *adev, enum typec_plug_index sop, u32 *vdo)
{
- struct altmode *partner = to_altmode(adev)->partner;
struct typec_altmode *pdev;
+ struct altmode *partner;
- if (!adev || adev->active)
+ if (!adev)
+ return 0;
+ if (adev->mode_kind != TYPEC_MODE_KIND_ALTMODE)
+ return -EOPNOTSUPP;
+ if (adev->active)
return 0;
+ partner = to_altmode(adev)->partner;
if (!partner)
return -ENODEV;
@@ -288,12 +316,17 @@ EXPORT_SYMBOL_GPL(typec_cable_altmode_enter);
*/
int typec_cable_altmode_exit(struct typec_altmode *adev, enum typec_plug_index sop)
{
- struct altmode *partner = to_altmode(adev)->partner;
struct typec_altmode *pdev;
+ struct altmode *partner;
- if (!adev || !adev->active)
+ if (!adev)
+ return 0;
+ if (adev->mode_kind != TYPEC_MODE_KIND_ALTMODE)
+ return -EOPNOTSUPP;
+ if (!adev->active)
return 0;
+ partner = to_altmode(adev)->partner;
if (!partner)
return -ENODEV;
@@ -326,6 +359,8 @@ int typec_cable_altmode_vdm(struct typec_altmode *adev, enum typec_plug_index so
if (!adev)
return 0;
+ if (adev->mode_kind != TYPEC_MODE_KIND_ALTMODE)
+ return -EOPNOTSUPP;
altmode = to_altmode(adev);
@@ -422,7 +457,8 @@ struct typec_altmode *typec_match_altmode(struct typec_altmode **altmodes,
for (i = 0; i < n; i++) {
if (!altmodes[i])
break;
- if (altmodes[i]->svid == svid && altmodes[i]->mode == mode)
+ if (altmodes[i]->mode_kind == TYPEC_MODE_KIND_ALTMODE &&
+ altmodes[i]->svid == svid && altmodes[i]->mode == mode)
return altmodes[i];
}
@@ -472,6 +508,10 @@ static int typec_match(struct device *dev, const struct device_driver *driver)
if (!is_typec_partner_altmode(dev))
return 0;
+ /* USB4 mode devices report firmware state through notifiers only. */
+ if (altmode->mode_kind != TYPEC_MODE_KIND_ALTMODE)
+ return 0;
+
for (id = drv->id_table; id->svid; id++)
if (id->svid == altmode->svid)
return 1;
@@ -485,6 +525,9 @@ static int typec_uevent(const struct device *dev, struct kobj_uevent_env *env)
if (!is_typec_partner_altmode(dev))
return 0;
+ if (altmode->mode_kind == TYPEC_MODE_KIND_USB4)
+ return add_uevent_var(env, "MODE_KIND=usb4");
+
if (add_uevent_var(env, "SVID=%04X", altmode->svid))
return -ENOMEM;
diff --git a/drivers/usb/typec/class.c b/drivers/usb/typec/class.c
index 5808a3ef0d6c..869d6857c0dc 100644
--- a/drivers/usb/typec/class.c
+++ b/drivers/usb/typec/class.c
@@ -233,23 +233,24 @@ static const char * const usb_modes[] = {
static int altmode_match(struct device *dev, const void *data)
{
struct typec_altmode *adev = to_typec_altmode(dev);
- const struct typec_device_id *id = data;
+ const struct typec_altmode *match = data;
if (!is_typec_port_altmode(dev))
return 0;
- return (adev->svid == id->svid);
+ if (adev->mode_kind != match->mode_kind)
+ return 0;
+ return adev->mode_kind == TYPEC_MODE_KIND_USB4 || adev->svid == match->svid;
}
static void typec_altmode_set_partner(struct altmode *altmode)
{
struct typec_altmode *adev = &altmode->adev;
- struct typec_device_id id = { adev->svid };
struct typec_port *port = typec_altmode2port(adev);
struct altmode *partner;
struct device *dev;
- dev = device_find_child(&port->dev, &id, altmode_match);
+ dev = device_find_child(&port->dev, adev, altmode_match);
if (!dev)
return;
@@ -296,6 +297,8 @@ static void typec_altmode_put_partner(struct altmode *altmode)
*
* If a partner or cable plug executes Enter/Exit Mode command successfully, the
* drivers use this routine to report the updated state of the mode.
+ * For USB4 partner modes, drivers use this routine to report entry through
+ * Enter_USB and exit from USB4.
*/
void typec_altmode_update_active(struct typec_altmode *adev, bool active)
{
@@ -319,8 +322,10 @@ void typec_altmode_update_active(struct typec_altmode *adev, bool active)
active ? TYPEC_ALTMODE_ENTERED :
TYPEC_ALTMODE_EXITED,
adev);
- snprintf(dir, sizeof(dir), "mode%d", adev->mode);
- sysfs_notify(&adev->dev.kobj, dir, "active");
+ if (adev->mode_kind == TYPEC_MODE_KIND_ALTMODE) {
+ snprintf(dir, sizeof(dir), "mode%d", adev->mode);
+ sysfs_notify(&adev->dev.kobj, dir, "active");
+ }
sysfs_notify(&adev->dev.kobj, NULL, "active");
kobject_uevent(&adev->dev.kobj, KOBJ_CHANGE);
}
@@ -502,13 +507,30 @@ svid_show(struct device *dev, struct device_attribute *attr, char *buf)
}
static DEVICE_ATTR_RO(svid);
+static ssize_t mode_kind_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct typec_altmode *adev = to_typec_altmode(dev);
+
+ return sysfs_emit(buf, "%s\n",
+ adev->mode_kind == TYPEC_MODE_KIND_USB4 ? "usb4" : "altmode");
+}
+static DEVICE_ATTR_RO(mode_kind);
+
+static ssize_t eudo_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ return sysfs_emit(buf, "0x%08x\n", to_typec_altmode(dev)->eudo);
+}
+static DEVICE_ATTR_RO(eudo);
+
static int increment_duplicated_priority(struct device *dev, void *data)
{
if (is_typec_port_altmode(dev)) {
struct typec_altmode **alt_target = (struct typec_altmode **)data;
struct typec_altmode *alt = to_typec_altmode(dev);
- if (alt != *alt_target && alt->priority == (*alt_target)->priority) {
+ if (alt->mode_kind == TYPEC_MODE_KIND_ALTMODE &&
+ alt != *alt_target && alt->priority == (*alt_target)->priority) {
alt->priority++;
*alt_target = alt;
return 1;
@@ -523,7 +545,8 @@ static int find_duplicated_priority(struct device *dev, void *data)
struct typec_altmode **alt_target = (struct typec_altmode **)data;
struct typec_altmode *alt = to_typec_altmode(dev);
- if (alt != *alt_target && alt->priority == (*alt_target)->priority)
+ if (alt->mode_kind == TYPEC_MODE_KIND_ALTMODE &&
+ alt != *alt_target && alt->priority == (*alt_target)->priority)
return 1;
}
return 0;
@@ -579,6 +602,8 @@ static ssize_t priority_show(struct device *dev,
static DEVICE_ATTR_RW(priority);
static struct attribute *typec_altmode_attrs[] = {
+ &dev_attr_mode_kind.attr,
+ &dev_attr_eudo.attr,
&dev_attr_active.attr,
&dev_attr_mode.attr,
&dev_attr_svid.attr,
@@ -593,6 +618,18 @@ static umode_t typec_altmode_attr_is_visible(struct kobject *kobj,
struct typec_altmode *adev = to_typec_altmode(kobj_to_dev(kobj));
struct typec_port *port = typec_altmode2port(adev);
+ if (adev->mode_kind == TYPEC_MODE_KIND_USB4) {
+ if (attr == &dev_attr_svid.attr || attr == &dev_attr_mode.attr ||
+ attr == &dev_attr_vdo.attr || attr == &dev_attr_priority.attr)
+ return 0;
+ if (attr == &dev_attr_eudo.attr && !is_typec_partner(adev->dev.parent))
+ return 0;
+ if (attr == &dev_attr_active.attr)
+ return 0444;
+ } else if (attr == &dev_attr_eudo.attr) {
+ return 0;
+ }
+
if (attr == &dev_attr_active.attr) {
if (!is_typec_port(adev->dev.parent)) {
if (!port->mode_control || !adev->ops || !adev->ops->activate)
@@ -698,20 +735,33 @@ typec_register_altmode(struct device *parent,
const struct typec_altmode_desc *desc,
const struct device_type *type)
{
- unsigned int id = altmode_id_get(parent);
bool is_port = is_typec_port(parent);
struct altmode *alt;
+ unsigned int id;
int ret;
+ if (desc->mode_kind != TYPEC_MODE_KIND_ALTMODE &&
+ desc->mode_kind != TYPEC_MODE_KIND_USB4)
+ return ERR_PTR(-EINVAL);
+ if (desc->mode_kind == TYPEC_MODE_KIND_USB4 &&
+ (desc->svid || desc->mode || is_typec_plug(parent)))
+ return ERR_PTR(-EINVAL);
+
+ id = altmode_id_get(parent);
+
alt = kzalloc_obj(*alt);
if (!alt) {
altmode_id_remove(parent, id);
return ERR_PTR(-ENOMEM);
}
+ alt->adev.mode_kind = desc->mode_kind;
alt->adev.svid = desc->svid;
alt->adev.mode = desc->mode;
- alt->adev.vdo = desc->vdo;
+ if (desc->mode_kind == TYPEC_MODE_KIND_USB4)
+ alt->adev.eudo = desc->eudo;
+ else
+ alt->adev.vdo = desc->vdo;
alt->adev.mode_selection = desc->mode_selection;
alt->roles = desc->roles;
alt->id = id;
@@ -730,7 +780,8 @@ typec_register_altmode(struct device *parent,
sprintf(alt->group_name, "mode%d", desc->mode);
alt->group.name = alt->group_name;
alt->group.attrs = alt->attrs;
- alt->groups[0] = &alt->group;
+ if (desc->mode_kind == TYPEC_MODE_KIND_ALTMODE)
+ alt->groups[0] = &alt->group;
alt->adev.dev.parent = parent;
alt->adev.dev.groups = alt->groups;
@@ -1110,6 +1161,10 @@ EXPORT_SYMBOL_GPL(typec_partner_set_num_altmodes);
* SVID listed in response to Discover Modes command need to be listed in an
* array in @desc.
*
+ * For USB4, set @desc->mode_kind to %TYPEC_MODE_KIND_USB4 and @desc->eudo to the
+ * EUDO and leave SVID and mode zero. USB4 devices expose firmware state
+ * through notifications and do not bind alternate-mode drivers.
+ *
* Returns handle to the alternate mode on success or ERR_PTR on failure.
*/
struct typec_altmode *
@@ -2698,6 +2753,9 @@ EXPORT_SYMBOL_GPL(typec_get_fw_cap);
* This routine is used to register an alternate mode that @port is capable of
* supporting.
*
+ * For USB4, set @desc->mode_kind to %TYPEC_MODE_KIND_USB4 and leave SVID and
+ * mode zero. Register it before the USB4 partner mode, which carries the EUDO.
+ *
* Returns handle to the alternate mode on success or ERR_PTR on failure.
*/
struct typec_altmode *
@@ -2727,6 +2785,9 @@ typec_port_register_altmode(struct typec_port *port,
to_altmode(adev)->mux = mux;
to_altmode(adev)->retimer = retimer;
+ if (adev->mode_kind == TYPEC_MODE_KIND_USB4)
+ return adev;
+
ret = typec_mode_set_priority(adev, 0);
if (ret) {
typec_unregister_altmode(adev);
diff --git a/drivers/usb/typec/mode_selection.c b/drivers/usb/typec/mode_selection.c
index cd376f67e445..5e378deed5c2 100644
--- a/drivers/usb/typec/mode_selection.c
+++ b/drivers/usb/typec/mode_selection.c
@@ -66,7 +66,8 @@ static int activate_altmode(struct device *dev, void *data)
struct typec_altmode *alt = to_typec_altmode(dev);
struct mode_order *order = (struct mode_order *)data;
- if (order->svid == alt->svid) {
+ if (alt->mode_kind == TYPEC_MODE_KIND_ALTMODE &&
+ order->svid == alt->svid) {
if (alt->ops && alt->ops->activate)
order->result = alt->ops->activate(alt, order->enter);
else
@@ -215,7 +216,8 @@ static int altmode_add_to_list(struct device *dev, void *data)
const struct typec_altmode *pdev = typec_altmode_get_partner(altmode);
struct mode_state *ms;
- if (pdev && altmode->ops && altmode->ops->activate) {
+ if (altmode->mode_kind == TYPEC_MODE_KIND_ALTMODE &&
+ pdev && altmode->ops && altmode->ops->activate) {
ms = kzalloc_obj(*ms);
if (!ms)
return -ENOMEM;
diff --git a/include/linux/usb/typec.h b/include/linux/usb/typec.h
index 914a6f0844c5..add2c1368774 100644
--- a/include/linux/usb/typec.h
+++ b/include/linux/usb/typec.h
@@ -138,11 +138,23 @@ struct usb_pd_identity {
int typec_partner_set_identity(struct typec_partner *partner);
int typec_cable_set_identity(struct typec_cable *cable);
+/**
+ * enum typec_mode_kind - Protocol represented by a Type-C mode device
+ * @TYPEC_MODE_KIND_ALTMODE: SVID-specific alternate mode (the default)
+ * @TYPEC_MODE_KIND_USB4: USB4, negotiated using Enter_USB
+ */
+enum typec_mode_kind {
+ TYPEC_MODE_KIND_ALTMODE,
+ TYPEC_MODE_KIND_USB4,
+};
+
/*
* struct typec_altmode_desc - USB Type-C Alternate Mode Descriptor
* @svid: Standard or Vendor ID
* @mode: Index of the Mode
- * @vdo: VDO returned by Discover Modes USB PD command
+ * @vdo: Discover Modes VDO for SVID-specific alternate modes
+ * @eudo: Enter_USB Data Object for USB4 partner modes
+ * @mode_kind: Protocol kind; SVID and mode are unused for USB4
* @roles: Only for ports. DRP if the mode is available in both roles
* @inactive: Only for ports. Make this port inactive (default is active).
*
@@ -152,11 +164,15 @@ int typec_cable_set_identity(struct typec_cable *cable);
struct typec_altmode_desc {
u16 svid;
u8 mode;
- u32 vdo;
+ union {
+ u32 vdo;
+ u32 eudo;
+ };
/* Only used with ports */
enum typec_port_data roles;
bool inactive;
bool mode_selection;
+ enum typec_mode_kind mode_kind;
};
void typec_partner_set_pd_revision(struct typec_partner *partner, u16 pd_revision);
diff --git a/include/linux/usb/typec_altmode.h b/include/linux/usb/typec_altmode.h
index ef21ead551be..8d31e35e9320 100644
--- a/include/linux/usb/typec_altmode.h
+++ b/include/linux/usb/typec_altmode.h
@@ -24,7 +24,9 @@ struct typec_altmode_ops;
* @dev: Driver model's view of this device
* @svid: Standard or Vendor ID (SVID) of the alternate mode
* @mode: Index of the Mode
- * @vdo: VDO returned by Discover Modes USB PD command
+ * @vdo: Discover Modes VDO for SVID-specific alternate modes
+ * @eudo: Enter_USB Data Object for USB4 partner modes
+ * @mode_kind: Protocol kind; SVID and mode are unused for USB4
* @active: Tells has the mode been entered or not
* @priority: Priority used by the automatic alternate mode selection process
* @mode_selection: Whether entry to this alternate mode is managed by the
@@ -37,7 +39,11 @@ struct typec_altmode {
struct device dev;
u16 svid;
int mode;
- u32 vdo;
+ union {
+ u32 vdo;
+ u32 eudo;
+ };
+ enum typec_mode_kind mode_kind;
unsigned int active:1;
u8 priority;
bool mode_selection;
--
2.55.0
^ permalink raw reply [flat|nested] 41+ messages in thread
* [PATCH v2 03/22] usb: typec: tipd: Register a USB4 port mode for CD321x
2026-09-06 18:36 [PATCH v2 00/22] Initial USB4/Thunderbolt support for Apple M1/M2/M3 SoCs Sven Peter
2026-09-06 18:36 ` [PATCH v2 01/22] usb: typec: Add alternate mode state notifiers Sven Peter
2026-09-06 18:36 ` [PATCH v2 02/22] usb: typec: Represent USB4 on the Type-C bus Sven Peter
@ 2026-09-06 18:36 ` Sven Peter
2026-09-06 18:36 ` [PATCH v2 04/22] usb: typec: tipd: Publish CD321x partner alternate modes Sven Peter
` (19 subsequent siblings)
22 siblings, 0 replies; 41+ messages in thread
From: Sven Peter @ 2026-09-06 18:36 UTC (permalink / raw)
To: Heikki Krogerus, Greg Kroah-Hartman, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, 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
Register a USB4 mode next to the DP and Thunderbolt alternate modes so
that negotiated USB4 partner modes can be linked to the port. Identify
it using TYPEC_MODE_KIND_USB4 without an SVID or mode index.
Signed-off-by: Sven Peter <sven@kernel.org>
---
drivers/usb/typec/tipd/core.c | 17 +++++++++++++++++
1 file changed, 17 insertions(+)
diff --git a/drivers/usb/typec/tipd/core.c b/drivers/usb/typec/tipd/core.c
index f76f563dc42b..8c7f93d4270e 100644
--- a/drivers/usb/typec/tipd/core.c
+++ b/drivers/usb/typec/tipd/core.c
@@ -211,6 +211,7 @@ struct cd321x {
struct typec_altmode *port_altmode_dp;
struct typec_altmode *port_altmode_tbt;
+ struct typec_altmode *port_altmode_usb4;
struct typec_mux *mux;
struct typec_mux_state state;
@@ -1312,6 +1313,18 @@ static int cd321x_register_port_altmodes(struct cd321x *cd321x)
}
cd321x->port_altmode_tbt = amode;
+ memset(&desc, 0, sizeof(desc));
+ desc.mode_kind = TYPEC_MODE_KIND_USB4;
+ amode = typec_port_register_altmode(cd321x->tps.port, &desc);
+ if (IS_ERR(amode)) {
+ typec_unregister_altmode(cd321x->port_altmode_dp);
+ typec_unregister_altmode(cd321x->port_altmode_tbt);
+ cd321x->port_altmode_dp = NULL;
+ cd321x->port_altmode_tbt = NULL;
+ return PTR_ERR(amode);
+ }
+ cd321x->port_altmode_usb4 = amode;
+
return 0;
}
@@ -1347,8 +1360,10 @@ cd321x_register_port(struct tps6598x *tps, struct fwnode_handle *fwnode)
err_unregister_altmodes:
typec_unregister_altmode(cd321x->port_altmode_dp);
typec_unregister_altmode(cd321x->port_altmode_tbt);
+ typec_unregister_altmode(cd321x->port_altmode_usb4);
cd321x->port_altmode_dp = NULL;
cd321x->port_altmode_tbt = NULL;
+ cd321x->port_altmode_usb4 = NULL;
err_unregister_port:
typec_unregister_port(tps->port);
return ret;
@@ -1371,6 +1386,8 @@ cd321x_unregister_port(struct tps6598x *tps)
cd321x->port_altmode_dp = NULL;
typec_unregister_altmode(cd321x->port_altmode_tbt);
cd321x->port_altmode_tbt = NULL;
+ typec_unregister_altmode(cd321x->port_altmode_usb4);
+ cd321x->port_altmode_usb4 = NULL;
typec_unregister_port(tps->port);
}
--
2.55.0
^ permalink raw reply [flat|nested] 41+ messages in thread
* [PATCH v2 04/22] usb: typec: tipd: Publish CD321x partner alternate modes
2026-09-06 18:36 [PATCH v2 00/22] Initial USB4/Thunderbolt support for Apple M1/M2/M3 SoCs Sven Peter
` (2 preceding siblings ...)
2026-09-06 18:36 ` [PATCH v2 03/22] usb: typec: tipd: Register a USB4 port mode for CD321x Sven Peter
@ 2026-09-06 18:36 ` Sven Peter
2026-09-06 18:36 ` [PATCH v2 05/22] dt-bindings: thunderbolt: Add Apple USB4/Thunderbolt NHI Sven Peter
` (18 subsequent siblings)
22 siblings, 0 replies; 41+ messages in thread
From: Sven Peter @ 2026-09-06 18:36 UTC (permalink / raw)
To: Heikki Krogerus, Greg Kroah-Hartman, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, 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
Register Thunderbolt partner and cable modes once their VDOs are
available and USB4 as a partner mode once firmware has negotiated it,
with the EUDO stored in its eudo field. The CD321X firmware still controls
mode selection and entry itself. This just makes the negotiated modes
available to consumers on the Type-C bus.
Mark them active after the Type-C mux has been configured and deactivate
them before the mux is switched back to its safe state to make sure any
consuming driver is initialized and shut down in the correct order.
Signed-off-by: Sven Peter <sven@kernel.org>
---
drivers/usb/typec/tipd/core.c | 224 +++++++++++++++++++++++++++++++++++++++---
1 file changed, 209 insertions(+), 15 deletions(-)
diff --git a/drivers/usb/typec/tipd/core.c b/drivers/usb/typec/tipd/core.c
index 8c7f93d4270e..218b52b07b5a 100644
--- a/drivers/usb/typec/tipd/core.c
+++ b/drivers/usb/typec/tipd/core.c
@@ -213,6 +213,12 @@ struct cd321x {
struct typec_altmode *port_altmode_tbt;
struct typec_altmode *port_altmode_usb4;
+ struct typec_cable *cable;
+ struct typec_plug *plug;
+ struct typec_altmode *plug_altmode_tbt;
+ struct typec_altmode *partner_altmode_tbt;
+ struct typec_altmode *partner_altmode_usb4;
+
struct typec_mux *mux;
struct typec_mux_state state;
@@ -674,13 +680,156 @@ static void tps6598x_handle_plug_event(struct tps6598x *tps, u32 status)
}
}
+static void cd321x_unregister_tbt_altmodes(struct cd321x *cd321x)
+{
+ if (cd321x->partner_altmode_tbt && cd321x->partner_altmode_tbt->active)
+ typec_altmode_update_active(cd321x->partner_altmode_tbt, false);
+ if (cd321x->plug_altmode_tbt && cd321x->plug_altmode_tbt->active)
+ typec_altmode_update_active(cd321x->plug_altmode_tbt, false);
+
+ typec_unregister_altmode(cd321x->partner_altmode_tbt);
+ cd321x->partner_altmode_tbt = NULL;
+ typec_unregister_altmode(cd321x->plug_altmode_tbt);
+ cd321x->plug_altmode_tbt = NULL;
+ typec_unregister_plug(cd321x->plug);
+ cd321x->plug = NULL;
+ typec_unregister_cable(cd321x->cable);
+ cd321x->cable = NULL;
+}
+
+static void cd321x_unregister_usb4_altmode(struct cd321x *cd321x)
+{
+ if (cd321x->partner_altmode_usb4 && cd321x->partner_altmode_usb4->active)
+ typec_altmode_update_active(cd321x->partner_altmode_usb4, false);
+
+ typec_unregister_altmode(cd321x->partner_altmode_usb4);
+ cd321x->partner_altmode_usb4 = NULL;
+}
+
+static void cd321x_unregister_partner_altmodes(struct cd321x *cd321x)
+{
+ cd321x_unregister_tbt_altmodes(cd321x);
+ cd321x_unregister_usb4_altmode(cd321x);
+}
+
+static void cd321x_deactivate_altmodes(struct cd321x *cd321x)
+{
+ if (cd321x->partner_altmode_tbt && cd321x->partner_altmode_tbt->active)
+ typec_altmode_update_active(cd321x->partner_altmode_tbt, false);
+ if (cd321x->plug_altmode_tbt && cd321x->plug_altmode_tbt->active)
+ typec_altmode_update_active(cd321x->plug_altmode_tbt, false);
+ if (cd321x->partner_altmode_usb4 && cd321x->partner_altmode_usb4->active)
+ typec_altmode_update_active(cd321x->partner_altmode_usb4, false);
+}
+
+static int cd321x_register_tbt_altmodes(struct cd321x *cd321x,
+ struct typec_thunderbolt_data *tbt_data,
+ u32 data_status)
+{
+ struct typec_cable_desc cable_desc = {
+ .type = USB_PLUG_TYPE_C,
+ .active = !!(data_status & TPS_DATA_STATUS_ACTIVE_CABLE),
+ };
+ struct typec_plug_desc plug_desc = {
+ .index = TYPEC_PLUG_SOP_P,
+ };
+ struct typec_altmode_desc desc = {
+ .svid = USB_TYPEC_TBT_SID,
+ .mode = TYPEC_ANY_MODE,
+ .mode_selection = true,
+ };
+ int ret;
+
+ if (cd321x->partner_altmode_tbt && cd321x->plug_altmode_tbt &&
+ cd321x->partner_altmode_tbt->vdo == tbt_data->device_mode &&
+ cd321x->plug_altmode_tbt->vdo == tbt_data->cable_mode)
+ return 0;
+
+ cd321x_unregister_tbt_altmodes(cd321x);
+
+ cd321x->cable = typec_register_cable(cd321x->tps.port, &cable_desc);
+ if (IS_ERR(cd321x->cable)) {
+ ret = PTR_ERR(cd321x->cable);
+ cd321x->cable = NULL;
+ return ret;
+ }
+
+ cd321x->plug = typec_register_plug(cd321x->cable, &plug_desc);
+ if (IS_ERR(cd321x->plug)) {
+ ret = PTR_ERR(cd321x->plug);
+ cd321x->plug = NULL;
+ goto err_unregister;
+ }
+
+ desc.vdo = tbt_data->cable_mode;
+ cd321x->plug_altmode_tbt = typec_plug_register_altmode(cd321x->plug, &desc);
+ if (IS_ERR(cd321x->plug_altmode_tbt)) {
+ ret = PTR_ERR(cd321x->plug_altmode_tbt);
+ cd321x->plug_altmode_tbt = NULL;
+ goto err_unregister;
+ }
+
+ desc.vdo = tbt_data->device_mode;
+ cd321x->partner_altmode_tbt =
+ typec_partner_register_altmode(cd321x->tps.partner, &desc);
+ if (IS_ERR(cd321x->partner_altmode_tbt)) {
+ ret = PTR_ERR(cd321x->partner_altmode_tbt);
+ cd321x->partner_altmode_tbt = NULL;
+ goto err_unregister;
+ }
+
+ return 0;
+
+err_unregister:
+ cd321x_unregister_tbt_altmodes(cd321x);
+ return ret;
+}
+
+static int cd321x_register_usb4_altmode(struct cd321x *cd321x, u32 eudo)
+{
+ struct typec_altmode_desc desc = {
+ .mode_kind = TYPEC_MODE_KIND_USB4,
+ .eudo = eudo,
+ .mode_selection = true,
+ };
+ int ret;
+
+ if (cd321x->partner_altmode_usb4 &&
+ cd321x->partner_altmode_usb4->eudo == eudo)
+ return 0;
+
+ cd321x_unregister_usb4_altmode(cd321x);
+ cd321x->partner_altmode_usb4 =
+ typec_partner_register_altmode(cd321x->tps.partner, &desc);
+ if (IS_ERR(cd321x->partner_altmode_usb4)) {
+ ret = PTR_ERR(cd321x->partner_altmode_usb4);
+ cd321x->partner_altmode_usb4 = NULL;
+ return ret;
+ }
+
+ return 0;
+}
+
static void cd321x_typec_update_mode(struct tps6598x *tps, struct cd321x_status *st)
{
struct cd321x *cd321x = container_of(tps, struct cd321x, tps);
+ int ret;
+
+ if ((st->data_status & TPS_DATA_STATUS_DATA_CONNECTION) &&
+ !(st->data_status & CD321X_DATA_STATUS_USB4_CONNECTION)) {
+ if (st->data_status & TPS_DATA_STATUS_USB3_CONNECTION)
+ typec_partner_set_usb_mode(tps->partner, USB_MODE_USB3);
+ else if (st->data_status & TPS_DATA_STATUS_USB2_CONNECTION)
+ typec_partner_set_usb_mode(tps->partner, USB_MODE_USB2);
+ }
if (!(st->data_status & TPS_DATA_STATUS_DATA_CONNECTION)) {
- if (cd321x->state.mode == TYPEC_STATE_SAFE)
+ if (cd321x->state.mode == TYPEC_STATE_SAFE &&
+ (!cd321x->partner_altmode_tbt || !cd321x->partner_altmode_tbt->active) &&
+ (!cd321x->partner_altmode_usb4 || !cd321x->partner_altmode_usb4->active))
return;
+
+ cd321x_deactivate_altmodes(cd321x);
cd321x->state.alt = NULL;
cd321x->state.mode = TYPEC_STATE_SAFE;
cd321x->state.data = NULL;
@@ -718,6 +867,7 @@ static void cd321x_typec_update_mode(struct tps6598x *tps, struct cd321x_status
return;
}
+ cd321x_deactivate_altmodes(cd321x);
dp_data.status = le32_to_cpu(st->dp_sid_status.status_rx);
dp_data.conf = le32_to_cpu(st->dp_sid_status.configure);
cd321x->state.alt = cd321x->port_altmode_dp;
@@ -728,7 +878,8 @@ static void cd321x_typec_update_mode(struct tps6598x *tps, struct cd321x_status
struct typec_thunderbolt_data tbt_data;
if (cd321x->state.alt == cd321x->port_altmode_tbt &&
- cd321x->state.mode == TYPEC_TBT_MODE)
+ cd321x->state.mode == TYPEC_TBT_MODE &&
+ cd321x->partner_altmode_tbt && cd321x->partner_altmode_tbt->active)
return;
tbt_data.cable_mode = TBT_MODE |
@@ -744,27 +895,54 @@ static void cd321x_typec_update_mode(struct tps6598x *tps, struct cd321x_status
(u32)le16_to_cpu(st->intel_vid_status.device_mode) << 16;
tbt_data.enter_vdo =
(u32)le16_to_cpu(st->intel_vid_status.enter_vdo) << 16;
+
+ ret = cd321x_register_tbt_altmodes(cd321x, &tbt_data, st->data_status);
+ if (ret) {
+ dev_err(tps->dev, "failed to register Thunderbolt altmodes: %d\n", ret);
+ return;
+ }
+
+ cd321x_deactivate_altmodes(cd321x);
cd321x->state.alt = cd321x->port_altmode_tbt;
cd321x->state.mode = TYPEC_TBT_MODE;
cd321x->state.data = &tbt_data;
- typec_mux_set(cd321x->mux, &cd321x->state);
+ ret = typec_mux_set(cd321x->mux, &cd321x->state);
+ if (!ret) {
+ typec_altmode_update_active(cd321x->plug_altmode_tbt, true);
+ typec_altmode_update_active(cd321x->partner_altmode_tbt, true);
+ }
} else if (st->data_status & CD321X_DATA_STATUS_USB4_CONNECTION) {
struct enter_usb_data eusb_data;
- if (cd321x->state.alt == NULL && cd321x->state.mode == TYPEC_MODE_USB4)
+ if (!cd321x->state.alt &&
+ cd321x->state.mode == TYPEC_MODE_USB4 &&
+ cd321x->partner_altmode_usb4 && cd321x->partner_altmode_usb4->active)
return;
eusb_data.eudo = le32_to_cpu(st->usb4_status.eudo);
eusb_data.active_link_training =
!!(st->data_status & TPS_DATA_STATUS_ACTIVE_LINK_TRAIN);
+ ret = cd321x_register_usb4_altmode(cd321x, eusb_data.eudo);
+ if (ret) {
+ dev_err(tps->dev, "failed to register USB4 altmode: %d\n", ret);
+ return;
+ }
+
+ cd321x_deactivate_altmodes(cd321x);
cd321x->state.alt = NULL;
cd321x->state.data = &eusb_data;
cd321x->state.mode = TYPEC_MODE_USB4;
- typec_mux_set(cd321x->mux, &cd321x->state);
+ ret = typec_mux_set(cd321x->mux, &cd321x->state);
+ if (!ret) {
+ typec_partner_set_usb_mode(tps->partner, USB_MODE_USB4);
+ typec_altmode_update_active(cd321x->partner_altmode_usb4, true);
+ }
} else {
if (cd321x->state.alt == NULL && cd321x->state.mode == TYPEC_STATE_USB)
return;
+
+ cd321x_deactivate_altmodes(cd321x);
cd321x->state.alt = NULL;
cd321x->state.mode = TYPEC_STATE_USB;
cd321x->state.data = NULL;
@@ -819,24 +997,30 @@ static void cd321x_update_work(struct work_struct *work)
&cd321x->cur_partner_identity, sizeof(struct usb_pd_identity))));
/* If we are switching from an active role, transition to USB_ROLE_NONE first */
- if (old_role != USB_ROLE_NONE && (new_role != old_role || was_disconnected))
+ if (old_role != USB_ROLE_NONE &&
+ (new_role != old_role || partner_changed || was_disconnected))
usb_role_switch_set_role(tps->role_sw, USB_ROLE_NONE);
- /* Process partner disconnection or change */
- if (!new_connected || partner_changed) {
- if (!IS_ERR(tps->partner))
- typec_unregister_partner(tps->partner);
- tps->partner = NULL;
- }
-
- /* If there was a disconnection, set PHY to off */
- if (!new_connected || was_disconnected) {
+ /*
+ * Also deactivate any altmodes and switch to SAFE_STATE if there was an
+ * active connection.
+ */
+ if (!new_connected || partner_changed || was_disconnected) {
+ cd321x_deactivate_altmodes(cd321x);
cd321x->state.alt = NULL;
cd321x->state.mode = TYPEC_STATE_SAFE;
cd321x->state.data = NULL;
typec_set_mode(tps->port, TYPEC_STATE_SAFE);
}
+ /* Process partner disconnection or change after all child modes exited. */
+ if (!new_connected || partner_changed) {
+ cd321x_unregister_partner_altmodes(cd321x);
+ if (!IS_ERR(tps->partner))
+ typec_unregister_partner(tps->partner);
+ tps->partner = NULL;
+ }
+
/* Update Type-C properties */
typec_set_pwr_opmode(tps->port, pwr_opmode);
typec_set_pwr_role(tps->port, TPS_STATUS_TO_TYPEC_PORTROLE(st.status));
@@ -863,6 +1047,7 @@ static void cd321x_update_work(struct work_struct *work)
tps->partner = typec_register_partner(tps->port, &desc);
if (IS_ERR(tps->partner)) {
dev_warn(tps->dev, "%s: failed to register partner\n", __func__);
+ tps->partner = NULL;
return;
}
@@ -1380,6 +1565,7 @@ cd321x_unregister_port(struct tps6598x *tps)
{
struct cd321x *cd321x = container_of(tps, struct cd321x, tps);
+ cd321x_unregister_partner_altmodes(cd321x);
typec_mux_put(cd321x->mux);
cd321x->mux = NULL;
typec_unregister_altmode(cd321x->port_altmode_dp);
@@ -1802,6 +1988,14 @@ static void cd321x_remove(struct tps6598x *tps)
struct cd321x *cd321x = container_of(tps, struct cd321x, tps);
cancel_delayed_work_sync(&cd321x->update_work);
+
+ guard(mutex)(&tps->lock);
+ cd321x_deactivate_altmodes(cd321x);
+ cd321x->state.alt = NULL;
+ cd321x->state.mode = TYPEC_STATE_SAFE;
+ cd321x->state.data = NULL;
+ typec_set_mode(tps->port, TYPEC_STATE_SAFE);
+ cd321x_unregister_partner_altmodes(cd321x);
}
static int tps6598x_probe(struct i2c_client *client)
--
2.55.0
^ permalink raw reply [flat|nested] 41+ messages in thread
* [PATCH v2 05/22] dt-bindings: thunderbolt: Add Apple USB4/Thunderbolt NHI
2026-09-06 18:36 [PATCH v2 00/22] Initial USB4/Thunderbolt support for Apple M1/M2/M3 SoCs Sven Peter
` (3 preceding siblings ...)
2026-09-06 18:36 ` [PATCH v2 04/22] usb: typec: tipd: Publish CD321x partner alternate modes Sven Peter
@ 2026-09-06 18:36 ` Sven Peter
2026-09-06 18:36 ` [PATCH v2 06/22] dt-bindings: thunderbolt: Add Apple USB4/Thunderbolt ACIO block Sven Peter
` (17 subsequent siblings)
22 siblings, 0 replies; 41+ messages in thread
From: Sven Peter @ 2026-09-06 18:36 UTC (permalink / raw)
To: Heikki Krogerus, Greg Kroah-Hartman, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, 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, Joshua Peisach
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>
Reviewed-by: Joshua Peisach <jpeisach@ubuntu.com>
---
.../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 096fe7535c02..a9d2e2a914c2 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2659,6 +2659,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
@@ -27217,6 +27218,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 [flat|nested] 41+ messages in thread
* [PATCH v2 06/22] dt-bindings: thunderbolt: Add Apple USB4/Thunderbolt ACIO block
2026-09-06 18:36 [PATCH v2 00/22] Initial USB4/Thunderbolt support for Apple M1/M2/M3 SoCs Sven Peter
` (4 preceding siblings ...)
2026-09-06 18:36 ` [PATCH v2 05/22] dt-bindings: thunderbolt: Add Apple USB4/Thunderbolt NHI Sven Peter
@ 2026-09-06 18:36 ` Sven Peter
2026-09-06 18:36 ` [PATCH v2 07/22] thunderbolt: Try reading host DROM from device tree first Sven Peter
` (16 subsequent siblings)
22 siblings, 0 replies; 41+ messages in thread
From: Sven Peter @ 2026-09-06 18:36 UTC (permalink / raw)
To: Heikki Krogerus, Greg Kroah-Hartman, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, 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 PD controller publishes the negotiated
mode and cable information through the Type-C class. ACIO finds the
corresponding Type-C port through the OF graph connection.
Signed-off-by: Sven Peter <sven@kernel.org>
---
.../thunderbolt/apple,t8103-usb4-acio.yaml | 213 +++++++++++++++++++++
MAINTAINERS | 1 +
2 files changed, 214 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..5bfe7a66923e
--- /dev/null
+++ b/Documentation/devicetree/bindings/thunderbolt/apple,t8103-usb4-acio.yaml
@@ -0,0 +1,213 @@
+# 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 publish the negotiated mode and
+ cable information through the Type-C class. ACIO finds the corresponding
+ Type-C port through the OF graph 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
+
+ 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: Lane 0 adapter of the connector-facing USB4 port
+
+ port@2:
+ $ref: /schemas/graph.yaml#/properties/port
+ description: Lane 1 adapter of the connector-facing USB4 port
+
+ 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
+ - '#address-cells'
+ - '#size-cells'
+ - ranges
+ - nonposted-mmio
+ - ports
+ - iommu@a80000
+ - nhi@f00000
+
+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>;
+ 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 a9d2e2a914c2..406cf664dedc 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2659,6 +2659,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 [flat|nested] 41+ messages in thread
* [PATCH v2 07/22] thunderbolt: Try reading host DROM from device tree first
2026-09-06 18:36 [PATCH v2 00/22] Initial USB4/Thunderbolt support for Apple M1/M2/M3 SoCs Sven Peter
` (5 preceding siblings ...)
2026-09-06 18:36 ` [PATCH v2 06/22] dt-bindings: thunderbolt: Add Apple USB4/Thunderbolt ACIO block Sven Peter
@ 2026-09-06 18:36 ` Sven Peter
2026-09-06 18:36 ` [PATCH v2 08/22] thunderbolt: Don't read the UID if we already know it Sven Peter
` (15 subsequent siblings)
22 siblings, 0 replies; 41+ messages in thread
From: Sven Peter @ 2026-09-06 18:36 UTC (permalink / raw)
To: Heikki Krogerus, Greg Kroah-Hartman, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, 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, Joshua Peisach
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>
Reviewed-by: Joshua Peisach <jpeisach@ubuntu.com>
---
drivers/thunderbolt/eeprom.c | 27 ++++++++++++++++++++++-----
1 file changed, 22 insertions(+), 5 deletions(-)
diff --git a/drivers/thunderbolt/eeprom.c b/drivers/thunderbolt/eeprom.c
index 2a13fa6888ba..69d5c5f70eaf 100644
--- a/drivers/thunderbolt/eeprom.c
+++ b/drivers/thunderbolt/eeprom.c
@@ -468,14 +468,15 @@ 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 +484,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 +500,16 @@ static int tb_drom_copy_efi(struct tb_switch *sw, u16 *size)
return -EINVAL;
}
+static int tb_drom_copy_of_apple(struct tb_switch *sw, u16 *size)
+{
+ return tb_drom_copy_property(sw, "apple,thunderbolt-drom", size);
+}
+
+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 +685,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] 41+ messages in thread
* [PATCH v2 08/22] thunderbolt: Don't read the UID if we already know it
2026-09-06 18:36 [PATCH v2 00/22] Initial USB4/Thunderbolt support for Apple M1/M2/M3 SoCs Sven Peter
` (6 preceding siblings ...)
2026-09-06 18:36 ` [PATCH v2 07/22] thunderbolt: Try reading host DROM from device tree first Sven Peter
@ 2026-09-06 18:36 ` Sven Peter
2026-09-06 18:36 ` [PATCH v2 09/22] thunderbolt: Allocate ring HopID before requesting the ring interrupt Sven Peter
` (14 subsequent siblings)
22 siblings, 0 replies; 41+ messages in thread
From: Sven Peter @ 2026-09-06 18:36 UTC (permalink / raw)
To: Heikki Krogerus, Greg Kroah-Hartman, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, 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, Joshua Peisach
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>
Reviewed-by: Joshua Peisach <jpeisach@ubuntu.com>
---
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 [flat|nested] 41+ messages in thread
* [PATCH v2 09/22] thunderbolt: Allocate ring HopID before requesting the ring interrupt
2026-09-06 18:36 [PATCH v2 00/22] Initial USB4/Thunderbolt support for Apple M1/M2/M3 SoCs Sven Peter
` (7 preceding siblings ...)
2026-09-06 18:36 ` [PATCH v2 08/22] thunderbolt: Don't read the UID if we already know it Sven Peter
@ 2026-09-06 18:36 ` Sven Peter
2026-09-06 18:36 ` [PATCH v2 10/22] thunderbolt: Unlock host router ports during startup Sven Peter
` (13 subsequent siblings)
22 siblings, 0 replies; 41+ messages in thread
From: Sven Peter @ 2026-09-06 18:36 UTC (permalink / raw)
To: Heikki Krogerus, Greg Kroah-Hartman, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, 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, Joshua Peisach
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>
Reviewed-by: Joshua Peisach <jpeisach@ubuntu.com>
---
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 [flat|nested] 41+ messages in thread
* [PATCH v2 10/22] thunderbolt: Unlock host router ports during startup
2026-09-06 18:36 [PATCH v2 00/22] Initial USB4/Thunderbolt support for Apple M1/M2/M3 SoCs Sven Peter
` (8 preceding siblings ...)
2026-09-06 18:36 ` [PATCH v2 09/22] thunderbolt: Allocate ring HopID before requesting the ring interrupt Sven Peter
@ 2026-09-06 18:36 ` Sven Peter
2026-09-08 8:22 ` Mika Westerberg
2026-09-06 18:36 ` [PATCH v2 11/22] thunderbolt: Find Apple VSE capability " Sven Peter
` (12 subsequent siblings)
22 siblings, 1 reply; 41+ messages in thread
From: Sven Peter @ 2026-09-06 18:36 UTC (permalink / raw)
To: Heikki Krogerus, Greg Kroah-Hartman, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, 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 downstream ports of the host router may still be locked after a
reset. Unlock them when the software connection manager starts before
scanning for any connected devices.
Signed-off-by: Sven Peter <sven@kernel.org>
---
drivers/thunderbolt/tb.c | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c
index 47753a5c0f2e..bf1c192ec10a 100644
--- a/drivers/thunderbolt/tb.c
+++ b/drivers/thunderbolt/tb.c
@@ -3016,6 +3016,7 @@ static int tb_scan_finalize_switch(struct device *dev, void *data)
static int tb_start(struct tb *tb, bool reset)
{
struct tb_cm *tcm = tb_priv(tb);
+ struct tb_port *port;
bool discover = true;
int ret;
@@ -3049,6 +3050,14 @@ static int tb_start(struct tb *tb, bool reset)
return dev_err_probe(tb->nhi->dev, ret, "failed to add host router\n");
}
+ /* Make all host router downstream ports accessible to the CM. */
+ tb_switch_for_each_port(tb->root_switch, port) {
+ if (!tb_port_is_null(port))
+ continue;
+ if (tb_port_unlock(port))
+ tb_port_warn(port, "failed to unlock port\n");
+ }
+
/*
* To support highest CLx state, we set host router's TMU to
* Normal mode.
--
2.55.0
^ permalink raw reply [flat|nested] 41+ messages in thread
* [PATCH v2 11/22] thunderbolt: Find Apple VSE capability during startup
2026-09-06 18:36 [PATCH v2 00/22] Initial USB4/Thunderbolt support for Apple M1/M2/M3 SoCs Sven Peter
` (9 preceding siblings ...)
2026-09-06 18:36 ` [PATCH v2 10/22] thunderbolt: Unlock host router ports during startup Sven Peter
@ 2026-09-06 18:36 ` Sven Peter
2026-09-07 13:38 ` Joshua Peisach
2026-09-06 18:36 ` [PATCH v2 12/22] thunderbolt: Add ring_interrupt_active to tb_nhi_ops Sven Peter
` (11 subsequent siblings)
22 siblings, 1 reply; 41+ messages in thread
From: Sven Peter @ 2026-09-06 18:36 UTC (permalink / raw)
To: Heikki Krogerus, Greg Kroah-Hartman, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, 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
Only Apple Silicon host routers have the Apple vendor-specific extended
capability. Look it up when the software connection manager starts and
store its offset in the root switch.
The Apple NHI driver can then use the stored offset when programming
cable information without searching the router capabilities itself.
Signed-off-by: Sven Peter <sven@kernel.org>
---
drivers/thunderbolt/tb.c | 4 ++++
drivers/thunderbolt/tb.h | 2 ++
drivers/thunderbolt/tb_regs.h | 1 +
3 files changed, 7 insertions(+)
diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c
index bf1c192ec10a..d2832323cc36 100644
--- a/drivers/thunderbolt/tb.c
+++ b/drivers/thunderbolt/tb.c
@@ -3025,6 +3025,10 @@ static int tb_start(struct tb *tb, bool reset)
return dev_err_probe(tb->nhi->dev, PTR_ERR(tb->root_switch),
"failed to allocate host router\n");
+ ret = tb_switch_find_vse_cap(tb->root_switch, TB_VSE_CAP_APPLE);
+ if (ret > 0)
+ tb->root_switch->cap_vsec_apple = ret;
+
/*
* ICM firmware upgrade needs running firmware and in native
* mode that is not available so disable firmware upgrade of the
diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h
index 4373336d9425..fbd1a0a9927a 100644
--- a/drivers/thunderbolt/tb.h
+++ b/drivers/thunderbolt/tb.h
@@ -132,6 +132,7 @@ struct tb_switch_tmu {
* @link_usb4: Upstream link is USB4
* @generation: Switch Thunderbolt generation
* @cap_plug_events: Offset to the plug events capability (%0 if not found)
+ * @cap_vsec_apple: Offset to the Apple vendor specific capability (%0 if not found)
* @cap_vsec_tmu: Offset to the TMU vendor specific capability (%0 if not found)
* @cap_lc: Offset to the link controller capability (%0 if not found)
* @cap_lp: Offset to the low power (CLx for TBT) capability (%0 if not found)
@@ -187,6 +188,7 @@ struct tb_switch {
bool link_usb4;
unsigned int generation;
int cap_plug_events;
+ int cap_vsec_apple;
int cap_vsec_tmu;
int cap_lc;
int cap_lp;
diff --git a/drivers/thunderbolt/tb_regs.h b/drivers/thunderbolt/tb_regs.h
index 92d893634d2b..a37e55fec157 100644
--- a/drivers/thunderbolt/tb_regs.h
+++ b/drivers/thunderbolt/tb_regs.h
@@ -31,6 +31,7 @@ enum tb_switch_cap {
};
enum tb_switch_vse_cap {
+ TB_VSE_CAP_APPLE = 0x00,
TB_VSE_CAP_PLUG_EVENTS = 0x01, /* also EEPROM */
TB_VSE_CAP_TIME2 = 0x03,
TB_VSE_CAP_CP_LP = 0x04,
--
2.55.0
^ permalink raw reply [flat|nested] 41+ messages in thread
* [PATCH v2 12/22] thunderbolt: Add ring_interrupt_active to tb_nhi_ops
2026-09-06 18:36 [PATCH v2 00/22] Initial USB4/Thunderbolt support for Apple M1/M2/M3 SoCs Sven Peter
` (10 preceding siblings ...)
2026-09-06 18:36 ` [PATCH v2 11/22] thunderbolt: Find Apple VSE capability " Sven Peter
@ 2026-09-06 18:36 ` Sven Peter
2026-09-06 18:36 ` [PATCH v2 13/22] thunderbolt: Add ring register accessors " Sven Peter
` (10 subsequent siblings)
22 siblings, 0 replies; 41+ messages in thread
From: Sven Peter @ 2026-09-06 18:36 UTC (permalink / raw)
To: Heikki Krogerus, Greg Kroah-Hartman, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, 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, Joshua Peisach
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>
Reviewed-by: Joshua Peisach <jpeisach@ubuntu.com>
---
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 [flat|nested] 41+ messages in thread
* [PATCH v2 13/22] thunderbolt: Add ring register accessors to tb_nhi_ops
2026-09-06 18:36 [PATCH v2 00/22] Initial USB4/Thunderbolt support for Apple M1/M2/M3 SoCs Sven Peter
` (11 preceding siblings ...)
2026-09-06 18:36 ` [PATCH v2 12/22] thunderbolt: Add ring_interrupt_active to tb_nhi_ops Sven Peter
@ 2026-09-06 18:36 ` Sven Peter
2026-09-08 8:32 ` Mika Westerberg
2026-09-06 18:36 ` [PATCH v2 14/22] thunderbolt: Add ring_interrupt_mask " Sven Peter
` (9 subsequent siblings)
22 siblings, 1 reply; 41+ messages in thread
From: Sven Peter @ 2026-09-06 18:36 UTC (permalink / raw)
To: Heikki Krogerus, Greg Kroah-Hartman, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, 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. Add optional tb_nhi_ops hooks for their base addresses
which fall back to the standard USB4 register layout when they are not
set.
Signed-off-by: Sven Peter <sven@kernel.org>
---
drivers/thunderbolt/nhi.c | 32 ++++++++++++++++++++++++++------
drivers/thunderbolt/nhi.h | 6 ++++++
2 files changed, 32 insertions(+), 6 deletions(-)
diff --git a/drivers/thunderbolt/nhi.c b/drivers/thunderbolt/nhi.c
index fc54ff7edfb9..1b1e4a12c744 100644
--- a/drivers/thunderbolt/nhi.c
+++ b/drivers/thunderbolt/nhi.c
@@ -179,19 +179,37 @@ void nhi_disable_interrupts(struct tb_nhi *nhi)
static void __iomem *ring_desc_base(struct tb_ring *ring)
{
void __iomem *io = ring->nhi->iobase;
+
io += ring->is_tx ? REG_TX_RING_BASE : REG_RX_RING_BASE;
io += ring->hop * 16;
return io;
}
+static void __iomem *nhi_ring_desc_base(struct tb_ring *ring)
+{
+ if (ring->nhi->ops->ring_desc_base)
+ return ring->nhi->ops->ring_desc_base(ring);
+
+ return ring_desc_base(ring);
+}
+
static void __iomem *ring_options_base(struct tb_ring *ring)
{
void __iomem *io = ring->nhi->iobase;
+
io += ring->is_tx ? REG_TX_OPTIONS_BASE : REG_RX_OPTIONS_BASE;
io += ring->hop * 32;
return io;
}
+static void __iomem *nhi_ring_options_base(struct tb_ring *ring)
+{
+ if (ring->nhi->ops->ring_options_base)
+ return ring->nhi->ops->ring_options_base(ring);
+
+ return ring_options_base(ring);
+}
+
static void ring_iowrite_cons(struct tb_ring *ring, u16 cons)
{
/*
@@ -199,29 +217,31 @@ static void ring_iowrite_cons(struct tb_ring *ring, u16 cons)
* are ignored by the hardware so we can save one ioread32() by
* filling the read-only bits with zeroes.
*/
- iowrite32(cons, ring_desc_base(ring) + 8);
+ iowrite32(cons, nhi_ring_desc_base(ring) + 8);
}
static void ring_iowrite_prod(struct tb_ring *ring, u16 prod)
{
/* See ring_iowrite_cons() above for explanation */
- iowrite32(prod << 16, ring_desc_base(ring) + 8);
+ iowrite32(prod << 16, nhi_ring_desc_base(ring) + 8);
}
static void ring_iowrite32desc(struct tb_ring *ring, u32 value, u32 offset)
{
- iowrite32(value, ring_desc_base(ring) + offset);
+ iowrite32(value, nhi_ring_desc_base(ring) + 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 = nhi_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)
{
- iowrite32(value, ring_options_base(ring) + offset);
+ iowrite32(value, nhi_ring_options_base(ring) + offset);
}
static bool ring_full(struct tb_ring *ring)
diff --git a/drivers/thunderbolt/nhi.h b/drivers/thunderbolt/nhi.h
index 4884c3f5a2b2..3a963b0b5b46 100644
--- a/drivers/thunderbolt/nhi.h
+++ b/drivers/thunderbolt/nhi.h
@@ -52,6 +52,10 @@ 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_desc_base: NHI specific hook returning the ring descriptor registers.
+ * If not set the standard USB4 NHI registers are used.
+ * @ring_options_base: NHI specific hook returning the ring option registers.
+ * If not set the standard USB4 NHI registers are used.
* @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.
@@ -70,6 +74,8 @@ 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 __iomem *(*ring_desc_base)(struct tb_ring *ring);
+ void __iomem *(*ring_options_base)(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);
--
2.55.0
^ permalink raw reply [flat|nested] 41+ messages in thread
* [PATCH v2 14/22] thunderbolt: Add ring_interrupt_mask to tb_nhi_ops
2026-09-06 18:36 [PATCH v2 00/22] Initial USB4/Thunderbolt support for Apple M1/M2/M3 SoCs Sven Peter
` (12 preceding siblings ...)
2026-09-06 18:36 ` [PATCH v2 13/22] thunderbolt: Add ring register accessors " Sven Peter
@ 2026-09-06 18:36 ` Sven Peter
2026-09-06 18:36 ` [PATCH v2 15/22] thunderbolt: Add ring_configure " Sven Peter
` (8 subsequent siblings)
22 siblings, 0 replies; 41+ messages in thread
From: Sven Peter @ 2026-09-06 18:36 UTC (permalink / raw)
To: Heikki Krogerus, Greg Kroah-Hartman, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, 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, Joshua Peisach
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>
Reviewed-by: Joshua Peisach <jpeisach@ubuntu.com>
---
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 1b1e4a12c744..4ef86e7c0285 100644
--- a/drivers/thunderbolt/nhi.c
+++ b/drivers/thunderbolt/nhi.c
@@ -427,6 +427,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)
{
@@ -434,7 +442,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);
@@ -455,7 +463,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 3a963b0b5b46..ca3eddf862e0 100644
--- a/drivers/thunderbolt/nhi.h
+++ b/drivers/thunderbolt/nhi.h
@@ -59,6 +59,9 @@ extern const struct dev_pm_ops nhi_pm_ops;
* @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
@@ -77,6 +80,7 @@ struct tb_nhi_ops {
void __iomem *(*ring_desc_base)(struct tb_ring *ring);
void __iomem *(*ring_options_base)(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 [flat|nested] 41+ messages in thread
* [PATCH v2 15/22] thunderbolt: Add ring_configure to tb_nhi_ops
2026-09-06 18:36 [PATCH v2 00/22] Initial USB4/Thunderbolt support for Apple M1/M2/M3 SoCs Sven Peter
` (13 preceding siblings ...)
2026-09-06 18:36 ` [PATCH v2 14/22] thunderbolt: Add ring_interrupt_mask " Sven Peter
@ 2026-09-06 18:36 ` Sven Peter
2026-09-06 18:36 ` [PATCH v2 16/22] thunderbolt: Add add_links " Sven Peter
` (7 subsequent siblings)
22 siblings, 0 replies; 41+ messages in thread
From: Sven Peter @ 2026-09-06 18:36 UTC (permalink / raw)
To: Heikki Krogerus, Greg Kroah-Hartman, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, 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 also 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.
For the standard method also just write E2E flow control at the same
time the rest of the configuration is written.
Signed-off-by: Sven Peter <sven@kernel.org>
---
drivers/thunderbolt/nhi.c | 48 +++++++++++++++++++++++++++--------------------
drivers/thunderbolt/nhi.h | 4 ++++
2 files changed, 32 insertions(+), 20 deletions(-)
diff --git a/drivers/thunderbolt/nhi.c b/drivers/thunderbolt/nhi.c
index 4ef86e7c0285..2d622b365c4f 100644
--- a/drivers/thunderbolt/nhi.c
+++ b/drivers/thunderbolt/nhi.c
@@ -244,6 +244,24 @@ static void ring_iowrite32options(struct tb_ring *ring, u32 value, u32 offset)
iowrite32(value, nhi_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 | 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;
@@ -699,6 +717,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;
@@ -722,30 +741,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",
@@ -755,10 +757,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 ca3eddf862e0..e4964772a442 100644
--- a/drivers/thunderbolt/nhi.h
+++ b/drivers/thunderbolt/nhi.h
@@ -62,6 +62,9 @@ extern const struct dev_pm_ops nhi_pm_ops;
* @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
@@ -81,6 +84,7 @@ struct tb_nhi_ops {
void __iomem *(*ring_options_base)(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 [flat|nested] 41+ messages in thread
* [PATCH v2 16/22] thunderbolt: Add add_links to tb_nhi_ops
2026-09-06 18:36 [PATCH v2 00/22] Initial USB4/Thunderbolt support for Apple M1/M2/M3 SoCs Sven Peter
` (14 preceding siblings ...)
2026-09-06 18:36 ` [PATCH v2 15/22] thunderbolt: Add ring_configure " Sven Peter
@ 2026-09-06 18:36 ` Sven Peter
2026-09-08 8:35 ` Mika Westerberg
2026-09-06 18:36 ` [PATCH v2 17/22] thunderbolt: Add QUIRK_NO_USB3_BW_ALLOC Sven Peter
` (6 subsequent siblings)
22 siblings, 1 reply; 41+ messages in thread
From: Sven Peter @ 2026-09-06 18:36 UTC (permalink / raw)
To: Heikki Krogerus, Greg Kroah-Hartman, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, 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
PCI NHI drivers discover device links through ACPI or PCI topology but
platform drivers, like the Apple Silicon one, can use neither of those
methods. Instead, add a callback so that platform drivers can discover
and add these links through e.g. device tree connections.
Signed-off-by: Sven Peter <sven@kernel.org>
---
drivers/thunderbolt/nhi.h | 2 ++
drivers/thunderbolt/tb.c | 3 ++-
2 files changed, 4 insertions(+), 1 deletion(-)
diff --git a/drivers/thunderbolt/nhi.h b/drivers/thunderbolt/nhi.h
index e4964772a442..addb8bcb4849 100644
--- a/drivers/thunderbolt/nhi.h
+++ b/drivers/thunderbolt/nhi.h
@@ -65,6 +65,7 @@ extern const struct dev_pm_ops nhi_pm_ops;
* @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.
+ * @add_links: NHI specific hook to add device links to tunneled native ports
* @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
@@ -85,6 +86,7 @@ struct tb_nhi_ops {
void (*ring_interrupt_active)(struct tb_ring *ring, bool active);
void (*ring_interrupt_mask)(struct tb_ring *ring, bool mask);
void (*ring_configure)(struct tb_ring *ring, u32 flags, u32 e2e_flags);
+ bool (*add_links)(struct tb_nhi *nhi);
bool (*is_present)(struct tb_nhi *nhi);
int (*init_interrupts)(struct tb_nhi *nhi);
void (*reset_interface)(struct tb_nhi *nhi);
diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c
index d2832323cc36..72418a07166e 100644
--- a/drivers/thunderbolt/tb.c
+++ b/drivers/thunderbolt/tb.c
@@ -3434,7 +3434,8 @@ struct tb *tb_probe(struct tb_nhi *nhi)
* before the PCIe/USB stack is resumed so complain here if we
* found them missing.
*/
- if (!tb_apple_add_links(nhi) && !tb_acpi_add_links(nhi))
+ if (!(nhi->ops->add_links && nhi->ops->add_links(nhi)) &&
+ !tb_apple_add_links(nhi) && !tb_acpi_add_links(nhi))
tb_warn(tb, "device links to tunneled native ports are missing!\n");
return tb;
--
2.55.0
^ permalink raw reply [flat|nested] 41+ messages in thread
* [PATCH v2 17/22] thunderbolt: Add QUIRK_NO_USB3_BW_ALLOC
2026-09-06 18:36 [PATCH v2 00/22] Initial USB4/Thunderbolt support for Apple M1/M2/M3 SoCs Sven Peter
` (15 preceding siblings ...)
2026-09-06 18:36 ` [PATCH v2 16/22] thunderbolt: Add add_links " Sven Peter
@ 2026-09-06 18:36 ` Sven Peter
2026-09-06 18:36 ` [PATCH v2 18/22] thunderbolt: Export symbols required by the Apple Silicon driver Sven Peter
` (5 subsequent siblings)
22 siblings, 0 replies; 41+ messages in thread
From: Sven Peter @ 2026-09-06 18:36 UTC (permalink / raw)
To: Heikki Krogerus, Greg Kroah-Hartman, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, 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/quirks.c | 9 +++++++
drivers/thunderbolt/tb.h | 2 ++
drivers/thunderbolt/tunnel.c | 60 +++++++++++++++++++++++++++++---------------
3 files changed, 51 insertions(+), 20 deletions(-)
diff --git a/drivers/thunderbolt/quirks.c b/drivers/thunderbolt/quirks.c
index 9f7914ac2f48..2ac1bfa01c15 100644
--- a/drivers/thunderbolt/quirks.c
+++ b/drivers/thunderbolt/quirks.c
@@ -46,6 +46,12 @@ static void quirk_usb3_maximum_bandwidth(struct tb_switch *sw)
}
}
+static void quirk_no_usb3_bw_alloc(struct tb_switch *sw)
+{
+ sw->quirks |= QUIRK_NO_USB3_BW_ALLOC;
+ tb_sw_dbg(sw, "disabling USB3 bandwidth allocation\n");
+}
+
static void quirk_block_rpm_in_redrive(struct tb_switch *sw)
{
sw->quirks |= QUIRK_KEEP_POWER_IN_DP_REDRIVE;
@@ -61,6 +67,9 @@ struct tb_quirk {
};
static const struct tb_quirk tb_quirks[] = {
+ /* Apple Silicon host routers do not support USB3 bandwidth allocation */
+ { 0x05ac, 0x2000, 0x0000, 0x0000, quirk_no_usb3_bw_alloc },
+
/* Dell WD19TB supports self-authentication on unplug */
{ 0x0000, 0x0000, 0x00d4, 0xb070, quirk_force_power_link },
{ 0x0000, 0x0000, 0x00d4, 0xb071, quirk_force_power_link },
diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h
index fbd1a0a9927a..7c2fcf20e49f 100644
--- a/drivers/thunderbolt/tb.h
+++ b/drivers/thunderbolt/tb.h
@@ -26,6 +26,8 @@
#define QUIRK_NO_CLX BIT(1)
/* Need to keep power on while USB4 port is in redrive mode */
#define QUIRK_KEEP_POWER_IN_DP_REDRIVE BIT(2)
+/* Does not support USB3 bandwidth allocation */
+#define QUIRK_NO_USB3_BW_ALLOC BIT(3)
/**
* struct tb_nvm - Structure holding NVM information
diff --git a/drivers/thunderbolt/tunnel.c b/drivers/thunderbolt/tunnel.c
index 7e8284575dff..0893ec19d606 100644
--- a/drivers/thunderbolt/tunnel.c
+++ b/drivers/thunderbolt/tunnel.c
@@ -2268,24 +2268,41 @@ 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->quirks & QUIRK_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 +2382,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->quirks & QUIRK_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] 41+ messages in thread
* [PATCH v2 18/22] thunderbolt: Export symbols required by the Apple Silicon driver
2026-09-06 18:36 [PATCH v2 00/22] Initial USB4/Thunderbolt support for Apple M1/M2/M3 SoCs Sven Peter
` (16 preceding siblings ...)
2026-09-06 18:36 ` [PATCH v2 17/22] thunderbolt: Add QUIRK_NO_USB3_BW_ALLOC Sven Peter
@ 2026-09-06 18:36 ` Sven Peter
2026-09-06 18:36 ` [PATCH v2 19/22] thunderbolt: Add Apple Silicon support Sven Peter
` (4 subsequent siblings)
22 siblings, 0 replies; 41+ messages in thread
From: Sven Peter @ 2026-09-06 18:36 UTC (permalink / raw)
To: Heikki Krogerus, Greg Kroah-Hartman, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, 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, Joshua Peisach
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_cfg_write() to configure the cable details for its co-processor.
Restrict these exports to the thunderbolt_apple module to keep them
internal to the Thunderbolt subsystem.
Signed-off-by: Sven Peter <sven@kernel.org>
Reviewed-by: Joshua Peisach <jpeisach@ubuntu.com>
---
drivers/thunderbolt/ctl.c | 2 ++
drivers/thunderbolt/domain.c | 2 ++
drivers/thunderbolt/tb.c | 2 ++
3 files changed, 6 insertions(+)
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/tb.c b/drivers/thunderbolt/tb.c
index 72418a07166e..98bf67360cc4 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>
@@ -3440,3 +3441,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 [flat|nested] 41+ messages in thread
* [PATCH v2 19/22] thunderbolt: Add Apple Silicon support
2026-09-06 18:36 [PATCH v2 00/22] Initial USB4/Thunderbolt support for Apple M1/M2/M3 SoCs Sven Peter
` (17 preceding siblings ...)
2026-09-06 18:36 ` [PATCH v2 18/22] thunderbolt: Export symbols required by the Apple Silicon driver Sven Peter
@ 2026-09-06 18:36 ` Sven Peter
2026-09-08 9:18 ` Mika Westerberg
2026-09-06 18:36 ` [PATCH v2 20/22] arm64: dts: apple: t8103: Add USB4 ACIO and NHI Sven Peter
` (3 subsequent siblings)
22 siblings, 1 reply; 41+ messages in thread
From: Sven Peter @ 2026-09-06 18:36 UTC (permalink / raw)
To: Heikki Krogerus, Greg Kroah-Hartman, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, 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 registers notifications for Thunderbolt and USB4 partner modes
on the Type-C bus and derives the cable details from the Thunderbolt VDOs
or USB4 EUDO.
After powering up ACIO, the driver 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 accessors, interrupt handling and ring configuration. It also
uses the OF graph connection between the USB3 adapter and the DWC3
controller to create the device link required to restore tunnels before
USB resumes. 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 | 1277 ++++++++++++++++++++++++++++++++++++++++++
4 files changed, 1294 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 406cf664dedc..a0ddea98813c 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2692,6 +2692,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..f4b16f60701e
--- /dev/null
+++ b/drivers/thunderbolt/apple.c
@@ -0,0 +1,1277 @@
+// 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) Mark the negotiated Type-C alternate mode active
+ * 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
+ *
+ * ACIO must not be started before the PHY is configured. 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.
+ *
+ * When the cable is disconnected ACIO shutdown and PHY shutdown can happen in
+ * either order as long as both have been shutdown before the next connection
+ * is established.
+ */
+
+#include <linux/bitfield.h>
+#include <linux/completion.h>
+#include <linux/device/bus.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/notifier.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/pm_domain.h>
+#include <linux/pm_runtime.h>
+#include <linux/property.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.h>
+#include <linux/usb/typec_altmode.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_USB3_ADAPTER 4
+
+#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 10000 /* ms */
+
+/*
+ * 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_CABLE_INFO 0x01
+#define TB_VSE_CAP_APPLE_CABLE_INFO_PRESENT BIT(0)
+#define TB_VSE_CAP_APPLE_CABLE_INFO_ORIENTATION_REVERSE BIT(1)
+#define TB_VSE_CAP_APPLE_CABLE_INFO_ACTIVE_CABLE BIT(2)
+#define TB_VSE_CAP_APPLE_CABLE_INFO_BIDIR_LSRX BIT(3)
+#define TB_VSE_CAP_APPLE_CABLE_INFO_20_GBPS BIT(4)
+#define TB_VSE_CAP_APPLE_CABLE_INFO_LEGACY_ADAPTER BIT(9)
+#define TB_VSE_CAP_APPLE_CABLE_INFO_TBT2_3 BIT(10)
+
+struct apple_cio;
+
+struct apple_cio_altmode {
+ struct apple_cio *acio;
+ struct typec_altmode *altmode;
+ struct notifier_block notifier;
+};
+
+/**
+ * struct apple_cio - Apple Converged I/O block
+ * @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 block
+ * @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
+ * @connector: Type-C connector firmware node linked to this ACIO
+ * @typec_lock: Serializes Type-C alternate mode attachment and removal
+ * @typec_notifier: Type-C bus notifier used to discover partner alternate modes
+ * @tbt_altmode: Thunderbolt partner alternate mode subscription
+ * @usb4_altmode: USB4 partner alternate mode subscription
+ */
+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 fwnode_handle *connector;
+ struct mutex typec_lock;
+ struct notifier_block typec_notifier;
+ struct apple_cio_altmode tbt_altmode;
+ struct apple_cio_altmode usb4_altmode;
+};
+
+/**
+ * struct apple_nhi - Apple Native Host Interface
+ * @dev: NHI device
+ * @pdev: NHI platform device
+ * @np: NHI device tree node
+ * @acio: Parent ACIO block
+ * @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
+ * @nrings: 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 nrings;
+};
+
+#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)
+{
+ char name[64];
+ int nirqs;
+
+ nirqs = platform_irq_count(anhi->pdev);
+ if (nirqs < 0)
+ return dev_err_probe(anhi->dev, nirqs, "platform_irq_count failed\n");
+ if (!nirqs)
+ return dev_err_probe(anhi->dev, -EINVAL, "no interrupts found\n");
+ if (nirqs % 2)
+ return dev_err_probe(anhi->dev, -EINVAL,
+ "invalid number of interrupts: %d must be even\n",
+ nirqs);
+ anhi->nrings = nirqs / 2;
+
+ anhi->rx_irqs = devm_kcalloc(anhi->dev, anhi->nrings,
+ sizeof(*anhi->rx_irqs), GFP_KERNEL);
+ if (!anhi->rx_irqs)
+ return -ENOMEM;
+ anhi->tx_irqs = devm_kcalloc(anhi->dev, anhi->nrings,
+ sizeof(*anhi->tx_irqs), GFP_KERNEL);
+ if (!anhi->tx_irqs)
+ return -ENOMEM;
+ anhi->rx_irq_names = devm_kcalloc(anhi->dev, anhi->nrings,
+ sizeof(*anhi->rx_irq_names), GFP_KERNEL);
+ if (!anhi->rx_irq_names)
+ return -ENOMEM;
+ anhi->tx_irq_names = devm_kcalloc(anhi->dev, anhi->nrings,
+ sizeof(*anhi->tx_irq_names), GFP_KERNEL);
+ if (!anhi->tx_irq_names)
+ return -ENOMEM;
+
+ for (int i = 0; i < anhi->nrings; ++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->nrings;
+}
+
+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;
+ unsigned int idx;
+
+ anhi = nhi_to_anhi(ring->nhi);
+ 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 __iomem *apple_nhi_ring_desc_base(struct tb_ring *ring)
+{
+ struct apple_nhi *anhi = nhi_to_anhi(ring->nhi);
+ void __iomem *io = anhi->nhi_base;
+
+ io += ring->hop * APPLE_CIO_NHI_RING_STRIDE;
+ io += ring->is_tx ? APPLE_CIO_NHI_TXRING_DESC_BASE :
+ APPLE_CIO_NHI_RXRING_DESC_BASE;
+ return io;
+}
+
+static void __iomem *apple_nhi_ring_options_base(struct tb_ring *ring)
+{
+ return apple_nhi_ring_desc_base(ring) + 0x10;
+}
+
+static void apple_nhi_ring_configure(struct tb_ring *ring, u32 flags, u32 e2e_flags)
+{
+ void __iomem *options = apple_nhi_ring_options_base(ring);
+ struct apple_nhi *anhi = nhi_to_anhi(ring->nhi);
+ u32 sof_eof_mask;
+
+ lockdep_assert_held(&ring->lock);
+
+ if (ring->is_tx) {
+ /*
+ * 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 {
+ 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 bool apple_nhi_add_links(struct tb_nhi *nhi)
+{
+ struct apple_nhi *anhi = nhi_to_anhi(nhi);
+ struct fwnode_handle *endpoint, *remote;
+ struct device_link *link;
+ struct device *consumer;
+
+ endpoint = fwnode_graph_get_endpoint_by_id(dev_fwnode(anhi->acio->dev),
+ APPLE_CIO_USB3_ADAPTER, 0, 0);
+ if (!endpoint)
+ return false;
+
+ remote = fwnode_graph_get_remote_port_parent(endpoint);
+ fwnode_handle_put(endpoint);
+ if (!remote)
+ return false;
+
+ consumer = bus_find_device_by_fwnode(&platform_bus_type, remote);
+ fwnode_handle_put(remote);
+ if (!consumer)
+ return false;
+
+ link = device_link_add(consumer, nhi->dev,
+ DL_FLAG_AUTOREMOVE_SUPPLIER |
+ DL_FLAG_PM_RUNTIME);
+ if (link)
+ dev_dbg(nhi->dev, "created link from %s\n", dev_name(consumer));
+ else
+ dev_warn(nhi->dev, "device link creation from %s failed\n",
+ dev_name(consumer));
+
+ put_device(consumer);
+
+ return !!link;
+}
+
+static const struct tb_nhi_ops apple_nhi_ops = {
+ .request_ring_irq = apple_nhi_request_irq,
+ .release_ring_irq = apple_nhi_release_irq,
+ .ring_desc_base = apple_nhi_ring_desc_base,
+ .ring_options_base = apple_nhi_ring_options_base,
+ .ring_interrupt_active = apple_nhi_ring_interrupt_active,
+ .ring_interrupt_mask = apple_nhi_ring_interrupt_mask,
+ .ring_configure = apple_nhi_ring_configure,
+ .add_links = apple_nhi_add_links,
+};
+
+static int apple_nhi_probe(struct platform_device *pdev)
+{
+ struct apple_cio *acio = dev_get_drvdata(pdev->dev.parent);
+ struct apple_tunable *tunable;
+ struct apple_nhi *anhi;
+ struct resource *res;
+ 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);
+
+ /*
+ * Only 42 bits seem to be wired up for the IOVA space but this may be further
+ * limited by the IOMMU's capabilities.
+ */
+ 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);
+ /* DMA transactions on this platform always go through a DART IOMMU. */
+ anhi->nhi.iommu_dma_protection = true;
+ anhi->nhi.ops = &apple_nhi_ops;
+ anhi->nhi.iobase = anhi->nhi_base;
+ 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->nrings) {
+ ret = dev_err_probe(anhi->dev, -EINVAL,
+ "ring IRQs (%zd) != HOP_COUNT (%d)\n",
+ anhi->nrings, 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 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;
+ }
+
+ if (!anhi->tb->root_switch->cap_vsec_apple) {
+ dev_err(anhi->dev, "unable to find VSE Apple capability\n");
+ ret = -ENODEV;
+ goto err_unlock_tb_domain;
+ }
+
+ ret = tb_sw_write(anhi->tb->root_switch, &acio->target_cable_info, TB_CFG_SWITCH,
+ anhi->tb->root_switch->cap_vsec_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 block
+ * @acio: ACIO block to stop
+ *
+ * Remove the NHI and DART children before shutting down the co-processor and
+ * dropping the power-domain links. 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 block, 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 block
+ * @acio: ACIO block 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. 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)
+{
+ 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++) {
+ struct device_link *link;
+
+ 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))) {
+ dev_err(acio->dev, "timed out waiting for the NHI to come up\n");
+ ret = -ETIMEDOUT;
+ goto err_depopulate;
+ }
+ if (acio->nhi_boot_status) {
+ ret = acio->nhi_boot_status;
+ goto err_depopulate;
+ }
+
+ acio->current_cable_info = acio->target_cable_info;
+ return 0;
+
+err_depopulate:
+ of_platform_depopulate(acio->dev);
+err_shutdown_rtkit:
+ /* Ignore errors here since we're about to cut power to the entire block anyway */
+ apple_rtkit_poweroff(acio->rtk);
+err_free_rtkit:
+ apple_rtkit_free(acio->rtk);
+remove_links:
+ /* Cut power to reset the entire block */
+ for (i = 0; i < acio->pd_list->num_pds; i++) {
+ if (acio->pd_list->pd_links[i])
+ device_link_del(acio->pd_list->pd_links[i]);
+ acio->pd_list->pd_links[i] = NULL;
+ }
+
+ return ret;
+}
+
+static int apple_cio_set_cable_info(struct apple_cio *acio, u32 cable_info)
+{
+ guard(mutex)(&acio->lock);
+
+ acio->target_cable_info = cable_info;
+ 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 block
+ * 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_tbt_cable_info(struct apple_cio *acio,
+ struct typec_altmode *altmode, u32 *cable_info)
+{
+ u32 info = TB_VSE_CAP_APPLE_CABLE_INFO_PRESENT |
+ TB_VSE_CAP_APPLE_CABLE_INFO_TBT2_3;
+ enum typec_orientation orientation;
+ struct typec_altmode *plug;
+ u32 cable_mode;
+
+ plug = typec_altmode_get_plug(altmode, TYPEC_PLUG_SOP_P);
+ if (!plug)
+ return -ENODEV;
+
+ cable_mode = plug->vdo;
+ typec_altmode_put_plug(plug);
+ orientation = typec_altmode_get_orientation(altmode);
+
+ if (cable_mode & TBT_CABLE_ACTIVE_PASSIVE) {
+ info |= TB_VSE_CAP_APPLE_CABLE_INFO_ACTIVE_CABLE;
+ if (!(cable_mode & TBT_CABLE_LINK_TRAINING))
+ info |= TB_VSE_CAP_APPLE_CABLE_INFO_BIDIR_LSRX;
+ }
+ if (TBT_ADAPTER(altmode->vdo))
+ info |= TB_VSE_CAP_APPLE_CABLE_INFO_LEGACY_ADAPTER;
+ if (TBT_CABLE_SPEED(cable_mode) == TBT_CABLE_10_AND_20GBPS)
+ info |= TB_VSE_CAP_APPLE_CABLE_INFO_20_GBPS;
+ if (orientation == TYPEC_ORIENTATION_REVERSE)
+ info |= TB_VSE_CAP_APPLE_CABLE_INFO_ORIENTATION_REVERSE;
+
+ dev_dbg(acio->dev,
+ "TBT cable: cable mode %#x, device mode %#x, orientation %d -> cable info %#x\n",
+ cable_mode, altmode->vdo, orientation, info);
+ *cable_info = info;
+ return 0;
+}
+
+static int apple_cio_usb4_cable_info(struct apple_cio *acio,
+ struct typec_altmode *altmode, u32 *cable_info)
+{
+ u32 info = TB_VSE_CAP_APPLE_CABLE_INFO_PRESENT;
+ enum typec_orientation orientation;
+ u32 eudo = altmode->eudo;
+
+ if (FIELD_GET(EUDO_USB_MODE_MASK, eudo) != EUDO_USB_MODE_USB4)
+ return -EINVAL;
+
+ orientation = typec_altmode_get_orientation(altmode);
+ if (FIELD_GET(EUDO_CABLE_TYPE_MASK, eudo) != EUDO_CABLE_TYPE_PASSIVE)
+ info |= TB_VSE_CAP_APPLE_CABLE_INFO_ACTIVE_CABLE;
+ if (FIELD_GET(EUDO_CABLE_SPEED_MASK, eudo) == EUDO_CABLE_SPEED_USB4_GEN3)
+ info |= TB_VSE_CAP_APPLE_CABLE_INFO_20_GBPS;
+ if (orientation == TYPEC_ORIENTATION_REVERSE)
+ info |= TB_VSE_CAP_APPLE_CABLE_INFO_ORIENTATION_REVERSE;
+
+ dev_dbg(acio->dev, "USB4 cable: EUDO %#x, orientation %d -> cable info %#x\n",
+ eudo, orientation, info);
+ *cable_info = info;
+ return 0;
+}
+
+static int apple_cio_altmode_notify(struct notifier_block *nb,
+ unsigned long action, void *data)
+{
+ struct apple_cio_altmode *subscription;
+ struct typec_altmode *altmode = data;
+ struct apple_cio *acio;
+ u32 cable_info;
+ int ret;
+
+ subscription = container_of(nb, struct apple_cio_altmode, notifier);
+ acio = subscription->acio;
+
+ if (altmode != subscription->altmode)
+ return NOTIFY_DONE;
+
+ switch (action) {
+ case TYPEC_ALTMODE_ENTERED:
+ if (altmode->mode_kind == TYPEC_MODE_KIND_ALTMODE &&
+ altmode->svid == USB_TYPEC_TBT_SID)
+ ret = apple_cio_tbt_cable_info(acio, altmode, &cable_info);
+ else if (altmode->mode_kind == TYPEC_MODE_KIND_USB4)
+ ret = apple_cio_usb4_cable_info(acio, altmode, &cable_info);
+ else
+ ret = -EINVAL;
+ if (ret) {
+ dev_err(acio->dev, "failed to read Type-C cable information: %d\n", ret);
+ return NOTIFY_OK;
+ }
+ break;
+ case TYPEC_ALTMODE_EXITED:
+ cable_info = 0;
+ break;
+ default:
+ return NOTIFY_DONE;
+ }
+
+ /*
+ * The altmode has already been negotiated and we can't exit it anyway
+ * on Apple Silicon so just warn the we couldn't start but still return
+ * success.
+ */
+ ret = apple_cio_set_cable_info(acio, cable_info);
+ if (ret)
+ dev_warn(acio->dev, "failed to start ACIO: %d\n", ret);
+
+ return NOTIFY_OK;
+}
+
+static struct apple_cio_altmode *
+apple_cio_altmode_subscription(struct apple_cio *acio, struct typec_altmode *altmode)
+{
+ if (altmode->mode_kind == TYPEC_MODE_KIND_ALTMODE &&
+ altmode->svid == USB_TYPEC_TBT_SID)
+ return &acio->tbt_altmode;
+ if (altmode->mode_kind == TYPEC_MODE_KIND_USB4)
+ return &acio->usb4_altmode;
+ return NULL;
+}
+
+static bool apple_cio_altmode_matches(struct apple_cio *acio,
+ struct typec_altmode *altmode)
+{
+ if (!apple_cio_altmode_subscription(acio, altmode))
+ return false;
+
+ return dev_fwnode(altmode->dev.parent->parent) == acio->connector;
+}
+
+static int apple_cio_altmode_attach_locked(struct apple_cio *acio,
+ struct typec_altmode *altmode)
+{
+ struct apple_cio_altmode *subscription;
+ int ret;
+
+ lockdep_assert_held(&acio->typec_lock);
+
+ if (!apple_cio_altmode_matches(acio, altmode))
+ return 0;
+
+ subscription = apple_cio_altmode_subscription(acio, altmode);
+
+ if (subscription->altmode == altmode)
+ return 0;
+ if (subscription->altmode)
+ return -EBUSY;
+
+ get_device(&altmode->dev);
+ subscription->altmode = altmode;
+ ret = typec_altmode_register_notifier(altmode, &subscription->notifier);
+ if (ret) {
+ subscription->altmode = NULL;
+ put_device(&altmode->dev);
+ }
+
+ return ret;
+}
+
+static int apple_cio_altmode_attach(struct apple_cio *acio,
+ struct typec_altmode *altmode)
+{
+ guard(mutex)(&acio->typec_lock);
+
+ return apple_cio_altmode_attach_locked(acio, altmode);
+}
+
+static void apple_cio_altmode_detach(struct apple_cio *acio,
+ struct typec_altmode *altmode)
+{
+ struct apple_cio_altmode *subscription;
+ bool active;
+
+ subscription = apple_cio_altmode_subscription(acio, altmode);
+ if (!subscription)
+ return;
+
+ guard(mutex)(&acio->typec_lock);
+ if (subscription->altmode != altmode)
+ return;
+
+ active = altmode->active;
+ typec_altmode_unregister_notifier(altmode, &subscription->notifier);
+ subscription->altmode = NULL;
+ if (active)
+ apple_cio_set_cable_info(acio, 0);
+ put_device(&altmode->dev);
+}
+
+static int apple_cio_typec_notify(struct notifier_block *nb,
+ unsigned long action, void *data)
+{
+ struct apple_cio *acio = container_of(nb, struct apple_cio, typec_notifier);
+ struct typec_altmode *altmode;
+ struct device *dev = data;
+ int ret;
+
+ if (!is_typec_partner_altmode(dev))
+ return NOTIFY_DONE;
+
+ altmode = to_typec_altmode(dev);
+ switch (action) {
+ case BUS_NOTIFY_ADD_DEVICE:
+ ret = apple_cio_altmode_attach(acio, altmode);
+ if (ret)
+ dev_err(acio->dev,
+ "failed to register for alternate mode notifications: %d\n", ret);
+ break;
+ case BUS_NOTIFY_DEL_DEVICE:
+ case BUS_NOTIFY_REMOVED_DEVICE:
+ apple_cio_altmode_detach(acio, altmode);
+ break;
+ default:
+ break;
+ }
+
+ return NOTIFY_DONE;
+}
+
+static int apple_cio_scan_altmode(struct device *dev, void *data)
+{
+ if (!is_typec_partner_altmode(dev))
+ return 0;
+
+ return apple_cio_altmode_attach_locked(data, to_typec_altmode(dev));
+}
+
+static void apple_cio_unregister_typec(struct apple_cio *acio)
+{
+ bus_unregister_notifier(&typec_bus, &acio->typec_notifier);
+ if (acio->tbt_altmode.altmode)
+ apple_cio_altmode_detach(acio, acio->tbt_altmode.altmode);
+ if (acio->usb4_altmode.altmode)
+ apple_cio_altmode_detach(acio, acio->usb4_altmode.altmode);
+}
+
+static void apple_cio_put_connector(void *data)
+{
+ struct apple_cio *acio = data;
+
+ fwnode_handle_put(acio->connector);
+}
+
+static int apple_cio_probe(struct platform_device *pdev)
+{
+ struct dev_pm_domain_attach_data pd_data = {
+ .pd_flags = PD_FLAG_NO_DEV_LINK,
+ };
+ struct device *dev = &pdev->dev;
+ struct fwnode_handle *endpoint;
+ 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;
+
+ ret = devm_mutex_init(dev, &acio->typec_lock);
+ if (ret)
+ return ret;
+
+ init_completion(&acio->nhi_boot_completion);
+ acio->dev = &pdev->dev;
+ acio->np = dev->of_node;
+ acio->typec_notifier.notifier_call = apple_cio_typec_notify;
+ acio->tbt_altmode.acio = acio;
+ acio->tbt_altmode.notifier.notifier_call = apple_cio_altmode_notify;
+ acio->usb4_altmode.acio = acio;
+ acio->usb4_altmode.notifier.notifier_call = apple_cio_altmode_notify;
+
+ endpoint = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), 1, 0, 0);
+ if (!endpoint)
+ return dev_err_probe(dev, -ENODEV, "unable to find Type-C graph endpoint\n");
+ acio->connector = fwnode_graph_get_remote_port_parent(endpoint);
+ fwnode_handle_put(endpoint);
+ if (!acio->connector)
+ return dev_err_probe(dev, -ENODEV, "unable to find Type-C connector\n");
+ ret = devm_add_action_or_reset(dev, apple_cio_put_connector, acio);
+ if (ret)
+ return ret;
+
+ 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 PM domain listed in the device tree the
+ * platform driver framework will already attach it. Thus, if we find an
+ * already attached PM 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.
+ */
+ 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");
+
+ ret = bus_register_notifier(&typec_bus, &acio->typec_notifier);
+ if (ret)
+ return dev_err_probe(dev, ret, "unable to register Type-C notifier\n");
+
+ scoped_guard(mutex, &acio->typec_lock) {
+ ret = bus_for_each_dev(&typec_bus, NULL, acio, apple_cio_scan_altmode);
+ }
+ if (ret) {
+ apple_cio_unregister_typec(acio);
+ return dev_err_probe(dev, ret,
+ "unable to register for alternate modes notifications\n");
+ }
+
+ return 0;
+}
+
+static void apple_cio_remove(struct platform_device *pdev)
+{
+ struct apple_cio *acio = platform_get_drvdata(pdev);
+
+ apple_cio_unregister_typec(acio);
+
+ 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));
+}
+module_init(apple_cio_init);
+
+static void __exit apple_cio_exit(void)
+{
+ platform_unregister_drivers(apple_cio_drivers,
+ ARRAY_SIZE(apple_cio_drivers));
+}
+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 [flat|nested] 41+ messages in thread
* [PATCH v2 20/22] arm64: dts: apple: t8103: Add USB4 ACIO and NHI
2026-09-06 18:36 [PATCH v2 00/22] Initial USB4/Thunderbolt support for Apple M1/M2/M3 SoCs Sven Peter
` (18 preceding siblings ...)
2026-09-06 18:36 ` [PATCH v2 19/22] thunderbolt: Add Apple Silicon support Sven Peter
@ 2026-09-06 18:36 ` Sven Peter
2026-09-06 18:36 ` [PATCH v2 21/22] arm64: dts: apple: t8112: " Sven Peter
` (2 subsequent siblings)
22 siblings, 0 replies; 41+ messages in thread
From: Sven Peter @ 2026-09-06 18:36 UTC (permalink / raw)
To: Heikki Krogerus, Greg Kroah-Hartman, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, 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.
Also describe the USB3 path from each host router to its DWC3 controller
so the NHI driver can create the required device link.
Signed-off-by: Sven Peter <sven@kernel.org>
---
arch/arm64/boot/dts/apple/t8103-jxxx.dtsi | 95 ++++++++++++++-
arch/arm64/boot/dts/apple/t8103.dtsi | 184 ++++++++++++++++++++++++++++++
2 files changed, 277 insertions(+), 2 deletions(-)
diff --git a/arch/arm64/boot/dts/apple/t8103-jxxx.dtsi b/arch/arm64/boot/dts/apple/t8103-jxxx.dtsi
index 686fb1dd215d..01c942b0a75b 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>;
+ };
+ };
};
};
};
@@ -126,10 +144,19 @@ dwc3_0_hs: endpoint {
};
port@1 {
+ #address-cells = <1>;
+ #size-cells = <0>;
reg = <1>;
- dwc3_0_ss: endpoint {
+
+ dwc3_0_ss: endpoint@0 {
+ reg = <0>;
remote-endpoint = <&atcphy0_usb3>;
};
+
+ dwc3_0_usb4: endpoint@1 {
+ reg = <1>;
+ remote-endpoint = <&usb4_0_acio_usb3>;
+ };
};
};
};
@@ -147,10 +174,19 @@ dwc3_1_hs: endpoint {
};
port@1 {
+ #address-cells = <1>;
+ #size-cells = <0>;
reg = <1>;
- dwc3_1_ss: endpoint {
+
+ dwc3_1_ss: endpoint@0 {
+ reg = <0>;
remote-endpoint = <&atcphy1_usb3>;
};
+
+ dwc3_1_usb4: endpoint@1 {
+ reg = <1>;
+ remote-endpoint = <&usb4_1_acio_usb3>;
+ };
};
};
};
@@ -198,6 +234,61 @@ 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: USB4 lane 1 adapter */
+ /* 3: PCIe */
+ port@4 {
+ reg = <4>;
+ usb4_0_acio_usb3: endpoint {
+ remote-endpoint = <&dwc3_0_usb4>;
+ };
+ };
+
+ /* 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: USB4 lane 1 adapter */
+ /* 3: PCIe */
+ port@4 {
+ reg = <4>;
+ usb4_1_acio_usb3: endpoint {
+ remote-endpoint = <&dwc3_1_usb4>;
+ };
+ };
+
+ /* 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 985dbd8e68ca..735591c72fde 100644
--- a/arch/arm64/boot/dts/apple/t8103.dtsi
+++ b/arch/arm64/boot/dts/apple/t8103.dtsi
@@ -988,6 +988,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 {
@@ -1024,6 +1032,94 @@ 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>;
+
+ 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>;
@@ -1076,6 +1172,94 @@ 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>;
+
+ 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] 41+ messages in thread
* [PATCH v2 21/22] arm64: dts: apple: t8112: Add USB4 ACIO and NHI
2026-09-06 18:36 [PATCH v2 00/22] Initial USB4/Thunderbolt support for Apple M1/M2/M3 SoCs Sven Peter
` (19 preceding siblings ...)
2026-09-06 18:36 ` [PATCH v2 20/22] arm64: dts: apple: t8103: Add USB4 ACIO and NHI Sven Peter
@ 2026-09-06 18:36 ` Sven Peter
2026-09-06 18:36 ` [PATCH v2 22/22] arm64: dts: apple: t60xx: " Sven Peter
2026-09-07 13:52 ` [PATCH v2 00/22] Initial USB4/Thunderbolt support for Apple M1/M2/M3 SoCs Joshua Peisach
22 siblings, 0 replies; 41+ messages in thread
From: Sven Peter @ 2026-09-06 18:36 UTC (permalink / raw)
To: Heikki Krogerus, Greg Kroah-Hartman, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, 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.
Also describe the USB3 path from each host router to its DWC3 controller
so the NHI driver can create the required device link.
Signed-off-by: Sven Peter <sven@kernel.org>
---
arch/arm64/boot/dts/apple/t8112-jxxx.dtsi | 95 ++++++++++++++-
arch/arm64/boot/dts/apple/t8112.dtsi | 186 ++++++++++++++++++++++++++++++
2 files changed, 279 insertions(+), 2 deletions(-)
diff --git a/arch/arm64/boot/dts/apple/t8112-jxxx.dtsi b/arch/arm64/boot/dts/apple/t8112-jxxx.dtsi
index 562e7a25a1e8..d0dc7eaaca35 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>;
+ };
+ };
};
};
};
@@ -126,10 +144,19 @@ dwc3_0_hs: endpoint {
};
port@1 {
+ #address-cells = <1>;
+ #size-cells = <0>;
reg = <1>;
- dwc3_0_ss: endpoint {
+
+ dwc3_0_ss: endpoint@0 {
+ reg = <0>;
remote-endpoint = <&atcphy0_usb3>;
};
+
+ dwc3_0_usb4: endpoint@1 {
+ reg = <1>;
+ remote-endpoint = <&usb4_0_acio_usb3>;
+ };
};
};
};
@@ -147,10 +174,19 @@ dwc3_1_hs: endpoint {
};
port@1 {
+ #address-cells = <1>;
+ #size-cells = <0>;
reg = <1>;
- dwc3_1_ss: endpoint {
+
+ dwc3_1_ss: endpoint@0 {
+ reg = <0>;
remote-endpoint = <&atcphy1_usb3>;
};
+
+ dwc3_1_usb4: endpoint@1 {
+ reg = <1>;
+ remote-endpoint = <&usb4_1_acio_usb3>;
+ };
};
};
};
@@ -198,6 +234,61 @@ 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: USB4 lane 1 adapter */
+ /* 3: PCIe */
+ port@4 {
+ reg = <4>;
+ usb4_0_acio_usb3: endpoint {
+ remote-endpoint = <&dwc3_0_usb4>;
+ };
+ };
+
+ /* 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: USB4 lane 1 adapter */
+ /* 3: PCIe */
+ port@4 {
+ reg = <4>;
+ usb4_1_acio_usb3: endpoint {
+ remote-endpoint = <&dwc3_1_usb4>;
+ };
+ };
+
+ /* 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 2c3b59e05e01..e70ed4a470e1 100644
--- a/arch/arm64/boot/dts/apple/t8112.dtsi
+++ b/arch/arm64/boot/dts/apple/t8112.dtsi
@@ -991,6 +991,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 {
@@ -1027,6 +1035,95 @@ 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>;
+
+ 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>;
@@ -1079,6 +1176,95 @@ 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>;
+
+ 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 [flat|nested] 41+ messages in thread
* [PATCH v2 22/22] arm64: dts: apple: t60xx: Add USB4 ACIO and NHI
2026-09-06 18:36 [PATCH v2 00/22] Initial USB4/Thunderbolt support for Apple M1/M2/M3 SoCs Sven Peter
` (20 preceding siblings ...)
2026-09-06 18:36 ` [PATCH v2 21/22] arm64: dts: apple: t8112: " Sven Peter
@ 2026-09-06 18:36 ` Sven Peter
2026-09-07 13:52 ` [PATCH v2 00/22] Initial USB4/Thunderbolt support for Apple M1/M2/M3 SoCs Joshua Peisach
22 siblings, 0 replies; 41+ messages in thread
From: Sven Peter @ 2026-09-06 18:36 UTC (permalink / raw)
To: Heikki Krogerus, Greg Kroah-Hartman, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, 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.
Also describe the USB3 path from each host router to its DWC3 controller
so the NHI driver can create the required device link.
Signed-off-by: Sven Peter <sven@kernel.org>
---
arch/arm64/boot/dts/apple/t6002-j375d.dts | 101 ++++++-
arch/arm64/boot/dts/apple/t600x-dieX.dtsi | 376 +++++++++++++++++++++++++
arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi | 158 ++++++++++-
arch/arm64/boot/dts/apple/t600x-j375.dtsi | 205 +++++++++++++-
arch/arm64/boot/dts/apple/t6022-j180d.dts | 304 +++++++++++++++++++-
arch/arm64/boot/dts/apple/t6022-j475d.dts | 10 +
arch/arm64/boot/dts/apple/t6022-jxxxd.dtsi | 91 +++++-
arch/arm64/boot/dts/apple/t602x-dieX.dtsi | 376 +++++++++++++++++++++++++
8 files changed, 1604 insertions(+), 17 deletions(-)
diff --git a/arch/arm64/boot/dts/apple/t6002-j375d.dts b/arch/arm64/boot/dts/apple/t6002-j375d.dts
index e6eb6b6c386d..93f07df1c006 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>;
+ };
+ };
};
};
};
@@ -112,10 +130,19 @@ dwc3_4_hs: endpoint {
};
port@1 {
+ #address-cells = <1>;
+ #size-cells = <0>;
reg = <1>;
- dwc3_4_ss: endpoint {
+
+ dwc3_4_ss: endpoint@0 {
+ reg = <0>;
remote-endpoint = <&atcphy4_usb3>;
};
+
+ dwc3_4_usb4: endpoint@1 {
+ reg = <1>;
+ remote-endpoint = <&usb4_4_acio_usb3>;
+ };
};
};
};
@@ -133,10 +160,19 @@ dwc3_5_hs: endpoint {
};
port@1 {
+ #address-cells = <1>;
+ #size-cells = <0>;
reg = <1>;
- dwc3_5_ss: endpoint {
+
+ dwc3_5_ss: endpoint@0 {
+ reg = <0>;
remote-endpoint = <&atcphy5_usb3>;
};
+
+ dwc3_5_usb4: endpoint@1 {
+ reg = <1>;
+ remote-endpoint = <&usb4_5_acio_usb3>;
+ };
};
};
};
@@ -186,8 +222,69 @@ 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: USB4 lane 1 adapter */
+ /* 3: PCIe */
+ port@4 {
+ reg = <4>;
+ usb4_4_acio_usb3: endpoint {
+ remote-endpoint = <&dwc3_4_usb4>;
+ };
+ };
+
+ /* 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: USB4 lane 1 adapter */
+ /* 3: PCIe */
+ port@4 {
+ reg = <4>;
+ usb4_5_acio_usb3: endpoint {
+ remote-endpoint = <&dwc3_5_usb4>;
+ };
+ };
+
+ /* 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 5e163d756a7f..b7ef3321b57d 100644
--- a/arch/arm64/boot/dts/apple/t600x-dieX.dtsi
+++ b/arch/arm64/boot/dts/apple/t600x-dieX.dtsi
@@ -99,6 +99,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 {
@@ -126,6 +142,96 @@ 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)>;
+
+ 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>;
@@ -179,6 +285,96 @@ 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)>;
+
+ 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>;
@@ -232,6 +428,96 @@ 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)>;
+
+ 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>;
@@ -285,6 +571,96 @@ 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)>;
+
+ 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..8287e2c0eb84 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;
};
@@ -222,10 +228,19 @@ dwc3_0_hs: endpoint {
};
port@1 {
+ #address-cells = <1>;
+ #size-cells = <0>;
reg = <1>;
- dwc3_0_ss: endpoint {
+
+ dwc3_0_ss: endpoint@0 {
+ reg = <0>;
remote-endpoint = <&atcphy0_usb3>;
};
+
+ dwc3_0_usb4: endpoint@1 {
+ reg = <1>;
+ remote-endpoint = <&usb4_0_acio_usb3>;
+ };
};
};
};
@@ -243,10 +258,19 @@ dwc3_1_hs: endpoint {
};
port@1 {
+ #address-cells = <1>;
+ #size-cells = <0>;
reg = <1>;
- dwc3_1_ss: endpoint {
+
+ dwc3_1_ss: endpoint@0 {
+ reg = <0>;
remote-endpoint = <&atcphy1_usb3>;
};
+
+ dwc3_1_usb4: endpoint@1 {
+ reg = <1>;
+ remote-endpoint = <&usb4_1_acio_usb3>;
+ };
};
};
};
@@ -264,10 +288,19 @@ dwc3_2_hs: endpoint {
};
port@1 {
+ #address-cells = <1>;
+ #size-cells = <0>;
reg = <1>;
- dwc3_2_ss: endpoint {
+
+ dwc3_2_ss: endpoint@0 {
+ reg = <0>;
remote-endpoint = <&atcphy2_usb3>;
};
+
+ dwc3_2_usb4: endpoint@1 {
+ reg = <1>;
+ remote-endpoint = <&usb4_2_acio_usb3>;
+ };
};
};
};
@@ -366,6 +399,125 @@ &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: USB4 lane 1 adapter */
+ /* 3: PCIe */
+ port@4 {
+ reg = <4>;
+ usb4_0_acio_usb3: endpoint {
+ remote-endpoint = <&dwc3_0_usb4>;
+ };
+ };
+
+ /* 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: USB4 lane 1 adapter */
+ /* 3: PCIe */
+ port@4 {
+ reg = <4>;
+ usb4_1_acio_usb3: endpoint {
+ remote-endpoint = <&dwc3_1_usb4>;
+ };
+ };
+
+ /* 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: USB4 lane 1 adapter */
+ /* 3: PCIe */
+ port@4 {
+ reg = <4>;
+ usb4_2_acio_usb3: endpoint {
+ remote-endpoint = <&dwc3_2_usb4>;
+ };
+ };
+
+ /* 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..18348192d143 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;
};
@@ -193,10 +201,19 @@ dwc3_0_hs: endpoint {
};
port@1 {
+ #address-cells = <1>;
+ #size-cells = <0>;
reg = <1>;
- dwc3_0_ss: endpoint {
+
+ dwc3_0_ss: endpoint@0 {
+ reg = <0>;
remote-endpoint = <&atcphy0_usb3>;
};
+
+ dwc3_0_usb4: endpoint@1 {
+ reg = <1>;
+ remote-endpoint = <&usb4_0_acio_usb3>;
+ };
};
};
};
@@ -214,10 +231,19 @@ dwc3_1_hs: endpoint {
};
port@1 {
+ #address-cells = <1>;
+ #size-cells = <0>;
reg = <1>;
- dwc3_1_ss: endpoint {
+
+ dwc3_1_ss: endpoint@0 {
+ reg = <0>;
remote-endpoint = <&atcphy1_usb3>;
};
+
+ dwc3_1_usb4: endpoint@1 {
+ reg = <1>;
+ remote-endpoint = <&usb4_1_acio_usb3>;
+ };
};
};
};
@@ -235,10 +261,19 @@ dwc3_2_hs: endpoint {
};
port@1 {
+ #address-cells = <1>;
+ #size-cells = <0>;
reg = <1>;
- dwc3_2_ss: endpoint {
+
+ dwc3_2_ss: endpoint@0 {
+ reg = <0>;
remote-endpoint = <&atcphy2_usb3>;
};
+
+ dwc3_2_usb4: endpoint@1 {
+ reg = <1>;
+ remote-endpoint = <&usb4_2_acio_usb3>;
+ };
};
};
};
@@ -256,10 +291,19 @@ dwc3_3_hs: endpoint {
};
port@1 {
+ #address-cells = <1>;
+ #size-cells = <0>;
reg = <1>;
- dwc3_3_ss: endpoint {
+
+ dwc3_3_ss: endpoint@0 {
+ reg = <0>;
remote-endpoint = <&atcphy3_usb3>;
};
+
+ dwc3_3_usb4: endpoint@1 {
+ reg = <1>;
+ remote-endpoint = <&usb4_3_acio_usb3>;
+ };
};
};
};
@@ -410,4 +454,157 @@ &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: USB4 lane 1 adapter */
+ /* 3: PCIe */
+ port@4 {
+ reg = <4>;
+ usb4_0_acio_usb3: endpoint {
+ remote-endpoint = <&dwc3_0_usb4>;
+ };
+ };
+
+ /* 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: USB4 lane 1 adapter */
+ /* 3: PCIe */
+ port@4 {
+ reg = <4>;
+ usb4_1_acio_usb3: endpoint {
+ remote-endpoint = <&dwc3_1_usb4>;
+ };
+ };
+
+ /* 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: USB4 lane 1 adapter */
+ /* 3: PCIe */
+ port@4 {
+ reg = <4>;
+ usb4_2_acio_usb3: endpoint {
+ remote-endpoint = <&dwc3_2_usb4>;
+ };
+ };
+
+ /* 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: USB4 lane 1 adapter */
+ /* 3: PCIe */
+ port@4 {
+ reg = <4>;
+ usb4_3_acio_usb3: endpoint {
+ remote-endpoint = <&dwc3_3_usb4>;
+ };
+ };
+
+ /* 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..1ddde77e00c0 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>;
+ };
+ };
};
};
};
@@ -280,10 +310,19 @@ dwc3_0_hs: endpoint {
};
port@1 {
+ #address-cells = <1>;
+ #size-cells = <0>;
reg = <1>;
- dwc3_0_ss: endpoint {
+
+ dwc3_0_ss: endpoint@0 {
+ reg = <0>;
remote-endpoint = <&atcphy0_usb3>;
};
+
+ dwc3_0_usb4: endpoint@1 {
+ reg = <1>;
+ remote-endpoint = <&usb4_0_acio_usb3>;
+ };
};
};
};
@@ -301,10 +340,19 @@ dwc3_1_hs: endpoint {
};
port@1 {
+ #address-cells = <1>;
+ #size-cells = <0>;
reg = <1>;
- dwc3_1_ss: endpoint {
+
+ dwc3_1_ss: endpoint@0 {
+ reg = <0>;
remote-endpoint = <&atcphy1_usb3>;
};
+
+ dwc3_1_usb4: endpoint@1 {
+ reg = <1>;
+ remote-endpoint = <&usb4_1_acio_usb3>;
+ };
};
};
};
@@ -322,10 +370,19 @@ dwc3_2_hs: endpoint {
};
port@1 {
+ #address-cells = <1>;
+ #size-cells = <0>;
reg = <1>;
- dwc3_2_ss: endpoint {
+
+ dwc3_2_ss: endpoint@0 {
+ reg = <0>;
remote-endpoint = <&atcphy2_usb3>;
};
+
+ dwc3_2_usb4: endpoint@1 {
+ reg = <1>;
+ remote-endpoint = <&usb4_2_acio_usb3>;
+ };
};
};
};
@@ -343,10 +400,19 @@ dwc3_3_hs: endpoint {
};
port@1 {
+ #address-cells = <1>;
+ #size-cells = <0>;
reg = <1>;
- dwc3_3_ss: endpoint {
+
+ dwc3_3_ss: endpoint@0 {
+ reg = <0>;
remote-endpoint = <&atcphy3_usb3>;
};
+
+ dwc3_3_usb4: endpoint@1 {
+ reg = <1>;
+ remote-endpoint = <&usb4_3_acio_usb3>;
+ };
};
};
};
@@ -365,10 +431,19 @@ dwc3_6_hs: endpoint {
};
port@1 {
+ #address-cells = <1>;
+ #size-cells = <0>;
reg = <1>;
- dwc3_6_ss: endpoint {
+
+ dwc3_6_ss: endpoint@0 {
+ reg = <0>;
remote-endpoint = <&atcphy6_usb3>;
};
+
+ dwc3_6_usb4: endpoint@1 {
+ reg = <1>;
+ remote-endpoint = <&usb4_6_acio_usb3>;
+ };
};
};
};
@@ -386,10 +461,19 @@ dwc3_7_hs: endpoint {
};
port@1 {
+ #address-cells = <1>;
+ #size-cells = <0>;
reg = <1>;
- dwc3_7_ss: endpoint {
+
+ dwc3_7_ss: endpoint@0 {
+ reg = <0>;
remote-endpoint = <&atcphy7_usb3>;
};
+
+ dwc3_7_usb4: endpoint@1 {
+ reg = <1>;
+ remote-endpoint = <&usb4_7_acio_usb3>;
+ };
};
};
};
@@ -535,4 +619,212 @@ &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: USB4 lane 1 adapter */
+ /* 3: PCIe */
+ port@4 {
+ reg = <4>;
+ usb4_6_acio_usb3: endpoint {
+ remote-endpoint = <&dwc3_6_usb4>;
+ };
+ };
+
+ /* 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: USB4 lane 1 adapter */
+ /* 3: PCIe */
+ port@4 {
+ reg = <4>;
+ usb4_7_acio_usb3: endpoint {
+ remote-endpoint = <&dwc3_7_usb4>;
+ };
+ };
+
+ /* 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: USB4 lane 1 adapter */
+ /* 3: PCIe */
+ port@4 {
+ reg = <4>;
+ usb4_0_acio_usb3: endpoint {
+ remote-endpoint = <&dwc3_0_usb4>;
+ };
+ };
+
+ /* 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: USB4 lane 1 adapter */
+ /* 3: PCIe */
+ port@4 {
+ reg = <4>;
+ usb4_1_acio_usb3: endpoint {
+ remote-endpoint = <&dwc3_1_usb4>;
+ };
+ };
+
+ /* 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: USB4 lane 1 adapter */
+ /* 3: PCIe */
+ port@4 {
+ reg = <4>;
+ usb4_2_acio_usb3: endpoint {
+ remote-endpoint = <&dwc3_2_usb4>;
+ };
+ };
+
+ /* 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: USB4 lane 1 adapter */
+ /* 3: PCIe */
+ port@4 {
+ reg = <4>;
+ usb4_3_acio_usb3: endpoint {
+ remote-endpoint = <&dwc3_3_usb4>;
+ };
+ };
+
+ /* 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..6a3e866f83be 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>;
+ };
+ };
};
};
};
@@ -98,10 +112,19 @@ dwc3_4_hs: endpoint {
};
port@1 {
+ #address-cells = <1>;
+ #size-cells = <0>;
reg = <1>;
- dwc3_4_ss: endpoint {
+
+ dwc3_4_ss: endpoint@0 {
+ reg = <0>;
remote-endpoint = <&atcphy4_usb3>;
};
+
+ dwc3_4_usb4: endpoint@1 {
+ reg = <1>;
+ remote-endpoint = <&usb4_4_acio_usb3>;
+ };
};
};
};
@@ -119,10 +142,19 @@ dwc3_5_hs: endpoint {
};
port@1 {
+ #address-cells = <1>;
+ #size-cells = <0>;
reg = <1>;
- dwc3_5_ss: endpoint {
+
+ dwc3_5_ss: endpoint@0 {
+ reg = <0>;
remote-endpoint = <&atcphy5_usb3>;
};
+
+ dwc3_5_usb4: endpoint@1 {
+ reg = <1>;
+ remote-endpoint = <&usb4_5_acio_usb3>;
+ };
};
};
};
@@ -169,3 +201,58 @@ 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: USB4 lane 1 adapter */
+ /* 3: PCIe */
+ port@4 {
+ reg = <4>;
+ usb4_4_acio_usb3: endpoint {
+ remote-endpoint = <&dwc3_4_usb4>;
+ };
+ };
+
+ /* 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: USB4 lane 1 adapter */
+ /* 3: PCIe */
+ port@4 {
+ reg = <4>;
+ usb4_5_acio_usb3: endpoint {
+ remote-endpoint = <&dwc3_5_usb4>;
+ };
+ };
+
+ /* 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 fd4d572a04e7..1fba52e38ad8 100644
--- a/arch/arm64/boot/dts/apple/t602x-dieX.dtsi
+++ b/arch/arm64/boot/dts/apple/t602x-dieX.dtsi
@@ -98,6 +98,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 {
@@ -133,6 +149,96 @@ 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)>;
+
+ 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>;
@@ -186,6 +292,96 @@ 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)>;
+
+ 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>;
@@ -239,6 +435,96 @@ 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)>;
+
+ 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>;
@@ -292,6 +578,96 @@ 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)>;
+
+ 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 [flat|nested] 41+ messages in thread
* Re: [PATCH v2 01/22] usb: typec: Add alternate mode state notifiers
2026-09-06 18:36 ` [PATCH v2 01/22] usb: typec: Add alternate mode state notifiers Sven Peter
@ 2026-09-07 13:27 ` Joshua Peisach
2026-09-08 12:00 ` Heikki Krogerus
1 sibling, 0 replies; 41+ messages in thread
From: Joshua Peisach @ 2026-09-07 13:27 UTC (permalink / raw)
To: Sven Peter, Heikki Krogerus, Greg Kroah-Hartman, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, 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 Sep 6, 2026 at 2:36 PM EDT, Sven Peter wrote:
> The Apple Silicon USB4/Thunderbolt driver needs to be made aware of
> cable details and whenever USB4 or Thunderbolt is entered. The cable
> details are already available as part of the typec subsystem but there
> is no way to get a notification on typec_altmode_update_active() so
> far.
>
> Add a blocking notifier chain to each alternate mode and call it
> whenever the mode is activated or deactivated right next to the already
> existing sysfs and kobject_uevent notifications. Replay activations when a
> notifier is registered.
>
> Signed-off-by: Sven Peter <sven@kernel.org>
> ---
> drivers/usb/typec/bus.h | 6 +++++
> drivers/usb/typec/class.c | 59 +++++++++++++++++++++++++++++++++++++++++++++++
> include/linux/usb/typec.h | 16 +++++++++++++
> 3 files changed, 81 insertions(+)
>
> diff --git a/drivers/usb/typec/bus.h b/drivers/usb/typec/bus.h
> index 7df5deb1dd3a..4dc60c48c91e 100644
> --- a/drivers/usb/typec/bus.h
> +++ b/drivers/usb/typec/bus.h
> @@ -3,6 +3,8 @@
> #ifndef __USB_TYPEC_ALTMODE_H__
> #define __USB_TYPEC_ALTMODE_H__
>
> +#include <linux/mutex.h>
> +#include <linux/notifier.h>
> #include <linux/usb/typec_altmode.h>
>
> struct typec_mux;
> @@ -23,6 +25,10 @@ struct altmode {
>
> struct altmode *partner;
> struct altmode *plug[2];
> +
> + /* Serializes active state changes and notifier registration. */
> + struct mutex state_lock;
> + struct blocking_notifier_head state_notifier;
> };
>
space/tab formatting
^ permalink raw reply [flat|nested] 41+ messages in thread
* Re: [PATCH v2 02/22] usb: typec: Represent USB4 on the Type-C bus
2026-09-06 18:36 ` [PATCH v2 02/22] usb: typec: Represent USB4 on the Type-C bus Sven Peter
@ 2026-09-07 13:31 ` Joshua Peisach
2026-09-08 12:07 ` Heikki Krogerus
1 sibling, 0 replies; 41+ messages in thread
From: Joshua Peisach @ 2026-09-07 13:31 UTC (permalink / raw)
To: Sven Peter, Heikki Krogerus, Greg Kroah-Hartman, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, 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 Sep 6, 2026 at 2:36 PM EDT, Sven Peter wrote:
> USB4 technically isn't alternate mode and entered using the EUDO and has
> no SVID or Discover Modes Response. We can still represent it like an
> altmode on the Type-C bus though by adding a mode kind. Exclude the USB4
> modes from any SVID-specific operations and driver binding, but allow
> consumers to observe their state through notifications.
>
> Like for other altmodes, expose the USB4 mode to sysfs: Add a mode_kind
> to be able to differentiate and then expose the EUDO, make the USB4
> active state read-only since we only represent firmware-negotiated USB4
> modes and omit anything that only applies to SVID-specific altmodes.
>
> Signed-off-by: Sven Peter <sven@kernel.org>
> ---
> Hope it's fine to add you as the contact for the sysfs documentation,
> happy to add myself otherwise though.
> ---
> Documentation/ABI/testing/sysfs-bus-typec | 36 ++++++++++++++
> drivers/usb/typec/bus.c | 67 ++++++++++++++++++++-----
> drivers/usb/typec/class.c | 83 +++++++++++++++++++++++++++----
> drivers/usb/typec/mode_selection.c | 6 ++-
> include/linux/usb/typec.h | 20 +++++++-
> include/linux/usb/typec_altmode.h | 10 +++-
> 6 files changed, 193 insertions(+), 29 deletions(-)
>
Reviewed-by: Joshua Peisach <jpeisach@ubuntu.com>
^ permalink raw reply [flat|nested] 41+ messages in thread
* Re: [PATCH v2 11/22] thunderbolt: Find Apple VSE capability during startup
2026-09-06 18:36 ` [PATCH v2 11/22] thunderbolt: Find Apple VSE capability " Sven Peter
@ 2026-09-07 13:38 ` Joshua Peisach
2026-09-08 20:24 ` Sven Peter
0 siblings, 1 reply; 41+ messages in thread
From: Joshua Peisach @ 2026-09-07 13:38 UTC (permalink / raw)
To: Sven Peter, Heikki Krogerus, Greg Kroah-Hartman, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, 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 Sep 6, 2026 at 2:36 PM EDT, Sven Peter wrote:
> Only Apple Silicon host routers have the Apple vendor-specific extended
> capability. Look it up when the software connection manager starts and
> store its offset in the root switch.
>
> The Apple NHI driver can then use the stored offset when programming
> cable information without searching the router capabilities itself.
>
> Signed-off-by: Sven Peter <sven@kernel.org>
> ---
> drivers/thunderbolt/tb.c | 4 ++++
> drivers/thunderbolt/tb.h | 2 ++
> drivers/thunderbolt/tb_regs.h | 1 +
> 3 files changed, 7 insertions(+)
>
> diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c
> index bf1c192ec10a..d2832323cc36 100644
> --- a/drivers/thunderbolt/tb.c
> +++ b/drivers/thunderbolt/tb.c
> @@ -3025,6 +3025,10 @@ static int tb_start(struct tb *tb, bool reset)
> return dev_err_probe(tb->nhi->dev, PTR_ERR(tb->root_switch),
> "failed to allocate host router\n");
>
> + ret = tb_switch_find_vse_cap(tb->root_switch, TB_VSE_CAP_APPLE);
> + if (ret > 0)
> + tb->root_switch->cap_vsec_apple = ret;
> +
Nitpick: I *think* based on the surrounding code that this can be
simplified to "if (ret)"
-Josh
^ permalink raw reply [flat|nested] 41+ messages in thread
* Re: [PATCH v2 00/22] Initial USB4/Thunderbolt support for Apple M1/M2/M3 SoCs
2026-09-06 18:36 [PATCH v2 00/22] Initial USB4/Thunderbolt support for Apple M1/M2/M3 SoCs Sven Peter
` (21 preceding siblings ...)
2026-09-06 18:36 ` [PATCH v2 22/22] arm64: dts: apple: t60xx: " Sven Peter
@ 2026-09-07 13:52 ` Joshua Peisach
22 siblings, 0 replies; 41+ messages in thread
From: Joshua Peisach @ 2026-09-07 13:52 UTC (permalink / raw)
To: Sven Peter, Heikki Krogerus, Greg Kroah-Hartman, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Janne Grunau, Neal Gompa,
Andreas Noever, Mika Westerberg, Yehezkel Bernat, Philipp Zabel
Cc: Konrad Dybcio, linux-usb, devicetree, linux-kernel, asahi,
linux-arm-kernel, Joshua Peisach
On Sun Sep 6, 2026 at 2:36 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 ideally has to be stopped again before the PHY is powered down but
> this doesn't break anything unlike getting startup wrong). It also
> needs cable details which are only known to the Type-C port controller.
>
> The first two patches add synchronous Type-C alternate mode state
> notifiers and support for representing USB4 connections on the Type-C
> bus. USB4 uses Enter_USB instead of Enter Mode and has no SVID, so it
> gets its own mode kind and stores the negotiated EUDO directly.
> The next two patches register a USB4 port mode and publish
> the negotiated Thunderbolt and USB4 partner modes on the Type-C bus,
> along with the Thunderbolt cable mode. These modes are marked active
> after the mux has been configured and deactivated before it is switched
> back to its safe state.
>
> The following two patches add the bindings for the NHI and ACIO. The
> Thunderbolt core patches prepare for the different NHI register layout
> and interrupt handling, read the USB4 router DROM from the device tree,
> unlock the host router ports and find the Apple VSE capability. They
> also add a hook for device links to tunneled native ports, a quirk for
> USB3 bandwidth allocation not implemented by the Apple hardware and
> the symbol exports needed by the driver.
>
> The main driver registers the newly introduced notifications for the
> negotiated Type-C modes, 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], 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 we'll probably need an immutable brach with the first two
> commits after they've been reviewed which is then merged in both the usb
> and thunderbolt tree sincethere are other tipd changes in flight as well.
>
> Happy to split this series in two as well if you prefer that.
>
> 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
>
> ---
> Changes in v2:
> - Dropped thunderbolt switch in favor of the typec bus and an additional
> notifer
> - Add USB4 to the Type-C bus with its own mode kind instead of an SVID
> - Register port/partner altmodes for CD321x
> - Replaced the struct with ring MMIO offsets with two callbacks insid
> tb_nhi_ops
> - Moved port unlock sequence and finding the Apple VSE capability into
> core tb code
> - Added device links for USB3 tunnels
> - Move the USB3 BW quirk from an NHI quirk to a router quirk
> - Added Joshua's rb to patches which didn't change
> - Link to v1: https://patch.msgid.link/20260830-b4-apple-soc-tbt-v1-0-44bc9348683c@kernel.org
>
>
> ---
> Sven Peter (22):
> usb: typec: Add alternate mode state notifiers
> usb: typec: Represent USB4 on the Type-C bus
> usb: typec: tipd: Register a USB4 port mode for CD321x
> usb: typec: tipd: Publish CD321x partner alternate modes
> 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: Unlock host router ports during startup
> thunderbolt: Find Apple VSE capability during startup
> thunderbolt: Add ring_interrupt_active to tb_nhi_ops
> thunderbolt: Add ring register accessors to tb_nhi_ops
> thunderbolt: Add ring_interrupt_mask to tb_nhi_ops
> thunderbolt: Add ring_configure to tb_nhi_ops
> thunderbolt: Add add_links to tb_nhi_ops
> 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
>
For patches
3: usb: typec: tipd: Register a USB4 port mode for CD321x
4: usb: typec: tipd: Publish CD321x partner alternate modes
10: thunderbolt: Unlock host router ports during startup
13: thunderbolt: Add ring register accessors to tb_nhi_ops
15: thunderbolt: Add ring_configure to tb_nhi_ops
16: thunderbolt: Add add_links to tb_nhi_ops
17: thunderbolt: Add QUIRK_NO_USB3_BW_ALLOC
19: thunderbolt: Add Apple Silicon support
Reviewed-by: Joshua Peisach <jpeisach@ubuntu.com>
Side note: I'm probably going to stop reviewing devicetrees as I am
not really an expert in the full hardware definitions and how it
should work.
^ permalink raw reply [flat|nested] 41+ messages in thread
* Re: [PATCH v2 10/22] thunderbolt: Unlock host router ports during startup
2026-09-06 18:36 ` [PATCH v2 10/22] thunderbolt: Unlock host router ports during startup Sven Peter
@ 2026-09-08 8:22 ` Mika Westerberg
0 siblings, 0 replies; 41+ messages in thread
From: Mika Westerberg @ 2026-09-08 8:22 UTC (permalink / raw)
To: Sven Peter
Cc: Heikki Krogerus, 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
On Sun, Sep 06, 2026 at 08:36:33PM +0200, Sven Peter wrote:
> The downstream ports of the host router may still be locked after a
> reset. Unlock them when the software connection manager starts before
> scanning for any connected devices.
>
> Signed-off-by: Sven Peter <sven@kernel.org>
> ---
> drivers/thunderbolt/tb.c | 9 +++++++++
> 1 file changed, 9 insertions(+)
>
> diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c
> index 47753a5c0f2e..bf1c192ec10a 100644
> --- a/drivers/thunderbolt/tb.c
> +++ b/drivers/thunderbolt/tb.c
> @@ -3016,6 +3016,7 @@ static int tb_scan_finalize_switch(struct device *dev, void *data)
> static int tb_start(struct tb *tb, bool reset)
> {
> struct tb_cm *tcm = tb_priv(tb);
> + struct tb_port *port;
> bool discover = true;
> int ret;
>
> @@ -3049,6 +3050,14 @@ static int tb_start(struct tb *tb, bool reset)
> return dev_err_probe(tb->nhi->dev, ret, "failed to add host router\n");
> }
>
> + /* Make all host router downstream ports accessible to the CM. */
Nit: Drop the . from single line comments
/* Make all host router downstream ports accessible to the CM */
> + tb_switch_for_each_port(tb->root_switch, port) {
We should introduce tb_switch_for_each_null_port() at some point, not the
job of this series though.
> + if (!tb_port_is_null(port))
> + continue;
> + if (tb_port_unlock(port))
> + tb_port_warn(port, "failed to unlock port\n");
> + }
> +
> /*
> * To support highest CLx state, we set host router's TMU to
> * Normal mode.
>
> --
> 2.55.0
>
^ permalink raw reply [flat|nested] 41+ messages in thread
* Re: [PATCH v2 13/22] thunderbolt: Add ring register accessors to tb_nhi_ops
2026-09-06 18:36 ` [PATCH v2 13/22] thunderbolt: Add ring register accessors " Sven Peter
@ 2026-09-08 8:32 ` Mika Westerberg
0 siblings, 0 replies; 41+ messages in thread
From: Mika Westerberg @ 2026-09-08 8:32 UTC (permalink / raw)
To: Sven Peter
Cc: Heikki Krogerus, 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,
On Sun, Sep 06, 2026 at 08:36:36PM +0200, Sven Peter wrote:
> The ring descriptor and options registers are laid out differently on
> the Apple NHI. Add optional tb_nhi_ops hooks for their base addresses
> which fall back to the standard USB4 register layout when they are not
> set.
>
> Signed-off-by: Sven Peter <sven@kernel.org>
> ---
> drivers/thunderbolt/nhi.c | 32 ++++++++++++++++++++++++++------
> drivers/thunderbolt/nhi.h | 6 ++++++
> 2 files changed, 32 insertions(+), 6 deletions(-)
>
> diff --git a/drivers/thunderbolt/nhi.c b/drivers/thunderbolt/nhi.c
> index fc54ff7edfb9..1b1e4a12c744 100644
> --- a/drivers/thunderbolt/nhi.c
> +++ b/drivers/thunderbolt/nhi.c
> @@ -179,19 +179,37 @@ void nhi_disable_interrupts(struct tb_nhi *nhi)
> static void __iomem *ring_desc_base(struct tb_ring *ring)
> {
> void __iomem *io = ring->nhi->iobase;
> +
Unintended change?
> io += ring->is_tx ? REG_TX_RING_BASE : REG_RX_RING_BASE;
> io += ring->hop * 16;
> return io;
> }
>
> +static void __iomem *nhi_ring_desc_base(struct tb_ring *ring)
> +{
> + if (ring->nhi->ops->ring_desc_base)
> + return ring->nhi->ops->ring_desc_base(ring);
> +
> + return ring_desc_base(ring);
> +}
> +
> static void __iomem *ring_options_base(struct tb_ring *ring)
> {
> void __iomem *io = ring->nhi->iobase;
> +
Ditto here.
> io += ring->is_tx ? REG_TX_OPTIONS_BASE : REG_RX_OPTIONS_BASE;
> io += ring->hop * 32;
> return io;
> }
>
> +static void __iomem *nhi_ring_options_base(struct tb_ring *ring)
> +{
> + if (ring->nhi->ops->ring_options_base)
> + return ring->nhi->ops->ring_options_base(ring);
> +
> + return ring_options_base(ring);
This is better IMO. Although I think even better would be to populate
nhi->ops for both the standard NHI and for Apple (and possibly others) and
then we can just do:
ring->nhi->ops->ring_options_base(..)
or with a wrapper like this that makes it slightly more readable.
It does not need to be part of this series but wanted to mention here.
> +}
> +
> static void ring_iowrite_cons(struct tb_ring *ring, u16 cons)
> {
> /*
> @@ -199,29 +217,31 @@ static void ring_iowrite_cons(struct tb_ring *ring, u16 cons)
> * are ignored by the hardware so we can save one ioread32() by
> * filling the read-only bits with zeroes.
> */
> - iowrite32(cons, ring_desc_base(ring) + 8);
> + iowrite32(cons, nhi_ring_desc_base(ring) + 8);
> }
>
> static void ring_iowrite_prod(struct tb_ring *ring, u16 prod)
> {
> /* See ring_iowrite_cons() above for explanation */
> - iowrite32(prod << 16, ring_desc_base(ring) + 8);
> + iowrite32(prod << 16, nhi_ring_desc_base(ring) + 8);
> }
>
> static void ring_iowrite32desc(struct tb_ring *ring, u32 value, u32 offset)
> {
> - iowrite32(value, ring_desc_base(ring) + offset);
> + iowrite32(value, nhi_ring_desc_base(ring) + 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 = nhi_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)
> {
> - iowrite32(value, ring_options_base(ring) + offset);
> + iowrite32(value, nhi_ring_options_base(ring) + offset);
> }
>
> static bool ring_full(struct tb_ring *ring)
> diff --git a/drivers/thunderbolt/nhi.h b/drivers/thunderbolt/nhi.h
> index 4884c3f5a2b2..3a963b0b5b46 100644
> --- a/drivers/thunderbolt/nhi.h
> +++ b/drivers/thunderbolt/nhi.h
> @@ -52,6 +52,10 @@ 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_desc_base: NHI specific hook returning the ring descriptor registers.
> + * If not set the standard USB4 NHI registers are used.
> + * @ring_options_base: NHI specific hook returning the ring option registers.
> + * If not set the standard USB4 NHI registers are used.
> * @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.
> @@ -70,6 +74,8 @@ 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 __iomem *(*ring_desc_base)(struct tb_ring *ring);
> + void __iomem *(*ring_options_base)(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);
>
> --
> 2.55.0
>
^ permalink raw reply [flat|nested] 41+ messages in thread
* Re: [PATCH v2 16/22] thunderbolt: Add add_links to tb_nhi_ops
2026-09-06 18:36 ` [PATCH v2 16/22] thunderbolt: Add add_links " Sven Peter
@ 2026-09-08 8:35 ` Mika Westerberg
0 siblings, 0 replies; 41+ messages in thread
From: Mika Westerberg @ 2026-09-08 8:35 UTC (permalink / raw)
To: Sven Peter
Cc: Heikki Krogerus, 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,
On Sun, Sep 06, 2026 at 08:36:39PM +0200, Sven Peter wrote:
> PCI NHI drivers discover device links through ACPI or PCI topology but
> platform drivers, like the Apple Silicon one, can use neither of those
> methods. Instead, add a callback so that platform drivers can discover
> and add these links through e.g. device tree connections.
>
> Signed-off-by: Sven Peter <sven@kernel.org>
> ---
> drivers/thunderbolt/nhi.h | 2 ++
> drivers/thunderbolt/tb.c | 3 ++-
> 2 files changed, 4 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/thunderbolt/nhi.h b/drivers/thunderbolt/nhi.h
> index e4964772a442..addb8bcb4849 100644
> --- a/drivers/thunderbolt/nhi.h
> +++ b/drivers/thunderbolt/nhi.h
> @@ -65,6 +65,7 @@ extern const struct dev_pm_ops nhi_pm_ops;
> * @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.
> + * @add_links: NHI specific hook to add device links to tunneled native ports
I just pushed a change that does similar to my thunderbolt.git/next tree.
This is to support Apple T2 macs (Intel Ice Lake based). Can you rebase
this on top of that change?
> * @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
> @@ -85,6 +86,7 @@ struct tb_nhi_ops {
> void (*ring_interrupt_active)(struct tb_ring *ring, bool active);
> void (*ring_interrupt_mask)(struct tb_ring *ring, bool mask);
> void (*ring_configure)(struct tb_ring *ring, u32 flags, u32 e2e_flags);
> + bool (*add_links)(struct tb_nhi *nhi);
> bool (*is_present)(struct tb_nhi *nhi);
> int (*init_interrupts)(struct tb_nhi *nhi);
> void (*reset_interface)(struct tb_nhi *nhi);
> diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c
> index d2832323cc36..72418a07166e 100644
> --- a/drivers/thunderbolt/tb.c
> +++ b/drivers/thunderbolt/tb.c
> @@ -3434,7 +3434,8 @@ struct tb *tb_probe(struct tb_nhi *nhi)
> * before the PCIe/USB stack is resumed so complain here if we
> * found them missing.
> */
> - if (!tb_apple_add_links(nhi) && !tb_acpi_add_links(nhi))
> + if (!(nhi->ops->add_links && nhi->ops->add_links(nhi)) &&
> + !tb_apple_add_links(nhi) && !tb_acpi_add_links(nhi))
> tb_warn(tb, "device links to tunneled native ports are missing!\n");
>
> return tb;
>
> --
> 2.55.0
>
^ permalink raw reply [flat|nested] 41+ messages in thread
* Re: [PATCH v2 19/22] thunderbolt: Add Apple Silicon support
2026-09-06 18:36 ` [PATCH v2 19/22] thunderbolt: Add Apple Silicon support Sven Peter
@ 2026-09-08 9:18 ` Mika Westerberg
2026-09-08 19:02 ` Sven Peter
0 siblings, 1 reply; 41+ messages in thread
From: Mika Westerberg @ 2026-09-08 9:18 UTC (permalink / raw)
To: Sven Peter
Cc: Heikki Krogerus, 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,
On Sun, Sep 06, 2026 at 08:36:42PM +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 registers notifications for Thunderbolt and USB4 partner modes
> on the Type-C bus and derives the cable details from the Thunderbolt VDOs
> or USB4 EUDO.
>
> After powering up ACIO, the driver 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 accessors, interrupt handling and ring configuration. It also
> uses the OF graph connection between the USB3 adapter and the DWC3
> controller to create the device link required to restore tunnels before
> USB resumes. 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 | 1277 ++++++++++++++++++++++++++++++++++++++++++
> 4 files changed, 1294 insertions(+)
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 406cf664dedc..a0ddea98813c 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -2692,6 +2692,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..f4b16f60701e
> --- /dev/null
> +++ b/drivers/thunderbolt/apple.c
> @@ -0,0 +1,1277 @@
> +// 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) Mark the negotiated Type-C alternate mode active
> + * 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
> + *
> + * ACIO must not be started before the PHY is configured. 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.
> + *
> + * When the cable is disconnected ACIO shutdown and PHY shutdown can happen in
> + * either order as long as both have been shutdown before the next connection
> + * is established.
> + */
> +
> +#include <linux/bitfield.h>
> +#include <linux/completion.h>
> +#include <linux/device/bus.h>
> +#include <linux/interrupt.h>
> +#include <linux/io.h>
> +#include <linux/iopoll.h>
> +#include <linux/module.h>
> +#include <linux/mutex.h>
> +#include <linux/notifier.h>
> +#include <linux/of.h>
> +#include <linux/of_platform.h>
> +#include <linux/platform_device.h>
> +#include <linux/pm_domain.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/property.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.h>
> +#include <linux/usb/typec_altmode.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_USB3_ADAPTER 4
Do you really need to hard-code this? It's not possible to figure out from
the host router adapters directly?
> +
> +#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 10000 /* ms */
> +
> +/*
> + * 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_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;
I think you may not need the forward declaration here.
> +struct apple_cio_altmode {
> + struct apple_cio *acio;
> + struct typec_altmode *altmode;
> + struct notifier_block notifier;
> +};
> +
> +/**
> + * struct apple_cio - Apple Converged I/O block
> + * @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 block
> + * @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
> + * @connector: Type-C connector firmware node linked to this ACIO
> + * @typec_lock: Serializes Type-C alternate mode attachment and removal
> + * @typec_notifier: Type-C bus notifier used to discover partner alternate modes
> + * @tbt_altmode: Thunderbolt partner alternate mode subscription
@tb_altmode if possible
The driver uses "tb" everywhere so that's preferred for consistency.
> + * @usb4_altmode: USB4 partner alternate mode subscription
> + */
> +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 fwnode_handle *connector;
> + struct mutex typec_lock;
> + struct notifier_block typec_notifier;
> + struct apple_cio_altmode tbt_altmode;
> + struct apple_cio_altmode usb4_altmode;
> +};
> +
> +/**
> + * struct apple_nhi - Apple Native Host Interface
> + * @dev: NHI device
> + * @pdev: NHI platform device
> + * @np: NHI device tree node
> + * @acio: Parent ACIO block
> + * @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
> + * @nrings: 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 nrings;
> +};
> +
> +#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)
> +{
> + char name[64];
I think you can move this into the block where it's used.
> + int nirqs;
> +
> + nirqs = platform_irq_count(anhi->pdev);
> + if (nirqs < 0)
> + return dev_err_probe(anhi->dev, nirqs, "platform_irq_count failed\n");
> + if (!nirqs)
> + return dev_err_probe(anhi->dev, -EINVAL, "no interrupts found\n");
> + if (nirqs % 2)
> + return dev_err_probe(anhi->dev, -EINVAL,
> + "invalid number of interrupts: %d must be even\n",
> + nirqs);
> + anhi->nrings = nirqs / 2;
> +
> + anhi->rx_irqs = devm_kcalloc(anhi->dev, anhi->nrings,
> + sizeof(*anhi->rx_irqs), GFP_KERNEL);
> + if (!anhi->rx_irqs)
> + return -ENOMEM;
> + anhi->tx_irqs = devm_kcalloc(anhi->dev, anhi->nrings,
> + sizeof(*anhi->tx_irqs), GFP_KERNEL);
> + if (!anhi->tx_irqs)
> + return -ENOMEM;
> + anhi->rx_irq_names = devm_kcalloc(anhi->dev, anhi->nrings,
> + sizeof(*anhi->rx_irq_names), GFP_KERNEL);
> + if (!anhi->rx_irq_names)
> + return -ENOMEM;
> + anhi->tx_irq_names = devm_kcalloc(anhi->dev, anhi->nrings,
> + sizeof(*anhi->tx_irq_names), GFP_KERNEL);
> + if (!anhi->tx_irq_names)
> + return -ENOMEM;
> +
> + for (int i = 0; i < anhi->nrings; ++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)
This can take const
> +{
> + struct apple_nhi *anhi = nhi_to_anhi(ring->nhi);
And this can be const too.
> +
> + if (ring->is_tx)
> + return ring->hop;
> + else
Drop the else here.
> + return ring->hop + anhi->nrings;
> +}
> +
> +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;
> + unsigned int idx;
> +
> + anhi = nhi_to_anhi(ring->nhi);
> + 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;
I think it reads better if you do
if (ring->irq > 0)
devm_free_irq(ring->nhi->dev, ring->irq, ring);
ring->irq = 0;
> +}
> +
> +static void __iomem *apple_nhi_ring_desc_base(struct tb_ring *ring)
> +{
> + struct apple_nhi *anhi = nhi_to_anhi(ring->nhi);
> + void __iomem *io = anhi->nhi_base;
> +
> + io += ring->hop * APPLE_CIO_NHI_RING_STRIDE;
> + io += ring->is_tx ? APPLE_CIO_NHI_TXRING_DESC_BASE :
> + APPLE_CIO_NHI_RXRING_DESC_BASE;
> + return io;
> +}
> +
> +static void __iomem *apple_nhi_ring_options_base(struct tb_ring *ring)
> +{
> + return apple_nhi_ring_desc_base(ring) + 0x10;
Here maybe use macro for the 0x10 magic number.
> +}
> +
> +static void apple_nhi_ring_configure(struct tb_ring *ring, u32 flags, u32 e2e_flags)
> +{
> + void __iomem *options = apple_nhi_ring_options_base(ring);
> + struct apple_nhi *anhi = nhi_to_anhi(ring->nhi);
> + u32 sof_eof_mask;
> +
> + lockdep_assert_held(&ring->lock);
> +
> + if (ring->is_tx) {
> + /*
> + * 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);
Any idea what's special with the ring 5? And why the numbers 40 and 5? I
think they refer intra-domain shared buffers (which is what we are going to
use for XDomain connections in Linux going forward too).
> + } else {
> + 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 bool apple_nhi_add_links(struct tb_nhi *nhi)
> +{
> + struct apple_nhi *anhi = nhi_to_anhi(nhi);
> + struct fwnode_handle *endpoint, *remote;
> + struct device_link *link;
> + struct device *consumer;
> +
> + endpoint = fwnode_graph_get_endpoint_by_id(dev_fwnode(anhi->acio->dev),
> + APPLE_CIO_USB3_ADAPTER, 0, 0);
Okay they have the power contract as part of the DT description that's
good. I just worry that if they change the USB3 Gen X adapter place this
will break.
Do you have a snippet what it looks like in DT description?
(I'm still in progress to setup Asahi tethered boot on my m1 mac so cannot
check it at the moment).
> + if (!endpoint)
> + return false;
> +
> + remote = fwnode_graph_get_remote_port_parent(endpoint);
> + fwnode_handle_put(endpoint);
> + if (!remote)
> + return false;
> +
> + consumer = bus_find_device_by_fwnode(&platform_bus_type, remote);
> + fwnode_handle_put(remote);
> + if (!consumer)
> + return false;
> +
> + link = device_link_add(consumer, nhi->dev,
> + DL_FLAG_AUTOREMOVE_SUPPLIER |
> + DL_FLAG_PM_RUNTIME);
> + if (link)
> + dev_dbg(nhi->dev, "created link from %s\n", dev_name(consumer));
> + else
> + dev_warn(nhi->dev, "device link creation from %s failed\n",
> + dev_name(consumer));
> +
> + put_device(consumer);
> +
> + return !!link;
> +}
> +
> +static const struct tb_nhi_ops apple_nhi_ops = {
> + .request_ring_irq = apple_nhi_request_irq,
> + .release_ring_irq = apple_nhi_release_irq,
> + .ring_desc_base = apple_nhi_ring_desc_base,
> + .ring_options_base = apple_nhi_ring_options_base,
> + .ring_interrupt_active = apple_nhi_ring_interrupt_active,
> + .ring_interrupt_mask = apple_nhi_ring_interrupt_mask,
> + .ring_configure = apple_nhi_ring_configure,
> + .add_links = apple_nhi_add_links,
> +};
> +
> +static int apple_nhi_probe(struct platform_device *pdev)
> +{
> + struct apple_cio *acio = dev_get_drvdata(pdev->dev.parent);
> + struct apple_tunable *tunable;
> + struct apple_nhi *anhi;
> + struct resource *res;
> + 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);
> +
> + /*
> + * Only 42 bits seem to be wired up for the IOVA space but this may be further
> + * limited by the IOMMU's capabilities.
> + */
> + 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);
> + /* DMA transactions on this platform always go through a DART IOMMU. */
> + anhi->nhi.iommu_dma_protection = true;
> + anhi->nhi.ops = &apple_nhi_ops;
> + anhi->nhi.iobase = anhi->nhi_base;
> + 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->nrings) {
> + ret = dev_err_probe(anhi->dev, -EINVAL,
> + "ring IRQs (%zd) != HOP_COUNT (%d)\n",
> + anhi->nrings, 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 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;
> + }
> +
> + if (!anhi->tb->root_switch->cap_vsec_apple) {
> + dev_err(anhi->dev, "unable to find VSE Apple capability\n");
Maybe "missing Apple VSE capability" or so because finding was already
done.
> + ret = -ENODEV;
> + goto err_unlock_tb_domain;
> + }
> +
> + ret = tb_sw_write(anhi->tb->root_switch, &acio->target_cable_info, TB_CFG_SWITCH,
> + anhi->tb->root_switch->cap_vsec_apple +
> + TB_VSE_CAP_APPLE_CABLE_INFO, 1);
I would prefer if we could keep the USB4 stuff outside of the host
interface. It should be fine to do this in tb.c based on the capability but
getting the cable info there is tricky :(
> + 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 block
> + * @acio: ACIO block to stop
> + *
> + * Remove the NHI and DART children before shutting down the co-processor and
> + * dropping the power-domain links. 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 block, 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 block
> + * @acio: ACIO block 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. 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)
> +{
> + 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++) {
> + struct device_link *link;
> +
> + 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))) {
> + dev_err(acio->dev, "timed out waiting for the NHI to come up\n");
> + ret = -ETIMEDOUT;
> + goto err_depopulate;
> + }
> + if (acio->nhi_boot_status) {
> + ret = acio->nhi_boot_status;
> + goto err_depopulate;
> + }
> +
> + acio->current_cable_info = acio->target_cable_info;
> + return 0;
> +
> +err_depopulate:
> + of_platform_depopulate(acio->dev);
> +err_shutdown_rtkit:
> + /* Ignore errors here since we're about to cut power to the entire block anyway */
> + apple_rtkit_poweroff(acio->rtk);
> +err_free_rtkit:
> + apple_rtkit_free(acio->rtk);
> +remove_links:
> + /* Cut power to reset the entire block */
> + for (i = 0; i < acio->pd_list->num_pds; i++) {
> + if (acio->pd_list->pd_links[i])
> + device_link_del(acio->pd_list->pd_links[i]);
> + acio->pd_list->pd_links[i] = NULL;
> + }
> +
> + return ret;
> +}
> +
> +static int apple_cio_set_cable_info(struct apple_cio *acio, u32 cable_info)
> +{
> + guard(mutex)(&acio->lock);
> +
> + acio->target_cable_info = cable_info;
> + 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 block
> + * 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_tbt_cable_info(struct apple_cio *acio,
> + struct typec_altmode *altmode, u32 *cable_info)
> +{
> + u32 info = TB_VSE_CAP_APPLE_CABLE_INFO_PRESENT |
> + TB_VSE_CAP_APPLE_CABLE_INFO_TBT2_3;
> + enum typec_orientation orientation;
> + struct typec_altmode *plug;
> + u32 cable_mode;
> +
> + plug = typec_altmode_get_plug(altmode, TYPEC_PLUG_SOP_P);
> + if (!plug)
> + return -ENODEV;
> +
> + cable_mode = plug->vdo;
> + typec_altmode_put_plug(plug);
> + orientation = typec_altmode_get_orientation(altmode);
> +
> + if (cable_mode & TBT_CABLE_ACTIVE_PASSIVE) {
> + info |= TB_VSE_CAP_APPLE_CABLE_INFO_ACTIVE_CABLE;
> + if (!(cable_mode & TBT_CABLE_LINK_TRAINING))
> + info |= TB_VSE_CAP_APPLE_CABLE_INFO_BIDIR_LSRX;
> + }
> + if (TBT_ADAPTER(altmode->vdo))
> + info |= TB_VSE_CAP_APPLE_CABLE_INFO_LEGACY_ADAPTER;
> + if (TBT_CABLE_SPEED(cable_mode) == TBT_CABLE_10_AND_20GBPS)
> + info |= TB_VSE_CAP_APPLE_CABLE_INFO_20_GBPS;
> + if (orientation == TYPEC_ORIENTATION_REVERSE)
> + info |= TB_VSE_CAP_APPLE_CABLE_INFO_ORIENTATION_REVERSE;
> +
> + dev_dbg(acio->dev,
> + "TBT cable: cable mode %#x, device mode %#x, orientation %d -> cable info %#x\n",
> + cable_mode, altmode->vdo, orientation, info);
> + *cable_info = info;
> + return 0;
> +}
> +
> +static int apple_cio_usb4_cable_info(struct apple_cio *acio,
> + struct typec_altmode *altmode, u32 *cable_info)
> +{
> + u32 info = TB_VSE_CAP_APPLE_CABLE_INFO_PRESENT;
> + enum typec_orientation orientation;
> + u32 eudo = altmode->eudo;
> +
> + if (FIELD_GET(EUDO_USB_MODE_MASK, eudo) != EUDO_USB_MODE_USB4)
> + return -EINVAL;
> +
> + orientation = typec_altmode_get_orientation(altmode);
> + if (FIELD_GET(EUDO_CABLE_TYPE_MASK, eudo) != EUDO_CABLE_TYPE_PASSIVE)
> + info |= TB_VSE_CAP_APPLE_CABLE_INFO_ACTIVE_CABLE;
> + if (FIELD_GET(EUDO_CABLE_SPEED_MASK, eudo) == EUDO_CABLE_SPEED_USB4_GEN3)
> + info |= TB_VSE_CAP_APPLE_CABLE_INFO_20_GBPS;
> + if (orientation == TYPEC_ORIENTATION_REVERSE)
> + info |= TB_VSE_CAP_APPLE_CABLE_INFO_ORIENTATION_REVERSE;
> +
> + dev_dbg(acio->dev, "USB4 cable: EUDO %#x, orientation %d -> cable info %#x\n",
> + eudo, orientation, info);
> + *cable_info = info;
> + return 0;
> +}
> +
> +static int apple_cio_altmode_notify(struct notifier_block *nb,
> + unsigned long action, void *data)
> +{
> + struct apple_cio_altmode *subscription;
> + struct typec_altmode *altmode = data;
> + struct apple_cio *acio;
> + u32 cable_info;
> + int ret;
> +
> + subscription = container_of(nb, struct apple_cio_altmode, notifier);
> + acio = subscription->acio;
> +
> + if (altmode != subscription->altmode)
> + return NOTIFY_DONE;
> +
> + switch (action) {
> + case TYPEC_ALTMODE_ENTERED:
> + if (altmode->mode_kind == TYPEC_MODE_KIND_ALTMODE &&
> + altmode->svid == USB_TYPEC_TBT_SID)
> + ret = apple_cio_tbt_cable_info(acio, altmode, &cable_info);
> + else if (altmode->mode_kind == TYPEC_MODE_KIND_USB4)
> + ret = apple_cio_usb4_cable_info(acio, altmode, &cable_info);
> + else
> + ret = -EINVAL;
> + if (ret) {
> + dev_err(acio->dev, "failed to read Type-C cable information: %d\n", ret);
> + return NOTIFY_OK;
> + }
> + break;
> + case TYPEC_ALTMODE_EXITED:
> + cable_info = 0;
> + break;
> + default:
> + return NOTIFY_DONE;
> + }
> +
> + /*
> + * The altmode has already been negotiated and we can't exit it anyway
> + * on Apple Silicon so just warn the we couldn't start but still return
> + * success.
> + */
> + ret = apple_cio_set_cable_info(acio, cable_info);
> + if (ret)
> + dev_warn(acio->dev, "failed to start ACIO: %d\n", ret);
> +
> + return NOTIFY_OK;
> +}
> +
> +static struct apple_cio_altmode *
> +apple_cio_altmode_subscription(struct apple_cio *acio, struct typec_altmode *altmode)
> +{
> + if (altmode->mode_kind == TYPEC_MODE_KIND_ALTMODE &&
> + altmode->svid == USB_TYPEC_TBT_SID)
> + return &acio->tbt_altmode;
> + if (altmode->mode_kind == TYPEC_MODE_KIND_USB4)
> + return &acio->usb4_altmode;
> + return NULL;
> +}
> +
> +static bool apple_cio_altmode_matches(struct apple_cio *acio,
> + struct typec_altmode *altmode)
> +{
> + if (!apple_cio_altmode_subscription(acio, altmode))
> + return false;
> +
> + return dev_fwnode(altmode->dev.parent->parent) == acio->connector;
> +}
> +
> +static int apple_cio_altmode_attach_locked(struct apple_cio *acio,
> + struct typec_altmode *altmode)
> +{
> + struct apple_cio_altmode *subscription;
> + int ret;
> +
> + lockdep_assert_held(&acio->typec_lock);
> +
> + if (!apple_cio_altmode_matches(acio, altmode))
> + return 0;
> +
> + subscription = apple_cio_altmode_subscription(acio, altmode);
> +
> + if (subscription->altmode == altmode)
> + return 0;
> + if (subscription->altmode)
> + return -EBUSY;
> +
> + get_device(&altmode->dev);
> + subscription->altmode = altmode;
> + ret = typec_altmode_register_notifier(altmode, &subscription->notifier);
> + if (ret) {
> + subscription->altmode = NULL;
> + put_device(&altmode->dev);
> + }
> +
> + return ret;
> +}
> +
> +static int apple_cio_altmode_attach(struct apple_cio *acio,
> + struct typec_altmode *altmode)
> +{
> + guard(mutex)(&acio->typec_lock);
> +
> + return apple_cio_altmode_attach_locked(acio, altmode);
> +}
> +
> +static void apple_cio_altmode_detach(struct apple_cio *acio,
> + struct typec_altmode *altmode)
> +{
> + struct apple_cio_altmode *subscription;
> + bool active;
> +
> + subscription = apple_cio_altmode_subscription(acio, altmode);
> + if (!subscription)
> + return;
> +
> + guard(mutex)(&acio->typec_lock);
> + if (subscription->altmode != altmode)
> + return;
> +
> + active = altmode->active;
> + typec_altmode_unregister_notifier(altmode, &subscription->notifier);
> + subscription->altmode = NULL;
> + if (active)
> + apple_cio_set_cable_info(acio, 0);
> + put_device(&altmode->dev);
> +}
> +
> +static int apple_cio_typec_notify(struct notifier_block *nb,
> + unsigned long action, void *data)
> +{
> + struct apple_cio *acio = container_of(nb, struct apple_cio, typec_notifier);
> + struct typec_altmode *altmode;
> + struct device *dev = data;
> + int ret;
> +
> + if (!is_typec_partner_altmode(dev))
> + return NOTIFY_DONE;
> +
> + altmode = to_typec_altmode(dev);
> + switch (action) {
> + case BUS_NOTIFY_ADD_DEVICE:
> + ret = apple_cio_altmode_attach(acio, altmode);
> + if (ret)
> + dev_err(acio->dev,
> + "failed to register for alternate mode notifications: %d\n", ret);
> + break;
> + case BUS_NOTIFY_DEL_DEVICE:
> + case BUS_NOTIFY_REMOVED_DEVICE:
> + apple_cio_altmode_detach(acio, altmode);
> + break;
> + default:
> + break;
> + }
> +
> + return NOTIFY_DONE;
> +}
> +
> +static int apple_cio_scan_altmode(struct device *dev, void *data)
> +{
> + if (!is_typec_partner_altmode(dev))
> + return 0;
> +
> + return apple_cio_altmode_attach_locked(data, to_typec_altmode(dev));
> +}
> +
> +static void apple_cio_unregister_typec(struct apple_cio *acio)
> +{
> + bus_unregister_notifier(&typec_bus, &acio->typec_notifier);
> + if (acio->tbt_altmode.altmode)
> + apple_cio_altmode_detach(acio, acio->tbt_altmode.altmode);
> + if (acio->usb4_altmode.altmode)
> + apple_cio_altmode_detach(acio, acio->usb4_altmode.altmode);
> +}
> +
> +static void apple_cio_put_connector(void *data)
> +{
> + struct apple_cio *acio = data;
> +
> + fwnode_handle_put(acio->connector);
> +}
> +
> +static int apple_cio_probe(struct platform_device *pdev)
> +{
> + struct dev_pm_domain_attach_data pd_data = {
> + .pd_flags = PD_FLAG_NO_DEV_LINK,
> + };
> + struct device *dev = &pdev->dev;
> + struct fwnode_handle *endpoint;
> + 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;
> +
> + ret = devm_mutex_init(dev, &acio->typec_lock);
> + if (ret)
> + return ret;
> +
> + init_completion(&acio->nhi_boot_completion);
> + acio->dev = &pdev->dev;
> + acio->np = dev->of_node;
> + acio->typec_notifier.notifier_call = apple_cio_typec_notify;
> + acio->tbt_altmode.acio = acio;
> + acio->tbt_altmode.notifier.notifier_call = apple_cio_altmode_notify;
> + acio->usb4_altmode.acio = acio;
> + acio->usb4_altmode.notifier.notifier_call = apple_cio_altmode_notify;
> +
> + endpoint = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), 1, 0, 0);
> + if (!endpoint)
> + return dev_err_probe(dev, -ENODEV, "unable to find Type-C graph endpoint\n");
> + acio->connector = fwnode_graph_get_remote_port_parent(endpoint);
> + fwnode_handle_put(endpoint);
> + if (!acio->connector)
> + return dev_err_probe(dev, -ENODEV, "unable to find Type-C connector\n");
> + ret = devm_add_action_or_reset(dev, apple_cio_put_connector, acio);
> + if (ret)
> + return ret;
> +
> + 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 PM domain listed in the device tree the
> + * platform driver framework will already attach it. Thus, if we find an
> + * already attached PM 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.
> + */
> + 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");
> +
> + ret = bus_register_notifier(&typec_bus, &acio->typec_notifier);
> + if (ret)
> + return dev_err_probe(dev, ret, "unable to register Type-C notifier\n");
> +
> + scoped_guard(mutex, &acio->typec_lock) {
> + ret = bus_for_each_dev(&typec_bus, NULL, acio, apple_cio_scan_altmode);
> + }
> + if (ret) {
> + apple_cio_unregister_typec(acio);
> + return dev_err_probe(dev, ret,
> + "unable to register for alternate modes notifications\n");
> + }
> +
> + return 0;
> +}
> +
> +static void apple_cio_remove(struct platform_device *pdev)
> +{
> + struct apple_cio *acio = platform_get_drvdata(pdev);
> +
> + apple_cio_unregister_typec(acio);
> +
> + 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) {
This could use a comment too explaining briefly why.
> + 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));
> +}
> +module_init(apple_cio_init);
> +
> +static void __exit apple_cio_exit(void)
> +{
> + platform_unregister_drivers(apple_cio_drivers,
> + ARRAY_SIZE(apple_cio_drivers));
> +}
> +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 [flat|nested] 41+ messages in thread
* Re: [PATCH v2 01/22] usb: typec: Add alternate mode state notifiers
2026-09-06 18:36 ` [PATCH v2 01/22] usb: typec: Add alternate mode state notifiers Sven Peter
2026-09-07 13:27 ` Joshua Peisach
@ 2026-09-08 12:00 ` Heikki Krogerus
1 sibling, 0 replies; 41+ messages in thread
From: Heikki Krogerus @ 2026-09-08 12:00 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
On Sun, Sep 06, 2026 at 08:36:24PM +0200, Sven Peter wrote:
> The Apple Silicon USB4/Thunderbolt driver needs to be made aware of
> cable details and whenever USB4 or Thunderbolt is entered. The cable
> details are already available as part of the typec subsystem but there
> is no way to get a notification on typec_altmode_update_active() so
> far.
>
> Add a blocking notifier chain to each alternate mode and call it
> whenever the mode is activated or deactivated right next to the already
> existing sysfs and kobject_uevent notifications. Replay activations when a
> notifier is registered.
>
> Signed-off-by: Sven Peter <sven@kernel.org>
> ---
> drivers/usb/typec/bus.h | 6 +++++
> drivers/usb/typec/class.c | 59 +++++++++++++++++++++++++++++++++++++++++++++++
> include/linux/usb/typec.h | 16 +++++++++++++
> 3 files changed, 81 insertions(+)
...
> diff --git a/include/linux/usb/typec.h b/include/linux/usb/typec.h
> index 10a783b738ef..914a6f0844c5 100644
> --- a/include/linux/usb/typec.h
> +++ b/include/linux/usb/typec.h
> @@ -19,6 +19,7 @@ struct typec_plug;
> struct typec_port;
> struct typec_altmode_ops;
> struct typec_cable_ops;
> +struct notifier_block;
>
> struct bus_type;
> struct fwnode_handle;
> @@ -184,6 +185,21 @@ struct typec_port *typec_altmode2port(struct typec_altmode *alt);
>
> void typec_altmode_update_active(struct typec_altmode *alt, bool active);
>
> +/**
> + * enum typec_altmode_event - USB Type-C alternate mode state event
> + * @TYPEC_ALTMODE_ENTERED: The alternate mode has been entered
> + * @TYPEC_ALTMODE_EXITED: The alternate mode has been exited
> + */
> +enum typec_altmode_event {
> + TYPEC_ALTMODE_ENTERED,
> + TYPEC_ALTMODE_EXITED,
> +};
Mode entry and exit are not the only state changes that can happen.
> +int typec_altmode_register_notifier(struct typec_altmode *alt,
> + struct notifier_block *nb);
> +int typec_altmode_unregister_notifier(struct typec_altmode *alt,
> + struct notifier_block *nb);
This is basically a partial revert of this commit bbe80c9a89b8 ("usb:
typec: altmode: Remove the notification chain"). Can you check if it
would enough to just completely revert that one.
Thanks,
--
heikki
^ permalink raw reply [flat|nested] 41+ messages in thread
* Re: [PATCH v2 02/22] usb: typec: Represent USB4 on the Type-C bus
2026-09-06 18:36 ` [PATCH v2 02/22] usb: typec: Represent USB4 on the Type-C bus Sven Peter
2026-09-07 13:31 ` Joshua Peisach
@ 2026-09-08 12:07 ` Heikki Krogerus
1 sibling, 0 replies; 41+ messages in thread
From: Heikki Krogerus @ 2026-09-08 12:07 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
On Sun, Sep 06, 2026 at 08:36:25PM +0200, Sven Peter wrote:
> USB4 technically isn't alternate mode and entered using the EUDO and has
> no SVID or Discover Modes Response. We can still represent it like an
> altmode on the Type-C bus though by adding a mode kind. Exclude the USB4
> modes from any SVID-specific operations and driver binding, but allow
> consumers to observe their state through notifications.
>
> Like for other altmodes, expose the USB4 mode to sysfs: Add a mode_kind
> to be able to differentiate and then expose the EUDO, make the USB4
> active state read-only since we only represent firmware-negotiated USB4
> modes and omit anything that only applies to SVID-specific altmodes.
>
> Signed-off-by: Sven Peter <sven@kernel.org>
> ---
> Hope it's fine to add you as the contact for the sysfs documentation,
> happy to add myself otherwise though.
> ---
> Documentation/ABI/testing/sysfs-bus-typec | 36 ++++++++++++++
> drivers/usb/typec/bus.c | 67 ++++++++++++++++++++-----
> drivers/usb/typec/class.c | 83 +++++++++++++++++++++++++++----
> drivers/usb/typec/mode_selection.c | 6 ++-
> include/linux/usb/typec.h | 20 +++++++-
> include/linux/usb/typec_altmode.h | 10 +++-
> 6 files changed, 193 insertions(+), 29 deletions(-)
>
> diff --git a/Documentation/ABI/testing/sysfs-bus-typec b/Documentation/ABI/testing/sysfs-bus-typec
> index 205d9c91e2e1..524bbcbadf64 100644
> --- a/Documentation/ABI/testing/sysfs-bus-typec
> +++ b/Documentation/ABI/testing/sysfs-bus-typec
> @@ -12,6 +12,35 @@ Description:
>
> Valid values are boolean.
>
> + For USB4 mode devices this attribute is read-only. On a port it
> + indicates whether USB4 support is enabled; on a partner it reports
> + whether USB4 is active. USB4 entry and exit are controlled by the
> + port controller. Partner state changes notify this attribute and
> + generate KOBJ_CHANGE uevents.
> +
> +What: /sys/bus/typec/devices/.../mode_kind
> +Date: September 2026
> +Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com>
> +Description:
> + Read-only protocol kind represented by this mode device:
> + "altmode" for an SVID-specific alternate mode, or "usb4" for
> + USB4 negotiated using Enter_USB.
> +
> + USB4 devices do not expose svid, mode, vdo, or priority
> + attributes, or the legacy modeN attribute group. USB4 partner
> + uevents contain MODE_KIND=usb4 instead of SVID, MODE, and
> + MODALIAS variables.
> +
> +What: /sys/bus/typec/devices/.../eudo
> +Date: September 2026
> +Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com>
> +Description:
> + Read-only Enter_USB Data Object (EUDO), formatted as
> + 0x followed by eight hexadecimal digits. Present only on USB4
> + partner mode devices, not on ports or SVID-specific alternate
> + modes. USB4 cable information is encoded in the EUDO; there are
> + no USB4 cable plug mode devices.
> +
> What: /sys/bus/typec/devices/.../description
> Date: July 2018
> Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com>
> @@ -36,6 +65,8 @@ Description:
> the mode index is not assigned, identifying the alternate mode
> must be done with either mode VDO or the description.
>
> + This attribute is not present on USB4 mode devices.
> +
> What: /sys/bus/typec/devices/.../svid
> Date: July 2018
> Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com>
> @@ -43,9 +74,14 @@ Description:
> The Standard or Vendor ID (SVID) assigned by USB-IF for this
> alternate mode.
>
> + This attribute is not present on USB4 mode devices.
> +
> What: /sys/bus/typec/devices/.../vdo
> Date: July 2018
> Contact: Heikki Krogerus <heikki.krogerus@linux.intel.com>
> Description:
> Shows the VDO in hexadecimal returned by Discover Modes command
> for this mode.
> +
> + This attribute is not present on USB4 mode devices. USB4 partner
> + mode devices expose the EUDO in the eudo attribute.
USB4 needs to have its own device type. The USB4 mode should not
expose things like svid and the vdo at all. And the USB4 mode
registration should be done with a dedicated API IMO.
Thanks,
--
heikki
^ permalink raw reply [flat|nested] 41+ messages in thread
* Re: [PATCH v2 19/22] thunderbolt: Add Apple Silicon support
2026-09-08 9:18 ` Mika Westerberg
@ 2026-09-08 19:02 ` Sven Peter
2026-09-08 19:04 ` Sven Peter
2026-09-09 6:06 ` Mika Westerberg
0 siblings, 2 replies; 41+ messages in thread
From: Sven Peter @ 2026-09-08 19:02 UTC (permalink / raw)
To: Mika Westerberg
Cc: Heikki Krogerus, 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 Mika,
thanks again for the quick review!
On 9/8/26 11:18, Mika Westerberg wrote:
> Hi,
>
> On Sun, Sep 06, 2026 at 08:36:42PM +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 registers notifications for Thunderbolt and USB4 partner modes
>> on the Type-C bus and derives the cable details from the Thunderbolt VDOs
>> or USB4 EUDO.
>>
>> After powering up ACIO, the driver 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 accessors, interrupt handling and ring configuration. It also
>> uses the OF graph connection between the USB3 adapter and the DWC3
>> controller to create the device link required to restore tunnels before
>> USB resumes. As of this commit only XDomain connections and
>> USB3-via-USB4 tunnels are supported.
>>
>> Signed-off-by: Sven Peter<sven@kernel.org>
[...]
>> +
>> +#define APPLE_CIO_NHI_PDF_STRIDE 0x4000
>> +
>> +#define APPLE_CIO_USB3_ADAPTER 4
> Do you really need to hard-code this? It's not possible to figure out from
> the host router adapters directly?
It's possible I think but the host router isn't up yet by the time I
need it to add the links in tb_probe.
>
>> +
>> +#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 10000 /* ms */
>> +
>> +/*
>> + * 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_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;
> I think you may not need the forward declaration here.
Yup, I think that's just a leftover. Will drop it.
>
>> +struct apple_cio_altmode {
>> + struct apple_cio *acio;
>> + struct typec_altmode *altmode;
>> + struct notifier_block notifier;
>> +};
>> +
>> +/**
>> + * struct apple_cio - Apple Converged I/O block
>> + * @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 block
>> + * @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
>> + * @connector: Type-C connector firmware node linked to this ACIO
>> + * @typec_lock: Serializes Type-C alternate mode attachment and removal
>> + * @typec_notifier: Type-C bus notifier used to discover partner alternate modes
>> + * @tbt_altmode: Thunderbolt partner alternate mode subscription
> @tb_altmode if possible
>
> The driver uses "tb" everywhere so that's preferred for consistency.
And I thought I got them all! Fixed that one (and another one below) as
well!
[....]
>> +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)
>> +{
>> + char name[64];
> I think you can move this into the block where it's used.
Yup, will move it.
>
>> + int nirqs;
>> +
>> + nirqs = platform_irq_count(anhi->pdev);
>> + if (nirqs < 0)
>> + return dev_err_probe(anhi->dev, nirqs, "platform_irq_count failed\n");
>> + if (!nirqs)
>> + return dev_err_probe(anhi->dev, -EINVAL, "no interrupts found\n");
>> + if (nirqs % 2)
>> + return dev_err_probe(anhi->dev, -EINVAL,
>> + "invalid number of interrupts: %d must be even\n",
>> + nirqs);
>> + anhi->nrings = nirqs / 2;
>> +
>> + anhi->rx_irqs = devm_kcalloc(anhi->dev, anhi->nrings,
>> + sizeof(*anhi->rx_irqs), GFP_KERNEL);
>> + if (!anhi->rx_irqs)
>> + return -ENOMEM;
>> + anhi->tx_irqs = devm_kcalloc(anhi->dev, anhi->nrings,
>> + sizeof(*anhi->tx_irqs), GFP_KERNEL);
>> + if (!anhi->tx_irqs)
>> + return -ENOMEM;
>> + anhi->rx_irq_names = devm_kcalloc(anhi->dev, anhi->nrings,
>> + sizeof(*anhi->rx_irq_names), GFP_KERNEL);
>> + if (!anhi->rx_irq_names)
>> + return -ENOMEM;
>> + anhi->tx_irq_names = devm_kcalloc(anhi->dev, anhi->nrings,
>> + sizeof(*anhi->tx_irq_names), GFP_KERNEL);
>> + if (!anhi->tx_irq_names)
>> + return -ENOMEM;
>> +
>> + for (int i = 0; i < anhi->nrings; ++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)
> This can take const
Ack
>
>> +{
>> + struct apple_nhi *anhi = nhi_to_anhi(ring->nhi);
> And this can be const too.
Ack
>
>> +
>> + if (ring->is_tx)
>> + return ring->hop;
>> + else
> Drop the else here.
Ack.
>> + return ring->hop + anhi->nrings;
>> +}
>> +
>> +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;
>> +
[...]
>> +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;
> I think it reads better if you do
>
> if (ring->irq > 0)
> devm_free_irq(ring->nhi->dev, ring->irq, ring);
> ring->irq = 0;
Agreed, will change it to that.
>
>> +}
>> +
>> +static void __iomem *apple_nhi_ring_desc_base(struct tb_ring *ring)
>> +{
>> + struct apple_nhi *anhi = nhi_to_anhi(ring->nhi);
>> + void __iomem *io = anhi->nhi_base;
>> +
>> + io += ring->hop * APPLE_CIO_NHI_RING_STRIDE;
>> + io += ring->is_tx ? APPLE_CIO_NHI_TXRING_DESC_BASE :
>> + APPLE_CIO_NHI_RXRING_DESC_BASE;
>> + return io;
>> +}
>> +
>> +static void __iomem *apple_nhi_ring_options_base(struct tb_ring *ring)
>> +{
>> + return apple_nhi_ring_desc_base(ring) + 0x10;
> Here maybe use macro for the 0x10 magic number.
Ack.
>
>> +}
>> +
>> +static void apple_nhi_ring_configure(struct tb_ring *ring, u32 flags, u32 e2e_flags)
>> +{
>> + void __iomem *options = apple_nhi_ring_options_base(ring);
>> + struct apple_nhi *anhi = nhi_to_anhi(ring->nhi);
>> + u32 sof_eof_mask;
>> +
>> + lockdep_assert_held(&ring->lock);
>> +
>> + if (ring->is_tx) {
>> + /*
>> + * 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);
> Any idea what's special with the ring 5? And why the numbers 40 and 5? I
> think they refer intra-domain shared buffers (which is what we are going to
> use for XDomain connections in Linux going forward too).
No idea really. All I know is that there's some string calling these
"shared buffers" and that there are 232 in total.
macOS assigns two to the first ring (probably because it only has
configuration traffic and doesn't need more), then 40 to the next 5
rings (which are presumably used for xdomains most of the time) and then
5 to the rest (which I guess are rarely used) so that it ends up at 232
spread across 12 rings. I think the setup is arbitrary but I have to
write something >0 here or otherwise the ring won't work IIRC.
>
>> + } else {
>> + 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 bool apple_nhi_add_links(struct tb_nhi *nhi)
>> +{
>> + struct apple_nhi *anhi = nhi_to_anhi(nhi);
>> + struct fwnode_handle *endpoint, *remote;
>> + struct device_link *link;
>> + struct device *consumer;
>> +
>> + endpoint = fwnode_graph_get_endpoint_by_id(dev_fwnode(anhi->acio->dev),
>> + APPLE_CIO_USB3_ADAPTER, 0, 0);
> Okay they have the power contract as part of the DT description that's
> good. I just worry that if they change the USB3 Gen X adapter place this
> will break.
>
> Do you have a snippet what it looks like in DT description?
We don't use Apple's Device Tree at all, we make up our own
representation for Linux device tree and supply that in our bootloader
m1n1 to the kernel. See the dt-binding commits and the later dts commits.
If the adapter place changes the new hardware will get a new compatible
and we can determine it from that one then. The other alternative would
be to just walk through all ports (except for the type-c lanes) defined
in the DT and just add all links here. It's a bit moot right now since
we can't do runtime or proper suspend anyway yet.
I'll need those graph connections later on for PCIe and DP tunnels as
well since they need a software notification whenever a tunnel is
brought up or torn down. Here's how the connection looks like right now
though. It's a graph connection that's also used to e.g. connect dwc3 to
the Type-C controller. This hasn't been reviewed by the devicetree
maintainers yet:
&dwc3_0 {
ports {
#address-cells = <1>;
#size-cells = <0>;
port@1 {
#address-cells = <1>;
#size-cells = <0>;
reg = <1>;
dwc3_0_ss: endpoint@0 {
reg = <0>;
remote-endpoint = <&atcphy0_usb3>;
};
dwc3_0_usb4: endpoint@1 {
reg = <1>;
remote-endpoint = <&usb4_0_acio_usb3>;
};
};
};
};
&usb4_0_acio {
ports {
#address-cells = <1>;
#size-cells = <0>;
port@4 {
reg = <4>;
usb4_0_acio_usb3: endpoint {
remote-endpoint = <&dwc3_0_usb4>;
};
};
};
};
> (I'm still in progress to setup Asahi tethered boot on my m1 mac so cannot
> check it at the moment).
You've probably found it already, but we have a guide here:
https://asahilinux.org/docs/sw/tethered-boot/
Let me know if you run into any issues!
>
>> + if (!endpoint)
>> + return false;
>> +
>> + remote = fwnode_graph_get_remote_port_parent(endpoint);
>> + fwnode_handle_put(endpoint);
>> + if (!remote)
>> + return false;
[....]
>> +
>> + 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;
>> + }
>> +
>> + if (!anhi->tb->root_switch->cap_vsec_apple) {
>> + dev_err(anhi->dev, "unable to find VSE Apple capability\n");
> Maybe "missing Apple VSE capability" or so because finding was already
> done.
Ack.
>
>> + ret = -ENODEV;
>> + goto err_unlock_tb_domain;
>> + }
>> +
>> + ret = tb_sw_write(anhi->tb->root_switch, &acio->target_cable_info, TB_CFG_SWITCH,
>> + anhi->tb->root_switch->cap_vsec_apple +
>> + TB_VSE_CAP_APPLE_CABLE_INFO, 1);
> I would prefer if we could keep the USB4 stuff outside of the host
> interface. It should be fine to do this in tb.c based on the capability but
> getting the cable info there is tricky :(
Hm... I wonder if adding apple_cable_info to struct tb and then
something like this would work
anhi->tb = tb_probe(...);
anhi->tb->apple_cable_info = acio->target_cable_info;
ret = tb_domain_add(anhi->tb, false);
since I have to call tb_domain_add for each new cable that's connected
anyway. I'll give it a try and maybe move the VSE cable bit #defines to
tb_regs.h as well then.
[...]
>> +
>> +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) {
> This could use a comment too explaining briefly why.
Sure, I'll add a comment. It's required for now because the power domain
would be brought down during suspend and neither the PHY nor this driver
has the proper bringup sequence right now. We'd just resume into an
SError because bringing this all up again requires more than just
enabling the power domains.
Thanks,
Sven
^ permalink raw reply [flat|nested] 41+ messages in thread
* Re: [PATCH v2 19/22] thunderbolt: Add Apple Silicon support
2026-09-08 19:02 ` Sven Peter
@ 2026-09-08 19:04 ` Sven Peter
2026-09-09 6:06 ` Mika Westerberg
1 sibling, 0 replies; 41+ messages in thread
From: Sven Peter @ 2026-09-08 19:04 UTC (permalink / raw)
To: Mika Westerberg
Cc: Heikki Krogerus, 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
>
> &dwc3_0 {
> ports {
> #address-cells = <1>;
> #size-cells = <0>;
>
> port@1 {
> #address-cells = <1>;
> #size-cells = <0>;
> reg = <1>;
>
> dwc3_0_ss: endpoint@0 {
> reg = <0>;
> remote-endpoint = <&atcphy0_usb3>;
> };
>
> dwc3_0_usb4: endpoint@1 {
> reg = <1>;
> remote-endpoint = <&usb4_0_acio_usb3>;
> };
> };
> };
> };
>
> &usb4_0_acio {
> ports {
> #address-cells = <1>;
> #size-cells = <0>;
>
> port@4 {
> reg = <4>;
> usb4_0_acio_usb3: endpoint {
> remote-endpoint = <&dwc3_0_usb4>;
> };
> };
> };
> };
>
ugh, Thunderbird messed that up quite a bit. let me try again and see if
it keeps the indentation this time:
&dwc3_0 {
ports {
#address-cells = <1>;
#size-cells = <0>;
port@1 {
#address-cells = <1>;
#size-cells = <0>;
reg = <1>;
dwc3_0_ss: endpoint@0 {
reg = <0>;
remote-endpoint = <&atcphy0_usb3>;
};
dwc3_0_usb4: endpoint@1 {
reg = <1>;
remote-endpoint = <&usb4_0_acio_usb3>;
};
};
};
};
&usb4_0_acio {
ports {
#address-cells = <1>;
#size-cells = <0>;
port@4 {
reg = <4>;
usb4_0_acio_usb3: endpoint {
remote-endpoint = <&dwc3_0_usb4>;
};
};
};
};
^ permalink raw reply [flat|nested] 41+ messages in thread
* Re: [PATCH v2 11/22] thunderbolt: Find Apple VSE capability during startup
2026-09-07 13:38 ` Joshua Peisach
@ 2026-09-08 20:24 ` Sven Peter
0 siblings, 0 replies; 41+ messages in thread
From: Sven Peter @ 2026-09-08 20:24 UTC (permalink / raw)
To: Joshua Peisach
Cc: Konrad Dybcio, Conor Dooley, Krzysztof Kozlowski, linux-usb,
Neal Gompa, Janne Grunau, devicetree, linux-kernel,
Yehezkel Bernat, Mika Westerberg, Andreas Noever, Philipp Zabel,
asahi, Rob Herring, Heikki Krogerus, linux-arm-kernel,
Greg Kroah-Hartman
On 9/7/26 15:38, Joshua Peisach wrote:
> On Sun Sep 6, 2026 at 2:36 PM EDT, Sven Peter wrote:
>> Only Apple Silicon host routers have the Apple vendor-specific extended
>> capability. Look it up when the software connection manager starts and
>> store its offset in the root switch.
>>
>> The Apple NHI driver can then use the stored offset when programming
>> cable information without searching the router capabilities itself.
>>
>> Signed-off-by: Sven Peter <sven@kernel.org>
>> ---
>> drivers/thunderbolt/tb.c | 4 ++++
>> drivers/thunderbolt/tb.h | 2 ++
>> drivers/thunderbolt/tb_regs.h | 1 +
>> 3 files changed, 7 insertions(+)
>>
>> diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c
>> index bf1c192ec10a..d2832323cc36 100644
>> --- a/drivers/thunderbolt/tb.c
>> +++ b/drivers/thunderbolt/tb.c
>> @@ -3025,6 +3025,10 @@ static int tb_start(struct tb *tb, bool reset)
>> return dev_err_probe(tb->nhi->dev, PTR_ERR(tb->root_switch),
>> "failed to allocate host router\n");
>>
>> + ret = tb_switch_find_vse_cap(tb->root_switch, TB_VSE_CAP_APPLE);
>> + if (ret > 0)
>> + tb->root_switch->cap_vsec_apple = ret;
>> +
>
> Nitpick: I *think* based on the surrounding code that this can be
> simplified to "if (ret)"
>
No, ret < 0 is an error and if something goes wrong cap_vsec_apple must
remain zero and not whatever errno tb_switch_find_vse_cap returned.
Sven
^ permalink raw reply [flat|nested] 41+ messages in thread
* Re: [PATCH v2 19/22] thunderbolt: Add Apple Silicon support
2026-09-08 19:02 ` Sven Peter
2026-09-08 19:04 ` Sven Peter
@ 2026-09-09 6:06 ` Mika Westerberg
2026-09-09 15:20 ` Sven Peter
1 sibling, 1 reply; 41+ messages in thread
From: Mika Westerberg @ 2026-09-09 6:06 UTC (permalink / raw)
To: Sven Peter
Cc: Heikki Krogerus, 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,
On Tue, Sep 08, 2026 at 09:02:09PM +0200, Sven Peter wrote:
> Hi Mika,
>
> thanks again for the quick review!
>
> On 9/8/26 11:18, Mika Westerberg wrote:
> > Hi,
> >
> > On Sun, Sep 06, 2026 at 08:36:42PM +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 registers notifications for Thunderbolt and USB4 partner modes
> > > on the Type-C bus and derives the cable details from the Thunderbolt VDOs
> > > or USB4 EUDO.
> > >
> > > After powering up ACIO, the driver 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 accessors, interrupt handling and ring configuration. It also
> > > uses the OF graph connection between the USB3 adapter and the DWC3
> > > controller to create the device link required to restore tunnels before
> > > USB resumes. As of this commit only XDomain connections and
> > > USB3-via-USB4 tunnels are supported.
> > >
> > > Signed-off-by: Sven Peter<sven@kernel.org>
> [...]
>
> > > +
> > > +#define APPLE_CIO_NHI_PDF_STRIDE 0x4000
> > > +
> > > +#define APPLE_CIO_USB3_ADAPTER 4
> > Do you really need to hard-code this? It's not possible to figure out from
> > the host router adapters directly?
>
> It's possible I think but the host router isn't up yet by the time I need it
> to add the links in tb_probe.
> >
> > > +
> > > +#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 10000 /* ms */
> > > +
> > > +/*
> > > + * 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_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;
> > I think you may not need the forward declaration here.
>
> Yup, I think that's just a leftover. Will drop it.
> >
> > > +struct apple_cio_altmode {
> > > + struct apple_cio *acio;
> > > + struct typec_altmode *altmode;
> > > + struct notifier_block notifier;
> > > +};
> > > +
> > > +/**
> > > + * struct apple_cio - Apple Converged I/O block
> > > + * @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 block
> > > + * @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
> > > + * @connector: Type-C connector firmware node linked to this ACIO
> > > + * @typec_lock: Serializes Type-C alternate mode attachment and removal
> > > + * @typec_notifier: Type-C bus notifier used to discover partner alternate modes
> > > + * @tbt_altmode: Thunderbolt partner alternate mode subscription
> > @tb_altmode if possible
> >
> > The driver uses "tb" everywhere so that's preferred for consistency.
>
> And I thought I got them all! Fixed that one (and another one below) as
> well!
>
> [....]
>
> > > +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)
> > > +{
> > > + char name[64];
> > I think you can move this into the block where it's used.
>
> Yup, will move it.
>
> >
> > > + int nirqs;
> > > +
> > > + nirqs = platform_irq_count(anhi->pdev);
> > > + if (nirqs < 0)
> > > + return dev_err_probe(anhi->dev, nirqs, "platform_irq_count failed\n");
> > > + if (!nirqs)
> > > + return dev_err_probe(anhi->dev, -EINVAL, "no interrupts found\n");
> > > + if (nirqs % 2)
> > > + return dev_err_probe(anhi->dev, -EINVAL,
> > > + "invalid number of interrupts: %d must be even\n",
> > > + nirqs);
> > > + anhi->nrings = nirqs / 2;
> > > +
> > > + anhi->rx_irqs = devm_kcalloc(anhi->dev, anhi->nrings,
> > > + sizeof(*anhi->rx_irqs), GFP_KERNEL);
> > > + if (!anhi->rx_irqs)
> > > + return -ENOMEM;
> > > + anhi->tx_irqs = devm_kcalloc(anhi->dev, anhi->nrings,
> > > + sizeof(*anhi->tx_irqs), GFP_KERNEL);
> > > + if (!anhi->tx_irqs)
> > > + return -ENOMEM;
> > > + anhi->rx_irq_names = devm_kcalloc(anhi->dev, anhi->nrings,
> > > + sizeof(*anhi->rx_irq_names), GFP_KERNEL);
> > > + if (!anhi->rx_irq_names)
> > > + return -ENOMEM;
> > > + anhi->tx_irq_names = devm_kcalloc(anhi->dev, anhi->nrings,
> > > + sizeof(*anhi->tx_irq_names), GFP_KERNEL);
> > > + if (!anhi->tx_irq_names)
> > > + return -ENOMEM;
> > > +
> > > + for (int i = 0; i < anhi->nrings; ++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)
> > This can take const
> Ack
> >
> > > +{
> > > + struct apple_nhi *anhi = nhi_to_anhi(ring->nhi);
> > And this can be const too.
> Ack
> >
> > > +
> > > + if (ring->is_tx)
> > > + return ring->hop;
> > > + else
> > Drop the else here.
> Ack.
>
> > > + return ring->hop + anhi->nrings;
> > > +}
> > > +
> > > +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;
> > > +
> [...]
> > > +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;
> > I think it reads better if you do
> >
> > if (ring->irq > 0)
> > devm_free_irq(ring->nhi->dev, ring->irq, ring);
> > ring->irq = 0;
>
> Agreed, will change it to that.
>
> >
> > > +}
> > > +
> > > +static void __iomem *apple_nhi_ring_desc_base(struct tb_ring *ring)
> > > +{
> > > + struct apple_nhi *anhi = nhi_to_anhi(ring->nhi);
> > > + void __iomem *io = anhi->nhi_base;
> > > +
> > > + io += ring->hop * APPLE_CIO_NHI_RING_STRIDE;
> > > + io += ring->is_tx ? APPLE_CIO_NHI_TXRING_DESC_BASE :
> > > + APPLE_CIO_NHI_RXRING_DESC_BASE;
> > > + return io;
> > > +}
> > > +
> > > +static void __iomem *apple_nhi_ring_options_base(struct tb_ring *ring)
> > > +{
> > > + return apple_nhi_ring_desc_base(ring) + 0x10;
> > Here maybe use macro for the 0x10 magic number.
>
> Ack.
>
> >
> > > +}
> > > +
> > > +static void apple_nhi_ring_configure(struct tb_ring *ring, u32 flags, u32 e2e_flags)
> > > +{
> > > + void __iomem *options = apple_nhi_ring_options_base(ring);
> > > + struct apple_nhi *anhi = nhi_to_anhi(ring->nhi);
> > > + u32 sof_eof_mask;
> > > +
> > > + lockdep_assert_held(&ring->lock);
> > > +
> > > + if (ring->is_tx) {
> > > + /*
> > > + * 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);
> > Any idea what's special with the ring 5? And why the numbers 40 and 5? I
> > think they refer intra-domain shared buffers (which is what we are going to
> > use for XDomain connections in Linux going forward too).
>
> No idea really. All I know is that there's some string calling these "shared
> buffers" and that there are 232 in total.
> macOS assigns two to the first ring (probably because it only has
> configuration traffic and doesn't need more), then 40 to the next 5 rings
> (which are presumably used for xdomains most of the time) and then 5 to the
> rest (which I guess are rarely used) so that it ends up at 232 spread across
> 12 rings. I think the setup is arbitrary but I have to write something >0
> here or otherwise the ring won't work IIRC.
Okay thanks. It makes sense for now to mimic what Apple is doing although
we are currently using dedicated buffering for all XDomain links but you
program these to the rings itself and the CM takes care of the rest so that
should be okay.
> > > + } else {
> > > + 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 bool apple_nhi_add_links(struct tb_nhi *nhi)
> > > +{
> > > + struct apple_nhi *anhi = nhi_to_anhi(nhi);
> > > + struct fwnode_handle *endpoint, *remote;
> > > + struct device_link *link;
> > > + struct device *consumer;
> > > +
> > > + endpoint = fwnode_graph_get_endpoint_by_id(dev_fwnode(anhi->acio->dev),
> > > + APPLE_CIO_USB3_ADAPTER, 0, 0);
> > Okay they have the power contract as part of the DT description that's
> > good. I just worry that if they change the USB3 Gen X adapter place this
> > will break.
> >
> > Do you have a snippet what it looks like in DT description?
>
> We don't use Apple's Device Tree at all, we make up our own representation
> for Linux device tree and supply that in our bootloader m1n1 to the kernel.
Ah right forgot that ;-)
> See the dt-binding commits and the later dts commits.
> If the adapter place changes the new hardware will get a new compatible and
> we can determine it from that one then. The other alternative would be to
> just walk through all ports (except for the type-c lanes) defined in the DT
> and just add all links here. It's a bit moot right now since we can't do
> runtime or proper suspend anyway yet.
Understood.
> I'll need those graph connections later on for PCIe and DP tunnels as well
> since they need a software notification whenever a tunnel is brought up or
> torn down. Here's how the connection looks like right now though. It's a
> graph connection that's also used to e.g. connect dwc3 to the Type-C
> controller. This hasn't been reviewed by the devicetree maintainers yet:
>
> &dwc3_0 {
> ports {
> #address-cells = <1>;
> #size-cells = <0>;
>
> port@1 {
> #address-cells = <1>;
> #size-cells = <0>;
> reg = <1>;
>
> dwc3_0_ss: endpoint@0 {
> reg = <0>;
> remote-endpoint = <&atcphy0_usb3>;
> };
>
> dwc3_0_usb4: endpoint@1 {
> reg = <1>;
> remote-endpoint = <&usb4_0_acio_usb3>;
> };
> };
> };
> };
>
> &usb4_0_acio {
> ports {
> #address-cells = <1>;
> #size-cells = <0>;
>
> port@4 {
> reg = <4>;
> usb4_0_acio_usb3: endpoint {
> remote-endpoint = <&dwc3_0_usb4>;
> };
> };
> };
> };
If it is possible, I suggest to use the same way we do in ACPI side here:
https://learn.microsoft.com/en-us/windows-hardware/drivers/pci/dsd-for-pcie-root-ports#map-native-protocols-pcie-displayport-tunneled-through-usb4-to-usb4-host-routers
It was specifically designed so that it should support DT as well (that's
why it uses the device properties _DSD).
>
>
>
> > (I'm still in progress to setup Asahi tethered boot on my m1 mac so cannot
> > check it at the moment).
>
> You've probably found it already, but we have a guide here:
> https://asahilinux.org/docs/sw/tethered-boot/
> Let me know if you run into any issues!
Sure thanks!
> >
> > > + if (!endpoint)
> > > + return false;
> > > +
> > > + remote = fwnode_graph_get_remote_port_parent(endpoint);
> > > + fwnode_handle_put(endpoint);
> > > + if (!remote)
> > > + return false;
> [....]
> > > +
> > > + 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;
> > > + }
> > > +
> > > + if (!anhi->tb->root_switch->cap_vsec_apple) {
> > > + dev_err(anhi->dev, "unable to find VSE Apple capability\n");
> > Maybe "missing Apple VSE capability" or so because finding was already
> > done.
>
> Ack.
>
> >
> > > + ret = -ENODEV;
> > > + goto err_unlock_tb_domain;
> > > + }
> > > +
> > > + ret = tb_sw_write(anhi->tb->root_switch, &acio->target_cable_info, TB_CFG_SWITCH,
> > > + anhi->tb->root_switch->cap_vsec_apple +
> > > + TB_VSE_CAP_APPLE_CABLE_INFO, 1);
> > I would prefer if we could keep the USB4 stuff outside of the host
> > interface. It should be fine to do this in tb.c based on the capability but
> > getting the cable info there is tricky :(
>
> Hm... I wonder if adding apple_cable_info to struct tb and then something
> like this would work
>
> anhi->tb = tb_probe(...);
> anhi->tb->apple_cable_info = acio->target_cable_info;
> ret = tb_domain_add(anhi->tb, false);
>
> since I have to call tb_domain_add for each new cable that's connected
> anyway. I'll give it a try and maybe move the VSE cable bit #defines to
> tb_regs.h as well then.
Yes that could work.
^ permalink raw reply [flat|nested] 41+ messages in thread
* Re: [PATCH v2 19/22] thunderbolt: Add Apple Silicon support
2026-09-09 6:06 ` Mika Westerberg
@ 2026-09-09 15:20 ` Sven Peter
2026-09-09 15:25 ` Sven Peter
2026-09-10 4:50 ` Mika Westerberg
0 siblings, 2 replies; 41+ messages in thread
From: Sven Peter @ 2026-09-09 15:20 UTC (permalink / raw)
To: Mika Westerberg
Cc: Heikki Krogerus, 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,
On 09.09.26 08:06, Mika Westerberg wrote:
[...]
>
> If it is possible, I suggest to use the same way we do in ACPI side here:
>
> https://learn.microsoft.com/en-us/windows-hardware/drivers/pci/dsd-for-pcie-root-ports#map-native-protocols-pcie-displayport-tunneled-through-usb4-to-usb4-host-routers
>
> It was specifically designed so that it should support DT as well (that's
> why it uses the device properties _DSD).
Oh, nice, I didn't know those existed. Konrad already has a PR open to
add these to dt-schema [1] and a PR that already makes use of these for
dwc3 [2].
I can just write generic code similar to the ACPI one then to add the
device links and split it off from this series, though I think that
dt-schema PR must be merged before we're allowed to use that representation.
I'll adjust the dt-bindings here for v3 to just drop the representation
as OF ports/endpoints as well.
For the PCIe/DP tunnel notifications I should be able to use the same
representation, possibly with an additional usb4-adapter-index property
or similar to differentiate between the two DP IN adapters, once I get
to that then.
[1] https://github.com/devicetree-org/dt-schema/pull/196
[2]
https://lore.kernel.org/linux-usb/20260901-topic-dwc3_tunneling_state-v3-0-22fdced8de55@oss.qualcomm.com/
Sven
^ permalink raw reply [flat|nested] 41+ messages in thread
* Re: [PATCH v2 19/22] thunderbolt: Add Apple Silicon support
2026-09-09 15:20 ` Sven Peter
@ 2026-09-09 15:25 ` Sven Peter
2026-09-10 4:52 ` Mika Westerberg
2026-09-10 4:50 ` Mika Westerberg
1 sibling, 1 reply; 41+ messages in thread
From: Sven Peter @ 2026-09-09 15:25 UTC (permalink / raw)
To: Mika Westerberg
Cc: Heikki Krogerus, 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
On 09.09.26 17:20, Sven Peter wrote:
> Hi,
>
> On 09.09.26 08:06, Mika Westerberg wrote:
>
> [...]
>
>>
>> If it is possible, I suggest to use the same way we do in ACPI side here:
>>
>> https://learn.microsoft.com/en-us/windows-hardware/drivers/pci/dsd-
>> for-pcie-root-ports#map-native-protocols-pcie-displayport-tunneled-
>> through-usb4-to-usb4-host-routers
>>
>> It was specifically designed so that it should support DT as well (that's
>> why it uses the device properties _DSD).
>
> Oh, nice, I didn't know those existed. Konrad already has a PR open to
> add these to dt-schema [1] and a PR that already makes use of these for
> dwc3 [2].
> I can just write generic code similar to the ACPI one then to add the
> device links and split it off from this series, though I think that dt-
> schema PR must be merged before we're allowed to use that representation.
> I'll adjust the dt-bindings here for v3 to just drop the representation
> as OF ports/endpoints as well.
> For the PCIe/DP tunnel notifications I should be able to use the same
> representation, possibly with an additional usb4-adapter-index property
> or similar to differentiate between the two DP IN adapters, once I get
> to that then.
Though now that I read it again: do you know what PortInstance# in
Package () { "usb4-port-number", PortInstance#}, // PortInstance# is
unused and can be 0
}
refers to? Is that meant to e.g. by 0 for the first DP IN adapter and 1
for the second? Or is that meant to be used when the NHI handles
multiple Type-C ports? The description isn't clear to me...
Sven
^ permalink raw reply [flat|nested] 41+ messages in thread
* Re: [PATCH v2 19/22] thunderbolt: Add Apple Silicon support
2026-09-09 15:20 ` Sven Peter
2026-09-09 15:25 ` Sven Peter
@ 2026-09-10 4:50 ` Mika Westerberg
1 sibling, 0 replies; 41+ messages in thread
From: Mika Westerberg @ 2026-09-10 4:50 UTC (permalink / raw)
To: Sven Peter
Cc: Heikki Krogerus, 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,
On Wed, Sep 09, 2026 at 05:20:35PM +0200, Sven Peter wrote:
> Hi,
>
> On 09.09.26 08:06, Mika Westerberg wrote:
>
> [...]
>
> >
> > If it is possible, I suggest to use the same way we do in ACPI side here:
> >
> > https://learn.microsoft.com/en-us/windows-hardware/drivers/pci/dsd-for-pcie-root-ports#map-native-protocols-pcie-displayport-tunneled-through-usb4-to-usb4-host-routers
> >
> > It was specifically designed so that it should support DT as well (that's
> > why it uses the device properties _DSD).
>
> Oh, nice, I didn't know those existed. Konrad already has a PR open to add
> these to dt-schema [1] and a PR that already makes use of these for dwc3
> [2].
Great.
> I can just write generic code similar to the ACPI one then to add the device
> links and split it off from this series, though I think that dt-schema PR
> must be merged before we're allowed to use that representation.
> I'll adjust the dt-bindings here for v3 to just drop the representation as
> OF ports/endpoints as well.
> For the PCIe/DP tunnel notifications I should be able to use the same
> representation, possibly with an additional usb4-adapter-index property or
> similar to differentiate between the two DP IN adapters, once I get to that
> then.
For PCIe yes, for DP you should not need those at all.
^ permalink raw reply [flat|nested] 41+ messages in thread
* Re: [PATCH v2 19/22] thunderbolt: Add Apple Silicon support
2026-09-09 15:25 ` Sven Peter
@ 2026-09-10 4:52 ` Mika Westerberg
0 siblings, 0 replies; 41+ messages in thread
From: Mika Westerberg @ 2026-09-10 4:52 UTC (permalink / raw)
To: Sven Peter
Cc: Heikki Krogerus, 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
On Wed, Sep 09, 2026 at 05:25:32PM +0200, Sven Peter wrote:
> On 09.09.26 17:20, Sven Peter wrote:
> > Hi,
> >
> > On 09.09.26 08:06, Mika Westerberg wrote:
> >
> > [...]
> >
> > >
> > > If it is possible, I suggest to use the same way we do in ACPI side here:
> > >
> > > https://learn.microsoft.com/en-us/windows-hardware/drivers/pci/dsd-
> > > for-pcie-root-ports#map-native-protocols-pcie-displayport-tunneled-
> > > through-usb4-to-usb4-host-routers
> > >
> > > It was specifically designed so that it should support DT as well (that's
> > > why it uses the device properties _DSD).
> >
> > Oh, nice, I didn't know those existed. Konrad already has a PR open to
> > add these to dt-schema [1] and a PR that already makes use of these for
> > dwc3 [2].
> > I can just write generic code similar to the ACPI one then to add the
> > device links and split it off from this series, though I think that dt-
> > schema PR must be merged before we're allowed to use that
> > representation.
> > I'll adjust the dt-bindings here for v3 to just drop the representation
> > as OF ports/endpoints as well.
> > For the PCIe/DP tunnel notifications I should be able to use the same
> > representation, possibly with an additional usb4-adapter-index property
> > or similar to differentiate between the two DP IN adapters, once I get
> > to that then.
>
> Though now that I read it again: do you know what PortInstance# in
>
> Package () { "usb4-port-number", PortInstance#}, // PortInstance# is unused
> and can be 0
> }
>
> refers to? Is that meant to e.g. by 0 for the first DP IN adapter and 1 for
> the second? Or is that meant to be used when the NHI handles multiple Type-C
> ports? The description isn't clear to me...
>
That's the USB4 port through the native PCIe/USB3 Gen X is tunneled through
but like I said in the previous email you don't need this for DP, only for
PCIe and USB3 (Gen X/T).
^ permalink raw reply [flat|nested] 41+ messages in thread
end of thread, other threads:[~2026-09-10 4:53 UTC | newest]
Thread overview: 41+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-06 18:36 [PATCH v2 00/22] Initial USB4/Thunderbolt support for Apple M1/M2/M3 SoCs Sven Peter
2026-09-06 18:36 ` [PATCH v2 01/22] usb: typec: Add alternate mode state notifiers Sven Peter
2026-09-07 13:27 ` Joshua Peisach
2026-09-08 12:00 ` Heikki Krogerus
2026-09-06 18:36 ` [PATCH v2 02/22] usb: typec: Represent USB4 on the Type-C bus Sven Peter
2026-09-07 13:31 ` Joshua Peisach
2026-09-08 12:07 ` Heikki Krogerus
2026-09-06 18:36 ` [PATCH v2 03/22] usb: typec: tipd: Register a USB4 port mode for CD321x Sven Peter
2026-09-06 18:36 ` [PATCH v2 04/22] usb: typec: tipd: Publish CD321x partner alternate modes Sven Peter
2026-09-06 18:36 ` [PATCH v2 05/22] dt-bindings: thunderbolt: Add Apple USB4/Thunderbolt NHI Sven Peter
2026-09-06 18:36 ` [PATCH v2 06/22] dt-bindings: thunderbolt: Add Apple USB4/Thunderbolt ACIO block Sven Peter
2026-09-06 18:36 ` [PATCH v2 07/22] thunderbolt: Try reading host DROM from device tree first Sven Peter
2026-09-06 18:36 ` [PATCH v2 08/22] thunderbolt: Don't read the UID if we already know it Sven Peter
2026-09-06 18:36 ` [PATCH v2 09/22] thunderbolt: Allocate ring HopID before requesting the ring interrupt Sven Peter
2026-09-06 18:36 ` [PATCH v2 10/22] thunderbolt: Unlock host router ports during startup Sven Peter
2026-09-08 8:22 ` Mika Westerberg
2026-09-06 18:36 ` [PATCH v2 11/22] thunderbolt: Find Apple VSE capability " Sven Peter
2026-09-07 13:38 ` Joshua Peisach
2026-09-08 20:24 ` Sven Peter
2026-09-06 18:36 ` [PATCH v2 12/22] thunderbolt: Add ring_interrupt_active to tb_nhi_ops Sven Peter
2026-09-06 18:36 ` [PATCH v2 13/22] thunderbolt: Add ring register accessors " Sven Peter
2026-09-08 8:32 ` Mika Westerberg
2026-09-06 18:36 ` [PATCH v2 14/22] thunderbolt: Add ring_interrupt_mask " Sven Peter
2026-09-06 18:36 ` [PATCH v2 15/22] thunderbolt: Add ring_configure " Sven Peter
2026-09-06 18:36 ` [PATCH v2 16/22] thunderbolt: Add add_links " Sven Peter
2026-09-08 8:35 ` Mika Westerberg
2026-09-06 18:36 ` [PATCH v2 17/22] thunderbolt: Add QUIRK_NO_USB3_BW_ALLOC Sven Peter
2026-09-06 18:36 ` [PATCH v2 18/22] thunderbolt: Export symbols required by the Apple Silicon driver Sven Peter
2026-09-06 18:36 ` [PATCH v2 19/22] thunderbolt: Add Apple Silicon support Sven Peter
2026-09-08 9:18 ` Mika Westerberg
2026-09-08 19:02 ` Sven Peter
2026-09-08 19:04 ` Sven Peter
2026-09-09 6:06 ` Mika Westerberg
2026-09-09 15:20 ` Sven Peter
2026-09-09 15:25 ` Sven Peter
2026-09-10 4:52 ` Mika Westerberg
2026-09-10 4:50 ` Mika Westerberg
2026-09-06 18:36 ` [PATCH v2 20/22] arm64: dts: apple: t8103: Add USB4 ACIO and NHI Sven Peter
2026-09-06 18:36 ` [PATCH v2 21/22] arm64: dts: apple: t8112: " Sven Peter
2026-09-06 18:36 ` [PATCH v2 22/22] arm64: dts: apple: t60xx: " Sven Peter
2026-09-07 13:52 ` [PATCH v2 00/22] Initial USB4/Thunderbolt support for Apple M1/M2/M3 SoCs Joshua Peisach
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®