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 
tbt_enter_mode(struct tbt_altmode * tbt)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 
tbt_altmode_work(struct work_struct * work)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  */
tbt_enter_modes_ordered(struct typec_altmode * alt)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 
tbt_cable_altmode_vdm(struct typec_altmode * alt,enum typec_plug_index sop,const u32 hdr,const u32 * vdo,int count)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 
tbt_altmode_vdm(struct typec_altmode * alt,const u32 hdr,const u32 * vdo,int count)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 
tbt_altmode_activate(struct typec_altmode * alt,int activate)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 
tbt_altmode_probe(struct typec_altmode * alt)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 
tbt_altmode_remove(struct typec_altmode * alt)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 
tbt_ready(struct typec_altmode * alt)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 (!plug)
355 			continue;
356 
357 		if (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");
389