xref: /linux/drivers/usb/typec/altmodes/thunderbolt.c (revision 00afb1811fa638dacf125dd1c343b7a181624dfd)
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 
tbt_enter_mode(struct tbt_altmode * tbt)40 static int tbt_enter_mode(struct tbt_altmode *tbt)
41 {
42 	return typec_altmode_enter(tbt->alt, &tbt->enter_vdo);
43 }
44 
tbt_altmode_work(struct work_struct * work)45 static 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 
93 disable_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  */
tbt_enter_modes_ordered(struct typec_altmode * alt)112 static 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 
tbt_cable_altmode_vdm(struct typec_altmode * alt,enum typec_plug_index sop,const u32 hdr,const u32 * vdo,int count)139 static 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 
tbt_altmode_vdm(struct typec_altmode * alt,const u32 hdr,const u32 * vdo,int count)189 static 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 
tbt_altmode_activate(struct typec_altmode * alt,int activate)247 static 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 
264 static const struct typec_altmode_ops tbt_altmode_ops = {
265 	.vdm		= tbt_altmode_vdm,
266 	.activate	= tbt_altmode_activate
267 };
268 
269 static const struct typec_cable_ops tbt_cable_ops = {
270 	.vdm		= tbt_cable_altmode_vdm,
271 };
272 
tbt_altmode_probe(struct typec_altmode * alt)273 static 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 (!alt->mode_selection && 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 
tbt_altmode_remove(struct typec_altmode * alt)302 static 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 
tbt_ready(struct typec_altmode * alt)315 static 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 
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");
389