Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

usb: typec: Add driver for Thunderbolt 3 Alternate Mode

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>
Reviewed-by: Benson Leung <bleung@chromium.org>
Link: https://lore.kernel.org/r/20241213153543.v5.2.I3080b036e8de0b9957c57c1c3059db7149c5e549@changeid
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>

authored by

Heikki Krogerus and committed by
Greg Kroah-Hartman
100e2573 8541bf02

+400
+9
drivers/usb/typec/altmodes/Kconfig
··· 23 23 To compile this driver as a module, choose M here: the 24 24 module will be called typec_nvidia. 25 25 26 + config TYPEC_TBT_ALTMODE 27 + tristate "Thunderbolt3 Alternate Mode driver" 28 + help 29 + Select this option if you have Thunderbolt3 hardware on your 30 + system. 31 + 32 + To compile this driver as a module, choose M here: the 33 + module will be called typec_thunderbolt. 34 + 26 35 endmenu
+2
drivers/usb/typec/altmodes/Makefile
··· 4 4 typec_displayport-y := displayport.o 5 5 obj-$(CONFIG_TYPEC_NVIDIA_ALTMODE) += typec_nvidia.o 6 6 typec_nvidia-y := nvidia.o 7 + obj-$(CONFIG_TYPEC_TBT_ALTMODE) += typec_thunderbolt.o 8 + typec_thunderbolt-y := thunderbolt.o
+388
drivers/usb/typec/altmodes/thunderbolt.c
··· 1 + // SPDX-License-Identifier: GPL-2.0 2 + /* 3 + * USB Typec-C Thunderbolt3 Alternate Mode driver 4 + * 5 + * Copyright (C) 2019 Intel Corporation 6 + * Author: Heikki Krogerus <heikki.krogerus@linux.intel.com> 7 + */ 8 + 9 + #include <linux/lockdep.h> 10 + #include <linux/module.h> 11 + #include <linux/mutex.h> 12 + #include <linux/workqueue.h> 13 + #include <linux/usb/pd_vdo.h> 14 + #include <linux/usb/typec_altmode.h> 15 + #include <linux/usb/typec_tbt.h> 16 + 17 + enum tbt_state { 18 + TBT_STATE_IDLE, 19 + TBT_STATE_SOP_P_ENTER, 20 + TBT_STATE_SOP_PP_ENTER, 21 + TBT_STATE_ENTER, 22 + TBT_STATE_EXIT, 23 + TBT_STATE_SOP_PP_EXIT, 24 + TBT_STATE_SOP_P_EXIT 25 + }; 26 + 27 + struct tbt_altmode { 28 + enum tbt_state state; 29 + struct typec_cable *cable; 30 + struct typec_altmode *alt; 31 + struct typec_altmode *plug[2]; 32 + u32 enter_vdo; 33 + 34 + struct work_struct work; 35 + struct mutex lock; /* device lock */ 36 + }; 37 + 38 + static bool tbt_ready(struct typec_altmode *alt); 39 + 40 + static int tbt_enter_mode(struct tbt_altmode *tbt) 41 + { 42 + struct typec_altmode *plug = tbt->plug[TYPEC_PLUG_SOP_P]; 43 + u32 vdo; 44 + 45 + vdo = tbt->alt->vdo & (TBT_VENDOR_SPECIFIC_B0 | TBT_VENDOR_SPECIFIC_B1); 46 + vdo |= tbt->alt->vdo & TBT_INTEL_SPECIFIC_B0; 47 + vdo |= TBT_MODE; 48 + 49 + if (plug) { 50 + if (typec_cable_is_active(tbt->cable)) 51 + vdo |= TBT_ENTER_MODE_ACTIVE_CABLE; 52 + 53 + vdo |= TBT_ENTER_MODE_CABLE_SPEED(TBT_CABLE_SPEED(plug->vdo)); 54 + vdo |= plug->vdo & TBT_CABLE_ROUNDED; 55 + vdo |= plug->vdo & TBT_CABLE_OPTICAL; 56 + vdo |= plug->vdo & TBT_CABLE_RETIMER; 57 + vdo |= plug->vdo & TBT_CABLE_LINK_TRAINING; 58 + } else { 59 + vdo |= TBT_ENTER_MODE_CABLE_SPEED(TBT_CABLE_USB3_PASSIVE); 60 + } 61 + 62 + tbt->enter_vdo = vdo; 63 + return typec_altmode_enter(tbt->alt, &vdo); 64 + } 65 + 66 + static void tbt_altmode_work(struct work_struct *work) 67 + { 68 + struct tbt_altmode *tbt = container_of(work, struct tbt_altmode, work); 69 + int ret; 70 + 71 + mutex_lock(&tbt->lock); 72 + 73 + switch (tbt->state) { 74 + case TBT_STATE_SOP_P_ENTER: 75 + ret = typec_cable_altmode_enter(tbt->alt, TYPEC_PLUG_SOP_P, NULL); 76 + if (ret) { 77 + dev_dbg(&tbt->plug[TYPEC_PLUG_SOP_P]->dev, 78 + "failed to enter mode (%d)\n", ret); 79 + goto disable_plugs; 80 + } 81 + break; 82 + case TBT_STATE_SOP_PP_ENTER: 83 + ret = typec_cable_altmode_enter(tbt->alt, TYPEC_PLUG_SOP_PP, NULL); 84 + if (ret) { 85 + dev_dbg(&tbt->plug[TYPEC_PLUG_SOP_PP]->dev, 86 + "failed to enter mode (%d)\n", ret); 87 + goto disable_plugs; 88 + } 89 + break; 90 + case TBT_STATE_ENTER: 91 + ret = tbt_enter_mode(tbt); 92 + if (ret) 93 + dev_dbg(&tbt->alt->dev, "failed to enter mode (%d)\n", 94 + ret); 95 + break; 96 + case TBT_STATE_EXIT: 97 + typec_altmode_exit(tbt->alt); 98 + break; 99 + case TBT_STATE_SOP_PP_EXIT: 100 + typec_cable_altmode_exit(tbt->alt, TYPEC_PLUG_SOP_PP); 101 + break; 102 + case TBT_STATE_SOP_P_EXIT: 103 + typec_cable_altmode_exit(tbt->alt, TYPEC_PLUG_SOP_P); 104 + break; 105 + default: 106 + break; 107 + } 108 + 109 + tbt->state = TBT_STATE_IDLE; 110 + 111 + mutex_unlock(&tbt->lock); 112 + return; 113 + 114 + disable_plugs: 115 + for (int i = TYPEC_PLUG_SOP_PP; i > 0; --i) { 116 + if (tbt->plug[i]) 117 + typec_altmode_put_plug(tbt->plug[i]); 118 + 119 + tbt->plug[i] = NULL; 120 + } 121 + 122 + tbt->state = TBT_STATE_ENTER; 123 + schedule_work(&tbt->work); 124 + mutex_unlock(&tbt->lock); 125 + } 126 + 127 + /* 128 + * If SOP' is available, enter that first (which will trigger a VDM response 129 + * that will enter SOP" if available and then the port). If entering SOP' fails, 130 + * stop attempting to enter either cable altmode (probably not supported) and 131 + * directly enter the port altmode. 132 + */ 133 + static int tbt_enter_modes_ordered(struct typec_altmode *alt) 134 + { 135 + struct tbt_altmode *tbt = typec_altmode_get_drvdata(alt); 136 + int ret = 0; 137 + 138 + lockdep_assert_held(&tbt->lock); 139 + 140 + if (!tbt_ready(tbt->alt)) 141 + return -ENODEV; 142 + 143 + if (tbt->plug[TYPEC_PLUG_SOP_P]) { 144 + ret = typec_cable_altmode_enter(alt, TYPEC_PLUG_SOP_P, NULL); 145 + if (ret < 0) { 146 + for (int i = TYPEC_PLUG_SOP_PP; i > 0; --i) { 147 + if (tbt->plug[i]) 148 + typec_altmode_put_plug(tbt->plug[i]); 149 + 150 + tbt->plug[i] = NULL; 151 + } 152 + } else { 153 + return ret; 154 + } 155 + } 156 + 157 + return tbt_enter_mode(tbt); 158 + } 159 + 160 + static int tbt_cable_altmode_vdm(struct typec_altmode *alt, 161 + enum typec_plug_index sop, const u32 hdr, 162 + const u32 *vdo, int count) 163 + { 164 + struct tbt_altmode *tbt = typec_altmode_get_drvdata(alt); 165 + int cmd_type = PD_VDO_CMDT(hdr); 166 + int cmd = PD_VDO_CMD(hdr); 167 + 168 + mutex_lock(&tbt->lock); 169 + 170 + if (tbt->state != TBT_STATE_IDLE) { 171 + mutex_unlock(&tbt->lock); 172 + return -EBUSY; 173 + } 174 + 175 + switch (cmd_type) { 176 + case CMDT_RSP_ACK: 177 + switch (cmd) { 178 + case CMD_ENTER_MODE: 179 + /* 180 + * Following the order described in USB Type-C Spec 181 + * R2.0 Section 6.7.3: SOP', SOP", then port. 182 + */ 183 + if (sop == TYPEC_PLUG_SOP_P) { 184 + if (tbt->plug[TYPEC_PLUG_SOP_PP]) 185 + tbt->state = TBT_STATE_SOP_PP_ENTER; 186 + else 187 + tbt->state = TBT_STATE_ENTER; 188 + } else if (sop == TYPEC_PLUG_SOP_PP) 189 + tbt->state = TBT_STATE_ENTER; 190 + 191 + break; 192 + case CMD_EXIT_MODE: 193 + /* Exit in opposite order: Port, SOP", then SOP'. */ 194 + if (sop == TYPEC_PLUG_SOP_PP) 195 + tbt->state = TBT_STATE_SOP_P_EXIT; 196 + break; 197 + } 198 + break; 199 + default: 200 + break; 201 + } 202 + 203 + if (tbt->state != TBT_STATE_IDLE) 204 + schedule_work(&tbt->work); 205 + 206 + mutex_unlock(&tbt->lock); 207 + return 0; 208 + } 209 + 210 + static int tbt_altmode_vdm(struct typec_altmode *alt, 211 + const u32 hdr, const u32 *vdo, int count) 212 + { 213 + struct tbt_altmode *tbt = typec_altmode_get_drvdata(alt); 214 + struct typec_thunderbolt_data data; 215 + int cmd_type = PD_VDO_CMDT(hdr); 216 + int cmd = PD_VDO_CMD(hdr); 217 + 218 + mutex_lock(&tbt->lock); 219 + 220 + if (tbt->state != TBT_STATE_IDLE) { 221 + mutex_unlock(&tbt->lock); 222 + return -EBUSY; 223 + } 224 + 225 + switch (cmd_type) { 226 + case CMDT_RSP_ACK: 227 + /* Port altmode is last to enter and first to exit. */ 228 + switch (cmd) { 229 + case CMD_ENTER_MODE: 230 + memset(&data, 0, sizeof(data)); 231 + 232 + data.device_mode = tbt->alt->vdo; 233 + data.enter_vdo = tbt->enter_vdo; 234 + if (tbt->plug[TYPEC_PLUG_SOP_P]) 235 + data.cable_mode = tbt->plug[TYPEC_PLUG_SOP_P]->vdo; 236 + 237 + typec_altmode_notify(alt, TYPEC_STATE_MODAL, &data); 238 + break; 239 + case CMD_EXIT_MODE: 240 + if (tbt->plug[TYPEC_PLUG_SOP_PP]) 241 + tbt->state = TBT_STATE_SOP_PP_EXIT; 242 + else if (tbt->plug[TYPEC_PLUG_SOP_P]) 243 + tbt->state = TBT_STATE_SOP_P_EXIT; 244 + break; 245 + } 246 + break; 247 + case CMDT_RSP_NAK: 248 + switch (cmd) { 249 + case CMD_ENTER_MODE: 250 + dev_warn(&alt->dev, "Enter Mode refused\n"); 251 + break; 252 + default: 253 + break; 254 + } 255 + break; 256 + default: 257 + break; 258 + } 259 + 260 + if (tbt->state != TBT_STATE_IDLE) 261 + schedule_work(&tbt->work); 262 + 263 + mutex_unlock(&tbt->lock); 264 + 265 + return 0; 266 + } 267 + 268 + static int tbt_altmode_activate(struct typec_altmode *alt, int activate) 269 + { 270 + struct tbt_altmode *tbt = typec_altmode_get_drvdata(alt); 271 + int ret; 272 + 273 + mutex_lock(&tbt->lock); 274 + 275 + if (activate) 276 + ret = tbt_enter_modes_ordered(alt); 277 + else 278 + ret = typec_altmode_exit(alt); 279 + 280 + mutex_unlock(&tbt->lock); 281 + 282 + return ret; 283 + } 284 + 285 + static const struct typec_altmode_ops tbt_altmode_ops = { 286 + .vdm = tbt_altmode_vdm, 287 + .activate = tbt_altmode_activate 288 + }; 289 + 290 + static const struct typec_cable_ops tbt_cable_ops = { 291 + .vdm = tbt_cable_altmode_vdm, 292 + }; 293 + 294 + static int tbt_altmode_probe(struct typec_altmode *alt) 295 + { 296 + struct tbt_altmode *tbt; 297 + 298 + tbt = devm_kzalloc(&alt->dev, sizeof(*tbt), GFP_KERNEL); 299 + if (!tbt) 300 + return -ENOMEM; 301 + 302 + INIT_WORK(&tbt->work, tbt_altmode_work); 303 + mutex_init(&tbt->lock); 304 + tbt->alt = alt; 305 + 306 + alt->desc = "Thunderbolt3"; 307 + typec_altmode_set_drvdata(alt, tbt); 308 + typec_altmode_set_ops(alt, &tbt_altmode_ops); 309 + 310 + if (tbt_ready(alt)) { 311 + if (tbt->plug[TYPEC_PLUG_SOP_P]) 312 + tbt->state = TBT_STATE_SOP_P_ENTER; 313 + else if (tbt->plug[TYPEC_PLUG_SOP_PP]) 314 + tbt->state = TBT_STATE_SOP_PP_ENTER; 315 + else 316 + tbt->state = TBT_STATE_ENTER; 317 + schedule_work(&tbt->work); 318 + } 319 + 320 + return 0; 321 + } 322 + 323 + static void tbt_altmode_remove(struct typec_altmode *alt) 324 + { 325 + struct tbt_altmode *tbt = typec_altmode_get_drvdata(alt); 326 + 327 + for (int i = TYPEC_PLUG_SOP_PP; i > 0; --i) { 328 + if (tbt->plug[i]) 329 + typec_altmode_put_plug(tbt->plug[i]); 330 + } 331 + 332 + if (tbt->cable) 333 + typec_cable_put(tbt->cable); 334 + } 335 + 336 + static bool tbt_ready(struct typec_altmode *alt) 337 + { 338 + struct tbt_altmode *tbt = typec_altmode_get_drvdata(alt); 339 + struct typec_altmode *plug; 340 + 341 + if (tbt->cable) 342 + return true; 343 + 344 + /* Thunderbolt 3 requires a cable with eMarker */ 345 + tbt->cable = typec_cable_get(typec_altmode2port(tbt->alt)); 346 + if (!tbt->cable) 347 + return false; 348 + 349 + /* We accept systems without SOP' or SOP''. This means the port altmode 350 + * driver will be responsible for properly ordering entry/exit. 351 + */ 352 + for (int i = 0; i < TYPEC_PLUG_SOP_PP + 1; i++) { 353 + plug = typec_altmode_get_plug(tbt->alt, i); 354 + if (IS_ERR(plug)) 355 + continue; 356 + 357 + if (!plug || plug->svid != USB_TYPEC_TBT_SID) 358 + break; 359 + 360 + plug->desc = "Thunderbolt3"; 361 + plug->cable_ops = &tbt_cable_ops; 362 + typec_altmode_set_drvdata(plug, tbt); 363 + 364 + tbt->plug[i] = plug; 365 + } 366 + 367 + return true; 368 + } 369 + 370 + static const struct typec_device_id tbt_typec_id[] = { 371 + { USB_TYPEC_TBT_SID }, 372 + { } 373 + }; 374 + MODULE_DEVICE_TABLE(typec, tbt_typec_id); 375 + 376 + static struct typec_altmode_driver tbt_altmode_driver = { 377 + .id_table = tbt_typec_id, 378 + .probe = tbt_altmode_probe, 379 + .remove = tbt_altmode_remove, 380 + .driver = { 381 + .name = "typec-thunderbolt", 382 + } 383 + }; 384 + module_typec_altmode_driver(tbt_altmode_driver); 385 + 386 + MODULE_AUTHOR("Heikki Krogerus <heikki.krogerus@linux.intel.com>"); 387 + MODULE_LICENSE("GPL"); 388 + MODULE_DESCRIPTION("Thunderbolt3 USB Type-C Alternate Mode");
+1
include/linux/usb/typec_tbt.h
··· 44 44 45 45 #define TBT_GEN3_NON_ROUNDED 0 46 46 #define TBT_GEN3_GEN4_ROUNDED_NON_ROUNDED 1 47 + #define TBT_CABLE_ROUNDED BIT(19) 47 48 #define TBT_CABLE_OPTICAL BIT(21) 48 49 #define TBT_CABLE_RETIMER BIT(22) 49 50 #define TBT_CABLE_LINK_TRAINING BIT(23)