Message ID | 20241107112955.v3.2.I3080b036e8de0b9957c57c1c3059db7149c5e549@changeid |
---|---|
State | New |
Headers | show |
Series | Thunderbolt and DP altmode support for cros-ec-typec | expand |
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 >
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.
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 > >
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)