usb: typec: Block mode entry if the port has the mode disabled
[linux-block.git] / drivers / usb / typec / altmodes / displayport.c
CommitLineData
0e3bb7d6
HK
1// SPDX-License-Identifier: GPL-2.0
2/**
3 * USB Typec-C DisplayPort Alternate Mode driver
4 *
5 * Copyright (C) 2018 Intel Corporation
6 * Author: Heikki Krogerus <heikki.krogerus@linux.intel.com>
7 *
8 * DisplayPort is trademark of VESA (www.vesa.org)
9 */
10
11#include <linux/delay.h>
12#include <linux/mutex.h>
13#include <linux/module.h>
14#include <linux/usb/pd_vdo.h>
15#include <linux/usb/typec_dp.h>
16
d266e968 17#define DP_HEADER(_dp, cmd) (VDO((_dp)->alt->svid, 1, cmd) | \
0e3bb7d6
HK
18 VDO_OPOS(USB_TYPEC_DP_MODE))
19
20enum {
21 DP_CONF_USB,
22 DP_CONF_DFP_D,
23 DP_CONF_UFP_D,
24 DP_CONF_DUAL_D,
25};
26
0e3bb7d6
HK
27/* Pin assignments that use USB3.1 Gen2 signaling to carry DP protocol */
28#define DP_PIN_ASSIGN_GEN2_BR_MASK (BIT(DP_PIN_ASSIGN_A) | \
29 BIT(DP_PIN_ASSIGN_B))
30
31/* Pin assignments that use DP v1.3 signaling to carry DP protocol */
32#define DP_PIN_ASSIGN_DP_BR_MASK (BIT(DP_PIN_ASSIGN_C) | \
33 BIT(DP_PIN_ASSIGN_D) | \
34 BIT(DP_PIN_ASSIGN_E) | \
35 BIT(DP_PIN_ASSIGN_F))
36
37/* DP only pin assignments */
38#define DP_PIN_ASSIGN_DP_ONLY_MASK (BIT(DP_PIN_ASSIGN_A) | \
39 BIT(DP_PIN_ASSIGN_C) | \
40 BIT(DP_PIN_ASSIGN_E))
41
42/* Pin assignments where one channel is for USB */
43#define DP_PIN_ASSIGN_MULTI_FUNC_MASK (BIT(DP_PIN_ASSIGN_B) | \
44 BIT(DP_PIN_ASSIGN_D) | \
45 BIT(DP_PIN_ASSIGN_F))
46
47enum dp_state {
48 DP_STATE_IDLE,
49 DP_STATE_ENTER,
50 DP_STATE_UPDATE,
51 DP_STATE_CONFIGURE,
52 DP_STATE_EXIT,
53};
54
55struct dp_altmode {
56 struct typec_displayport_data data;
57
58 enum dp_state state;
59
60 struct mutex lock; /* device lock */
61 struct work_struct work;
62 struct typec_altmode *alt;
63 const struct typec_altmode *port;
64};
65
66static int dp_altmode_notify(struct dp_altmode *dp)
67{
68 u8 state = get_count_order(DP_CONF_GET_PIN_ASSIGN(dp->data.conf));
69
70 return typec_altmode_notify(dp->alt, TYPEC_MODAL_STATE(state),
71 &dp->data);
72}
73
74static int dp_altmode_configure(struct dp_altmode *dp, u8 con)
75{
76 u32 conf = DP_CONF_SIGNALING_DP; /* Only DP signaling supported */
77 u8 pin_assign = 0;
78
79 switch (con) {
80 case DP_STATUS_CON_DISABLED:
81 return 0;
82 case DP_STATUS_CON_DFP_D:
83 conf |= DP_CONF_UFP_U_AS_DFP_D;
84 pin_assign = DP_CAP_UFP_D_PIN_ASSIGN(dp->alt->vdo) &
85 DP_CAP_DFP_D_PIN_ASSIGN(dp->port->vdo);
86 break;
87 case DP_STATUS_CON_UFP_D:
88 case DP_STATUS_CON_BOTH: /* NOTE: First acting as DP source */
89 conf |= DP_CONF_UFP_U_AS_UFP_D;
90 pin_assign = DP_CAP_DFP_D_PIN_ASSIGN(dp->alt->vdo) &
91 DP_CAP_UFP_D_PIN_ASSIGN(dp->port->vdo);
92 break;
93 default:
94 break;
95 }
96
97 /* Determining the initial pin assignment. */
98 if (!DP_CONF_GET_PIN_ASSIGN(dp->data.conf)) {
99 /* Is USB together with DP preferred */
100 if (dp->data.status & DP_STATUS_PREFER_MULTI_FUNC &&
101 pin_assign & DP_PIN_ASSIGN_MULTI_FUNC_MASK)
102 pin_assign &= DP_PIN_ASSIGN_MULTI_FUNC_MASK;
09fed4d6 103 else if (pin_assign & DP_PIN_ASSIGN_DP_ONLY_MASK)
0e3bb7d6
HK
104 pin_assign &= DP_PIN_ASSIGN_DP_ONLY_MASK;
105
106 if (!pin_assign)
107 return -EINVAL;
108
109 conf |= DP_CONF_SET_PIN_ASSIGN(pin_assign);
110 }
111
112 dp->data.conf = conf;
113
114 return 0;
115}
116
117static int dp_altmode_status_update(struct dp_altmode *dp)
118{
119 bool configured = !!DP_CONF_GET_PIN_ASSIGN(dp->data.conf);
120 u8 con = DP_STATUS_CONNECTION(dp->data.status);
121 int ret = 0;
122
123 if (configured && (dp->data.status & DP_STATUS_SWITCH_TO_USB)) {
124 dp->data.conf = 0;
125 dp->state = DP_STATE_CONFIGURE;
126 } else if (dp->data.status & DP_STATUS_EXIT_DP_MODE) {
127 dp->state = DP_STATE_EXIT;
128 } else if (!(con & DP_CONF_CURRENTLY(dp->data.conf))) {
129 ret = dp_altmode_configure(dp, con);
130 if (!ret)
131 dp->state = DP_STATE_CONFIGURE;
132 }
133
134 return ret;
135}
136
137static int dp_altmode_configured(struct dp_altmode *dp)
138{
139 int ret;
140
141 sysfs_notify(&dp->alt->dev.kobj, "displayport", "configuration");
142
143 if (!dp->data.conf)
144 return typec_altmode_notify(dp->alt, TYPEC_STATE_USB,
145 &dp->data);
146
147 ret = dp_altmode_notify(dp);
148 if (ret)
149 return ret;
150
151 sysfs_notify(&dp->alt->dev.kobj, "displayport", "pin_assignment");
152
153 return 0;
154}
155
156static int dp_altmode_configure_vdm(struct dp_altmode *dp, u32 conf)
157{
d266e968 158 u32 header = DP_HEADER(dp, DP_CMD_CONFIGURE);
0e3bb7d6
HK
159 int ret;
160
161 ret = typec_altmode_notify(dp->alt, TYPEC_STATE_SAFE, &dp->data);
162 if (ret) {
163 dev_err(&dp->alt->dev,
164 "unable to put to connector to safe mode\n");
165 return ret;
166 }
167
168 ret = typec_altmode_vdm(dp->alt, header, &conf, 2);
169 if (ret) {
170 if (DP_CONF_GET_PIN_ASSIGN(dp->data.conf))
171 dp_altmode_notify(dp);
172 else
173 typec_altmode_notify(dp->alt, TYPEC_STATE_USB,
174 &dp->data);
175 }
176
177 return ret;
178}
179
180static void dp_altmode_work(struct work_struct *work)
181{
182 struct dp_altmode *dp = container_of(work, struct dp_altmode, work);
183 u32 header;
184 u32 vdo;
185 int ret;
186
187 mutex_lock(&dp->lock);
188
189 switch (dp->state) {
190 case DP_STATE_ENTER:
191 ret = typec_altmode_enter(dp->alt);
192 if (ret)
193 dev_err(&dp->alt->dev, "failed to enter mode\n");
194 break;
195 case DP_STATE_UPDATE:
d266e968 196 header = DP_HEADER(dp, DP_CMD_STATUS_UPDATE);
0e3bb7d6
HK
197 vdo = 1;
198 ret = typec_altmode_vdm(dp->alt, header, &vdo, 2);
199 if (ret)
200 dev_err(&dp->alt->dev,
201 "unable to send Status Update command (%d)\n",
202 ret);
203 break;
204 case DP_STATE_CONFIGURE:
205 ret = dp_altmode_configure_vdm(dp, dp->data.conf);
206 if (ret)
207 dev_err(&dp->alt->dev,
208 "unable to send Configure command (%d)\n", ret);
209 break;
210 case DP_STATE_EXIT:
211 if (typec_altmode_exit(dp->alt))
212 dev_err(&dp->alt->dev, "Exit Mode Failed!\n");
213 break;
214 default:
215 break;
216 }
217
218 dp->state = DP_STATE_IDLE;
219
220 mutex_unlock(&dp->lock);
221}
222
223static void dp_altmode_attention(struct typec_altmode *alt, const u32 vdo)
224{
225 struct dp_altmode *dp = typec_altmode_get_drvdata(alt);
226 u8 old_state;
227
228 mutex_lock(&dp->lock);
229
230 old_state = dp->state;
231 dp->data.status = vdo;
232
233 if (old_state != DP_STATE_IDLE)
234 dev_warn(&alt->dev, "ATTENTION while processing state %d\n",
235 old_state);
236
237 if (dp_altmode_status_update(dp))
238 dev_warn(&alt->dev, "%s: status update failed\n", __func__);
239
240 if (dp_altmode_notify(dp))
241 dev_err(&alt->dev, "%s: notification failed\n", __func__);
242
243 if (old_state == DP_STATE_IDLE && dp->state != DP_STATE_IDLE)
244 schedule_work(&dp->work);
245
246 mutex_unlock(&dp->lock);
247}
248
249static int dp_altmode_vdm(struct typec_altmode *alt,
250 const u32 hdr, const u32 *vdo, int count)
251{
252 struct dp_altmode *dp = typec_altmode_get_drvdata(alt);
253 int cmd_type = PD_VDO_CMDT(hdr);
254 int cmd = PD_VDO_CMD(hdr);
255 int ret = 0;
256
257 mutex_lock(&dp->lock);
258
259 if (dp->state != DP_STATE_IDLE) {
260 ret = -EBUSY;
261 goto err_unlock;
262 }
263
264 switch (cmd_type) {
265 case CMDT_RSP_ACK:
266 switch (cmd) {
267 case CMD_ENTER_MODE:
268 dp->state = DP_STATE_UPDATE;
269 break;
270 case CMD_EXIT_MODE:
271 dp->data.status = 0;
272 dp->data.conf = 0;
273 break;
274 case DP_CMD_STATUS_UPDATE:
275 dp->data.status = *vdo;
276 ret = dp_altmode_status_update(dp);
277 break;
278 case DP_CMD_CONFIGURE:
279 ret = dp_altmode_configured(dp);
280 break;
281 default:
282 break;
283 }
284 break;
285 case CMDT_RSP_NAK:
286 switch (cmd) {
287 case DP_CMD_CONFIGURE:
288 dp->data.conf = 0;
289 ret = dp_altmode_configured(dp);
290 break;
291 default:
292 break;
293 }
294 break;
295 default:
296 break;
297 }
298
299 if (dp->state != DP_STATE_IDLE)
300 schedule_work(&dp->work);
301
302err_unlock:
303 mutex_unlock(&dp->lock);
304 return ret;
305}
306
307static int dp_altmode_activate(struct typec_altmode *alt, int activate)
308{
309 return activate ? typec_altmode_enter(alt) : typec_altmode_exit(alt);
310}
311
312static const struct typec_altmode_ops dp_altmode_ops = {
313 .attention = dp_altmode_attention,
314 .vdm = dp_altmode_vdm,
315 .activate = dp_altmode_activate,
316};
317
318static const char * const configurations[] = {
319 [DP_CONF_USB] = "USB",
320 [DP_CONF_DFP_D] = "source",
321 [DP_CONF_UFP_D] = "sink",
322};
323
324static ssize_t
325configuration_store(struct device *dev, struct device_attribute *attr,
326 const char *buf, size_t size)
327{
328 struct dp_altmode *dp = dev_get_drvdata(dev);
329 u32 conf;
330 u32 cap;
331 int con;
98a1a0c7 332 int ret = 0;
0e3bb7d6
HK
333
334 con = sysfs_match_string(configurations, buf);
335 if (con < 0)
336 return con;
337
338 mutex_lock(&dp->lock);
339
340 if (dp->state != DP_STATE_IDLE) {
341 ret = -EBUSY;
342 goto err_unlock;
343 }
344
345 cap = DP_CAP_CAPABILITY(dp->alt->vdo);
346
347 if ((con == DP_CONF_DFP_D && !(cap & DP_CAP_DFP_D)) ||
98a1a0c7
CIK
348 (con == DP_CONF_UFP_D && !(cap & DP_CAP_UFP_D))) {
349 ret = -EINVAL;
350 goto err_unlock;
351 }
0e3bb7d6
HK
352
353 conf = dp->data.conf & ~DP_CONF_DUAL_D;
354 conf |= con;
355
356 if (dp->alt->active) {
357 ret = dp_altmode_configure_vdm(dp, conf);
358 if (ret)
359 goto err_unlock;
360 }
361
362 dp->data.conf = conf;
363
364err_unlock:
365 mutex_unlock(&dp->lock);
366
367 return ret ? ret : size;
368}
369
370static ssize_t configuration_show(struct device *dev,
371 struct device_attribute *attr, char *buf)
372{
373 struct dp_altmode *dp = dev_get_drvdata(dev);
374 int len;
375 u8 cap;
376 u8 cur;
377 int i;
378
379 mutex_lock(&dp->lock);
380
381 cap = DP_CAP_CAPABILITY(dp->alt->vdo);
382 cur = DP_CONF_CURRENTLY(dp->data.conf);
383
384 len = sprintf(buf, "%s ", cur ? "USB" : "[USB]");
385
386 for (i = 1; i < ARRAY_SIZE(configurations); i++) {
387 if (i == cur)
388 len += sprintf(buf + len, "[%s] ", configurations[i]);
389 else if ((i == DP_CONF_DFP_D && cap & DP_CAP_DFP_D) ||
390 (i == DP_CONF_UFP_D && cap & DP_CAP_UFP_D))
391 len += sprintf(buf + len, "%s ", configurations[i]);
392 }
393
394 mutex_unlock(&dp->lock);
395
396 buf[len - 1] = '\n';
397 return len;
398}
399static DEVICE_ATTR_RW(configuration);
400
401static const char * const pin_assignments[] = {
402 [DP_PIN_ASSIGN_A] = "A",
403 [DP_PIN_ASSIGN_B] = "B",
404 [DP_PIN_ASSIGN_C] = "C",
405 [DP_PIN_ASSIGN_D] = "D",
406 [DP_PIN_ASSIGN_E] = "E",
407 [DP_PIN_ASSIGN_F] = "F",
408};
409
410static ssize_t
411pin_assignment_store(struct device *dev, struct device_attribute *attr,
412 const char *buf, size_t size)
413{
414 struct dp_altmode *dp = dev_get_drvdata(dev);
415 u8 assignments;
416 u32 conf;
417 int ret;
418
419 ret = sysfs_match_string(pin_assignments, buf);
420 if (ret < 0)
421 return ret;
422
423 conf = DP_CONF_SET_PIN_ASSIGN(BIT(ret));
424 ret = 0;
425
426 mutex_lock(&dp->lock);
427
428 if (conf & dp->data.conf)
429 goto out_unlock;
430
431 if (dp->state != DP_STATE_IDLE) {
432 ret = -EBUSY;
433 goto out_unlock;
434 }
435
436 if (DP_CONF_CURRENTLY(dp->data.conf) == DP_CONF_DFP_D)
437 assignments = DP_CAP_UFP_D_PIN_ASSIGN(dp->alt->vdo);
438 else
439 assignments = DP_CAP_DFP_D_PIN_ASSIGN(dp->alt->vdo);
440
441 if (!(DP_CONF_GET_PIN_ASSIGN(conf) & assignments)) {
442 ret = -EINVAL;
443 goto out_unlock;
444 }
445
446 conf |= dp->data.conf & ~DP_CONF_PIN_ASSIGNEMENT_MASK;
447
448 /* Only send Configure command if a configuration has been set */
449 if (dp->alt->active && DP_CONF_CURRENTLY(dp->data.conf)) {
450 ret = dp_altmode_configure_vdm(dp, conf);
451 if (ret)
452 goto out_unlock;
453 }
454
455 dp->data.conf = conf;
456
457out_unlock:
458 mutex_unlock(&dp->lock);
459
460 return ret ? ret : size;
461}
462
463static ssize_t pin_assignment_show(struct device *dev,
464 struct device_attribute *attr, char *buf)
465{
466 struct dp_altmode *dp = dev_get_drvdata(dev);
467 u8 assignments;
468 int len = 0;
469 u8 cur;
470 int i;
471
472 mutex_lock(&dp->lock);
473
474 cur = get_count_order(DP_CONF_GET_PIN_ASSIGN(dp->data.conf));
475
476 if (DP_CONF_CURRENTLY(dp->data.conf) == DP_CONF_DFP_D)
477 assignments = DP_CAP_UFP_D_PIN_ASSIGN(dp->alt->vdo);
478 else
479 assignments = DP_CAP_DFP_D_PIN_ASSIGN(dp->alt->vdo);
480
481 for (i = 0; assignments; assignments >>= 1, i++) {
482 if (assignments & 1) {
483 if (i == cur)
484 len += sprintf(buf + len, "[%s] ",
485 pin_assignments[i]);
486 else
487 len += sprintf(buf + len, "%s ",
488 pin_assignments[i]);
489 }
490 }
491
492 mutex_unlock(&dp->lock);
493
494 buf[len - 1] = '\n';
495 return len;
496}
497static DEVICE_ATTR_RW(pin_assignment);
498
499static struct attribute *dp_altmode_attrs[] = {
500 &dev_attr_configuration.attr,
501 &dev_attr_pin_assignment.attr,
502 NULL
503};
504
505static const struct attribute_group dp_altmode_group = {
506 .name = "displayport",
507 .attrs = dp_altmode_attrs,
508};
509
d266e968 510int dp_altmode_probe(struct typec_altmode *alt)
0e3bb7d6
HK
511{
512 const struct typec_altmode *port = typec_altmode_get_partner(alt);
513 struct dp_altmode *dp;
514 int ret;
515
516 /* FIXME: Port can only be DFP_U. */
517
518 /* Make sure we have compatiple pin configurations */
519 if (!(DP_CAP_DFP_D_PIN_ASSIGN(port->vdo) &
520 DP_CAP_UFP_D_PIN_ASSIGN(alt->vdo)) &&
521 !(DP_CAP_UFP_D_PIN_ASSIGN(port->vdo) &
522 DP_CAP_DFP_D_PIN_ASSIGN(alt->vdo)))
523 return -ENODEV;
524
525 ret = sysfs_create_group(&alt->dev.kobj, &dp_altmode_group);
526 if (ret)
527 return ret;
528
529 dp = devm_kzalloc(&alt->dev, sizeof(*dp), GFP_KERNEL);
530 if (!dp)
531 return -ENOMEM;
532
533 INIT_WORK(&dp->work, dp_altmode_work);
534 mutex_init(&dp->lock);
535 dp->port = port;
536 dp->alt = alt;
537
538 alt->desc = "DisplayPort";
539 alt->ops = &dp_altmode_ops;
540
541 typec_altmode_set_drvdata(alt, dp);
542
543 dp->state = DP_STATE_ENTER;
544 schedule_work(&dp->work);
545
546 return 0;
547}
d266e968 548EXPORT_SYMBOL_GPL(dp_altmode_probe);
0e3bb7d6 549
d266e968 550void dp_altmode_remove(struct typec_altmode *alt)
0e3bb7d6
HK
551{
552 struct dp_altmode *dp = typec_altmode_get_drvdata(alt);
553
554 sysfs_remove_group(&alt->dev.kobj, &dp_altmode_group);
555 cancel_work_sync(&dp->work);
556}
d266e968 557EXPORT_SYMBOL_GPL(dp_altmode_remove);
0e3bb7d6
HK
558
559static const struct typec_device_id dp_typec_id[] = {
560 { USB_TYPEC_DP_SID, USB_TYPEC_DP_MODE },
561 { },
562};
563MODULE_DEVICE_TABLE(typec, dp_typec_id);
564
565static struct typec_altmode_driver dp_altmode_driver = {
566 .id_table = dp_typec_id,
567 .probe = dp_altmode_probe,
568 .remove = dp_altmode_remove,
569 .driver = {
570 .name = "typec_displayport",
571 .owner = THIS_MODULE,
572 },
573};
574module_typec_altmode_driver(dp_altmode_driver);
575
576MODULE_AUTHOR("Heikki Krogerus <heikki.krogerus@linux.intel.com>");
577MODULE_LICENSE("GPL v2");
578MODULE_DESCRIPTION("DisplayPort Alternate Mode");