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/property.h> |
15 | #include <linux/usb/pd_vdo.h> |
16 | #include <linux/usb/typec_dp.h> |
17 | #include <drm/drm_connector.h> |
18 | #include "displayport.h" |
19 | |
20 | #define (_dp, ver, cmd) (VDO((_dp)->alt->svid, 1, ver, cmd) \ |
21 | | VDO_OPOS(USB_TYPEC_DP_MODE)) |
22 | |
23 | enum { |
24 | DP_CONF_USB, |
25 | DP_CONF_DFP_D, |
26 | DP_CONF_UFP_D, |
27 | DP_CONF_DUAL_D, |
28 | }; |
29 | |
30 | /* Pin assignments that use USB3.1 Gen2 signaling to carry DP protocol */ |
31 | #define DP_PIN_ASSIGN_GEN2_BR_MASK (BIT(DP_PIN_ASSIGN_A) | \ |
32 | BIT(DP_PIN_ASSIGN_B)) |
33 | |
34 | /* Pin assignments that use DP v1.3 signaling to carry DP protocol */ |
35 | #define DP_PIN_ASSIGN_DP_BR_MASK (BIT(DP_PIN_ASSIGN_C) | \ |
36 | BIT(DP_PIN_ASSIGN_D) | \ |
37 | BIT(DP_PIN_ASSIGN_E) | \ |
38 | BIT(DP_PIN_ASSIGN_F)) |
39 | |
40 | /* DP only pin assignments */ |
41 | #define DP_PIN_ASSIGN_DP_ONLY_MASK (BIT(DP_PIN_ASSIGN_A) | \ |
42 | BIT(DP_PIN_ASSIGN_C) | \ |
43 | BIT(DP_PIN_ASSIGN_E)) |
44 | |
45 | /* Pin assignments where one channel is for USB */ |
46 | #define DP_PIN_ASSIGN_MULTI_FUNC_MASK (BIT(DP_PIN_ASSIGN_B) | \ |
47 | BIT(DP_PIN_ASSIGN_D) | \ |
48 | BIT(DP_PIN_ASSIGN_F)) |
49 | |
50 | enum dp_state { |
51 | DP_STATE_IDLE, |
52 | DP_STATE_ENTER, |
53 | DP_STATE_ENTER_PRIME, |
54 | DP_STATE_UPDATE, |
55 | DP_STATE_CONFIGURE, |
56 | DP_STATE_CONFIGURE_PRIME, |
57 | DP_STATE_EXIT, |
58 | DP_STATE_EXIT_PRIME, |
59 | }; |
60 | |
61 | struct dp_altmode { |
62 | struct typec_displayport_data data; |
63 | struct typec_displayport_data data_prime; |
64 | |
65 | enum dp_state state; |
66 | bool hpd; |
67 | bool pending_hpd; |
68 | |
69 | struct mutex lock; /* device lock */ |
70 | struct work_struct work; |
71 | struct typec_altmode *alt; |
72 | const struct typec_altmode *port; |
73 | struct fwnode_handle *connector_fwnode; |
74 | struct typec_altmode *plug_prime; |
75 | }; |
76 | |
77 | static int dp_altmode_notify(struct dp_altmode *dp) |
78 | { |
79 | unsigned long conf; |
80 | u8 state; |
81 | |
82 | if (dp->data.conf) { |
83 | state = get_count_order(DP_CONF_GET_PIN_ASSIGN(dp->data.conf)); |
84 | conf = TYPEC_MODAL_STATE(state); |
85 | } else { |
86 | conf = TYPEC_STATE_USB; |
87 | } |
88 | |
89 | return typec_altmode_notify(altmode: dp->alt, conf, data: &dp->data); |
90 | } |
91 | |
92 | static int dp_altmode_configure(struct dp_altmode *dp, u8 con) |
93 | { |
94 | u8 pin_assign = 0; |
95 | u32 conf; |
96 | |
97 | /* DP Signalling */ |
98 | conf = (dp->data.conf & DP_CONF_SIGNALLING_MASK) >> DP_CONF_SIGNALLING_SHIFT; |
99 | |
100 | switch (con) { |
101 | case DP_STATUS_CON_DISABLED: |
102 | return 0; |
103 | case DP_STATUS_CON_DFP_D: |
104 | conf |= DP_CONF_UFP_U_AS_DFP_D; |
105 | pin_assign = DP_CAP_UFP_D_PIN_ASSIGN(dp->alt->vdo) & |
106 | DP_CAP_DFP_D_PIN_ASSIGN(dp->port->vdo); |
107 | /* Account for active cable capabilities */ |
108 | if (dp->plug_prime) |
109 | pin_assign &= DP_CAP_DFP_D_PIN_ASSIGN(dp->plug_prime->vdo); |
110 | break; |
111 | case DP_STATUS_CON_UFP_D: |
112 | case DP_STATUS_CON_BOTH: /* NOTE: First acting as DP source */ |
113 | conf |= DP_CONF_UFP_U_AS_UFP_D; |
114 | pin_assign = DP_CAP_PIN_ASSIGN_UFP_D(dp->alt->vdo) & |
115 | DP_CAP_PIN_ASSIGN_DFP_D(dp->port->vdo); |
116 | /* Account for active cable capabilities */ |
117 | if (dp->plug_prime) |
118 | pin_assign &= DP_CAP_UFP_D_PIN_ASSIGN(dp->plug_prime->vdo); |
119 | break; |
120 | default: |
121 | break; |
122 | } |
123 | |
124 | /* Determining the initial pin assignment. */ |
125 | if (!DP_CONF_GET_PIN_ASSIGN(dp->data.conf)) { |
126 | /* Is USB together with DP preferred */ |
127 | if (dp->data.status & DP_STATUS_PREFER_MULTI_FUNC && |
128 | pin_assign & DP_PIN_ASSIGN_MULTI_FUNC_MASK) |
129 | pin_assign &= DP_PIN_ASSIGN_MULTI_FUNC_MASK; |
130 | else if (pin_assign & DP_PIN_ASSIGN_DP_ONLY_MASK) { |
131 | pin_assign &= DP_PIN_ASSIGN_DP_ONLY_MASK; |
132 | /* Default to pin assign C if available */ |
133 | if (pin_assign & BIT(DP_PIN_ASSIGN_C)) |
134 | pin_assign = BIT(DP_PIN_ASSIGN_C); |
135 | } |
136 | |
137 | if (!pin_assign) |
138 | return -EINVAL; |
139 | |
140 | conf |= DP_CONF_SET_PIN_ASSIGN(pin_assign); |
141 | } |
142 | |
143 | dp->data.conf = conf; |
144 | if (dp->plug_prime) |
145 | dp->data_prime.conf = conf; |
146 | |
147 | return 0; |
148 | } |
149 | |
150 | static int dp_altmode_status_update(struct dp_altmode *dp) |
151 | { |
152 | bool configured = !!DP_CONF_GET_PIN_ASSIGN(dp->data.conf); |
153 | bool hpd = !!(dp->data.status & DP_STATUS_HPD_STATE); |
154 | u8 con = DP_STATUS_CONNECTION(dp->data.status); |
155 | int ret = 0; |
156 | |
157 | if (configured && (dp->data.status & DP_STATUS_SWITCH_TO_USB)) { |
158 | dp->data.conf = 0; |
159 | dp->data_prime.conf = 0; |
160 | dp->state = dp->plug_prime ? DP_STATE_CONFIGURE_PRIME : |
161 | DP_STATE_CONFIGURE; |
162 | } else if (dp->data.status & DP_STATUS_EXIT_DP_MODE) { |
163 | dp->state = DP_STATE_EXIT; |
164 | } else if (!(con & DP_CONF_CURRENTLY(dp->data.conf))) { |
165 | ret = dp_altmode_configure(dp, con); |
166 | if (!ret) { |
167 | dp->state = dp->plug_prime ? DP_STATE_CONFIGURE_PRIME : |
168 | DP_STATE_CONFIGURE; |
169 | if (dp->hpd != hpd) { |
170 | dp->hpd = hpd; |
171 | dp->pending_hpd = true; |
172 | } |
173 | } |
174 | } else { |
175 | drm_connector_oob_hotplug_event(connector_fwnode: dp->connector_fwnode, |
176 | status: hpd ? connector_status_connected : |
177 | connector_status_disconnected); |
178 | dp->hpd = hpd; |
179 | sysfs_notify(kobj: &dp->alt->dev.kobj, dir: "displayport" , attr: "hpd" ); |
180 | } |
181 | |
182 | return ret; |
183 | } |
184 | |
185 | static int dp_altmode_configured(struct dp_altmode *dp) |
186 | { |
187 | sysfs_notify(kobj: &dp->alt->dev.kobj, dir: "displayport" , attr: "configuration" ); |
188 | sysfs_notify(kobj: &dp->alt->dev.kobj, dir: "displayport" , attr: "pin_assignment" ); |
189 | /* |
190 | * If the DFP_D/UFP_D sends a change in HPD when first notifying the |
191 | * DisplayPort driver that it is connected, then we wait until |
192 | * configuration is complete to signal HPD. |
193 | */ |
194 | if (dp->pending_hpd) { |
195 | drm_connector_oob_hotplug_event(connector_fwnode: dp->connector_fwnode, |
196 | status: connector_status_connected); |
197 | sysfs_notify(kobj: &dp->alt->dev.kobj, dir: "displayport" , attr: "hpd" ); |
198 | dp->pending_hpd = false; |
199 | } |
200 | |
201 | return dp_altmode_notify(dp); |
202 | } |
203 | |
204 | static int dp_altmode_configure_vdm(struct dp_altmode *dp, u32 conf) |
205 | { |
206 | int svdm_version = typec_altmode_get_svdm_version(altmode: dp->alt); |
207 | u32 ; |
208 | int ret; |
209 | |
210 | if (svdm_version < 0) |
211 | return svdm_version; |
212 | |
213 | header = DP_HEADER(dp, svdm_version, DP_CMD_CONFIGURE); |
214 | ret = typec_altmode_notify(altmode: dp->alt, conf: TYPEC_STATE_SAFE, data: &dp->data); |
215 | if (ret) { |
216 | dev_err(&dp->alt->dev, |
217 | "unable to put to connector to safe mode\n" ); |
218 | return ret; |
219 | } |
220 | |
221 | ret = typec_altmode_vdm(altmode: dp->alt, header, vdo: &conf, count: 2); |
222 | if (ret) |
223 | dp_altmode_notify(dp); |
224 | |
225 | return ret; |
226 | } |
227 | |
228 | static int dp_altmode_configure_vdm_cable(struct dp_altmode *dp, u32 conf) |
229 | { |
230 | int svdm_version = typec_altmode_get_cable_svdm_version(altmode: dp->plug_prime); |
231 | u32 ; |
232 | |
233 | if (svdm_version < 0) |
234 | return svdm_version; |
235 | |
236 | header = DP_HEADER(dp, svdm_version, DP_CMD_CONFIGURE); |
237 | |
238 | return typec_cable_altmode_vdm(altmode: dp->plug_prime, sop: TYPEC_PLUG_SOP_P, header, vdo: &conf, count: 2); |
239 | } |
240 | |
241 | static void dp_altmode_work(struct work_struct *work) |
242 | { |
243 | struct dp_altmode *dp = container_of(work, struct dp_altmode, work); |
244 | int svdm_version; |
245 | u32 ; |
246 | u32 vdo; |
247 | int ret; |
248 | |
249 | mutex_lock(&dp->lock); |
250 | |
251 | switch (dp->state) { |
252 | case DP_STATE_ENTER: |
253 | ret = typec_altmode_enter(altmode: dp->alt, NULL); |
254 | if (ret && ret != -EBUSY) |
255 | dev_err(&dp->alt->dev, "failed to enter mode\n" ); |
256 | break; |
257 | case DP_STATE_ENTER_PRIME: |
258 | ret = typec_cable_altmode_enter(altmode: dp->alt, sop: TYPEC_PLUG_SOP_P, NULL); |
259 | /* |
260 | * If we fail to enter Alt Mode on SOP', then we should drop the |
261 | * plug from the driver and attempt to run the driver without |
262 | * it. |
263 | */ |
264 | if (ret && ret != -EBUSY) { |
265 | dev_err(&dp->alt->dev, "plug failed to enter mode\n" ); |
266 | dp->state = DP_STATE_ENTER; |
267 | goto disable_prime; |
268 | } |
269 | break; |
270 | case DP_STATE_UPDATE: |
271 | svdm_version = typec_altmode_get_svdm_version(altmode: dp->alt); |
272 | if (svdm_version < 0) |
273 | break; |
274 | header = DP_HEADER(dp, svdm_version, DP_CMD_STATUS_UPDATE); |
275 | vdo = 1; |
276 | ret = typec_altmode_vdm(altmode: dp->alt, header, vdo: &vdo, count: 2); |
277 | if (ret) |
278 | dev_err(&dp->alt->dev, |
279 | "unable to send Status Update command (%d)\n" , |
280 | ret); |
281 | break; |
282 | case DP_STATE_CONFIGURE: |
283 | ret = dp_altmode_configure_vdm(dp, conf: dp->data.conf); |
284 | if (ret) |
285 | dev_err(&dp->alt->dev, |
286 | "unable to send Configure command (%d)\n" , ret); |
287 | break; |
288 | case DP_STATE_CONFIGURE_PRIME: |
289 | ret = dp_altmode_configure_vdm_cable(dp, conf: dp->data_prime.conf); |
290 | if (ret) { |
291 | dev_err(&dp->plug_prime->dev, |
292 | "unable to send Configure command (%d)\n" , |
293 | ret); |
294 | dp->state = DP_STATE_CONFIGURE; |
295 | goto disable_prime; |
296 | } |
297 | break; |
298 | case DP_STATE_EXIT: |
299 | if (typec_altmode_exit(altmode: dp->alt)) |
300 | dev_err(&dp->alt->dev, "Exit Mode Failed!\n" ); |
301 | break; |
302 | case DP_STATE_EXIT_PRIME: |
303 | if (typec_cable_altmode_exit(altmode: dp->plug_prime, sop: TYPEC_PLUG_SOP_P)) |
304 | dev_err(&dp->plug_prime->dev, "Exit Mode Failed!\n" ); |
305 | break; |
306 | default: |
307 | break; |
308 | } |
309 | |
310 | dp->state = DP_STATE_IDLE; |
311 | |
312 | mutex_unlock(lock: &dp->lock); |
313 | return; |
314 | |
315 | disable_prime: |
316 | typec_altmode_put_plug(plug: dp->plug_prime); |
317 | dp->plug_prime = NULL; |
318 | schedule_work(work: &dp->work); |
319 | mutex_unlock(lock: &dp->lock); |
320 | } |
321 | |
322 | static void dp_altmode_attention(struct typec_altmode *alt, const u32 vdo) |
323 | { |
324 | struct dp_altmode *dp = typec_altmode_get_drvdata(altmode: alt); |
325 | u8 old_state; |
326 | |
327 | mutex_lock(&dp->lock); |
328 | |
329 | old_state = dp->state; |
330 | dp->data.status = vdo; |
331 | |
332 | if (old_state != DP_STATE_IDLE) |
333 | dev_warn(&alt->dev, "ATTENTION while processing state %d\n" , |
334 | old_state); |
335 | |
336 | if (dp_altmode_status_update(dp)) |
337 | dev_warn(&alt->dev, "%s: status update failed\n" , __func__); |
338 | |
339 | if (dp_altmode_notify(dp)) |
340 | dev_err(&alt->dev, "%s: notification failed\n" , __func__); |
341 | |
342 | if (old_state == DP_STATE_IDLE && dp->state != DP_STATE_IDLE) |
343 | schedule_work(work: &dp->work); |
344 | |
345 | mutex_unlock(lock: &dp->lock); |
346 | } |
347 | |
348 | static int dp_altmode_vdm(struct typec_altmode *alt, |
349 | const u32 hdr, const u32 *vdo, int count) |
350 | { |
351 | struct dp_altmode *dp = typec_altmode_get_drvdata(altmode: alt); |
352 | int cmd_type = PD_VDO_CMDT(hdr); |
353 | int cmd = PD_VDO_CMD(hdr); |
354 | int ret = 0; |
355 | |
356 | mutex_lock(&dp->lock); |
357 | |
358 | if (dp->state != DP_STATE_IDLE) { |
359 | ret = -EBUSY; |
360 | goto err_unlock; |
361 | } |
362 | |
363 | switch (cmd_type) { |
364 | case CMDT_RSP_ACK: |
365 | switch (cmd) { |
366 | case CMD_ENTER_MODE: |
367 | typec_altmode_update_active(alt, active: true); |
368 | dp->state = DP_STATE_UPDATE; |
369 | break; |
370 | case CMD_EXIT_MODE: |
371 | typec_altmode_update_active(alt, active: false); |
372 | dp->data.status = 0; |
373 | dp->data.conf = 0; |
374 | if (dp->hpd) { |
375 | drm_connector_oob_hotplug_event(connector_fwnode: dp->connector_fwnode, |
376 | status: connector_status_disconnected); |
377 | dp->hpd = false; |
378 | sysfs_notify(kobj: &dp->alt->dev.kobj, dir: "displayport" , attr: "hpd" ); |
379 | } |
380 | if (dp->plug_prime) |
381 | dp->state = DP_STATE_EXIT_PRIME; |
382 | break; |
383 | case DP_CMD_STATUS_UPDATE: |
384 | dp->data.status = *vdo; |
385 | ret = dp_altmode_status_update(dp); |
386 | break; |
387 | case DP_CMD_CONFIGURE: |
388 | ret = dp_altmode_configured(dp); |
389 | break; |
390 | default: |
391 | break; |
392 | } |
393 | break; |
394 | case CMDT_RSP_NAK: |
395 | switch (cmd) { |
396 | case DP_CMD_CONFIGURE: |
397 | dp->data.conf = 0; |
398 | ret = dp_altmode_configured(dp); |
399 | break; |
400 | default: |
401 | break; |
402 | } |
403 | break; |
404 | default: |
405 | break; |
406 | } |
407 | |
408 | if (dp->state != DP_STATE_IDLE) |
409 | schedule_work(work: &dp->work); |
410 | |
411 | err_unlock: |
412 | mutex_unlock(lock: &dp->lock); |
413 | return ret; |
414 | } |
415 | |
416 | static int dp_cable_altmode_vdm(struct typec_altmode *alt, enum typec_plug_index sop, |
417 | const u32 hdr, const u32 *vdo, int count) |
418 | { |
419 | struct dp_altmode *dp = typec_altmode_get_drvdata(altmode: alt); |
420 | int cmd_type = PD_VDO_CMDT(hdr); |
421 | int cmd = PD_VDO_CMD(hdr); |
422 | int ret = 0; |
423 | |
424 | mutex_lock(&dp->lock); |
425 | |
426 | if (dp->state != DP_STATE_IDLE) { |
427 | ret = -EBUSY; |
428 | goto err_unlock; |
429 | } |
430 | |
431 | switch (cmd_type) { |
432 | case CMDT_RSP_ACK: |
433 | switch (cmd) { |
434 | case CMD_ENTER_MODE: |
435 | typec_altmode_update_active(alt: dp->plug_prime, active: true); |
436 | dp->state = DP_STATE_ENTER; |
437 | break; |
438 | case CMD_EXIT_MODE: |
439 | dp->data_prime.status = 0; |
440 | dp->data_prime.conf = 0; |
441 | typec_altmode_update_active(alt: dp->plug_prime, active: false); |
442 | break; |
443 | case DP_CMD_CONFIGURE: |
444 | dp->state = DP_STATE_CONFIGURE; |
445 | break; |
446 | default: |
447 | break; |
448 | } |
449 | break; |
450 | case CMDT_RSP_NAK: |
451 | switch (cmd) { |
452 | case DP_CMD_CONFIGURE: |
453 | dp->data_prime.conf = 0; |
454 | /* Attempt to configure on SOP, drop plug */ |
455 | typec_altmode_put_plug(plug: dp->plug_prime); |
456 | dp->plug_prime = NULL; |
457 | dp->state = DP_STATE_CONFIGURE; |
458 | break; |
459 | default: |
460 | break; |
461 | } |
462 | break; |
463 | default: |
464 | break; |
465 | } |
466 | |
467 | if (dp->state != DP_STATE_IDLE) |
468 | schedule_work(work: &dp->work); |
469 | |
470 | err_unlock: |
471 | mutex_unlock(lock: &dp->lock); |
472 | return ret; |
473 | } |
474 | |
475 | static int dp_altmode_activate(struct typec_altmode *alt, int activate) |
476 | { |
477 | struct dp_altmode *dp = typec_altmode_get_drvdata(altmode: alt); |
478 | int ret; |
479 | |
480 | if (activate) { |
481 | if (dp->plug_prime) { |
482 | ret = typec_cable_altmode_enter(altmode: alt, sop: TYPEC_PLUG_SOP_P, NULL); |
483 | if (ret < 0) { |
484 | typec_altmode_put_plug(plug: dp->plug_prime); |
485 | dp->plug_prime = NULL; |
486 | } else { |
487 | return ret; |
488 | } |
489 | } |
490 | return typec_altmode_enter(altmode: alt, NULL); |
491 | } else { |
492 | return typec_altmode_exit(altmode: alt); |
493 | } |
494 | } |
495 | |
496 | static const struct typec_altmode_ops dp_altmode_ops = { |
497 | .attention = dp_altmode_attention, |
498 | .vdm = dp_altmode_vdm, |
499 | .activate = dp_altmode_activate, |
500 | }; |
501 | |
502 | static const struct typec_cable_ops dp_cable_ops = { |
503 | .vdm = dp_cable_altmode_vdm, |
504 | }; |
505 | |
506 | static const char * const configurations[] = { |
507 | [DP_CONF_USB] = "USB" , |
508 | [DP_CONF_DFP_D] = "source" , |
509 | [DP_CONF_UFP_D] = "sink" , |
510 | }; |
511 | |
512 | static ssize_t |
513 | configuration_store(struct device *dev, struct device_attribute *attr, |
514 | const char *buf, size_t size) |
515 | { |
516 | struct dp_altmode *dp = dev_get_drvdata(dev); |
517 | u32 conf; |
518 | u32 cap; |
519 | int con; |
520 | int ret = 0; |
521 | |
522 | con = sysfs_match_string(configurations, buf); |
523 | if (con < 0) |
524 | return con; |
525 | |
526 | mutex_lock(&dp->lock); |
527 | |
528 | if (dp->state != DP_STATE_IDLE) { |
529 | ret = -EBUSY; |
530 | goto err_unlock; |
531 | } |
532 | |
533 | cap = DP_CAP_CAPABILITY(dp->alt->vdo); |
534 | |
535 | if ((con == DP_CONF_DFP_D && !(cap & DP_CAP_DFP_D)) || |
536 | (con == DP_CONF_UFP_D && !(cap & DP_CAP_UFP_D))) { |
537 | ret = -EINVAL; |
538 | goto err_unlock; |
539 | } |
540 | |
541 | conf = dp->data.conf & ~DP_CONF_DUAL_D; |
542 | conf |= con; |
543 | |
544 | if (dp->alt->active) { |
545 | ret = dp_altmode_configure_vdm(dp, conf); |
546 | if (ret) |
547 | goto err_unlock; |
548 | } |
549 | |
550 | dp->data.conf = conf; |
551 | |
552 | err_unlock: |
553 | mutex_unlock(lock: &dp->lock); |
554 | |
555 | return ret ? ret : size; |
556 | } |
557 | |
558 | static ssize_t configuration_show(struct device *dev, |
559 | struct device_attribute *attr, char *buf) |
560 | { |
561 | struct dp_altmode *dp = dev_get_drvdata(dev); |
562 | int len; |
563 | u8 cap; |
564 | u8 cur; |
565 | int i; |
566 | |
567 | mutex_lock(&dp->lock); |
568 | |
569 | cap = DP_CAP_CAPABILITY(dp->alt->vdo); |
570 | cur = DP_CONF_CURRENTLY(dp->data.conf); |
571 | |
572 | len = sprintf(buf, fmt: "%s " , cur ? "USB" : "[USB]" ); |
573 | |
574 | for (i = 1; i < ARRAY_SIZE(configurations); i++) { |
575 | if (i == cur) |
576 | len += sprintf(buf: buf + len, fmt: "[%s] " , configurations[i]); |
577 | else if ((i == DP_CONF_DFP_D && cap & DP_CAP_DFP_D) || |
578 | (i == DP_CONF_UFP_D && cap & DP_CAP_UFP_D)) |
579 | len += sprintf(buf: buf + len, fmt: "%s " , configurations[i]); |
580 | } |
581 | |
582 | mutex_unlock(lock: &dp->lock); |
583 | |
584 | buf[len - 1] = '\n'; |
585 | return len; |
586 | } |
587 | static DEVICE_ATTR_RW(configuration); |
588 | |
589 | static const char * const pin_assignments[] = { |
590 | [DP_PIN_ASSIGN_A] = "A" , |
591 | [DP_PIN_ASSIGN_B] = "B" , |
592 | [DP_PIN_ASSIGN_C] = "C" , |
593 | [DP_PIN_ASSIGN_D] = "D" , |
594 | [DP_PIN_ASSIGN_E] = "E" , |
595 | [DP_PIN_ASSIGN_F] = "F" , |
596 | }; |
597 | |
598 | /* |
599 | * Helper function to extract a peripheral's currently supported |
600 | * Pin Assignments from its DisplayPort alternate mode state. |
601 | */ |
602 | static u8 get_current_pin_assignments(struct dp_altmode *dp) |
603 | { |
604 | if (DP_CONF_CURRENTLY(dp->data.conf) == DP_CONF_UFP_U_AS_DFP_D) |
605 | return DP_CAP_PIN_ASSIGN_DFP_D(dp->alt->vdo); |
606 | else |
607 | return DP_CAP_PIN_ASSIGN_UFP_D(dp->alt->vdo); |
608 | } |
609 | |
610 | static ssize_t |
611 | pin_assignment_store(struct device *dev, struct device_attribute *attr, |
612 | const char *buf, size_t size) |
613 | { |
614 | struct dp_altmode *dp = dev_get_drvdata(dev); |
615 | u8 assignments; |
616 | u32 conf; |
617 | int ret; |
618 | |
619 | ret = sysfs_match_string(pin_assignments, buf); |
620 | if (ret < 0) |
621 | return ret; |
622 | |
623 | conf = DP_CONF_SET_PIN_ASSIGN(BIT(ret)); |
624 | ret = 0; |
625 | |
626 | mutex_lock(&dp->lock); |
627 | |
628 | if (conf & dp->data.conf) |
629 | goto out_unlock; |
630 | |
631 | if (dp->state != DP_STATE_IDLE) { |
632 | ret = -EBUSY; |
633 | goto out_unlock; |
634 | } |
635 | |
636 | assignments = get_current_pin_assignments(dp); |
637 | |
638 | if (!(DP_CONF_GET_PIN_ASSIGN(conf) & assignments)) { |
639 | ret = -EINVAL; |
640 | goto out_unlock; |
641 | } |
642 | |
643 | conf |= dp->data.conf & ~DP_CONF_PIN_ASSIGNEMENT_MASK; |
644 | |
645 | /* Only send Configure command if a configuration has been set */ |
646 | if (dp->alt->active && DP_CONF_CURRENTLY(dp->data.conf)) { |
647 | /* todo: send manual configure over SOP'*/ |
648 | ret = dp_altmode_configure_vdm(dp, conf); |
649 | if (ret) |
650 | goto out_unlock; |
651 | } |
652 | |
653 | dp->data.conf = conf; |
654 | |
655 | out_unlock: |
656 | mutex_unlock(lock: &dp->lock); |
657 | |
658 | return ret ? ret : size; |
659 | } |
660 | |
661 | static ssize_t pin_assignment_show(struct device *dev, |
662 | struct device_attribute *attr, char *buf) |
663 | { |
664 | struct dp_altmode *dp = dev_get_drvdata(dev); |
665 | u8 assignments; |
666 | int len = 0; |
667 | u8 cur; |
668 | int i; |
669 | |
670 | mutex_lock(&dp->lock); |
671 | |
672 | cur = get_count_order(DP_CONF_GET_PIN_ASSIGN(dp->data.conf)); |
673 | |
674 | assignments = get_current_pin_assignments(dp); |
675 | |
676 | for (i = 0; assignments; assignments >>= 1, i++) { |
677 | if (assignments & 1) { |
678 | if (i == cur) |
679 | len += sprintf(buf: buf + len, fmt: "[%s] " , |
680 | pin_assignments[i]); |
681 | else |
682 | len += sprintf(buf: buf + len, fmt: "%s " , |
683 | pin_assignments[i]); |
684 | } |
685 | } |
686 | |
687 | mutex_unlock(lock: &dp->lock); |
688 | |
689 | /* get_current_pin_assignments can return 0 when no matching pin assignments are found */ |
690 | if (len == 0) |
691 | len++; |
692 | |
693 | buf[len - 1] = '\n'; |
694 | return len; |
695 | } |
696 | static DEVICE_ATTR_RW(pin_assignment); |
697 | |
698 | static ssize_t hpd_show(struct device *dev, struct device_attribute *attr, char *buf) |
699 | { |
700 | struct dp_altmode *dp = dev_get_drvdata(dev); |
701 | |
702 | return sysfs_emit(buf, fmt: "%d\n" , dp->hpd); |
703 | } |
704 | static DEVICE_ATTR_RO(hpd); |
705 | |
706 | static struct attribute *displayport_attrs[] = { |
707 | &dev_attr_configuration.attr, |
708 | &dev_attr_pin_assignment.attr, |
709 | &dev_attr_hpd.attr, |
710 | NULL |
711 | }; |
712 | |
713 | static const struct attribute_group displayport_group = { |
714 | .name = "displayport" , |
715 | .attrs = displayport_attrs, |
716 | }; |
717 | |
718 | static const struct attribute_group *displayport_groups[] = { |
719 | &displayport_group, |
720 | NULL, |
721 | }; |
722 | |
723 | int dp_altmode_probe(struct typec_altmode *alt) |
724 | { |
725 | const struct typec_altmode *port = typec_altmode_get_partner(altmode: alt); |
726 | struct typec_altmode *plug = typec_altmode_get_plug(altmode: alt, index: TYPEC_PLUG_SOP_P); |
727 | struct fwnode_handle *fwnode; |
728 | struct dp_altmode *dp; |
729 | |
730 | /* FIXME: Port can only be DFP_U. */ |
731 | |
732 | /* Make sure we have compatiple pin configurations */ |
733 | if (!(DP_CAP_PIN_ASSIGN_DFP_D(port->vdo) & |
734 | DP_CAP_PIN_ASSIGN_UFP_D(alt->vdo)) && |
735 | !(DP_CAP_PIN_ASSIGN_UFP_D(port->vdo) & |
736 | DP_CAP_PIN_ASSIGN_DFP_D(alt->vdo))) |
737 | return -ENODEV; |
738 | |
739 | dp = devm_kzalloc(dev: &alt->dev, size: sizeof(*dp), GFP_KERNEL); |
740 | if (!dp) |
741 | return -ENOMEM; |
742 | |
743 | INIT_WORK(&dp->work, dp_altmode_work); |
744 | mutex_init(&dp->lock); |
745 | dp->port = port; |
746 | dp->alt = alt; |
747 | |
748 | alt->desc = "DisplayPort" ; |
749 | alt->ops = &dp_altmode_ops; |
750 | |
751 | if (plug) { |
752 | plug->desc = "Displayport" ; |
753 | plug->cable_ops = &dp_cable_ops; |
754 | } |
755 | |
756 | dp->plug_prime = plug; |
757 | |
758 | fwnode = dev_fwnode(alt->dev.parent->parent); /* typec_port fwnode */ |
759 | if (fwnode_property_present(fwnode, propname: "displayport" )) |
760 | dp->connector_fwnode = fwnode_find_reference(fwnode, name: "displayport" , index: 0); |
761 | else |
762 | dp->connector_fwnode = fwnode_handle_get(fwnode); /* embedded DP */ |
763 | if (IS_ERR(ptr: dp->connector_fwnode)) |
764 | dp->connector_fwnode = NULL; |
765 | |
766 | typec_altmode_set_drvdata(altmode: alt, data: dp); |
767 | if (plug) |
768 | typec_altmode_set_drvdata(altmode: plug, data: dp); |
769 | |
770 | dp->state = plug ? DP_STATE_ENTER_PRIME : DP_STATE_ENTER; |
771 | schedule_work(work: &dp->work); |
772 | |
773 | return 0; |
774 | } |
775 | EXPORT_SYMBOL_GPL(dp_altmode_probe); |
776 | |
777 | void dp_altmode_remove(struct typec_altmode *alt) |
778 | { |
779 | struct dp_altmode *dp = typec_altmode_get_drvdata(altmode: alt); |
780 | |
781 | cancel_work_sync(work: &dp->work); |
782 | typec_altmode_put_plug(plug: dp->plug_prime); |
783 | |
784 | if (dp->connector_fwnode) { |
785 | drm_connector_oob_hotplug_event(connector_fwnode: dp->connector_fwnode, |
786 | status: connector_status_disconnected); |
787 | |
788 | fwnode_handle_put(fwnode: dp->connector_fwnode); |
789 | } |
790 | } |
791 | EXPORT_SYMBOL_GPL(dp_altmode_remove); |
792 | |
793 | static const struct typec_device_id dp_typec_id[] = { |
794 | { USB_TYPEC_DP_SID, USB_TYPEC_DP_MODE }, |
795 | { }, |
796 | }; |
797 | MODULE_DEVICE_TABLE(typec, dp_typec_id); |
798 | |
799 | static struct typec_altmode_driver dp_altmode_driver = { |
800 | .id_table = dp_typec_id, |
801 | .probe = dp_altmode_probe, |
802 | .remove = dp_altmode_remove, |
803 | .driver = { |
804 | .name = "typec_displayport" , |
805 | .owner = THIS_MODULE, |
806 | .dev_groups = displayport_groups, |
807 | }, |
808 | }; |
809 | module_typec_altmode_driver(dp_altmode_driver); |
810 | |
811 | MODULE_AUTHOR("Heikki Krogerus <heikki.krogerus@linux.intel.com>" ); |
812 | MODULE_LICENSE("GPL v2" ); |
813 | MODULE_DESCRIPTION("DisplayPort Alternate Mode" ); |
814 | |