From: Heikki Krogerus <heikki.krogerus@linux.intel.com>
Thunderbolt 3 Alternate Mode entry flow is described in
USB Type-C Specification Release 2.0.
Signed-off-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>
Co-developed-by: Abhishek Pandit-Subedi <abhishekpandit@chromium.org>
Signed-off-by: Abhishek Pandit-Subedi <abhishekpandit@chromium.org>
---
Changes:
* Delay cable + plug checks so that the module doesn't fail to probe
if cable + plug information isn't available by the time the partner
altmode is registered.
* Remove unncessary brace after if (IS_ERR(plug))
The rest of this patch should be the same as Heikki's original RFC.
Changes in v3:
- Revert rename of TYPEC_TBT_MODE
- Remove mode from typec_device_id
Changes in v2:
- Use <linux/usb/typec_tbt.h> and add missing TBT_CABLE_ROUNDED
- Pass struct typec_thunderbolt_data to typec_altmode_notify
- Rename TYPEC_TBT_MODE to USB_TYPEC_TBT_MODE
- Use USB_TYPEC_TBT_SID and USB_TYPEC_TBT_MODE for device id
- Change module license to GPL due to checkpatch warning
drivers/usb/typec/altmodes/Kconfig | 9 +
drivers/usb/typec/altmodes/Makefile | 2 +
drivers/usb/typec/altmodes/thunderbolt.c | 308 +++++++++++++++++++++++
include/linux/usb/typec_tbt.h | 1 +
4 files changed, 320 insertions(+)
create mode 100644 drivers/usb/typec/altmodes/thunderbolt.c
diff --git a/drivers/usb/typec/altmodes/Kconfig b/drivers/usb/typec/altmodes/Kconfig
index 1a6b5e872b0d..7867fa7c405d 100644
--- a/drivers/usb/typec/altmodes/Kconfig
+++ b/drivers/usb/typec/altmodes/Kconfig
@@ -23,4 +23,13 @@ config TYPEC_NVIDIA_ALTMODE
To compile this driver as a module, choose M here: the
module will be called typec_nvidia.
+config TYPEC_TBT_ALTMODE
+ tristate "Thunderbolt3 Alternate Mode driver"
+ help
+ Select this option if you have Thunderbolt3 hardware on your
+ system.
+
+ To compile this driver as a module, choose M here: the
+ module will be called typec_thunderbolt.
+
endmenu
diff --git a/drivers/usb/typec/altmodes/Makefile b/drivers/usb/typec/altmodes/Makefile
index 45717548b396..508a68351bd2 100644
--- a/drivers/usb/typec/altmodes/Makefile
+++ b/drivers/usb/typec/altmodes/Makefile
@@ -4,3 +4,5 @@ obj-$(CONFIG_TYPEC_DP_ALTMODE) += typec_displayport.o
typec_displayport-y := displayport.o
obj-$(CONFIG_TYPEC_NVIDIA_ALTMODE) += typec_nvidia.o
typec_nvidia-y := nvidia.o
+obj-$(CONFIG_TYPEC_TBT_ALTMODE) += typec_thunderbolt.o
+typec_thunderbolt-y := thunderbolt.o
diff --git a/drivers/usb/typec/altmodes/thunderbolt.c b/drivers/usb/typec/altmodes/thunderbolt.c
new file mode 100644
index 000000000000..a945b9d35a1d
--- /dev/null
+++ b/drivers/usb/typec/altmodes/thunderbolt.c
@@ -0,0 +1,308 @@
+// SPDX-License-Identifier: GPL-2.0
+/**
+ * USB Typec-C Thuderbolt3 Alternate Mode driver
+ *
+ * Copyright (C) 2019 Intel Corporation
+ * Author: Heikki Krogerus <heikki.krogerus@linux.intel.com>
+ */
+
+#include <linux/delay.h>
+#include <linux/mutex.h>
+#include <linux/module.h>
+#include <linux/usb/pd_vdo.h>
+#include <linux/usb/typec_altmode.h>
+#include <linux/usb/typec_tbt.h>
+
+enum tbt_state {
+ TBT_STATE_IDLE,
+ TBT_STATE_SOP_P_ENTER,
+ TBT_STATE_SOP_PP_ENTER,
+ TBT_STATE_ENTER,
+ TBT_STATE_EXIT,
+ TBT_STATE_SOP_PP_EXIT,
+ TBT_STATE_SOP_P_EXIT
+};
+
+struct tbt_altmode {
+ enum tbt_state state;
+ struct typec_cable *cable;
+ struct typec_altmode *alt;
+ struct typec_altmode *plug[2];
+ u32 enter_vdo;
+
+ struct work_struct work;
+ struct mutex lock; /* device lock */
+};
+
+static bool tbt_ready(struct typec_altmode *alt);
+
+static int tbt_enter_mode(struct tbt_altmode *tbt)
+{
+ struct typec_altmode *plug = tbt->plug[TYPEC_PLUG_SOP_P];
+ u32 vdo;
+
+ vdo = tbt->alt->vdo & (TBT_VENDOR_SPECIFIC_B0 | TBT_VENDOR_SPECIFIC_B1);
+ vdo |= tbt->alt->vdo & TBT_INTEL_SPECIFIC_B0;
+ vdo |= TBT_MODE;
+
+ if (plug) {
+ if (typec_cable_is_active(tbt->cable))
+ vdo |= TBT_ENTER_MODE_ACTIVE_CABLE;
+
+ vdo |= TBT_ENTER_MODE_CABLE_SPEED(TBT_CABLE_SPEED(plug->vdo));
+ vdo |= plug->vdo & TBT_CABLE_ROUNDED;
+ vdo |= plug->vdo & TBT_CABLE_OPTICAL;
+ vdo |= plug->vdo & TBT_CABLE_RETIMER;
+ vdo |= plug->vdo & TBT_CABLE_LINK_TRAINING;
+ } else {
+ vdo |= TBT_ENTER_MODE_CABLE_SPEED(TBT_CABLE_USB3_PASSIVE);
+ }
+
+ tbt->enter_vdo = vdo;
+ return typec_altmode_enter(tbt->alt, &vdo);
+}
+
+static void tbt_altmode_work(struct work_struct *work)
+{
+ struct tbt_altmode *tbt = container_of(work, struct tbt_altmode, work);
+ int ret;
+
+ mutex_lock(&tbt->lock);
+
+ switch (tbt->state) {
+ case TBT_STATE_SOP_P_ENTER:
+ ret = typec_altmode_enter(tbt->plug[TYPEC_PLUG_SOP_P], NULL);
+ if (ret)
+ dev_dbg(&tbt->plug[TYPEC_PLUG_SOP_P]->dev,
+ "failed to enter mode (%d)\n", ret);
+ break;
+ case TBT_STATE_SOP_PP_ENTER:
+ ret = typec_altmode_enter(tbt->plug[TYPEC_PLUG_SOP_PP], NULL);
+ if (ret)
+ dev_dbg(&tbt->plug[TYPEC_PLUG_SOP_PP]->dev,
+ "failed to enter mode (%d)\n", ret);
+ break;
+ case TBT_STATE_ENTER:
+ ret = tbt_enter_mode(tbt);
+ if (ret)
+ dev_dbg(&tbt->alt->dev, "failed to enter mode (%d)\n",
+ ret);
+ break;
+ case TBT_STATE_EXIT:
+ typec_altmode_exit(tbt->alt);
+ break;
+ case TBT_STATE_SOP_PP_EXIT:
+ typec_altmode_exit(tbt->plug[TYPEC_PLUG_SOP_PP]);
+ break;
+ case TBT_STATE_SOP_P_EXIT:
+ typec_altmode_exit(tbt->plug[TYPEC_PLUG_SOP_P]);
+ break;
+ default:
+ break;
+ }
+
+ tbt->state = TBT_STATE_IDLE;
+
+ mutex_unlock(&tbt->lock);
+}
+
+static int tbt_altmode_vdm(struct typec_altmode *alt,
+ const u32 hdr, const u32 *vdo, int count)
+{
+ struct tbt_altmode *tbt = typec_altmode_get_drvdata(alt);
+ int cmd_type = PD_VDO_CMDT(hdr);
+ int cmd = PD_VDO_CMD(hdr);
+
+ mutex_lock(&tbt->lock);
+
+ if (tbt->state != TBT_STATE_IDLE) {
+ mutex_unlock(&tbt->lock);
+ return -EBUSY;
+ }
+
+ switch (cmd_type) {
+ case CMDT_RSP_ACK:
+ switch (cmd) {
+ case CMD_ENTER_MODE:
+ /*
+ * Following the order describeded in USB Type-C Spec
+ * R2.0 Section 6.7.3.
+ */
+ if (alt == tbt->plug[TYPEC_PLUG_SOP_P]) {
+ if (tbt->plug[TYPEC_PLUG_SOP_PP])
+ tbt->state = TBT_STATE_SOP_PP_ENTER;
+ else
+ tbt->state = TBT_STATE_ENTER;
+ } else if (alt == tbt->plug[TYPEC_PLUG_SOP_PP]) {
+ tbt->state = TBT_STATE_ENTER;
+ } else {
+ struct typec_thunderbolt_data data;
+
+ data.device_mode = tbt->alt->vdo;
+ data.cable_mode =
+ tbt->plug[TYPEC_PLUG_SOP_P] ?
+ tbt->plug[TYPEC_PLUG_SOP_P]
+ ->vdo :
+ 0;
+ data.enter_vdo = tbt->enter_vdo;
+
+ typec_altmode_notify(alt, TYPEC_STATE_MODAL, &data);
+ }
+ break;
+ case CMD_EXIT_MODE:
+ if (alt == tbt->alt) {
+ if (tbt->plug[TYPEC_PLUG_SOP_PP])
+ tbt->state = TBT_STATE_SOP_PP_EXIT;
+ else if (tbt->plug[TYPEC_PLUG_SOP_P])
+ tbt->state = TBT_STATE_SOP_P_EXIT;
+ } else if (alt == tbt->plug[TYPEC_PLUG_SOP_PP]) {
+ tbt->state = TBT_STATE_SOP_P_EXIT;
+ }
+ break;
+ }
+ break;
+ case CMDT_RSP_NAK:
+ switch (cmd) {
+ case CMD_ENTER_MODE:
+ dev_warn(&alt->dev, "Enter Mode refused\n");
+ break;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+
+ if (tbt->state != TBT_STATE_IDLE)
+ schedule_work(&tbt->work);
+
+ mutex_unlock(&tbt->lock);
+
+ return 0;
+}
+
+static int tbt_altmode_activate(struct typec_altmode *alt, int activate)
+{
+ struct tbt_altmode *tbt = typec_altmode_get_drvdata(alt);
+ int ret;
+
+ mutex_lock(&tbt->lock);
+
+ if (!tbt_ready(alt))
+ return -ENODEV;
+
+ /* Preventing the user space from entering/exiting the cable alt mode */
+ if (alt != tbt->alt)
+ ret = -EPERM;
+ else if (activate)
+ ret = tbt_enter_mode(tbt);
+ else
+ ret = typec_altmode_exit(alt);
+
+ mutex_unlock(&tbt->lock);
+
+ return ret;
+}
+
+static const struct typec_altmode_ops tbt_altmode_ops = {
+ .vdm = tbt_altmode_vdm,
+ .activate = tbt_altmode_activate
+};
+
+static int tbt_altmode_probe(struct typec_altmode *alt)
+{
+ struct tbt_altmode *tbt;
+
+ tbt = devm_kzalloc(&alt->dev, sizeof(*tbt), GFP_KERNEL);
+ if (!tbt)
+ return -ENOMEM;
+
+ INIT_WORK(&tbt->work, tbt_altmode_work);
+ mutex_init(&tbt->lock);
+ tbt->alt = alt;
+
+ alt->desc = "Thunderbolt3";
+ typec_altmode_set_drvdata(alt, tbt);
+ typec_altmode_set_ops(alt, &tbt_altmode_ops);
+
+ if (tbt_ready(alt)) {
+ if (tbt->plug[TYPEC_PLUG_SOP_PP])
+ tbt->state = TBT_STATE_SOP_PP_ENTER;
+ else if (tbt->plug[TYPEC_PLUG_SOP_P])
+ tbt->state = TBT_STATE_SOP_P_ENTER;
+ else
+ tbt->state = TBT_STATE_ENTER;
+ schedule_work(&tbt->work);
+ }
+
+ return 0;
+}
+
+static void tbt_altmode_remove(struct typec_altmode *alt)
+{
+ struct tbt_altmode *tbt = typec_altmode_get_drvdata(alt);
+
+ for (int i = TYPEC_PLUG_SOP_PP; i > 0; --i) {
+ if (tbt->plug[i])
+ typec_altmode_put_plug(tbt->plug[i]);
+ }
+
+ if (tbt->cable)
+ typec_cable_put(tbt->cable);
+}
+
+static bool tbt_ready(struct typec_altmode *alt)
+{
+ struct tbt_altmode *tbt = typec_altmode_get_drvdata(alt);
+ struct typec_altmode *plug;
+
+ if (tbt->cable)
+ return true;
+
+ /* Thundebolt 3 requires a cable with eMarker */
+ tbt->cable = typec_cable_get(typec_altmode2port(tbt->alt));
+ if (!tbt->cable)
+ return false;
+
+ /* We accept systems without SOP' or SOP''. This means the port altmode
+ * driver will be responsible for properly ordering entry/exit.
+ */
+ for (int i = 0; i < TYPEC_PLUG_SOP_PP + 1; i++) {
+ plug = typec_altmode_get_plug(tbt->alt, i);
+ if (IS_ERR(plug))
+ continue;
+
+ if (!plug || plug->svid != USB_TYPEC_VENDOR_INTEL)
+ break;
+
+ plug->desc = "Thunderbolt3";
+ plug->ops = &tbt_altmode_ops;
+ typec_altmode_set_drvdata(plug, tbt);
+
+ tbt->plug[i] = plug;
+ }
+
+ return true;
+}
+
+static const struct typec_device_id tbt_typec_id[] = {
+ { USB_TYPEC_TBT_SID },
+ { }
+};
+MODULE_DEVICE_TABLE(typec, tbt_typec_id);
+
+static struct typec_altmode_driver tbt_altmode_driver = {
+ .id_table = tbt_typec_id,
+ .probe = tbt_altmode_probe,
+ .remove = tbt_altmode_remove,
+ .driver = {
+ .name = "typec-thunderbolt",
+ .owner = THIS_MODULE,
+ }
+};
+module_typec_altmode_driver(tbt_altmode_driver);
+
+MODULE_AUTHOR("Heikki Krogerus <heikki.krogerus@linux.intel.com>");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Thunderbolt3 USB Type-C Alternate Mode");
diff --git a/include/linux/usb/typec_tbt.h b/include/linux/usb/typec_tbt.h
index fa97d7e00f5c..55dcea12082c 100644
--- a/include/linux/usb/typec_tbt.h
+++ b/include/linux/usb/typec_tbt.h
@@ -44,6 +44,7 @@ struct typec_thunderbolt_data {
#define TBT_GEN3_NON_ROUNDED 0
#define TBT_GEN3_GEN4_ROUNDED_NON_ROUNDED 1
+#define TBT_CABLE_ROUNDED BIT(19)
#define TBT_CABLE_OPTICAL BIT(21)
#define TBT_CABLE_RETIMER BIT(22)
#define TBT_CABLE_LINK_TRAINING BIT(23)
--
2.47.0.277.g8800431eea-goog
Hi Abhishek,
On Thu, Nov 07, 2024 at 11:29:55AM -0800, Abhishek Pandit-Subedi wrote:
> From: Heikki Krogerus <heikki.krogerus@linux.intel.com>
>
> Thunderbolt 3 Alternate Mode entry flow is described in
> USB Type-C Specification Release 2.0.
>
> Signed-off-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>
> Co-developed-by: Abhishek Pandit-Subedi <abhishekpandit@chromium.org>
> Signed-off-by: Abhishek Pandit-Subedi <abhishekpandit@chromium.org>
> ---
>
> Changes:
> * Delay cable + plug checks so that the module doesn't fail to probe
> if cable + plug information isn't available by the time the partner
> altmode is registered.
> * Remove unncessary brace after if (IS_ERR(plug))
>
> The rest of this patch should be the same as Heikki's original RFC.
>
>
> Changes in v3:
> - Revert rename of TYPEC_TBT_MODE
> - Remove mode from typec_device_id
>
> Changes in v2:
> - Use <linux/usb/typec_tbt.h> and add missing TBT_CABLE_ROUNDED
> - Pass struct typec_thunderbolt_data to typec_altmode_notify
> - Rename TYPEC_TBT_MODE to USB_TYPEC_TBT_MODE
> - Use USB_TYPEC_TBT_SID and USB_TYPEC_TBT_MODE for device id
> - Change module license to GPL due to checkpatch warning
>
> drivers/usb/typec/altmodes/Kconfig | 9 +
> drivers/usb/typec/altmodes/Makefile | 2 +
> drivers/usb/typec/altmodes/thunderbolt.c | 308 +++++++++++++++++++++++
> include/linux/usb/typec_tbt.h | 1 +
> 4 files changed, 320 insertions(+)
> create mode 100644 drivers/usb/typec/altmodes/thunderbolt.c
>
> diff --git a/drivers/usb/typec/altmodes/Kconfig b/drivers/usb/typec/altmodes/Kconfig
> index 1a6b5e872b0d..7867fa7c405d 100644
> --- a/drivers/usb/typec/altmodes/Kconfig
> +++ b/drivers/usb/typec/altmodes/Kconfig
> @@ -23,4 +23,13 @@ config TYPEC_NVIDIA_ALTMODE
> To compile this driver as a module, choose M here: the
> module will be called typec_nvidia.
>
> +config TYPEC_TBT_ALTMODE
> + tristate "Thunderbolt3 Alternate Mode driver"
> + help
> + Select this option if you have Thunderbolt3 hardware on your
> + system.
> +
> + To compile this driver as a module, choose M here: the
> + module will be called typec_thunderbolt.
> +
> endmenu
> diff --git a/drivers/usb/typec/altmodes/Makefile b/drivers/usb/typec/altmodes/Makefile
> index 45717548b396..508a68351bd2 100644
> --- a/drivers/usb/typec/altmodes/Makefile
> +++ b/drivers/usb/typec/altmodes/Makefile
> @@ -4,3 +4,5 @@ obj-$(CONFIG_TYPEC_DP_ALTMODE) += typec_displayport.o
> typec_displayport-y := displayport.o
> obj-$(CONFIG_TYPEC_NVIDIA_ALTMODE) += typec_nvidia.o
> typec_nvidia-y := nvidia.o
> +obj-$(CONFIG_TYPEC_TBT_ALTMODE) += typec_thunderbolt.o
> +typec_thunderbolt-y := thunderbolt.o
> diff --git a/drivers/usb/typec/altmodes/thunderbolt.c b/drivers/usb/typec/altmodes/thunderbolt.c
> new file mode 100644
> index 000000000000..a945b9d35a1d
> --- /dev/null
> +++ b/drivers/usb/typec/altmodes/thunderbolt.c
> @@ -0,0 +1,308 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/**
> + * USB Typec-C Thuderbolt3 Alternate Mode driver
> + *
> + * Copyright (C) 2019 Intel Corporation
> + * Author: Heikki Krogerus <heikki.krogerus@linux.intel.com>
> + */
> +
> +#include <linux/delay.h>
> +#include <linux/mutex.h>
> +#include <linux/module.h>
> +#include <linux/usb/pd_vdo.h>
> +#include <linux/usb/typec_altmode.h>
> +#include <linux/usb/typec_tbt.h>
> +
> +enum tbt_state {
> + TBT_STATE_IDLE,
> + TBT_STATE_SOP_P_ENTER,
> + TBT_STATE_SOP_PP_ENTER,
> + TBT_STATE_ENTER,
> + TBT_STATE_EXIT,
> + TBT_STATE_SOP_PP_EXIT,
> + TBT_STATE_SOP_P_EXIT
> +};
> +
> +struct tbt_altmode {
> + enum tbt_state state;
> + struct typec_cable *cable;
> + struct typec_altmode *alt;
> + struct typec_altmode *plug[2];
> + u32 enter_vdo;
> +
> + struct work_struct work;
> + struct mutex lock; /* device lock */
> +};
> +
> +static bool tbt_ready(struct typec_altmode *alt);
> +
> +static int tbt_enter_mode(struct tbt_altmode *tbt)
> +{
> + struct typec_altmode *plug = tbt->plug[TYPEC_PLUG_SOP_P];
> + u32 vdo;
> +
> + vdo = tbt->alt->vdo & (TBT_VENDOR_SPECIFIC_B0 | TBT_VENDOR_SPECIFIC_B1);
> + vdo |= tbt->alt->vdo & TBT_INTEL_SPECIFIC_B0;
> + vdo |= TBT_MODE;
> +
> + if (plug) {
> + if (typec_cable_is_active(tbt->cable))
> + vdo |= TBT_ENTER_MODE_ACTIVE_CABLE;
> +
> + vdo |= TBT_ENTER_MODE_CABLE_SPEED(TBT_CABLE_SPEED(plug->vdo));
> + vdo |= plug->vdo & TBT_CABLE_ROUNDED;
> + vdo |= plug->vdo & TBT_CABLE_OPTICAL;
> + vdo |= plug->vdo & TBT_CABLE_RETIMER;
> + vdo |= plug->vdo & TBT_CABLE_LINK_TRAINING;
> + } else {
> + vdo |= TBT_ENTER_MODE_CABLE_SPEED(TBT_CABLE_USB3_PASSIVE);
> + }
> +
> + tbt->enter_vdo = vdo;
> + return typec_altmode_enter(tbt->alt, &vdo);
> +}
> +
> +static void tbt_altmode_work(struct work_struct *work)
> +{
> + struct tbt_altmode *tbt = container_of(work, struct tbt_altmode, work);
> + int ret;
> +
> + mutex_lock(&tbt->lock);
> +
> + switch (tbt->state) {
> + case TBT_STATE_SOP_P_ENTER:
> + ret = typec_altmode_enter(tbt->plug[TYPEC_PLUG_SOP_P], NULL);
> + if (ret)
> + dev_dbg(&tbt->plug[TYPEC_PLUG_SOP_P]->dev,
> + "failed to enter mode (%d)\n", ret);
> + break;
> + case TBT_STATE_SOP_PP_ENTER:
> + ret = typec_altmode_enter(tbt->plug[TYPEC_PLUG_SOP_PP], NULL);
> + if (ret)
> + dev_dbg(&tbt->plug[TYPEC_PLUG_SOP_PP]->dev,
> + "failed to enter mode (%d)\n", ret);
> + break;
> + case TBT_STATE_ENTER:
> + ret = tbt_enter_mode(tbt);
> + if (ret)
> + dev_dbg(&tbt->alt->dev, "failed to enter mode (%d)\n",
> + ret);
> + break;
> + case TBT_STATE_EXIT:
> + typec_altmode_exit(tbt->alt);
> + break;
> + case TBT_STATE_SOP_PP_EXIT:
> + typec_altmode_exit(tbt->plug[TYPEC_PLUG_SOP_PP]);
> + break;
> + case TBT_STATE_SOP_P_EXIT:
> + typec_altmode_exit(tbt->plug[TYPEC_PLUG_SOP_P]);
> + break;
> + default:
> + break;
> + }
> +
> + tbt->state = TBT_STATE_IDLE;
> +
> + mutex_unlock(&tbt->lock);
> +}
> +
> +static int tbt_altmode_vdm(struct typec_altmode *alt,
> + const u32 hdr, const u32 *vdo, int count)
> +{
> + struct tbt_altmode *tbt = typec_altmode_get_drvdata(alt);
> + int cmd_type = PD_VDO_CMDT(hdr);
> + int cmd = PD_VDO_CMD(hdr);
> +
> + mutex_lock(&tbt->lock);
> +
> + if (tbt->state != TBT_STATE_IDLE) {
> + mutex_unlock(&tbt->lock);
> + return -EBUSY;
> + }
> +
> + switch (cmd_type) {
> + case CMDT_RSP_ACK:
> + switch (cmd) {
> + case CMD_ENTER_MODE:
> + /*
> + * Following the order describeded in USB Type-C Spec
> + * R2.0 Section 6.7.3.
> + */
> + if (alt == tbt->plug[TYPEC_PLUG_SOP_P]) {
> + if (tbt->plug[TYPEC_PLUG_SOP_PP])
> + tbt->state = TBT_STATE_SOP_PP_ENTER;
> + else
> + tbt->state = TBT_STATE_ENTER;
> + } else if (alt == tbt->plug[TYPEC_PLUG_SOP_PP]) {
> + tbt->state = TBT_STATE_ENTER;
> + } else {
> + struct typec_thunderbolt_data data;
> +
> + data.device_mode = tbt->alt->vdo;
> + data.cable_mode =
> + tbt->plug[TYPEC_PLUG_SOP_P] ?
> + tbt->plug[TYPEC_PLUG_SOP_P]
> + ->vdo :
> + 0;
> + data.enter_vdo = tbt->enter_vdo;
> +
> + typec_altmode_notify(alt, TYPEC_STATE_MODAL, &data);
> + }
> + break;
> + case CMD_EXIT_MODE:
> + if (alt == tbt->alt) {
> + if (tbt->plug[TYPEC_PLUG_SOP_PP])
> + tbt->state = TBT_STATE_SOP_PP_EXIT;
> + else if (tbt->plug[TYPEC_PLUG_SOP_P])
> + tbt->state = TBT_STATE_SOP_P_EXIT;
> + } else if (alt == tbt->plug[TYPEC_PLUG_SOP_PP]) {
> + tbt->state = TBT_STATE_SOP_P_EXIT;
> + }
> + break;
> + }
> + break;
> + case CMDT_RSP_NAK:
> + switch (cmd) {
> + case CMD_ENTER_MODE:
> + dev_warn(&alt->dev, "Enter Mode refused\n");
> + break;
> + default:
> + break;
> + }
> + break;
> + default:
> + break;
> + }
> +
> + if (tbt->state != TBT_STATE_IDLE)
> + schedule_work(&tbt->work);
> +
> + mutex_unlock(&tbt->lock);
> +
> + return 0;
> +}
> +
> +static int tbt_altmode_activate(struct typec_altmode *alt, int activate)
> +{
> + struct tbt_altmode *tbt = typec_altmode_get_drvdata(alt);
> + int ret;
> +
> + mutex_lock(&tbt->lock);
> +
> + if (!tbt_ready(alt))
> + return -ENODEV;
You need to mutex_unlock(&tbt->lock); before the return.
Credit to danielgeorgem@google.com for his catching this in his code review.
> +
> + /* Preventing the user space from entering/exiting the cable alt mode */
> + if (alt != tbt->alt)
> + ret = -EPERM;
> + else if (activate)
> + ret = tbt_enter_mode(tbt);
> + else
> + ret = typec_altmode_exit(alt);
> +
> + mutex_unlock(&tbt->lock);
> +
> + return ret;
> +}
> +
> +static const struct typec_altmode_ops tbt_altmode_ops = {
> + .vdm = tbt_altmode_vdm,
> + .activate = tbt_altmode_activate
> +};
> +
> +static int tbt_altmode_probe(struct typec_altmode *alt)
> +{
> + struct tbt_altmode *tbt;
> +
> + tbt = devm_kzalloc(&alt->dev, sizeof(*tbt), GFP_KERNEL);
> + if (!tbt)
> + return -ENOMEM;
> +
> + INIT_WORK(&tbt->work, tbt_altmode_work);
> + mutex_init(&tbt->lock);
> + tbt->alt = alt;
> +
> + alt->desc = "Thunderbolt3";
> + typec_altmode_set_drvdata(alt, tbt);
> + typec_altmode_set_ops(alt, &tbt_altmode_ops);
> +
> + if (tbt_ready(alt)) {
> + if (tbt->plug[TYPEC_PLUG_SOP_PP])
> + tbt->state = TBT_STATE_SOP_PP_ENTER;
> + else if (tbt->plug[TYPEC_PLUG_SOP_P])
> + tbt->state = TBT_STATE_SOP_P_ENTER;
> + else
> + tbt->state = TBT_STATE_ENTER;
> + schedule_work(&tbt->work);
> + }
> +
> + return 0;
> +}
> +
> +static void tbt_altmode_remove(struct typec_altmode *alt)
> +{
> + struct tbt_altmode *tbt = typec_altmode_get_drvdata(alt);
> +
> + for (int i = TYPEC_PLUG_SOP_PP; i > 0; --i) {
> + if (tbt->plug[i])
> + typec_altmode_put_plug(tbt->plug[i]);
> + }
> +
> + if (tbt->cable)
> + typec_cable_put(tbt->cable);
> +}
> +
> +static bool tbt_ready(struct typec_altmode *alt)
> +{
> + struct tbt_altmode *tbt = typec_altmode_get_drvdata(alt);
> + struct typec_altmode *plug;
> +
> + if (tbt->cable)
> + return true;
> +
> + /* Thundebolt 3 requires a cable with eMarker */
> + tbt->cable = typec_cable_get(typec_altmode2port(tbt->alt));
> + if (!tbt->cable)
> + return false;
> +
> + /* We accept systems without SOP' or SOP''. This means the port altmode
> + * driver will be responsible for properly ordering entry/exit.
> + */
> + for (int i = 0; i < TYPEC_PLUG_SOP_PP + 1; i++) {
> + plug = typec_altmode_get_plug(tbt->alt, i);
> + if (IS_ERR(plug))
> + continue;
> +
> + if (!plug || plug->svid != USB_TYPEC_VENDOR_INTEL)
> + break;
> +
> + plug->desc = "Thunderbolt3";
> + plug->ops = &tbt_altmode_ops;
> + typec_altmode_set_drvdata(plug, tbt);
> +
> + tbt->plug[i] = plug;
> + }
> +
> + return true;
> +}
> +
> +static const struct typec_device_id tbt_typec_id[] = {
> + { USB_TYPEC_TBT_SID },
> + { }
> +};
> +MODULE_DEVICE_TABLE(typec, tbt_typec_id);
> +
> +static struct typec_altmode_driver tbt_altmode_driver = {
> + .id_table = tbt_typec_id,
> + .probe = tbt_altmode_probe,
> + .remove = tbt_altmode_remove,
> + .driver = {
> + .name = "typec-thunderbolt",
> + .owner = THIS_MODULE,
> + }
> +};
> +module_typec_altmode_driver(tbt_altmode_driver);
> +
> +MODULE_AUTHOR("Heikki Krogerus <heikki.krogerus@linux.intel.com>");
> +MODULE_LICENSE("GPL");
> +MODULE_DESCRIPTION("Thunderbolt3 USB Type-C Alternate Mode");
> diff --git a/include/linux/usb/typec_tbt.h b/include/linux/usb/typec_tbt.h
> index fa97d7e00f5c..55dcea12082c 100644
> --- a/include/linux/usb/typec_tbt.h
> +++ b/include/linux/usb/typec_tbt.h
> @@ -44,6 +44,7 @@ struct typec_thunderbolt_data {
>
> #define TBT_GEN3_NON_ROUNDED 0
> #define TBT_GEN3_GEN4_ROUNDED_NON_ROUNDED 1
> +#define TBT_CABLE_ROUNDED BIT(19)
> #define TBT_CABLE_OPTICAL BIT(21)
> #define TBT_CABLE_RETIMER BIT(22)
> #define TBT_CABLE_LINK_TRAINING BIT(23)
> --
> 2.47.0.277.g8800431eea-goog
>
>
On Fri, Nov 22, 2024 at 10:51 AM Benson Leung <bleung@google.com> wrote:
>
> Hi Abhishek,
>
>
> On Thu, Nov 07, 2024 at 11:29:55AM -0800, Abhishek Pandit-Subedi wrote:
> > From: Heikki Krogerus <heikki.krogerus@linux.intel.com>
> >
> > Thunderbolt 3 Alternate Mode entry flow is described in
> > USB Type-C Specification Release 2.0.
> >
> > Signed-off-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>
> > Co-developed-by: Abhishek Pandit-Subedi <abhishekpandit@chromium.org>
> > Signed-off-by: Abhishek Pandit-Subedi <abhishekpandit@chromium.org>
> > ---
> >
> > Changes:
> > * Delay cable + plug checks so that the module doesn't fail to probe
> > if cable + plug information isn't available by the time the partner
> > altmode is registered.
> > * Remove unncessary brace after if (IS_ERR(plug))
> >
> > The rest of this patch should be the same as Heikki's original RFC.
> >
> >
> > Changes in v3:
> > - Revert rename of TYPEC_TBT_MODE
> > - Remove mode from typec_device_id
> >
> > Changes in v2:
> > - Use <linux/usb/typec_tbt.h> and add missing TBT_CABLE_ROUNDED
> > - Pass struct typec_thunderbolt_data to typec_altmode_notify
> > - Rename TYPEC_TBT_MODE to USB_TYPEC_TBT_MODE
> > - Use USB_TYPEC_TBT_SID and USB_TYPEC_TBT_MODE for device id
> > - Change module license to GPL due to checkpatch warning
> >
> > drivers/usb/typec/altmodes/Kconfig | 9 +
> > drivers/usb/typec/altmodes/Makefile | 2 +
> > drivers/usb/typec/altmodes/thunderbolt.c | 308 +++++++++++++++++++++++
> > include/linux/usb/typec_tbt.h | 1 +
> > 4 files changed, 320 insertions(+)
> > create mode 100644 drivers/usb/typec/altmodes/thunderbolt.c
> >
> > diff --git a/drivers/usb/typec/altmodes/Kconfig b/drivers/usb/typec/altmodes/Kconfig
> > index 1a6b5e872b0d..7867fa7c405d 100644
> > --- a/drivers/usb/typec/altmodes/Kconfig
> > +++ b/drivers/usb/typec/altmodes/Kconfig
> > @@ -23,4 +23,13 @@ config TYPEC_NVIDIA_ALTMODE
> > To compile this driver as a module, choose M here: the
> > module will be called typec_nvidia.
> >
> > +config TYPEC_TBT_ALTMODE
> > + tristate "Thunderbolt3 Alternate Mode driver"
> > + help
> > + Select this option if you have Thunderbolt3 hardware on your
> > + system.
> > +
> > + To compile this driver as a module, choose M here: the
> > + module will be called typec_thunderbolt.
> > +
> > endmenu
> > diff --git a/drivers/usb/typec/altmodes/Makefile b/drivers/usb/typec/altmodes/Makefile
> > index 45717548b396..508a68351bd2 100644
> > --- a/drivers/usb/typec/altmodes/Makefile
> > +++ b/drivers/usb/typec/altmodes/Makefile
> > @@ -4,3 +4,5 @@ obj-$(CONFIG_TYPEC_DP_ALTMODE) += typec_displayport.o
> > typec_displayport-y := displayport.o
> > obj-$(CONFIG_TYPEC_NVIDIA_ALTMODE) += typec_nvidia.o
> > typec_nvidia-y := nvidia.o
> > +obj-$(CONFIG_TYPEC_TBT_ALTMODE) += typec_thunderbolt.o
> > +typec_thunderbolt-y := thunderbolt.o
> > diff --git a/drivers/usb/typec/altmodes/thunderbolt.c b/drivers/usb/typec/altmodes/thunderbolt.c
> > new file mode 100644
> > index 000000000000..a945b9d35a1d
> > --- /dev/null
> > +++ b/drivers/usb/typec/altmodes/thunderbolt.c
> > @@ -0,0 +1,308 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/**
> > + * USB Typec-C Thuderbolt3 Alternate Mode driver
> > + *
> > + * Copyright (C) 2019 Intel Corporation
> > + * Author: Heikki Krogerus <heikki.krogerus@linux.intel.com>
> > + */
> > +
> > +#include <linux/delay.h>
> > +#include <linux/mutex.h>
> > +#include <linux/module.h>
> > +#include <linux/usb/pd_vdo.h>
> > +#include <linux/usb/typec_altmode.h>
> > +#include <linux/usb/typec_tbt.h>
> > +
> > +enum tbt_state {
> > + TBT_STATE_IDLE,
> > + TBT_STATE_SOP_P_ENTER,
> > + TBT_STATE_SOP_PP_ENTER,
> > + TBT_STATE_ENTER,
> > + TBT_STATE_EXIT,
> > + TBT_STATE_SOP_PP_EXIT,
> > + TBT_STATE_SOP_P_EXIT
> > +};
> > +
> > +struct tbt_altmode {
> > + enum tbt_state state;
> > + struct typec_cable *cable;
> > + struct typec_altmode *alt;
> > + struct typec_altmode *plug[2];
> > + u32 enter_vdo;
> > +
> > + struct work_struct work;
> > + struct mutex lock; /* device lock */
> > +};
> > +
> > +static bool tbt_ready(struct typec_altmode *alt);
> > +
> > +static int tbt_enter_mode(struct tbt_altmode *tbt)
> > +{
> > + struct typec_altmode *plug = tbt->plug[TYPEC_PLUG_SOP_P];
> > + u32 vdo;
> > +
> > + vdo = tbt->alt->vdo & (TBT_VENDOR_SPECIFIC_B0 | TBT_VENDOR_SPECIFIC_B1);
> > + vdo |= tbt->alt->vdo & TBT_INTEL_SPECIFIC_B0;
> > + vdo |= TBT_MODE;
> > +
> > + if (plug) {
> > + if (typec_cable_is_active(tbt->cable))
> > + vdo |= TBT_ENTER_MODE_ACTIVE_CABLE;
> > +
> > + vdo |= TBT_ENTER_MODE_CABLE_SPEED(TBT_CABLE_SPEED(plug->vdo));
> > + vdo |= plug->vdo & TBT_CABLE_ROUNDED;
> > + vdo |= plug->vdo & TBT_CABLE_OPTICAL;
> > + vdo |= plug->vdo & TBT_CABLE_RETIMER;
> > + vdo |= plug->vdo & TBT_CABLE_LINK_TRAINING;
> > + } else {
> > + vdo |= TBT_ENTER_MODE_CABLE_SPEED(TBT_CABLE_USB3_PASSIVE);
> > + }
> > +
> > + tbt->enter_vdo = vdo;
> > + return typec_altmode_enter(tbt->alt, &vdo);
> > +}
> > +
> > +static void tbt_altmode_work(struct work_struct *work)
> > +{
> > + struct tbt_altmode *tbt = container_of(work, struct tbt_altmode, work);
> > + int ret;
> > +
> > + mutex_lock(&tbt->lock);
> > +
> > + switch (tbt->state) {
> > + case TBT_STATE_SOP_P_ENTER:
> > + ret = typec_altmode_enter(tbt->plug[TYPEC_PLUG_SOP_P], NULL);
> > + if (ret)
> > + dev_dbg(&tbt->plug[TYPEC_PLUG_SOP_P]->dev,
> > + "failed to enter mode (%d)\n", ret);
> > + break;
> > + case TBT_STATE_SOP_PP_ENTER:
> > + ret = typec_altmode_enter(tbt->plug[TYPEC_PLUG_SOP_PP], NULL);
> > + if (ret)
> > + dev_dbg(&tbt->plug[TYPEC_PLUG_SOP_PP]->dev,
> > + "failed to enter mode (%d)\n", ret);
> > + break;
> > + case TBT_STATE_ENTER:
> > + ret = tbt_enter_mode(tbt);
> > + if (ret)
> > + dev_dbg(&tbt->alt->dev, "failed to enter mode (%d)\n",
> > + ret);
> > + break;
> > + case TBT_STATE_EXIT:
> > + typec_altmode_exit(tbt->alt);
> > + break;
> > + case TBT_STATE_SOP_PP_EXIT:
> > + typec_altmode_exit(tbt->plug[TYPEC_PLUG_SOP_PP]);
> > + break;
> > + case TBT_STATE_SOP_P_EXIT:
> > + typec_altmode_exit(tbt->plug[TYPEC_PLUG_SOP_P]);
> > + break;
> > + default:
> > + break;
> > + }
> > +
> > + tbt->state = TBT_STATE_IDLE;
> > +
> > + mutex_unlock(&tbt->lock);
> > +}
> > +
> > +static int tbt_altmode_vdm(struct typec_altmode *alt,
> > + const u32 hdr, const u32 *vdo, int count)
> > +{
> > + struct tbt_altmode *tbt = typec_altmode_get_drvdata(alt);
> > + int cmd_type = PD_VDO_CMDT(hdr);
> > + int cmd = PD_VDO_CMD(hdr);
> > +
> > + mutex_lock(&tbt->lock);
> > +
> > + if (tbt->state != TBT_STATE_IDLE) {
> > + mutex_unlock(&tbt->lock);
> > + return -EBUSY;
> > + }
> > +
> > + switch (cmd_type) {
> > + case CMDT_RSP_ACK:
> > + switch (cmd) {
> > + case CMD_ENTER_MODE:
> > + /*
> > + * Following the order describeded in USB Type-C Spec
> > + * R2.0 Section 6.7.3.
> > + */
> > + if (alt == tbt->plug[TYPEC_PLUG_SOP_P]) {
> > + if (tbt->plug[TYPEC_PLUG_SOP_PP])
> > + tbt->state = TBT_STATE_SOP_PP_ENTER;
> > + else
> > + tbt->state = TBT_STATE_ENTER;
> > + } else if (alt == tbt->plug[TYPEC_PLUG_SOP_PP]) {
> > + tbt->state = TBT_STATE_ENTER;
> > + } else {
> > + struct typec_thunderbolt_data data;
> > +
> > + data.device_mode = tbt->alt->vdo;
> > + data.cable_mode =
> > + tbt->plug[TYPEC_PLUG_SOP_P] ?
> > + tbt->plug[TYPEC_PLUG_SOP_P]
> > + ->vdo :
> > + 0;
> > + data.enter_vdo = tbt->enter_vdo;
> > +
> > + typec_altmode_notify(alt, TYPEC_STATE_MODAL, &data);
> > + }
> > + break;
> > + case CMD_EXIT_MODE:
> > + if (alt == tbt->alt) {
> > + if (tbt->plug[TYPEC_PLUG_SOP_PP])
> > + tbt->state = TBT_STATE_SOP_PP_EXIT;
> > + else if (tbt->plug[TYPEC_PLUG_SOP_P])
> > + tbt->state = TBT_STATE_SOP_P_EXIT;
> > + } else if (alt == tbt->plug[TYPEC_PLUG_SOP_PP]) {
> > + tbt->state = TBT_STATE_SOP_P_EXIT;
> > + }
> > + break;
> > + }
> > + break;
> > + case CMDT_RSP_NAK:
> > + switch (cmd) {
> > + case CMD_ENTER_MODE:
> > + dev_warn(&alt->dev, "Enter Mode refused\n");
> > + break;
> > + default:
> > + break;
> > + }
> > + break;
> > + default:
> > + break;
> > + }
> > +
> > + if (tbt->state != TBT_STATE_IDLE)
> > + schedule_work(&tbt->work);
> > +
> > + mutex_unlock(&tbt->lock);
> > +
> > + return 0;
> > +}
> > +
> > +static int tbt_altmode_activate(struct typec_altmode *alt, int activate)
> > +{
> > + struct tbt_altmode *tbt = typec_altmode_get_drvdata(alt);
> > + int ret;
> > +
> > + mutex_lock(&tbt->lock);
> > +
> > + if (!tbt_ready(alt))
> > + return -ENODEV;
>
>
> You need to mutex_unlock(&tbt->lock); before the return.
>
> Credit to danielgeorgem@google.com for his catching this in his code review.
Good catch! Next update will have this.
Sorry for late replies -- it took me a while to get back to this +
refactor to use the new cable ops apis. In the process, I also
discovered some mistakes in how we were entering TBT from .activate so
I opted to follow a similar pattern to what RD has done for DP with
cables.
v4 will come soon.
>
> > +
> > + /* Preventing the user space from entering/exiting the cable alt mode */
> > + if (alt != tbt->alt)
> > + ret = -EPERM;
> > + else if (activate)
> > + ret = tbt_enter_mode(tbt);
> > + else
> > + ret = typec_altmode_exit(alt);
> > +
> > + mutex_unlock(&tbt->lock);
> > +
> > + return ret;
> > +}
> > +
> > +static const struct typec_altmode_ops tbt_altmode_ops = {
> > + .vdm = tbt_altmode_vdm,
> > + .activate = tbt_altmode_activate
> > +};
> > +
> > +static int tbt_altmode_probe(struct typec_altmode *alt)
> > +{
> > + struct tbt_altmode *tbt;
> > +
> > + tbt = devm_kzalloc(&alt->dev, sizeof(*tbt), GFP_KERNEL);
> > + if (!tbt)
> > + return -ENOMEM;
> > +
> > + INIT_WORK(&tbt->work, tbt_altmode_work);
> > + mutex_init(&tbt->lock);
> > + tbt->alt = alt;
> > +
> > + alt->desc = "Thunderbolt3";
> > + typec_altmode_set_drvdata(alt, tbt);
> > + typec_altmode_set_ops(alt, &tbt_altmode_ops);
> > +
> > + if (tbt_ready(alt)) {
> > + if (tbt->plug[TYPEC_PLUG_SOP_PP])
> > + tbt->state = TBT_STATE_SOP_PP_ENTER;
> > + else if (tbt->plug[TYPEC_PLUG_SOP_P])
> > + tbt->state = TBT_STATE_SOP_P_ENTER;
> > + else
> > + tbt->state = TBT_STATE_ENTER;
> > + schedule_work(&tbt->work);
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static void tbt_altmode_remove(struct typec_altmode *alt)
> > +{
> > + struct tbt_altmode *tbt = typec_altmode_get_drvdata(alt);
> > +
> > + for (int i = TYPEC_PLUG_SOP_PP; i > 0; --i) {
> > + if (tbt->plug[i])
> > + typec_altmode_put_plug(tbt->plug[i]);
> > + }
> > +
> > + if (tbt->cable)
> > + typec_cable_put(tbt->cable);
> > +}
> > +
> > +static bool tbt_ready(struct typec_altmode *alt)
> > +{
> > + struct tbt_altmode *tbt = typec_altmode_get_drvdata(alt);
> > + struct typec_altmode *plug;
> > +
> > + if (tbt->cable)
> > + return true;
> > +
> > + /* Thundebolt 3 requires a cable with eMarker */
> > + tbt->cable = typec_cable_get(typec_altmode2port(tbt->alt));
> > + if (!tbt->cable)
> > + return false;
> > +
> > + /* We accept systems without SOP' or SOP''. This means the port altmode
> > + * driver will be responsible for properly ordering entry/exit.
> > + */
> > + for (int i = 0; i < TYPEC_PLUG_SOP_PP + 1; i++) {
> > + plug = typec_altmode_get_plug(tbt->alt, i);
> > + if (IS_ERR(plug))
> > + continue;
> > +
> > + if (!plug || plug->svid != USB_TYPEC_VENDOR_INTEL)
> > + break;
> > +
> > + plug->desc = "Thunderbolt3";
> > + plug->ops = &tbt_altmode_ops;
> > + typec_altmode_set_drvdata(plug, tbt);
> > +
> > + tbt->plug[i] = plug;
> > + }
> > +
> > + return true;
> > +}
> > +
> > +static const struct typec_device_id tbt_typec_id[] = {
> > + { USB_TYPEC_TBT_SID },
> > + { }
> > +};
> > +MODULE_DEVICE_TABLE(typec, tbt_typec_id);
> > +
> > +static struct typec_altmode_driver tbt_altmode_driver = {
> > + .id_table = tbt_typec_id,
> > + .probe = tbt_altmode_probe,
> > + .remove = tbt_altmode_remove,
> > + .driver = {
> > + .name = "typec-thunderbolt",
> > + .owner = THIS_MODULE,
> > + }
> > +};
> > +module_typec_altmode_driver(tbt_altmode_driver);
> > +
> > +MODULE_AUTHOR("Heikki Krogerus <heikki.krogerus@linux.intel.com>");
> > +MODULE_LICENSE("GPL");
> > +MODULE_DESCRIPTION("Thunderbolt3 USB Type-C Alternate Mode");
> > diff --git a/include/linux/usb/typec_tbt.h b/include/linux/usb/typec_tbt.h
> > index fa97d7e00f5c..55dcea12082c 100644
> > --- a/include/linux/usb/typec_tbt.h
> > +++ b/include/linux/usb/typec_tbt.h
> > @@ -44,6 +44,7 @@ struct typec_thunderbolt_data {
> >
> > #define TBT_GEN3_NON_ROUNDED 0
> > #define TBT_GEN3_GEN4_ROUNDED_NON_ROUNDED 1
> > +#define TBT_CABLE_ROUNDED BIT(19)
> > #define TBT_CABLE_OPTICAL BIT(21)
> > #define TBT_CABLE_RETIMER BIT(22)
> > #define TBT_CABLE_LINK_TRAINING BIT(23)
> > --
> > 2.47.0.277.g8800431eea-goog
> >
> >
On Thu, Nov 07, 2024 at 11:29:55AM -0800, Abhishek Pandit-Subedi wrote:
> From: Heikki Krogerus <heikki.krogerus@linux.intel.com>
>
> Thunderbolt 3 Alternate Mode entry flow is described in
> USB Type-C Specification Release 2.0.
>
> Signed-off-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>
> Co-developed-by: Abhishek Pandit-Subedi <abhishekpandit@chromium.org>
> Signed-off-by: Abhishek Pandit-Subedi <abhishekpandit@chromium.org>
> ---
>
> Changes:
> * Delay cable + plug checks so that the module doesn't fail to probe
> if cable + plug information isn't available by the time the partner
> altmode is registered.
> * Remove unncessary brace after if (IS_ERR(plug))
>
> The rest of this patch should be the same as Heikki's original RFC.
>
>
> Changes in v3:
> - Revert rename of TYPEC_TBT_MODE
> - Remove mode from typec_device_id
>
> Changes in v2:
> - Use <linux/usb/typec_tbt.h> and add missing TBT_CABLE_ROUNDED
> - Pass struct typec_thunderbolt_data to typec_altmode_notify
> - Rename TYPEC_TBT_MODE to USB_TYPEC_TBT_MODE
> - Use USB_TYPEC_TBT_SID and USB_TYPEC_TBT_MODE for device id
> - Change module license to GPL due to checkpatch warning
>
> drivers/usb/typec/altmodes/Kconfig | 9 +
> drivers/usb/typec/altmodes/Makefile | 2 +
> drivers/usb/typec/altmodes/thunderbolt.c | 308 +++++++++++++++++++++++
> include/linux/usb/typec_tbt.h | 1 +
> 4 files changed, 320 insertions(+)
> create mode 100644 drivers/usb/typec/altmodes/thunderbolt.c
>
> diff --git a/drivers/usb/typec/altmodes/Kconfig b/drivers/usb/typec/altmodes/Kconfig
> index 1a6b5e872b0d..7867fa7c405d 100644
> --- a/drivers/usb/typec/altmodes/Kconfig
> +++ b/drivers/usb/typec/altmodes/Kconfig
> @@ -23,4 +23,13 @@ config TYPEC_NVIDIA_ALTMODE
> To compile this driver as a module, choose M here: the
> module will be called typec_nvidia.
>
> +config TYPEC_TBT_ALTMODE
> + tristate "Thunderbolt3 Alternate Mode driver"
> + help
> + Select this option if you have Thunderbolt3 hardware on your
> + system.
> +
> + To compile this driver as a module, choose M here: the
> + module will be called typec_thunderbolt.
> +
> endmenu
> diff --git a/drivers/usb/typec/altmodes/Makefile b/drivers/usb/typec/altmodes/Makefile
> index 45717548b396..508a68351bd2 100644
> --- a/drivers/usb/typec/altmodes/Makefile
> +++ b/drivers/usb/typec/altmodes/Makefile
> @@ -4,3 +4,5 @@ obj-$(CONFIG_TYPEC_DP_ALTMODE) += typec_displayport.o
> typec_displayport-y := displayport.o
> obj-$(CONFIG_TYPEC_NVIDIA_ALTMODE) += typec_nvidia.o
> typec_nvidia-y := nvidia.o
> +obj-$(CONFIG_TYPEC_TBT_ALTMODE) += typec_thunderbolt.o
> +typec_thunderbolt-y := thunderbolt.o
> diff --git a/drivers/usb/typec/altmodes/thunderbolt.c b/drivers/usb/typec/altmodes/thunderbolt.c
> new file mode 100644
> index 000000000000..a945b9d35a1d
> --- /dev/null
> +++ b/drivers/usb/typec/altmodes/thunderbolt.c
> @@ -0,0 +1,308 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/**
> + * USB Typec-C Thuderbolt3 Alternate Mode driver
> + *
> + * Copyright (C) 2019 Intel Corporation
> + * Author: Heikki Krogerus <heikki.krogerus@linux.intel.com>
> + */
> +
> +#include <linux/delay.h>
> +#include <linux/mutex.h>
> +#include <linux/module.h>
> +#include <linux/usb/pd_vdo.h>
> +#include <linux/usb/typec_altmode.h>
> +#include <linux/usb/typec_tbt.h>
> +
> +enum tbt_state {
> + TBT_STATE_IDLE,
> + TBT_STATE_SOP_P_ENTER,
> + TBT_STATE_SOP_PP_ENTER,
> + TBT_STATE_ENTER,
> + TBT_STATE_EXIT,
> + TBT_STATE_SOP_PP_EXIT,
> + TBT_STATE_SOP_P_EXIT
> +};
> +
> +struct tbt_altmode {
> + enum tbt_state state;
> + struct typec_cable *cable;
> + struct typec_altmode *alt;
> + struct typec_altmode *plug[2];
> + u32 enter_vdo;
> +
> + struct work_struct work;
> + struct mutex lock; /* device lock */
> +};
> +
> +static bool tbt_ready(struct typec_altmode *alt);
> +
> +static int tbt_enter_mode(struct tbt_altmode *tbt)
> +{
> + struct typec_altmode *plug = tbt->plug[TYPEC_PLUG_SOP_P];
> + u32 vdo;
> +
> + vdo = tbt->alt->vdo & (TBT_VENDOR_SPECIFIC_B0 | TBT_VENDOR_SPECIFIC_B1);
> + vdo |= tbt->alt->vdo & TBT_INTEL_SPECIFIC_B0;
> + vdo |= TBT_MODE;
> +
> + if (plug) {
> + if (typec_cable_is_active(tbt->cable))
> + vdo |= TBT_ENTER_MODE_ACTIVE_CABLE;
> +
> + vdo |= TBT_ENTER_MODE_CABLE_SPEED(TBT_CABLE_SPEED(plug->vdo));
> + vdo |= plug->vdo & TBT_CABLE_ROUNDED;
> + vdo |= plug->vdo & TBT_CABLE_OPTICAL;
> + vdo |= plug->vdo & TBT_CABLE_RETIMER;
> + vdo |= plug->vdo & TBT_CABLE_LINK_TRAINING;
> + } else {
> + vdo |= TBT_ENTER_MODE_CABLE_SPEED(TBT_CABLE_USB3_PASSIVE);
> + }
> +
> + tbt->enter_vdo = vdo;
> + return typec_altmode_enter(tbt->alt, &vdo);
> +}
> +
> +static void tbt_altmode_work(struct work_struct *work)
> +{
> + struct tbt_altmode *tbt = container_of(work, struct tbt_altmode, work);
> + int ret;
> +
> + mutex_lock(&tbt->lock);
> +
> + switch (tbt->state) {
> + case TBT_STATE_SOP_P_ENTER:
> + ret = typec_altmode_enter(tbt->plug[TYPEC_PLUG_SOP_P], NULL);
typec_cable_altmode_enter() ?
> + if (ret)
> + dev_dbg(&tbt->plug[TYPEC_PLUG_SOP_P]->dev,
> + "failed to enter mode (%d)\n", ret);
> + break;
> + case TBT_STATE_SOP_PP_ENTER:
> + ret = typec_altmode_enter(tbt->plug[TYPEC_PLUG_SOP_PP], NULL);
typec_cable_altmode_enter() ?
> + if (ret)
> + dev_dbg(&tbt->plug[TYPEC_PLUG_SOP_PP]->dev,
> + "failed to enter mode (%d)\n", ret);
> + break;
> + case TBT_STATE_ENTER:
> + ret = tbt_enter_mode(tbt);
> + if (ret)
> + dev_dbg(&tbt->alt->dev, "failed to enter mode (%d)\n",
> + ret);
> + break;
> + case TBT_STATE_EXIT:
> + typec_altmode_exit(tbt->alt);
> + break;
> + case TBT_STATE_SOP_PP_EXIT:
> + typec_altmode_exit(tbt->plug[TYPEC_PLUG_SOP_PP]);
> + break;
> + case TBT_STATE_SOP_P_EXIT:
> + typec_altmode_exit(tbt->plug[TYPEC_PLUG_SOP_P]);
> + break;
> + default:
> + break;
> + }
> +
> + tbt->state = TBT_STATE_IDLE;
> +
> + mutex_unlock(&tbt->lock);
> +}
> +
> +static int tbt_altmode_vdm(struct typec_altmode *alt,
> + const u32 hdr, const u32 *vdo, int count)
> +{
> + struct tbt_altmode *tbt = typec_altmode_get_drvdata(alt);
> + int cmd_type = PD_VDO_CMDT(hdr);
> + int cmd = PD_VDO_CMD(hdr);
> +
> + mutex_lock(&tbt->lock);
> +
> + if (tbt->state != TBT_STATE_IDLE) {
> + mutex_unlock(&tbt->lock);
> + return -EBUSY;
> + }
> +
> + switch (cmd_type) {
> + case CMDT_RSP_ACK:
> + switch (cmd) {
> + case CMD_ENTER_MODE:
> + /*
> + * Following the order describeded in USB Type-C Spec
> + * R2.0 Section 6.7.3.
> + */
> + if (alt == tbt->plug[TYPEC_PLUG_SOP_P]) {
> + if (tbt->plug[TYPEC_PLUG_SOP_PP])
> + tbt->state = TBT_STATE_SOP_PP_ENTER;
> + else
> + tbt->state = TBT_STATE_ENTER;
> + } else if (alt == tbt->plug[TYPEC_PLUG_SOP_PP]) {
> + tbt->state = TBT_STATE_ENTER;
> + } else {
> + struct typec_thunderbolt_data data;
> +
> + data.device_mode = tbt->alt->vdo;
> + data.cable_mode =
> + tbt->plug[TYPEC_PLUG_SOP_P] ?
> + tbt->plug[TYPEC_PLUG_SOP_P]
> + ->vdo :
I'd say, please move the -> vdo to the previous line, otherwise it's a
bit unreadable.
> + 0;
> + data.enter_vdo = tbt->enter_vdo;
> +
> + typec_altmode_notify(alt, TYPEC_STATE_MODAL, &data);
> + }
> + break;
> + case CMD_EXIT_MODE:
> + if (alt == tbt->alt) {
> + if (tbt->plug[TYPEC_PLUG_SOP_PP])
> + tbt->state = TBT_STATE_SOP_PP_EXIT;
> + else if (tbt->plug[TYPEC_PLUG_SOP_P])
> + tbt->state = TBT_STATE_SOP_P_EXIT;
> + } else if (alt == tbt->plug[TYPEC_PLUG_SOP_PP]) {
> + tbt->state = TBT_STATE_SOP_P_EXIT;
> + }
> + break;
> + }
> + break;
> + case CMDT_RSP_NAK:
> + switch (cmd) {
> + case CMD_ENTER_MODE:
> + dev_warn(&alt->dev, "Enter Mode refused\n");
> + break;
> + default:
> + break;
> + }
> + break;
> + default:
> + break;
> + }
> +
> + if (tbt->state != TBT_STATE_IDLE)
> + schedule_work(&tbt->work);
> +
> + mutex_unlock(&tbt->lock);
> +
> + return 0;
> +}
> +
> +static int tbt_altmode_activate(struct typec_altmode *alt, int activate)
> +{
> + struct tbt_altmode *tbt = typec_altmode_get_drvdata(alt);
> + int ret;
> +
> + mutex_lock(&tbt->lock);
> +
> + if (!tbt_ready(alt))
> + return -ENODEV;
> +
> + /* Preventing the user space from entering/exiting the cable alt mode */
> + if (alt != tbt->alt)
> + ret = -EPERM;
> + else if (activate)
> + ret = tbt_enter_mode(tbt);
> + else
> + ret = typec_altmode_exit(alt);
> +
> + mutex_unlock(&tbt->lock);
> +
> + return ret;
> +}
> +
> +static const struct typec_altmode_ops tbt_altmode_ops = {
> + .vdm = tbt_altmode_vdm,
> + .activate = tbt_altmode_activate
> +};
> +
> +static int tbt_altmode_probe(struct typec_altmode *alt)
> +{
> + struct tbt_altmode *tbt;
> +
> + tbt = devm_kzalloc(&alt->dev, sizeof(*tbt), GFP_KERNEL);
> + if (!tbt)
> + return -ENOMEM;
> +
> + INIT_WORK(&tbt->work, tbt_altmode_work);
> + mutex_init(&tbt->lock);
> + tbt->alt = alt;
> +
> + alt->desc = "Thunderbolt3";
> + typec_altmode_set_drvdata(alt, tbt);
> + typec_altmode_set_ops(alt, &tbt_altmode_ops);
> +
> + if (tbt_ready(alt)) {
> + if (tbt->plug[TYPEC_PLUG_SOP_PP])
> + tbt->state = TBT_STATE_SOP_PP_ENTER;
> + else if (tbt->plug[TYPEC_PLUG_SOP_P])
> + tbt->state = TBT_STATE_SOP_P_ENTER;
> + else
> + tbt->state = TBT_STATE_ENTER;
> + schedule_work(&tbt->work);
> + }
> +
> + return 0;
> +}
> +
> +static void tbt_altmode_remove(struct typec_altmode *alt)
> +{
> + struct tbt_altmode *tbt = typec_altmode_get_drvdata(alt);
> +
> + for (int i = TYPEC_PLUG_SOP_PP; i > 0; --i) {
> + if (tbt->plug[i])
> + typec_altmode_put_plug(tbt->plug[i]);
> + }
> +
> + if (tbt->cable)
> + typec_cable_put(tbt->cable);
> +}
> +
> +static bool tbt_ready(struct typec_altmode *alt)
> +{
> + struct tbt_altmode *tbt = typec_altmode_get_drvdata(alt);
> + struct typec_altmode *plug;
> +
> + if (tbt->cable)
> + return true;
> +
> + /* Thundebolt 3 requires a cable with eMarker */
> + tbt->cable = typec_cable_get(typec_altmode2port(tbt->alt));
> + if (!tbt->cable)
> + return false;
> +
> + /* We accept systems without SOP' or SOP''. This means the port altmode
> + * driver will be responsible for properly ordering entry/exit.
> + */
> + for (int i = 0; i < TYPEC_PLUG_SOP_PP + 1; i++) {
> + plug = typec_altmode_get_plug(tbt->alt, i);
> + if (IS_ERR(plug))
> + continue;
> +
> + if (!plug || plug->svid != USB_TYPEC_VENDOR_INTEL)
!= USB_TYPEC_TBT_SID
> + break;
> +
> + plug->desc = "Thunderbolt3";
> + plug->ops = &tbt_altmode_ops;
Should this be plug->cable_ops ?
> + typec_altmode_set_drvdata(plug, tbt);
> +
> + tbt->plug[i] = plug;
> + }
> +
> + return true;
> +}
> +
> +static const struct typec_device_id tbt_typec_id[] = {
> + { USB_TYPEC_TBT_SID },
> + { }
> +};
> +MODULE_DEVICE_TABLE(typec, tbt_typec_id);
> +
> +static struct typec_altmode_driver tbt_altmode_driver = {
> + .id_table = tbt_typec_id,
> + .probe = tbt_altmode_probe,
> + .remove = tbt_altmode_remove,
> + .driver = {
> + .name = "typec-thunderbolt",
> + .owner = THIS_MODULE,
Should not be necessary, it is set by __typec_altmode_register_driver()
> + }
> +};
> +module_typec_altmode_driver(tbt_altmode_driver);
> +
> +MODULE_AUTHOR("Heikki Krogerus <heikki.krogerus@linux.intel.com>");
> +MODULE_LICENSE("GPL");
> +MODULE_DESCRIPTION("Thunderbolt3 USB Type-C Alternate Mode");
> diff --git a/include/linux/usb/typec_tbt.h b/include/linux/usb/typec_tbt.h
> index fa97d7e00f5c..55dcea12082c 100644
> --- a/include/linux/usb/typec_tbt.h
> +++ b/include/linux/usb/typec_tbt.h
> @@ -44,6 +44,7 @@ struct typec_thunderbolt_data {
>
> #define TBT_GEN3_NON_ROUNDED 0
> #define TBT_GEN3_GEN4_ROUNDED_NON_ROUNDED 1
> +#define TBT_CABLE_ROUNDED BIT(19)
> #define TBT_CABLE_OPTICAL BIT(21)
> #define TBT_CABLE_RETIMER BIT(22)
> #define TBT_CABLE_LINK_TRAINING BIT(23)
> --
> 2.47.0.277.g8800431eea-goog
>
--
With best wishes
Dmitry
On Fri, Nov 8, 2024 at 10:21 PM Dmitry Baryshkov
<dmitry.baryshkov@linaro.org> wrote:
>
> On Thu, Nov 07, 2024 at 11:29:55AM -0800, Abhishek Pandit-Subedi wrote:
> > From: Heikki Krogerus <heikki.krogerus@linux.intel.com>
> >
> > Thunderbolt 3 Alternate Mode entry flow is described in
> > USB Type-C Specification Release 2.0.
> >
> > Signed-off-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>
> > Co-developed-by: Abhishek Pandit-Subedi <abhishekpandit@chromium.org>
> > Signed-off-by: Abhishek Pandit-Subedi <abhishekpandit@chromium.org>
> > ---
> >
> > Changes:
> > * Delay cable + plug checks so that the module doesn't fail to probe
> > if cable + plug information isn't available by the time the partner
> > altmode is registered.
> > * Remove unncessary brace after if (IS_ERR(plug))
> >
> > The rest of this patch should be the same as Heikki's original RFC.
> >
> >
> > Changes in v3:
> > - Revert rename of TYPEC_TBT_MODE
> > - Remove mode from typec_device_id
> >
> > Changes in v2:
> > - Use <linux/usb/typec_tbt.h> and add missing TBT_CABLE_ROUNDED
> > - Pass struct typec_thunderbolt_data to typec_altmode_notify
> > - Rename TYPEC_TBT_MODE to USB_TYPEC_TBT_MODE
> > - Use USB_TYPEC_TBT_SID and USB_TYPEC_TBT_MODE for device id
> > - Change module license to GPL due to checkpatch warning
> >
> > drivers/usb/typec/altmodes/Kconfig | 9 +
> > drivers/usb/typec/altmodes/Makefile | 2 +
> > drivers/usb/typec/altmodes/thunderbolt.c | 308 +++++++++++++++++++++++
> > include/linux/usb/typec_tbt.h | 1 +
> > 4 files changed, 320 insertions(+)
> > create mode 100644 drivers/usb/typec/altmodes/thunderbolt.c
> >
> > diff --git a/drivers/usb/typec/altmodes/Kconfig b/drivers/usb/typec/altmodes/Kconfig
> > index 1a6b5e872b0d..7867fa7c405d 100644
> > --- a/drivers/usb/typec/altmodes/Kconfig
> > +++ b/drivers/usb/typec/altmodes/Kconfig
> > @@ -23,4 +23,13 @@ config TYPEC_NVIDIA_ALTMODE
> > To compile this driver as a module, choose M here: the
> > module will be called typec_nvidia.
> >
> > +config TYPEC_TBT_ALTMODE
> > + tristate "Thunderbolt3 Alternate Mode driver"
> > + help
> > + Select this option if you have Thunderbolt3 hardware on your
> > + system.
> > +
> > + To compile this driver as a module, choose M here: the
> > + module will be called typec_thunderbolt.
> > +
> > endmenu
> > diff --git a/drivers/usb/typec/altmodes/Makefile b/drivers/usb/typec/altmodes/Makefile
> > index 45717548b396..508a68351bd2 100644
> > --- a/drivers/usb/typec/altmodes/Makefile
> > +++ b/drivers/usb/typec/altmodes/Makefile
> > @@ -4,3 +4,5 @@ obj-$(CONFIG_TYPEC_DP_ALTMODE) += typec_displayport.o
> > typec_displayport-y := displayport.o
> > obj-$(CONFIG_TYPEC_NVIDIA_ALTMODE) += typec_nvidia.o
> > typec_nvidia-y := nvidia.o
> > +obj-$(CONFIG_TYPEC_TBT_ALTMODE) += typec_thunderbolt.o
> > +typec_thunderbolt-y := thunderbolt.o
> > diff --git a/drivers/usb/typec/altmodes/thunderbolt.c b/drivers/usb/typec/altmodes/thunderbolt.c
> > new file mode 100644
> > index 000000000000..a945b9d35a1d
> > --- /dev/null
> > +++ b/drivers/usb/typec/altmodes/thunderbolt.c
> > @@ -0,0 +1,308 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/**
> > + * USB Typec-C Thuderbolt3 Alternate Mode driver
> > + *
> > + * Copyright (C) 2019 Intel Corporation
> > + * Author: Heikki Krogerus <heikki.krogerus@linux.intel.com>
> > + */
> > +
> > +#include <linux/delay.h>
> > +#include <linux/mutex.h>
> > +#include <linux/module.h>
> > +#include <linux/usb/pd_vdo.h>
> > +#include <linux/usb/typec_altmode.h>
> > +#include <linux/usb/typec_tbt.h>
> > +
> > +enum tbt_state {
> > + TBT_STATE_IDLE,
> > + TBT_STATE_SOP_P_ENTER,
> > + TBT_STATE_SOP_PP_ENTER,
> > + TBT_STATE_ENTER,
> > + TBT_STATE_EXIT,
> > + TBT_STATE_SOP_PP_EXIT,
> > + TBT_STATE_SOP_P_EXIT
> > +};
> > +
> > +struct tbt_altmode {
> > + enum tbt_state state;
> > + struct typec_cable *cable;
> > + struct typec_altmode *alt;
> > + struct typec_altmode *plug[2];
> > + u32 enter_vdo;
> > +
> > + struct work_struct work;
> > + struct mutex lock; /* device lock */
> > +};
> > +
> > +static bool tbt_ready(struct typec_altmode *alt);
> > +
> > +static int tbt_enter_mode(struct tbt_altmode *tbt)
> > +{
> > + struct typec_altmode *plug = tbt->plug[TYPEC_PLUG_SOP_P];
> > + u32 vdo;
> > +
> > + vdo = tbt->alt->vdo & (TBT_VENDOR_SPECIFIC_B0 | TBT_VENDOR_SPECIFIC_B1);
> > + vdo |= tbt->alt->vdo & TBT_INTEL_SPECIFIC_B0;
> > + vdo |= TBT_MODE;
> > +
> > + if (plug) {
> > + if (typec_cable_is_active(tbt->cable))
> > + vdo |= TBT_ENTER_MODE_ACTIVE_CABLE;
> > +
> > + vdo |= TBT_ENTER_MODE_CABLE_SPEED(TBT_CABLE_SPEED(plug->vdo));
> > + vdo |= plug->vdo & TBT_CABLE_ROUNDED;
> > + vdo |= plug->vdo & TBT_CABLE_OPTICAL;
> > + vdo |= plug->vdo & TBT_CABLE_RETIMER;
> > + vdo |= plug->vdo & TBT_CABLE_LINK_TRAINING;
> > + } else {
> > + vdo |= TBT_ENTER_MODE_CABLE_SPEED(TBT_CABLE_USB3_PASSIVE);
> > + }
> > +
> > + tbt->enter_vdo = vdo;
> > + return typec_altmode_enter(tbt->alt, &vdo);
> > +}
> > +
> > +static void tbt_altmode_work(struct work_struct *work)
> > +{
> > + struct tbt_altmode *tbt = container_of(work, struct tbt_altmode, work);
> > + int ret;
> > +
> > + mutex_lock(&tbt->lock);
> > +
> > + switch (tbt->state) {
> > + case TBT_STATE_SOP_P_ENTER:
> > + ret = typec_altmode_enter(tbt->plug[TYPEC_PLUG_SOP_P], NULL);
>
> typec_cable_altmode_enter() ?
>
> > + if (ret)
> > + dev_dbg(&tbt->plug[TYPEC_PLUG_SOP_P]->dev,
> > + "failed to enter mode (%d)\n", ret);
> > + break;
> > + case TBT_STATE_SOP_PP_ENTER:
> > + ret = typec_altmode_enter(tbt->plug[TYPEC_PLUG_SOP_PP], NULL);
>
> typec_cable_altmode_enter() ?
>
> > + if (ret)
> > + dev_dbg(&tbt->plug[TYPEC_PLUG_SOP_PP]->dev,
> > + "failed to enter mode (%d)\n", ret);
> > + break;
> > + case TBT_STATE_ENTER:
> > + ret = tbt_enter_mode(tbt);
> > + if (ret)
> > + dev_dbg(&tbt->alt->dev, "failed to enter mode (%d)\n",
> > + ret);
> > + break;
> > + case TBT_STATE_EXIT:
> > + typec_altmode_exit(tbt->alt);
> > + break;
> > + case TBT_STATE_SOP_PP_EXIT:
> > + typec_altmode_exit(tbt->plug[TYPEC_PLUG_SOP_PP]);
> > + break;
> > + case TBT_STATE_SOP_P_EXIT:
> > + typec_altmode_exit(tbt->plug[TYPEC_PLUG_SOP_P]);
> > + break;
> > + default:
> > + break;
> > + }
> > +
> > + tbt->state = TBT_STATE_IDLE;
> > +
> > + mutex_unlock(&tbt->lock);
> > +}
> > +
> > +static int tbt_altmode_vdm(struct typec_altmode *alt,
> > + const u32 hdr, const u32 *vdo, int count)
> > +{
> > + struct tbt_altmode *tbt = typec_altmode_get_drvdata(alt);
> > + int cmd_type = PD_VDO_CMDT(hdr);
> > + int cmd = PD_VDO_CMD(hdr);
> > +
> > + mutex_lock(&tbt->lock);
> > +
> > + if (tbt->state != TBT_STATE_IDLE) {
> > + mutex_unlock(&tbt->lock);
> > + return -EBUSY;
> > + }
> > +
> > + switch (cmd_type) {
> > + case CMDT_RSP_ACK:
> > + switch (cmd) {
> > + case CMD_ENTER_MODE:
> > + /*
> > + * Following the order describeded in USB Type-C Spec
> > + * R2.0 Section 6.7.3.
> > + */
> > + if (alt == tbt->plug[TYPEC_PLUG_SOP_P]) {
> > + if (tbt->plug[TYPEC_PLUG_SOP_PP])
> > + tbt->state = TBT_STATE_SOP_PP_ENTER;
> > + else
> > + tbt->state = TBT_STATE_ENTER;
> > + } else if (alt == tbt->plug[TYPEC_PLUG_SOP_PP]) {
> > + tbt->state = TBT_STATE_ENTER;
> > + } else {
> > + struct typec_thunderbolt_data data;
> > +
> > + data.device_mode = tbt->alt->vdo;
> > + data.cable_mode =
> > + tbt->plug[TYPEC_PLUG_SOP_P] ?
> > + tbt->plug[TYPEC_PLUG_SOP_P]
> > + ->vdo :
>
> I'd say, please move the -> vdo to the previous line, otherwise it's a
> bit unreadable.
>
> > + 0;
> > + data.enter_vdo = tbt->enter_vdo;
> > +
> > + typec_altmode_notify(alt, TYPEC_STATE_MODAL, &data);
> > + }
> > + break;
> > + case CMD_EXIT_MODE:
> > + if (alt == tbt->alt) {
> > + if (tbt->plug[TYPEC_PLUG_SOP_PP])
> > + tbt->state = TBT_STATE_SOP_PP_EXIT;
> > + else if (tbt->plug[TYPEC_PLUG_SOP_P])
> > + tbt->state = TBT_STATE_SOP_P_EXIT;
> > + } else if (alt == tbt->plug[TYPEC_PLUG_SOP_PP]) {
> > + tbt->state = TBT_STATE_SOP_P_EXIT;
> > + }
> > + break;
> > + }
> > + break;
> > + case CMDT_RSP_NAK:
> > + switch (cmd) {
> > + case CMD_ENTER_MODE:
> > + dev_warn(&alt->dev, "Enter Mode refused\n");
> > + break;
> > + default:
> > + break;
> > + }
> > + break;
> > + default:
> > + break;
> > + }
> > +
> > + if (tbt->state != TBT_STATE_IDLE)
> > + schedule_work(&tbt->work);
> > +
> > + mutex_unlock(&tbt->lock);
> > +
> > + return 0;
> > +}
> > +
> > +static int tbt_altmode_activate(struct typec_altmode *alt, int activate)
> > +{
> > + struct tbt_altmode *tbt = typec_altmode_get_drvdata(alt);
> > + int ret;
> > +
> > + mutex_lock(&tbt->lock);
> > +
> > + if (!tbt_ready(alt))
> > + return -ENODEV;
> > +
> > + /* Preventing the user space from entering/exiting the cable alt mode */
> > + if (alt != tbt->alt)
> > + ret = -EPERM;
> > + else if (activate)
> > + ret = tbt_enter_mode(tbt);
> > + else
> > + ret = typec_altmode_exit(alt);
> > +
> > + mutex_unlock(&tbt->lock);
> > +
> > + return ret;
> > +}
> > +
> > +static const struct typec_altmode_ops tbt_altmode_ops = {
> > + .vdm = tbt_altmode_vdm,
> > + .activate = tbt_altmode_activate
> > +};
> > +
> > +static int tbt_altmode_probe(struct typec_altmode *alt)
> > +{
> > + struct tbt_altmode *tbt;
> > +
> > + tbt = devm_kzalloc(&alt->dev, sizeof(*tbt), GFP_KERNEL);
> > + if (!tbt)
> > + return -ENOMEM;
> > +
> > + INIT_WORK(&tbt->work, tbt_altmode_work);
> > + mutex_init(&tbt->lock);
> > + tbt->alt = alt;
> > +
> > + alt->desc = "Thunderbolt3";
> > + typec_altmode_set_drvdata(alt, tbt);
> > + typec_altmode_set_ops(alt, &tbt_altmode_ops);
> > +
> > + if (tbt_ready(alt)) {
> > + if (tbt->plug[TYPEC_PLUG_SOP_PP])
> > + tbt->state = TBT_STATE_SOP_PP_ENTER;
> > + else if (tbt->plug[TYPEC_PLUG_SOP_P])
> > + tbt->state = TBT_STATE_SOP_P_ENTER;
> > + else
> > + tbt->state = TBT_STATE_ENTER;
> > + schedule_work(&tbt->work);
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static void tbt_altmode_remove(struct typec_altmode *alt)
> > +{
> > + struct tbt_altmode *tbt = typec_altmode_get_drvdata(alt);
> > +
> > + for (int i = TYPEC_PLUG_SOP_PP; i > 0; --i) {
> > + if (tbt->plug[i])
> > + typec_altmode_put_plug(tbt->plug[i]);
> > + }
> > +
> > + if (tbt->cable)
> > + typec_cable_put(tbt->cable);
> > +}
> > +
> > +static bool tbt_ready(struct typec_altmode *alt)
> > +{
> > + struct tbt_altmode *tbt = typec_altmode_get_drvdata(alt);
> > + struct typec_altmode *plug;
> > +
> > + if (tbt->cable)
> > + return true;
> > +
> > + /* Thundebolt 3 requires a cable with eMarker */
> > + tbt->cable = typec_cable_get(typec_altmode2port(tbt->alt));
> > + if (!tbt->cable)
> > + return false;
> > +
> > + /* We accept systems without SOP' or SOP''. This means the port altmode
> > + * driver will be responsible for properly ordering entry/exit.
> > + */
> > + for (int i = 0; i < TYPEC_PLUG_SOP_PP + 1; i++) {
> > + plug = typec_altmode_get_plug(tbt->alt, i);
> > + if (IS_ERR(plug))
> > + continue;
> > +
> > + if (!plug || plug->svid != USB_TYPEC_VENDOR_INTEL)
>
> != USB_TYPEC_TBT_SID
>
> > + break;
> > +
> > + plug->desc = "Thunderbolt3";
> > + plug->ops = &tbt_altmode_ops;
>
> Should this be plug->cable_ops ?
>
> > + typec_altmode_set_drvdata(plug, tbt);
> > +
> > + tbt->plug[i] = plug;
> > + }
> > +
> > + return true;
> > +}
> > +
> > +static const struct typec_device_id tbt_typec_id[] = {
> > + { USB_TYPEC_TBT_SID },
> > + { }
> > +};
> > +MODULE_DEVICE_TABLE(typec, tbt_typec_id);
> > +
> > +static struct typec_altmode_driver tbt_altmode_driver = {
> > + .id_table = tbt_typec_id,
> > + .probe = tbt_altmode_probe,
> > + .remove = tbt_altmode_remove,
> > + .driver = {
> > + .name = "typec-thunderbolt",
> > + .owner = THIS_MODULE,
>
> Should not be necessary, it is set by __typec_altmode_register_driver()
>
> > + }
> > +};
> > +module_typec_altmode_driver(tbt_altmode_driver);
> > +
> > +MODULE_AUTHOR("Heikki Krogerus <heikki.krogerus@linux.intel.com>");
> > +MODULE_LICENSE("GPL");
> > +MODULE_DESCRIPTION("Thunderbolt3 USB Type-C Alternate Mode");
> > diff --git a/include/linux/usb/typec_tbt.h b/include/linux/usb/typec_tbt.h
> > index fa97d7e00f5c..55dcea12082c 100644
> > --- a/include/linux/usb/typec_tbt.h
> > +++ b/include/linux/usb/typec_tbt.h
> > @@ -44,6 +44,7 @@ struct typec_thunderbolt_data {
> >
> > #define TBT_GEN3_NON_ROUNDED 0
> > #define TBT_GEN3_GEN4_ROUNDED_NON_ROUNDED 1
> > +#define TBT_CABLE_ROUNDED BIT(19)
> > #define TBT_CABLE_OPTICAL BIT(21)
> > #define TBT_CABLE_RETIMER BIT(22)
> > #define TBT_CABLE_LINK_TRAINING BIT(23)
> > --
> > 2.47.0.277.g8800431eea-goog
> >
>
> --
> With best wishes
> Dmitry
Will address all comments in the next series.
The cable alt-modes are new to me. Looking at the implementation, one
part confuses me -- it looks like cable alt-modes expect the partner
to already be active when entering
(https://github.com/torvalds/linux/blob/master/drivers/usb/typec/bus.c#L272).
I'll reach out to the author of cable altmodes to see how to integrate
this correctly with the current series.
© 2016 - 2026 Red Hat, Inc.