Skip to content

Commit 71ba4fe

Browse files
RD Babieragregkh
authored andcommitted
usb: typec: altmodes/displayport: add SOP' support
Implement active cable VDM support for SOP' according to the DisplayPort Alt Mode 2.0 specification. When probing the DisplayPort driver, the state machine will transition to Enter Mode on SOP' if an active cable altmode is detected. The SVDM flow is as followed: (1) Enter Mode SOP' (2) Enter Mode SOP (3) Status Update SOP (4) Configure SOP' (5) Configure SOP Status Update on SOP' after Enter Mode is optional and not implemented for now. When exiting the alt mode, send Exit Mode over SOP' after SOP. Should an altmode vdm fail on SOP', the DisplayPort driver will drop its reference to the plug and attempt to continue in SOP operation. Add new dp_state enums DP_STATE_ENTER_PRIME, DP_STATE_CONFIGURE_PRIME, and DP_STATE_EXIT_PRIME. dp_altmode adds typec_displayport_data for the cable plug to store the plug configuration and adds a typec_altmode reference for the cable plug. dp_altmode_configure takes the cable pin assignment capabilities into account when deciding on pin configuration. dp_altmode_configure_vdm_cable sends the configure message on SOP'. dp_altmode_activate now attempts to enter on SOP' if applicable, and will attempt to enter on SOP on failure. dp_cable_altmode_vdm handles VDMs passed to the DisplayPort driver from the tcpm. Signed-off-by: RD Babiera <[email protected]> Link: https://lore.kernel.org/r/[email protected] Signed-off-by: Greg Kroah-Hartman <[email protected]>
1 parent 7e7877c commit 71ba4fe

File tree

1 file changed

+158
-4
lines changed

1 file changed

+158
-4
lines changed

drivers/usb/typec/altmodes/displayport.c

Lines changed: 158 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,13 +50,17 @@ enum {
5050
enum dp_state {
5151
DP_STATE_IDLE,
5252
DP_STATE_ENTER,
53+
DP_STATE_ENTER_PRIME,
5354
DP_STATE_UPDATE,
5455
DP_STATE_CONFIGURE,
56+
DP_STATE_CONFIGURE_PRIME,
5557
DP_STATE_EXIT,
58+
DP_STATE_EXIT_PRIME,
5659
};
5760

5861
struct dp_altmode {
5962
struct typec_displayport_data data;
63+
struct typec_displayport_data data_prime;
6064

6165
enum dp_state state;
6266
bool hpd;
@@ -67,6 +71,7 @@ struct dp_altmode {
6771
struct typec_altmode *alt;
6872
const struct typec_altmode *port;
6973
struct fwnode_handle *connector_fwnode;
74+
struct typec_altmode *plug_prime;
7075
};
7176

7277
static int dp_altmode_notify(struct dp_altmode *dp)
@@ -99,12 +104,18 @@ static int dp_altmode_configure(struct dp_altmode *dp, u8 con)
99104
conf |= DP_CONF_UFP_U_AS_DFP_D;
100105
pin_assign = DP_CAP_UFP_D_PIN_ASSIGN(dp->alt->vdo) &
101106
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);
102110
break;
103111
case DP_STATUS_CON_UFP_D:
104112
case DP_STATUS_CON_BOTH: /* NOTE: First acting as DP source */
105113
conf |= DP_CONF_UFP_U_AS_UFP_D;
106114
pin_assign = DP_CAP_PIN_ASSIGN_UFP_D(dp->alt->vdo) &
107115
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);
108119
break;
109120
default:
110121
break;
@@ -130,6 +141,8 @@ static int dp_altmode_configure(struct dp_altmode *dp, u8 con)
130141
}
131142

132143
dp->data.conf = conf;
144+
if (dp->plug_prime)
145+
dp->data_prime.conf = conf;
133146

134147
return 0;
135148
}
@@ -143,7 +156,9 @@ static int dp_altmode_status_update(struct dp_altmode *dp)
143156

144157
if (configured && (dp->data.status & DP_STATUS_SWITCH_TO_USB)) {
145158
dp->data.conf = 0;
146-
dp->state = DP_STATE_CONFIGURE;
159+
dp->data_prime.conf = 0;
160+
dp->state = dp->plug_prime ? DP_STATE_CONFIGURE_PRIME :
161+
DP_STATE_CONFIGURE;
147162
} else if (dp->data.status & DP_STATUS_EXIT_DP_MODE) {
148163
dp->state = DP_STATE_EXIT;
149164
} else if (!(con & DP_CONF_CURRENTLY(dp->data.conf))) {
@@ -209,6 +224,19 @@ static int dp_altmode_configure_vdm(struct dp_altmode *dp, u32 conf)
209224
return ret;
210225
}
211226

227+
static int dp_altmode_configure_vdm_cable(struct dp_altmode *dp, u32 conf)
228+
{
229+
int svdm_version = typec_altmode_get_cable_svdm_version(dp->plug_prime);
230+
u32 header;
231+
232+
if (svdm_version < 0)
233+
return svdm_version;
234+
235+
header = DP_HEADER(dp, svdm_version, DP_CMD_CONFIGURE);
236+
237+
return typec_cable_altmode_vdm(dp->plug_prime, TYPEC_PLUG_SOP_P, header, &conf, 2);
238+
}
239+
212240
static void dp_altmode_work(struct work_struct *work)
213241
{
214242
struct dp_altmode *dp = container_of(work, struct dp_altmode, work);
@@ -225,6 +253,19 @@ static void dp_altmode_work(struct work_struct *work)
225253
if (ret && ret != -EBUSY)
226254
dev_err(&dp->alt->dev, "failed to enter mode\n");
227255
break;
256+
case DP_STATE_ENTER_PRIME:
257+
ret = typec_cable_altmode_enter(dp->alt, TYPEC_PLUG_SOP_P, NULL);
258+
/*
259+
* If we fail to enter Alt Mode on SOP', then we should drop the
260+
* plug from the driver and attempt to run the driver without
261+
* it.
262+
*/
263+
if (ret && ret != -EBUSY) {
264+
dev_err(&dp->alt->dev, "plug failed to enter mode\n");
265+
dp->state = DP_STATE_ENTER;
266+
goto disable_prime;
267+
}
268+
break;
228269
case DP_STATE_UPDATE:
229270
svdm_version = typec_altmode_get_svdm_version(dp->alt);
230271
if (svdm_version < 0)
@@ -243,17 +284,38 @@ static void dp_altmode_work(struct work_struct *work)
243284
dev_err(&dp->alt->dev,
244285
"unable to send Configure command (%d)\n", ret);
245286
break;
287+
case DP_STATE_CONFIGURE_PRIME:
288+
ret = dp_altmode_configure_vdm_cable(dp, dp->data_prime.conf);
289+
if (ret) {
290+
dev_err(&dp->plug_prime->dev,
291+
"unable to send Configure command (%d)\n",
292+
ret);
293+
dp->state = DP_STATE_CONFIGURE;
294+
goto disable_prime;
295+
}
296+
break;
246297
case DP_STATE_EXIT:
247298
if (typec_altmode_exit(dp->alt))
248299
dev_err(&dp->alt->dev, "Exit Mode Failed!\n");
249300
break;
301+
case DP_STATE_EXIT_PRIME:
302+
if (typec_cable_altmode_exit(dp->plug_prime, TYPEC_PLUG_SOP_P))
303+
dev_err(&dp->plug_prime->dev, "Exit Mode Failed!\n");
304+
break;
250305
default:
251306
break;
252307
}
253308

254309
dp->state = DP_STATE_IDLE;
255310

256311
mutex_unlock(&dp->lock);
312+
return;
313+
314+
disable_prime:
315+
typec_altmode_put_plug(dp->plug_prime);
316+
dp->plug_prime = NULL;
317+
schedule_work(&dp->work);
318+
mutex_unlock(&dp->lock);
257319
}
258320

259321
static void dp_altmode_attention(struct typec_altmode *alt, const u32 vdo)
@@ -314,6 +376,8 @@ static int dp_altmode_vdm(struct typec_altmode *alt,
314376
dp->hpd = false;
315377
sysfs_notify(&dp->alt->dev.kobj, "displayport", "hpd");
316378
}
379+
if (dp->plug_prime)
380+
dp->state = DP_STATE_EXIT_PRIME;
317381
break;
318382
case DP_CMD_STATUS_UPDATE:
319383
dp->data.status = *vdo;
@@ -348,10 +412,84 @@ static int dp_altmode_vdm(struct typec_altmode *alt,
348412
return ret;
349413
}
350414

415+
static int dp_cable_altmode_vdm(struct typec_altmode *alt, enum typec_plug_index sop,
416+
const u32 hdr, const u32 *vdo, int count)
417+
{
418+
struct dp_altmode *dp = typec_altmode_get_drvdata(alt);
419+
int cmd_type = PD_VDO_CMDT(hdr);
420+
int cmd = PD_VDO_CMD(hdr);
421+
int ret = 0;
422+
423+
mutex_lock(&dp->lock);
424+
425+
if (dp->state != DP_STATE_IDLE) {
426+
ret = -EBUSY;
427+
goto err_unlock;
428+
}
429+
430+
switch (cmd_type) {
431+
case CMDT_RSP_ACK:
432+
switch (cmd) {
433+
case CMD_ENTER_MODE:
434+
typec_altmode_update_active(dp->plug_prime, true);
435+
dp->state = DP_STATE_ENTER;
436+
break;
437+
case CMD_EXIT_MODE:
438+
dp->data_prime.status = 0;
439+
dp->data_prime.conf = 0;
440+
typec_altmode_update_active(dp->plug_prime, false);
441+
break;
442+
case DP_CMD_CONFIGURE:
443+
dp->state = DP_STATE_CONFIGURE;
444+
break;
445+
default:
446+
break;
447+
}
448+
break;
449+
case CMDT_RSP_NAK:
450+
switch (cmd) {
451+
case DP_CMD_CONFIGURE:
452+
dp->data_prime.conf = 0;
453+
/* Attempt to configure on SOP, drop plug */
454+
typec_altmode_put_plug(dp->plug_prime);
455+
dp->plug_prime = NULL;
456+
dp->state = DP_STATE_CONFIGURE;
457+
break;
458+
default:
459+
break;
460+
}
461+
break;
462+
default:
463+
break;
464+
}
465+
466+
if (dp->state != DP_STATE_IDLE)
467+
schedule_work(&dp->work);
468+
469+
err_unlock:
470+
mutex_unlock(&dp->lock);
471+
return ret;
472+
}
473+
351474
static int dp_altmode_activate(struct typec_altmode *alt, int activate)
352475
{
353-
return activate ? typec_altmode_enter(alt, NULL) :
354-
typec_altmode_exit(alt);
476+
struct dp_altmode *dp = typec_altmode_get_drvdata(alt);
477+
int ret;
478+
479+
if (activate) {
480+
if (dp->plug_prime) {
481+
ret = typec_cable_altmode_enter(alt, TYPEC_PLUG_SOP_P, NULL);
482+
if (ret < 0) {
483+
typec_altmode_put_plug(dp->plug_prime);
484+
dp->plug_prime = NULL;
485+
} else {
486+
return ret;
487+
}
488+
}
489+
return typec_altmode_enter(alt, NULL);
490+
} else {
491+
return typec_altmode_exit(alt);
492+
}
355493
}
356494

357495
static const struct typec_altmode_ops dp_altmode_ops = {
@@ -360,6 +498,10 @@ static const struct typec_altmode_ops dp_altmode_ops = {
360498
.activate = dp_altmode_activate,
361499
};
362500

501+
static const struct typec_cable_ops dp_cable_ops = {
502+
.vdm = dp_cable_altmode_vdm,
503+
};
504+
363505
static const char * const configurations[] = {
364506
[DP_CONF_USB] = "USB",
365507
[DP_CONF_DFP_D] = "source",
@@ -501,6 +643,7 @@ pin_assignment_store(struct device *dev, struct device_attribute *attr,
501643

502644
/* Only send Configure command if a configuration has been set */
503645
if (dp->alt->active && DP_CONF_CURRENTLY(dp->data.conf)) {
646+
/* todo: send manual configure over SOP'*/
504647
ret = dp_altmode_configure_vdm(dp, conf);
505648
if (ret)
506649
goto out_unlock;
@@ -574,6 +717,7 @@ static const struct attribute_group dp_altmode_group = {
574717
int dp_altmode_probe(struct typec_altmode *alt)
575718
{
576719
const struct typec_altmode *port = typec_altmode_get_partner(alt);
720+
struct typec_altmode *plug = typec_altmode_get_plug(alt, TYPEC_PLUG_SOP_P);
577721
struct fwnode_handle *fwnode;
578722
struct dp_altmode *dp;
579723
int ret;
@@ -603,6 +747,13 @@ int dp_altmode_probe(struct typec_altmode *alt)
603747
alt->desc = "DisplayPort";
604748
alt->ops = &dp_altmode_ops;
605749

750+
if (plug) {
751+
plug->desc = "Displayport";
752+
plug->cable_ops = &dp_cable_ops;
753+
}
754+
755+
dp->plug_prime = plug;
756+
606757
fwnode = dev_fwnode(alt->dev.parent->parent); /* typec_port fwnode */
607758
if (fwnode_property_present(fwnode, "displayport"))
608759
dp->connector_fwnode = fwnode_find_reference(fwnode, "displayport", 0);
@@ -612,8 +763,10 @@ int dp_altmode_probe(struct typec_altmode *alt)
612763
dp->connector_fwnode = NULL;
613764

614765
typec_altmode_set_drvdata(alt, dp);
766+
if (plug)
767+
typec_altmode_set_drvdata(plug, dp);
615768

616-
dp->state = DP_STATE_ENTER;
769+
dp->state = plug ? DP_STATE_ENTER_PRIME : DP_STATE_ENTER;
617770
schedule_work(&dp->work);
618771

619772
return 0;
@@ -626,6 +779,7 @@ void dp_altmode_remove(struct typec_altmode *alt)
626779

627780
sysfs_remove_group(&alt->dev.kobj, &dp_altmode_group);
628781
cancel_work_sync(&dp->work);
782+
typec_altmode_put_plug(dp->plug_prime);
629783

630784
if (dp->connector_fwnode) {
631785
drm_connector_oob_hotplug_event(dp->connector_fwnode,

0 commit comments

Comments
 (0)