]> Git Repo - linux.git/blob - drivers/usb/typec/altmodes/thunderbolt.c
Linux 6.14-rc3
[linux.git] / 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 <[email protected]>
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 <[email protected]>");
387 MODULE_LICENSE("GPL");
388 MODULE_DESCRIPTION("Thunderbolt3 USB Type-C Alternate Mode");
This page took 0.050404 seconds and 4 git commands to generate.