Devicetree
 help / color / mirror / Atom feed
From: Sven Peter <sven@kernel.org>
To: Greg Kroah-Hartman <gregkh@linuxfoundation.org>,
	 Rob Herring <robh@kernel.org>,
	Krzysztof Kozlowski <krzk+dt@kernel.org>,
	 Conor Dooley <conor+dt@kernel.org>,
	 Heikki Krogerus <heikki.krogerus@linux.intel.com>,
	 Janne Grunau <j@jannau.net>, Neal Gompa <neal@gompa.dev>,
	 Andreas Noever <andreas.noever@gmail.com>,
	 Mika Westerberg <westeri@kernel.org>,
	 Yehezkel Bernat <YehezkelShB@gmail.com>,
	 Philipp Zabel <p.zabel@pengutronix.de>
Cc: Konrad Dybcio <konradybcio@kernel.org>,
	linux-usb@vger.kernel.org,  devicetree@vger.kernel.org,
	linux-kernel@vger.kernel.org,  asahi@lists.linux.dev,
	linux-arm-kernel@lists.infradead.org,
	 Sven Peter <sven@kernel.org>
Subject: [PATCH 02/19] usb: typec: Add thunderbolt switch
Date: Sun, 30 Aug 2026 22:19:20 +0200	[thread overview]
Message-ID: <20260830-b4-apple-soc-tbt-v1-2-44bc9348683c@kernel.org> (raw)
In-Reply-To: <20260830-b4-apple-soc-tbt-v1-0-44bc9348683c@kernel.org>

On Apple Silicon the USB4/Thunderbolt host router can only be brought
up after the Type-C PHY has been configured and requires cable details
known only to the Type-C port controller.

The existing orientation switch only forwards the cable orientation.
The mode switch also forwards most of the required details but
represents devices which configure or route the Type-C signal path. The
ACIO host router is a consumer of that path and has additional ordering
requirements: it has to be started after the PHY has been configured
and stopped again before the PHY is disabled.

The mode switch code calls all handlers in the same order both when
entering and leaving a mode and cannot express this reverse teardown
order. Treating ACIO as another mode switch could therefore leave the
NHI active after its PHY has been disabled, which can result in an
asynchronous SError or a SoC watchdog reset.

Add a separate Thunderbolt switch which Type-C port drivers can use to
forward the negotiated Thunderbolt or USB4 cable details and explicitly
order host router setup and teardown around the mode switch. This is
similar to a USB role switch: each connection has exactly one host
router and the callback controls the lifetime of that functional
controller instead of configuring a signal mux.

Signed-off-by: Sven Peter <sven@kernel.org>
---
 drivers/usb/typec/mux.c       | 201 ++++++++++++++++++++++++++++++++++++++++++
 drivers/usb/typec/mux.h       |  12 +++
 include/linux/usb/typec_mux.h | 114 ++++++++++++++++++++++++
 3 files changed, 327 insertions(+)

diff --git a/drivers/usb/typec/mux.c b/drivers/usb/typec/mux.c
index 9b908c46bd7d..8339846ba355 100644
--- a/drivers/usb/typec/mux.c
+++ b/drivers/usb/typec/mux.c
@@ -479,6 +479,207 @@ void *typec_mux_get_drvdata(struct typec_mux_dev *mux_dev)
 }
 EXPORT_SYMBOL_GPL(typec_mux_get_drvdata);
 
+/* ------------------------------------------------------------------------- */
+
+struct typec_thunderbolt_switch {
+	struct typec_thunderbolt_switch_dev *sw_dev;
+};
+
+static int thunderbolt_switch_fwnode_match(struct device *dev,
+					   const void *fwnode)
+{
+	if (!is_typec_thunderbolt_switch_dev(dev))
+		return 0;
+
+	return device_match_fwnode(dev, fwnode);
+}
+
+static void *typec_thunderbolt_switch_match(const struct fwnode_handle *fwnode,
+					    const char *id, void *data)
+{
+	struct device *dev;
+
+	if (id && !fwnode_property_present(fwnode, id))
+		return NULL;
+
+	dev = class_find_device(&typec_mux_class, NULL, fwnode,
+				thunderbolt_switch_fwnode_match);
+
+	return dev ? to_typec_thunderbolt_switch_dev(dev) :
+		     ERR_PTR(-EPROBE_DEFER);
+}
+
+/**
+ * fwnode_typec_thunderbolt_switch_get - Find USB4/Thunderbolt switch
+ * @fwnode: The caller device node
+ *
+ * Finds a USB4/Thunderbolt switch linked with @fwnode. Returns a
+ * reference to the switch on success, NULL if no matching connection
+ * was found, or ERR_PTR(-EPROBE_DEFER) when a connection was found but
+ * the switch has not been enumerated yet.
+ */
+struct typec_thunderbolt_switch *
+fwnode_typec_thunderbolt_switch_get(struct fwnode_handle *fwnode)
+{
+	struct typec_thunderbolt_switch_dev *sw_dev;
+	struct typec_thunderbolt_switch *sw;
+	void *match;
+
+	match = fwnode_connection_find_match(fwnode, "thunderbolt-switch", NULL,
+					     typec_thunderbolt_switch_match);
+	if (!match)
+		return NULL;
+	if (IS_ERR(match))
+		return ERR_CAST(match);
+	sw_dev = match;
+
+	sw = kzalloc_obj(*sw);
+	if (!sw) {
+		put_device(&sw_dev->dev);
+		return ERR_PTR(-ENOMEM);
+	}
+
+	sw->sw_dev = sw_dev;
+	WARN_ON(!try_module_get(sw_dev->dev.parent->driver->owner));
+
+	return sw;
+}
+EXPORT_SYMBOL_GPL(fwnode_typec_thunderbolt_switch_get);
+
+/**
+ * typec_thunderbolt_switch_put - Release handle to USB4/Thunderbolt switch
+ * @sw: USB4/Thunderbolt switch
+ *
+ * Decrements the reference count of @sw and releases the handle.
+ */
+void typec_thunderbolt_switch_put(struct typec_thunderbolt_switch *sw)
+{
+	if (IS_ERR_OR_NULL(sw))
+		return;
+
+	module_put(sw->sw_dev->dev.parent->driver->owner);
+	put_device(&sw->sw_dev->dev);
+	kfree(sw);
+}
+EXPORT_SYMBOL_GPL(typec_thunderbolt_switch_put);
+
+/**
+ * typec_thunderbolt_switch_set - Forward cable details to USB4/Thunderbolt switch
+ * @sw: USB4/Thunderbolt switch
+ * @data: Cable state and details
+ *
+ * Called by the Type-C port driver to forward the cable details
+ * out-of-band to the switch handler whenever a USB4/Thunderbolt
+ * connection comes up or goes away.
+ */
+int typec_thunderbolt_switch_set(struct typec_thunderbolt_switch *sw,
+				 const struct typec_thunderbolt_switch_data *data)
+{
+	if (IS_ERR_OR_NULL(sw))
+		return 0;
+
+	return sw->sw_dev->set(sw->sw_dev, data);
+}
+EXPORT_SYMBOL_GPL(typec_thunderbolt_switch_set);
+
+static void typec_thunderbolt_switch_release(struct device *dev)
+{
+	kfree(to_typec_thunderbolt_switch_dev(dev));
+}
+
+const struct device_type typec_thunderbolt_switch_dev_type = {
+	.name = "thunderbolt_switch",
+	.release = typec_thunderbolt_switch_release,
+};
+
+/**
+ * typec_thunderbolt_switch_register - Register USB4/Thunderbolt switch
+ * @parent: Parent device
+ * @desc: USB4/Thunderbolt switch description
+ *
+ * This function registers a handler for USB4/Thunderbolt mode switching
+ * which Type-C port drivers use to forward the cable details once a
+ * USB4/Thunderbolt connection comes up.
+ */
+struct typec_thunderbolt_switch_dev *
+typec_thunderbolt_switch_register(struct device *parent,
+				  const struct typec_thunderbolt_switch_desc *desc)
+{
+	struct typec_thunderbolt_switch_dev *sw_dev;
+	int ret;
+
+	if (!desc || !desc->set)
+		return ERR_PTR(-EINVAL);
+
+	sw_dev = kzalloc_obj(*sw_dev);
+	if (!sw_dev)
+		return ERR_PTR(-ENOMEM);
+
+	sw_dev->set = desc->set;
+
+	device_initialize(&sw_dev->dev);
+	sw_dev->dev.parent = parent;
+	sw_dev->dev.fwnode = desc->fwnode;
+	sw_dev->dev.class = &typec_mux_class;
+	sw_dev->dev.type = &typec_thunderbolt_switch_dev_type;
+	sw_dev->dev.driver_data = desc->drvdata;
+	ret = dev_set_name(&sw_dev->dev, "%s-thunderbolt-switch",
+			   desc->name ? desc->name : dev_name(parent));
+	if (ret) {
+		put_device(&sw_dev->dev);
+		return ERR_PTR(ret);
+	}
+
+	ret = device_add(&sw_dev->dev);
+	if (ret) {
+		dev_err(parent, "failed to register switch (%d)\n", ret);
+		put_device(&sw_dev->dev);
+		return ERR_PTR(ret);
+	}
+
+	return sw_dev;
+}
+EXPORT_SYMBOL_GPL(typec_thunderbolt_switch_register);
+
+/**
+ * typec_thunderbolt_switch_unregister - Unregister USB4/Thunderbolt switch
+ * @sw_dev: USB4/Thunderbolt switch
+ *
+ * Unregister switch that was registered with
+ * typec_thunderbolt_switch_register().
+ */
+void typec_thunderbolt_switch_unregister(struct typec_thunderbolt_switch_dev *sw_dev)
+{
+	if (!IS_ERR_OR_NULL(sw_dev))
+		device_unregister(&sw_dev->dev);
+}
+EXPORT_SYMBOL_GPL(typec_thunderbolt_switch_unregister);
+
+/**
+ * typec_thunderbolt_switch_set_drvdata - Assign private data to the switch
+ * @sw_dev: USB4/Thunderbolt switch
+ * @data: Private data
+ */
+void typec_thunderbolt_switch_set_drvdata(struct typec_thunderbolt_switch_dev *sw_dev,
+					  void *data)
+{
+	dev_set_drvdata(&sw_dev->dev, data);
+}
+EXPORT_SYMBOL_GPL(typec_thunderbolt_switch_set_drvdata);
+
+/**
+ * typec_thunderbolt_switch_get_drvdata - Get the private data from the switch
+ * @sw_dev: USB4/Thunderbolt switch
+ *
+ * Returns the private data previously assigned with
+ * typec_thunderbolt_switch_set_drvdata().
+ */
+void *typec_thunderbolt_switch_get_drvdata(struct typec_thunderbolt_switch_dev *sw_dev)
+{
+	return dev_get_drvdata(&sw_dev->dev);
+}
+EXPORT_SYMBOL_GPL(typec_thunderbolt_switch_get_drvdata);
+
 const struct class typec_mux_class = {
 	.name = "typec_mux",
 };
diff --git a/drivers/usb/typec/mux.h b/drivers/usb/typec/mux.h
index 58f0f28b6dc8..a2c92cab1211 100644
--- a/drivers/usb/typec/mux.h
+++ b/drivers/usb/typec/mux.h
@@ -23,5 +23,17 @@ extern const struct device_type typec_mux_dev_type;
 
 #define is_typec_switch_dev(dev) ((dev)->type == &typec_switch_dev_type)
 #define is_typec_mux_dev(dev) ((dev)->type == &typec_mux_dev_type)
+#define is_typec_thunderbolt_switch_dev(dev) \
+	((dev)->type == &typec_thunderbolt_switch_dev_type)
+
+struct typec_thunderbolt_switch_dev {
+	struct device dev;
+	typec_thunderbolt_switch_set_fn_t set;
+};
+
+#define to_typec_thunderbolt_switch_dev(_dev_) \
+	container_of(_dev_, struct typec_thunderbolt_switch_dev, dev)
+
+extern const struct device_type typec_thunderbolt_switch_dev_type;
 
 #endif /* __USB_TYPEC_MUX__ */
diff --git a/include/linux/usb/typec_mux.h b/include/linux/usb/typec_mux.h
index aa9ebb7e2fe0..0726407bb624 100644
--- a/include/linux/usb/typec_mux.h
+++ b/include/linux/usb/typec_mux.h
@@ -6,6 +6,7 @@
 #include <linux/err.h>
 #include <linux/property.h>
 #include <linux/usb/typec.h>
+#include <linux/usb/typec_tbt.h>
 
 struct device;
 struct typec_mux;
@@ -141,4 +142,117 @@ static inline struct typec_mux *typec_mux_get(struct device *dev)
 	return fwnode_typec_mux_get(dev_fwnode(dev));
 }
 
+struct typec_thunderbolt_switch;
+struct typec_thunderbolt_switch_dev;
+
+enum typec_thunderbolt_switch_state {
+	TYPEC_THUNDERBOLT_SWITCH_OFF,
+	TYPEC_THUNDERBOLT_SWITCH_TBT,
+	TYPEC_THUNDERBOLT_SWITCH_USB4,
+};
+
+/**
+ * struct typec_thunderbolt_switch_data - Type-C Thunderbolt/USB4 Switch Data
+ * @state: Switch state (TBT, USB4, or OFF)
+ * @orientation: Orientation of the connection
+ * @tbt: Thunderbolt 3 specific data, only valid in
+ *	 TYPEC_THUNDERBOLT_SWITCH_TBT
+ * @usb4: Enter_USB data, only valid in TYPEC_THUNDERBOLT_SWITCH_USB4
+ */
+struct typec_thunderbolt_switch_data {
+	enum typec_thunderbolt_switch_state state;
+	enum typec_orientation orientation;
+	union {
+		struct typec_thunderbolt_data tbt;
+		struct enter_usb_data usb4;
+	};
+};
+
+typedef int (*typec_thunderbolt_switch_set_fn_t)(struct typec_thunderbolt_switch_dev *sw,
+						 const struct typec_thunderbolt_switch_data *data);
+
+/**
+ * struct typec_thunderbolt_switch_desc - USB4/Thunderbolt switch description
+ * @fwnode: The fwnode the switch is associated with
+ * @set: Callback invoked with the cable details on connection changes
+ * @name: Optional switch name, the parent device name is used if not set
+ * @drvdata: Private data
+ */
+struct typec_thunderbolt_switch_desc {
+	struct fwnode_handle *fwnode;
+	typec_thunderbolt_switch_set_fn_t set;
+	const char *name;
+	void *drvdata;
+};
+
+#if IS_ENABLED(CONFIG_TYPEC)
+
+struct typec_thunderbolt_switch *
+fwnode_typec_thunderbolt_switch_get(struct fwnode_handle *fwnode);
+void typec_thunderbolt_switch_put(struct typec_thunderbolt_switch *sw);
+int typec_thunderbolt_switch_set(struct typec_thunderbolt_switch *sw,
+				 const struct typec_thunderbolt_switch_data *data);
+
+struct typec_thunderbolt_switch_dev *
+typec_thunderbolt_switch_register(struct device *parent,
+				  const struct typec_thunderbolt_switch_desc *desc);
+void typec_thunderbolt_switch_unregister(struct typec_thunderbolt_switch_dev *sw);
+
+void typec_thunderbolt_switch_set_drvdata(struct typec_thunderbolt_switch_dev *sw,
+					  void *data);
+void *
+typec_thunderbolt_switch_get_drvdata(struct typec_thunderbolt_switch_dev *sw);
+
+#else
+
+static inline struct typec_thunderbolt_switch *
+fwnode_typec_thunderbolt_switch_get(struct fwnode_handle *fwnode)
+{
+	return NULL;
+}
+
+static inline void
+typec_thunderbolt_switch_put(struct typec_thunderbolt_switch *sw)
+{
+}
+
+static inline int
+typec_thunderbolt_switch_set(struct typec_thunderbolt_switch *sw,
+			     const struct typec_thunderbolt_switch_data *data)
+{
+	return 0;
+}
+
+static inline struct typec_thunderbolt_switch_dev *
+typec_thunderbolt_switch_register(struct device *parent,
+				  const struct typec_thunderbolt_switch_desc *desc)
+{
+	return ERR_PTR(-EOPNOTSUPP);
+}
+
+static inline void
+typec_thunderbolt_switch_unregister(struct typec_thunderbolt_switch_dev *sw)
+{
+}
+
+static inline void
+typec_thunderbolt_switch_set_drvdata(struct typec_thunderbolt_switch_dev *sw,
+				     void *data)
+{
+}
+
+static inline void *
+typec_thunderbolt_switch_get_drvdata(struct typec_thunderbolt_switch_dev *sw)
+{
+	return NULL;
+}
+
+#endif /* CONFIG_TYPEC */
+
+static inline struct typec_thunderbolt_switch *
+typec_thunderbolt_switch_get(struct device *dev)
+{
+	return fwnode_typec_thunderbolt_switch_get(dev_fwnode(dev));
+}
+
 #endif /* __USB_TYPEC_MUX */

-- 
2.55.0



  parent reply	other threads:[~2026-08-30 20:19 UTC|newest]

Thread overview: 40+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-08-30 20:19 [PATCH 00/19] Initial USB4/Thunderbolt support for Apple M1/M2/M3 SoCs Sven Peter
2026-08-30 20:19 ` [PATCH 01/19] dt-bindings: usb: Add thunderbolt-switch property Sven Peter
2026-08-30 20:19 ` Sven Peter [this message]
2026-08-30 20:30   ` [PATCH 02/19] usb: typec: Add thunderbolt switch sashiko-bot
2026-09-01 11:17   ` Heikki Krogerus
2026-09-01 18:53     ` Sven Peter
2026-08-30 20:19 ` [PATCH 03/19] usb: typec: tipd: Hook up Thunderbolt switch for CD321x Sven Peter
2026-08-30 20:36   ` sashiko-bot
2026-08-30 20:19 ` [PATCH 04/19] dt-bindings: thunderbolt: Add Apple USB4/Thunderbolt NHI Sven Peter
2026-08-30 20:19 ` [PATCH 05/19] dt-bindings: thunderbolt: Add Apple USB4/Thunderbolt ACIO block Sven Peter
2026-08-30 20:31   ` sashiko-bot
2026-08-30 20:19 ` [PATCH 06/19] thunderbolt: Try reading host DROM from device tree first Sven Peter
2026-08-30 20:33   ` sashiko-bot
2026-09-01  8:48   ` Mika Westerberg
2026-08-30 20:19 ` [PATCH 07/19] thunderbolt: Don't read the UID if we already know it Sven Peter
2026-08-30 20:19 ` [PATCH 08/19] thunderbolt: Allocate ring HopID before requesting the ring interrupt Sven Peter
2026-08-30 20:32   ` sashiko-bot
2026-08-30 20:19 ` [PATCH 09/19] thunderbolt: Add ring_interrupt_active to tb_nhi_ops Sven Peter
2026-08-30 20:19 ` [PATCH 10/19] thunderbolt: Make the ring register layout configurable Sven Peter
2026-09-01  8:58   ` Mika Westerberg
2026-09-01 18:56     ` Sven Peter
2026-08-30 20:19 ` [PATCH 11/19] thunderbolt: Add ring_interrupt_mask to tb_nhi_ops Sven Peter
2026-08-30 20:19 ` [PATCH 12/19] thunderbolt: Add ring_configure " Sven Peter
2026-08-30 20:28   ` sashiko-bot
2026-08-30 20:19 ` [PATCH 13/19] thunderbolt: Add QUIRK_NO_DMA_PORT Sven Peter
2026-09-01  9:04   ` Mika Westerberg
2026-09-01 17:06     ` Sven Peter
2026-08-30 20:19 ` [PATCH 14/19] thunderbolt: Add QUIRK_NO_USB3_BW_ALLOC Sven Peter
2026-09-01  9:12   ` Mika Westerberg
2026-08-30 20:19 ` [PATCH 15/19] thunderbolt: Export symbols required by the Apple Silicon driver Sven Peter
2026-08-30 20:19 ` [PATCH 16/19] thunderbolt: Add Apple Silicon support Sven Peter
2026-08-30 20:39   ` sashiko-bot
2026-09-01 10:09   ` Mika Westerberg
2026-09-01 19:06     ` Sven Peter
2026-08-30 20:19 ` [PATCH 17/19] arm64: dts: apple: t8103: Add USB4 ACIO and NHI Sven Peter
2026-09-01 10:20   ` Mika Westerberg
2026-08-30 20:19 ` [PATCH 18/19] arm64: dts: apple: t8112: " Sven Peter
2026-08-30 20:39   ` sashiko-bot
2026-08-30 20:19 ` [PATCH 19/19] arm64: dts: apple: t60xx: " Sven Peter
2026-08-31 17:44 ` [PATCH 00/19] Initial USB4/Thunderbolt support for Apple M1/M2/M3 SoCs Joshua Peisach

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260830-b4-apple-soc-tbt-v1-2-44bc9348683c@kernel.org \
    --to=sven@kernel.org \
    --cc=YehezkelShB@gmail.com \
    --cc=andreas.noever@gmail.com \
    --cc=asahi@lists.linux.dev \
    --cc=conor+dt@kernel.org \
    --cc=devicetree@vger.kernel.org \
    --cc=gregkh@linuxfoundation.org \
    --cc=heikki.krogerus@linux.intel.com \
    --cc=j@jannau.net \
    --cc=konradybcio@kernel.org \
    --cc=krzk+dt@kernel.org \
    --cc=linux-arm-kernel@lists.infradead.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-usb@vger.kernel.org \
    --cc=neal@gompa.dev \
    --cc=p.zabel@pengutronix.de \
    --cc=robh@kernel.org \
    --cc=westeri@kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox