[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <20251201122604.1268071-8-akuchynski@chromium.org>
Date: Mon, 1 Dec 2025 12:26:03 +0000
From: Andrei Kuchynski <akuchynski@...omium.org>
To: Heikki Krogerus <heikki.krogerus@...ux.intel.com>,
Abhishek Pandit-Subedi <abhishekpandit@...omium.org>,
Benson Leung <bleung@...omium.org>,
Jameson Thies <jthies@...gle.com>,
Tzung-Bi Shih <tzungbi@...nel.org>,
linux-usb@...r.kernel.org,
chrome-platform@...ts.linux.dev
Cc: Guenter Roeck <groeck@...omium.org>,
Greg Kroah-Hartman <gregkh@...uxfoundation.org>,
Dmitry Baryshkov <dmitry.baryshkov@....qualcomm.com>,
"Christian A. Ehrhardt" <lk@...e.de>,
Abel Vesa <abel.vesa@...aro.org>,
Pooja Katiyar <pooja.katiyar@...el.com>,
Pavan Holla <pholla@...omium.org>,
Madhu M <madhu.m@...el.com>,
Venkat Jayaraman <venkat.jayaraman@...el.com>,
linux-kernel@...r.kernel.org,
Andrei Kuchynski <akuchynski@...omium.org>
Subject: [PATCH RFC 7/8] usb: typec: ucsi: Support for Thunderbolt alt mode
This makes it possible to bind a driver to a Thunderbolt
alt mode adapter devices.
Signed-off-by: Andrei Kuchynski <akuchynski@...omium.org>
---
drivers/usb/typec/ucsi/Makefile | 4 +
drivers/usb/typec/ucsi/thunderbolt.c | 199 +++++++++++++++++++++++++++
drivers/usb/typec/ucsi/ucsi.c | 17 ++-
drivers/usb/typec/ucsi/ucsi.h | 20 +++
4 files changed, 235 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 dbc571763eff..c7e38bf01350 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 000000000000..b48aba30fb9f
--- /dev/null
+++ b/drivers/usb/typec/ucsi/thunderbolt.c
@@ -0,0 +1,199 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * UCSI Thunderbolt Alternate Mode Support
+ *
+ * Copyright 2025 Google LLC
+ */
+
+#include <linux/usb/typec_tbt.h>
+#include <linux/usb/pd_vdo.h>
+
+#include "ucsi.h"
+
+struct ucsi_tbt {
+ struct ucsi_connector *con;
+ struct typec_altmode *alt;
+ struct work_struct work;
+
+ int cam;
+ u32 header;
+};
+
+static int ucsi_thunderbolt_send_set_new_cam(struct ucsi_tbt *tbt,
+ const int enter)
+{
+ int ret;
+ const u64 command = UCSI_SET_NEW_CAM |
+ UCSI_CONNECTOR_NUMBER(tbt->con->num) |
+ UCSI_SET_NEW_CAM_SET_ENTER(enter) |
+ UCSI_SET_NEW_CAM_SET_AM(tbt->cam);
+
+ tbt->con->ucsi->message_in_size = 0;
+ ret = ucsi_send_command(tbt->con->ucsi, command);
+
+ return ret < 0 ? ret : 0;
+}
+
+static void ucsi_thunderbolt_work(struct work_struct *work)
+{
+ struct ucsi_tbt *tbt = container_of(work, struct ucsi_tbt, work);
+ const int ret = typec_altmode_vdm(tbt->alt, tbt->header, NULL, 0);
+
+ if (ret)
+ dev_err(&tbt->alt->dev, "VDM 0x%x failed\n", tbt->header);
+}
+
+static int ucsi_thunderbolt_send_vdm(struct ucsi_tbt *tbt,
+ const int cmd, const int cmdt,
+ const int svdm_version)
+{
+ if (svdm_version < 0)
+ return 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);
+
+ 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 *ucsi = tbt->con->ucsi;
+ int svdm_version;
+ u64 command;
+ u8 cur = 0;
+ int ret = 0;
+
+ if (!ucsi_con_mutex_lock(tbt->con))
+ return -ENOTCONN;
+
+ command = UCSI_GET_CURRENT_CAM | UCSI_CONNECTOR_NUMBER(tbt->con->num);
+ ucsi->message_in_size = 0;
+ ret = ucsi_send_command(ucsi, command);
+ if (ret < 0) {
+ if (tbt->con->ucsi->version > 0x0100)
+ goto err_unlock;
+ cur = 0xff;
+ } else {
+ memcpy(&cur, ucsi->message_in, ucsi->message_in_size);
+ }
+
+ if (cur != 0xff) {
+ ret = tbt->con->port_altmode[cur] == alt ? 0 : -EBUSY;
+ } else {
+ ret = ucsi_thunderbolt_send_set_new_cam(tbt, 1);
+ if (!ret) {
+ svdm_version = typec_altmode_get_svdm_version(alt);
+ ret = ucsi_thunderbolt_send_vdm(tbt, CMD_ENTER_MODE, CMDT_RSP_ACK,
+ svdm_version);
+ }
+ }
+
+err_unlock:
+ ucsi_con_mutex_unlock(tbt->con);
+
+ return ret;
+}
+
+static int ucsi_thunderbolt_exit(struct typec_altmode *alt)
+{
+ struct ucsi_tbt *tbt = typec_altmode_get_drvdata(alt);
+ int svdm_version;
+ int ret;
+
+ if (!ucsi_con_mutex_lock(tbt->con))
+ return -ENOTCONN;
+
+ ret = ucsi_thunderbolt_send_set_new_cam(tbt, 0);
+ if (!ret) {
+ svdm_version = typec_altmode_get_svdm_version(alt);
+ ret = ucsi_thunderbolt_send_vdm(tbt, CMD_EXIT_MODE, CMDT_RSP_ACK,
+ svdm_version);
+ }
+
+ 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);
+ const int cmd_type = PD_VDO_CMDT(header);
+ const 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);
+ }
+ ucsi_thunderbolt_send_vdm(tbt, cmd, CMDT_RSP_ACK, svdm_version);
+ 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;
+ typec_altmode_set_drvdata(alt, tbt);
+ typec_altmode_set_ops(alt, &ucsi_thunderbolt_ops);
+ INIT_WORK(&tbt->work, ucsi_thunderbolt_work);
+
+ 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 3d4c277bcd49..d2b00b3a8fd1 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"
@@ -442,6 +443,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;
@@ -678,12 +682,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;
diff --git a/drivers/usb/typec/ucsi/ucsi.h b/drivers/usb/typec/ucsi/ucsi.h
index a5ef35d9dce5..b405cce554af 100644
--- a/drivers/usb/typec/ucsi/ucsi.h
+++ b/drivers/usb/typec/ucsi/ucsi.h
@@ -242,6 +242,7 @@ void ucsi_connector_change(struct ucsi *ucsi, u8 num);
#define UCSI_ERROR_SET_SINK_PATH_REJECTED BIT(14)
#define UCSI_SET_NEW_CAM_ENTER(x) (((x) >> 23) & 0x1)
+#define UCSI_SET_NEW_CAM_SET_ENTER(x) (((x) & 1) << 23)
#define UCSI_SET_NEW_CAM_GET_AM(x) (((x) >> 24) & 0xff)
#define UCSI_SET_NEW_CAM_AM_MASK (0xff << 24)
#define UCSI_SET_NEW_CAM_SET_AM(x) (((x) & 0xff) << 24)
@@ -613,6 +614,25 @@ 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.52.0.158.g65b55ccf14-goog
Powered by blists - more mailing lists