mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Andrei Kuchynski <akuchynski@chromium.org>
To: Heikki Krogerus <heikki.krogerus@linux.intel.com>,
	Jameson Thies <jthies@google.com>,
	Benson Leung <bleung@chromium.org>,
	linux-usb@vger.kernel.org, linux-kernel@vger.kernel.org
Cc: Abhishek Pandit-Subedi <abhishekpandit@chromium.org>,
	Greg Kroah-Hartman <gregkh@linuxfoundation.org>,
	Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>,
	Pooja Katiyar <pooja.katiyar@intel.com>,
	Johan Hovold <johan@kernel.org>,
	Hsin-Te Yuan <yuanhsinte@chromium.org>,
	Madhu M <madhu.m@intel.com>,
	Venkat Jayaraman <venkat.jayaraman@intel.com>,
	Pengyu Luo <mitltlatltl@gmail.com>,
	Fedor Pchelkin <boddah8794@gmail.com>,
	Andrei Kuchynski <akuchynski@chromium.org>
Subject: [PATCH v2] usb: typec: ucsi: Add Thunderbolt alternate mode support
Date: Fri,  6 Feb 2026 11:57:54 +0000	[thread overview]
Message-ID: <20260206115754.828954-1-akuchynski@chromium.org> (raw)

Introduce support for Thunderbolt (TBT) alternate mode to the UCSI driver.
This allows the driver to manage the entry and exit of TBT altmode by
providing the necessary typec_altmode_ops.

ucsi_altmode_update_active() is invoked when the Connector Partner Changed
bit is set in the GET_CONNECTOR_STATUS data. This ensures that the
alternate mode's active state is synchronized with the current mode the
connector is operating in.

Signed-off-by: Andrei Kuchynski <akuchynski@chromium.org>
---
Changes in V2:
- Update copyright year to 2026.
- Explicitly include all required headers.
- No functional changes compared to V1.

 drivers/usb/typec/ucsi/Makefile      |   4 +
 drivers/usb/typec/ucsi/thunderbolt.c | 212 +++++++++++++++++++++++++++
 drivers/usb/typec/ucsi/ucsi.c        |  18 ++-
 drivers/usb/typec/ucsi/ucsi.h        |  20 +++
 4 files changed, 249 insertions(+), 5 deletions(-)
 create mode 100644 drivers/usb/typec/ucsi/thunderbolt.c

diff --git a/drivers/usb/typec/ucsi/Makefile b/drivers/usb/typec/ucsi/Makefile
index dbc571763eff6..c7e38bf01350d 100644
--- a/drivers/usb/typec/ucsi/Makefile
+++ b/drivers/usb/typec/ucsi/Makefile
@@ -17,6 +17,10 @@ ifneq ($(CONFIG_TYPEC_DP_ALTMODE),)
 	typec_ucsi-y			+= displayport.o
 endif
 
+ifneq ($(CONFIG_TYPEC_TBT_ALTMODE),)
+	typec_ucsi-y			+= thunderbolt.o
+endif
+
 obj-$(CONFIG_UCSI_ACPI)			+= ucsi_acpi.o
 obj-$(CONFIG_UCSI_CCG)			+= ucsi_ccg.o
 obj-$(CONFIG_UCSI_STM32G0)		+= ucsi_stm32g0.o
diff --git a/drivers/usb/typec/ucsi/thunderbolt.c b/drivers/usb/typec/ucsi/thunderbolt.c
new file mode 100644
index 0000000000000..434d3d8ea5b6e
--- /dev/null
+++ b/drivers/usb/typec/ucsi/thunderbolt.c
@@ -0,0 +1,212 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * UCSI Thunderbolt Alternate Mode Support
+ *
+ * Copyright 2026 Google LLC
+ */
+
+#include <linux/usb/typec_tbt.h>
+#include <linux/usb/pd_vdo.h>
+#include <linux/err.h>
+#include <linux/dev_printk.h>
+#include <linux/device/devres.h>
+#include <linux/gfp_types.h>
+#include <linux/types.h>
+#include <linux/usb/typec_altmode.h>
+#include <linux/workqueue.h>
+
+#include "ucsi.h"
+
+/**
+ * struct ucsi_tbt - Thunderbolt Alternate Mode private data structure
+ * @con: Pointer to UCSI connector structure
+ * @alt: Pointer to typec altmode structure
+ * @work: Work structure
+ * @cam: An offset into the list of alternate modes supported by the PPM
+ * @header: VDO header
+ */
+struct ucsi_tbt {
+	struct ucsi_connector *con;
+	struct typec_altmode *alt;
+	struct work_struct work;
+	int cam;
+	u32 header;
+};
+
+static void ucsi_thunderbolt_work(struct work_struct *work)
+{
+	struct ucsi_tbt *tbt = container_of(work, struct ucsi_tbt, work);
+
+	if (typec_altmode_vdm(tbt->alt, tbt->header, NULL, 0))
+		dev_err(&tbt->alt->dev, "VDM 0x%x failed\n", tbt->header);
+
+	tbt->header = 0;
+}
+
+static int ucsi_thunderbolt_set_altmode(struct ucsi_tbt *tbt,
+					bool enter, u32 vdo)
+{
+	int svdm_version;
+	int cmd;
+	int ret;
+	u64 command = UCSI_SET_NEW_CAM |
+		      UCSI_CONNECTOR_NUMBER(tbt->con->num) |
+		      UCSI_SET_NEW_CAM_SET_AM(tbt->cam) |
+		      ((u64)vdo << 32);
+
+	if (enter)
+		command |= (1 << 23);
+
+	ret = ucsi_send_command(tbt->con->ucsi, command, NULL, 0);
+	if (ret < 0)
+		return ret;
+
+	svdm_version = typec_altmode_get_svdm_version(tbt->alt);
+	if (svdm_version < 0)
+		return svdm_version;
+
+	if (enter)
+		cmd = CMD_ENTER_MODE;
+	else
+		cmd = CMD_EXIT_MODE;
+	tbt->header = VDO(USB_TYPEC_TBT_SID, 1, svdm_version, cmd);
+	tbt->header |= VDO_OPOS(TYPEC_TBT_MODE);
+	tbt->header |= VDO_CMDT(CMDT_RSP_ACK);
+
+	schedule_work(&tbt->work);
+
+	return 0;
+}
+
+static int ucsi_thunderbolt_enter(struct typec_altmode *alt, u32 *vdo)
+{
+	struct ucsi_tbt *tbt = typec_altmode_get_drvdata(alt);
+	struct ucsi_connector *con = tbt->con;
+	u64 command;
+	u8 cur = 0;
+	int ret;
+
+	if (!ucsi_con_mutex_lock(con))
+		return -ENOTCONN;
+
+	command = UCSI_GET_CURRENT_CAM | UCSI_CONNECTOR_NUMBER(con->num);
+	ret = ucsi_send_command(con->ucsi, command, &cur, sizeof(cur));
+	if (ret < 0) {
+		if (con->ucsi->version > 0x0100)
+			goto err_unlock;
+		cur = 0xff;
+	}
+
+	if (cur != 0xff) {
+		if (cur >= UCSI_MAX_ALTMODES || con->port_altmode[cur] != alt)
+			ret = -EBUSY;
+		else
+			ret = 0;
+		goto err_unlock;
+	}
+
+	ret = ucsi_thunderbolt_set_altmode(tbt, true, *vdo);
+	ucsi_altmode_update_active(tbt->con);
+
+err_unlock:
+	ucsi_con_mutex_unlock(con);
+
+	return ret;
+}
+
+static int ucsi_thunderbolt_exit(struct typec_altmode *alt)
+{
+	struct ucsi_tbt *tbt = typec_altmode_get_drvdata(alt);
+	int ret;
+
+	if (!ucsi_con_mutex_lock(tbt->con))
+		return -ENOTCONN;
+
+	ret = ucsi_thunderbolt_set_altmode(tbt, false, 0);
+
+	ucsi_con_mutex_unlock(tbt->con);
+
+	return ret;
+}
+
+static int ucsi_thunderbolt_vdm(struct typec_altmode *alt,
+				u32 header, const u32 *data, int count)
+{
+	struct ucsi_tbt *tbt = typec_altmode_get_drvdata(alt);
+	int cmd_type = PD_VDO_CMDT(header);
+	int cmd = PD_VDO_CMD(header);
+	int svdm_version;
+
+	if (!ucsi_con_mutex_lock(tbt->con))
+		return -ENOTCONN;
+
+	svdm_version = typec_altmode_get_svdm_version(alt);
+	if (svdm_version < 0) {
+		ucsi_con_mutex_unlock(tbt->con);
+		return svdm_version;
+	}
+
+	switch (cmd_type) {
+	case CMDT_INIT:
+		if (PD_VDO_SVDM_VER(header) < svdm_version) {
+			svdm_version = PD_VDO_SVDM_VER(header);
+			typec_partner_set_svdm_version(tbt->con->partner, svdm_version);
+		}
+		tbt->header = VDO(USB_TYPEC_TBT_SID, 1, svdm_version, cmd);
+		tbt->header |= VDO_OPOS(TYPEC_TBT_MODE);
+		tbt->header |= VDO_CMDT(CMDT_RSP_ACK);
+
+		schedule_work(&tbt->work);
+		break;
+	default:
+		break;
+	}
+
+	ucsi_con_mutex_unlock(tbt->con);
+
+	return 0;
+}
+
+static const struct typec_altmode_ops ucsi_thunderbolt_ops = {
+	.enter = ucsi_thunderbolt_enter,
+	.exit = ucsi_thunderbolt_exit,
+	.vdm = ucsi_thunderbolt_vdm,
+};
+
+struct typec_altmode *ucsi_register_thunderbolt(struct ucsi_connector *con,
+						bool override, int offset,
+						struct typec_altmode_desc *desc)
+{
+	struct typec_altmode *alt;
+	struct ucsi_tbt *tbt;
+
+	alt = typec_port_register_altmode(con->port, desc);
+	if (IS_ERR(alt) || !override)
+		return alt;
+
+	tbt = devm_kzalloc(&alt->dev, sizeof(*tbt), GFP_KERNEL);
+	if (!tbt) {
+		typec_unregister_altmode(alt);
+		return ERR_PTR(-ENOMEM);
+	}
+
+	tbt->cam = offset;
+	tbt->con = con;
+	tbt->alt = alt;
+	INIT_WORK(&tbt->work, ucsi_thunderbolt_work);
+	typec_altmode_set_drvdata(alt, tbt);
+	typec_altmode_set_ops(alt, &ucsi_thunderbolt_ops);
+
+	return alt;
+}
+
+void ucsi_thunderbolt_remove_partner(struct typec_altmode *alt)
+{
+	struct ucsi_tbt *tbt;
+
+	if (alt) {
+		tbt = typec_altmode_get_drvdata(alt);
+		if (tbt)
+			cancel_work_sync(&tbt->work);
+	}
+}
diff --git a/drivers/usb/typec/ucsi/ucsi.c b/drivers/usb/typec/ucsi/ucsi.c
index 251990475faa7..91b6c71dd7396 100644
--- a/drivers/usb/typec/ucsi/ucsi.c
+++ b/drivers/usb/typec/ucsi/ucsi.c
@@ -13,6 +13,7 @@
 #include <linux/delay.h>
 #include <linux/slab.h>
 #include <linux/usb/typec_dp.h>
+#include <linux/usb/typec_tbt.h>
 
 #include "ucsi.h"
 #include "trace.h"
@@ -417,6 +418,9 @@ static int ucsi_register_altmode(struct ucsi_connector *con,
 				alt = ucsi_register_displayport(con, override,
 								i, desc);
 			break;
+		case USB_TYPEC_TBT_SID:
+			alt = ucsi_register_thunderbolt(con, override, i, desc);
+			break;
 		default:
 			alt = typec_port_register_altmode(con->port, desc);
 			break;
@@ -647,12 +651,15 @@ static void ucsi_unregister_altmodes(struct ucsi_connector *con, u8 recipient)
 	}
 
 	while (adev[i]) {
-		if (recipient == UCSI_RECIPIENT_SOP &&
-		    (adev[i]->svid == USB_TYPEC_DP_SID ||
-			(adev[i]->svid == USB_TYPEC_NVIDIA_VLINK_SID &&
-			adev[i]->vdo != USB_TYPEC_NVIDIA_VLINK_DBG_VDO))) {
+		if (recipient == UCSI_RECIPIENT_SOP) {
 			pdev = typec_altmode_get_partner(adev[i]);
-			ucsi_displayport_remove_partner((void *)pdev);
+
+			if (adev[i]->svid == USB_TYPEC_DP_SID ||
+			    (adev[i]->svid == USB_TYPEC_NVIDIA_VLINK_SID &&
+			     adev[i]->vdo != USB_TYPEC_NVIDIA_VLINK_DBG_VDO))
+				ucsi_displayport_remove_partner((void *)pdev);
+			else if (adev[i]->svid == USB_TYPEC_TBT_SID)
+				ucsi_thunderbolt_remove_partner((void *)pdev);
 		}
 		typec_unregister_altmode(adev[i]);
 		adev[i++] = NULL;
@@ -1318,6 +1325,7 @@ static void ucsi_handle_connector_change(struct work_struct *work)
 
 	if (con->partner && (change & UCSI_CONSTAT_PARTNER_CHANGE)) {
 		ucsi_partner_change(con);
+		ucsi_altmode_update_active(con);
 
 		/* Complete pending data role swap */
 		if (!completion_done(&con->complete))
diff --git a/drivers/usb/typec/ucsi/ucsi.h b/drivers/usb/typec/ucsi/ucsi.h
index 4797b4aa1e35b..43a0d01ade8ff 100644
--- a/drivers/usb/typec/ucsi/ucsi.h
+++ b/drivers/usb/typec/ucsi/ucsi.h
@@ -600,6 +600,26 @@ static inline void
 ucsi_displayport_remove_partner(struct typec_altmode *adev) { }
 #endif /* CONFIG_TYPEC_DP_ALTMODE */
 
+#if IS_ENABLED(CONFIG_TYPEC_TBT_ALTMODE)
+struct typec_altmode *
+ucsi_register_thunderbolt(struct ucsi_connector *con,
+			  bool override, int offset,
+			  struct typec_altmode_desc *desc);
+
+void ucsi_thunderbolt_remove_partner(struct typec_altmode *adev);
+#else
+static inline struct typec_altmode *
+ucsi_register_thunderbolt(struct ucsi_connector *con,
+			  bool override, int offset,
+			  struct typec_altmode_desc *desc)
+{
+	return typec_port_register_altmode(con->port, desc);
+}
+
+static inline void
+ucsi_thunderbolt_remove_partner(struct typec_altmode *adev) { }
+#endif /* CONFIG_TYPEC_TBT_ALTMODE */
+
 #ifdef CONFIG_DEBUG_FS
 void ucsi_debugfs_init(void);
 void ucsi_debugfs_exit(void);
-- 
2.53.0.rc2.204.g2597b5adb4-goog


             reply	other threads:[~2026-02-06 11:58 UTC|newest]

Thread overview: 2+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-02-06 11:57 Andrei Kuchynski [this message]
2026-02-06 14:01 ` Heikki Krogerus

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=20260206115754.828954-1-akuchynski@chromium.org \
    --to=akuchynski@chromium.org \
    --cc=abhishekpandit@chromium.org \
    --cc=bleung@chromium.org \
    --cc=boddah8794@gmail.com \
    --cc=dmitry.baryshkov@oss.qualcomm.com \
    --cc=gregkh@linuxfoundation.org \
    --cc=heikki.krogerus@linux.intel.com \
    --cc=johan@kernel.org \
    --cc=jthies@google.com \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-usb@vger.kernel.org \
    --cc=madhu.m@intel.com \
    --cc=mitltlatltl@gmail.com \
    --cc=pooja.katiyar@intel.com \
    --cc=venkat.jayaraman@intel.com \
    --cc=yuanhsinte@chromium.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

all inboxes | Powered by JetHome®