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