Skip to content

Commit e9576fe

Browse files
Heikki Krogerusgregkh
authored andcommitted
usb: typec: tcpm: Support for Alternate Modes
This adds more complete handling of VDMs and registration of partner alternate modes, and introduces callbacks for alternate mode operations. Only DFP role is supported for now. Signed-off-by: Heikki Krogerus <[email protected]> Tested-by: Hans de Goede <[email protected]> Signed-off-by: Greg Kroah-Hartman <[email protected]>
1 parent 49cbb33 commit e9576fe

File tree

2 files changed

+135
-39
lines changed

2 files changed

+135
-39
lines changed

drivers/usb/typec/tcpm.c

Lines changed: 135 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
#include <linux/usb/pd_vdo.h>
2727
#include <linux/usb/role.h>
2828
#include <linux/usb/tcpm.h>
29-
#include <linux/usb/typec.h>
29+
#include <linux/usb/typec_altmode.h>
3030
#include <linux/workqueue.h>
3131

3232
#define FOREACH_STATE(S) \
@@ -169,13 +169,14 @@ enum pd_msg_request {
169169
/* Alternate mode support */
170170

171171
#define SVID_DISCOVERY_MAX 16
172+
#define ALTMODE_DISCOVERY_MAX (SVID_DISCOVERY_MAX * MODE_DISCOVERY_MAX)
172173

173174
struct pd_mode_data {
174175
int svid_index; /* current SVID index */
175176
int nsvids;
176177
u16 svids[SVID_DISCOVERY_MAX];
177178
int altmodes; /* number of alternate modes */
178-
struct typec_altmode_desc altmode_desc[SVID_DISCOVERY_MAX];
179+
struct typec_altmode_desc altmode_desc[ALTMODE_DISCOVERY_MAX];
179180
};
180181

181182
struct pd_pps_data {
@@ -310,8 +311,8 @@ struct tcpm_port {
310311

311312
/* Alternate mode data */
312313
struct pd_mode_data mode_data;
313-
struct typec_altmode *partner_altmode[SVID_DISCOVERY_MAX * 6];
314-
struct typec_altmode *port_altmode[SVID_DISCOVERY_MAX * 6];
314+
struct typec_altmode *partner_altmode[ALTMODE_DISCOVERY_MAX];
315+
struct typec_altmode *port_altmode[ALTMODE_DISCOVERY_MAX];
315316

316317
/* Deadline in jiffies to exit src_try_wait state */
317318
unsigned long max_wait;
@@ -641,14 +642,14 @@ void tcpm_pd_transmit_complete(struct tcpm_port *port,
641642
}
642643
EXPORT_SYMBOL_GPL(tcpm_pd_transmit_complete);
643644

644-
static int tcpm_mux_set(struct tcpm_port *port, enum tcpc_mux_mode mode,
645+
static int tcpm_mux_set(struct tcpm_port *port, int state,
645646
enum usb_role usb_role,
646647
enum typec_orientation orientation)
647648
{
648649
int ret;
649650

650-
tcpm_log(port, "Requesting mux mode %d, usb-role %d, orientation %d",
651-
mode, usb_role, orientation);
651+
tcpm_log(port, "Requesting mux state %d, usb-role %d, orientation %d",
652+
state, usb_role, orientation);
652653

653654
ret = typec_set_orientation(port->typec_port, orientation);
654655
if (ret)
@@ -660,7 +661,7 @@ static int tcpm_mux_set(struct tcpm_port *port, enum tcpc_mux_mode mode,
660661
return ret;
661662
}
662663

663-
return typec_set_mode(port->typec_port, mode);
664+
return typec_set_mode(port->typec_port, state);
664665
}
665666

666667
static int tcpm_set_polarity(struct tcpm_port *port,
@@ -787,7 +788,7 @@ static int tcpm_set_roles(struct tcpm_port *port, bool attached,
787788
else
788789
usb_role = USB_ROLE_DEVICE;
789790

790-
ret = tcpm_mux_set(port, TYPEC_MUX_USB, usb_role, orientation);
791+
ret = tcpm_mux_set(port, TYPEC_STATE_USB, usb_role, orientation);
791792
if (ret < 0)
792793
return ret;
793794

@@ -1014,36 +1015,57 @@ static void svdm_consume_modes(struct tcpm_port *port, const __le32 *payload,
10141015
pmdata->altmodes, paltmode->svid,
10151016
paltmode->mode, paltmode->vdo);
10161017

1017-
port->partner_altmode[pmdata->altmodes] =
1018-
typec_partner_register_altmode(port->partner, paltmode);
1019-
if (!port->partner_altmode[pmdata->altmodes]) {
1020-
tcpm_log(port,
1021-
"Failed to register modes for SVID 0x%04x",
1022-
paltmode->svid);
1023-
return;
1024-
}
10251018
pmdata->altmodes++;
10261019
}
10271020
}
10281021

1022+
static void tcpm_register_partner_altmodes(struct tcpm_port *port)
1023+
{
1024+
struct pd_mode_data *modep = &port->mode_data;
1025+
struct typec_altmode *altmode;
1026+
int i;
1027+
1028+
for (i = 0; i < modep->altmodes; i++) {
1029+
altmode = typec_partner_register_altmode(port->partner,
1030+
&modep->altmode_desc[i]);
1031+
if (!altmode)
1032+
tcpm_log(port, "Failed to register partner SVID 0x%04x",
1033+
modep->altmode_desc[i].svid);
1034+
port->partner_altmode[i] = altmode;
1035+
}
1036+
}
1037+
10291038
#define supports_modal(port) PD_IDH_MODAL_SUPP((port)->partner_ident.id_header)
10301039

10311040
static int tcpm_pd_svdm(struct tcpm_port *port, const __le32 *payload, int cnt,
10321041
u32 *response)
10331042
{
1034-
u32 p0 = le32_to_cpu(payload[0]);
1035-
int cmd_type = PD_VDO_CMDT(p0);
1036-
int cmd = PD_VDO_CMD(p0);
1043+
struct typec_altmode *adev;
1044+
struct typec_altmode *pdev;
10371045
struct pd_mode_data *modep;
1046+
u32 p[PD_MAX_PAYLOAD];
10381047
int rlen = 0;
1039-
u16 svid;
1048+
int cmd_type;
1049+
int cmd;
10401050
int i;
10411051

1052+
for (i = 0; i < cnt; i++)
1053+
p[i] = le32_to_cpu(payload[i]);
1054+
1055+
cmd_type = PD_VDO_CMDT(p[0]);
1056+
cmd = PD_VDO_CMD(p[0]);
1057+
10421058
tcpm_log(port, "Rx VDM cmd 0x%x type %d cmd %d len %d",
1043-
p0, cmd_type, cmd, cnt);
1059+
p[0], cmd_type, cmd, cnt);
10441060

10451061
modep = &port->mode_data;
10461062

1063+
adev = typec_match_altmode(port->port_altmode, ALTMODE_DISCOVERY_MAX,
1064+
PD_VDO_VID(p[0]), PD_VDO_OPOS(p[0]));
1065+
1066+
pdev = typec_match_altmode(port->partner_altmode, ALTMODE_DISCOVERY_MAX,
1067+
PD_VDO_VID(p[0]), PD_VDO_OPOS(p[0]));
1068+
10471069
switch (cmd_type) {
10481070
case CMDT_INIT:
10491071
switch (cmd) {
@@ -1065,17 +1087,19 @@ static int tcpm_pd_svdm(struct tcpm_port *port, const __le32 *payload, int cnt,
10651087
case CMD_EXIT_MODE:
10661088
break;
10671089
case CMD_ATTENTION:
1068-
break;
1090+
/* Attention command does not have response */
1091+
typec_altmode_attention(adev, p[1]);
1092+
return 0;
10691093
default:
10701094
break;
10711095
}
10721096
if (rlen >= 1) {
1073-
response[0] = p0 | VDO_CMDT(CMDT_RSP_ACK);
1097+
response[0] = p[0] | VDO_CMDT(CMDT_RSP_ACK);
10741098
} else if (rlen == 0) {
1075-
response[0] = p0 | VDO_CMDT(CMDT_RSP_NAK);
1099+
response[0] = p[0] | VDO_CMDT(CMDT_RSP_NAK);
10761100
rlen = 1;
10771101
} else {
1078-
response[0] = p0 | VDO_CMDT(CMDT_RSP_BUSY);
1102+
response[0] = p[0] | VDO_CMDT(CMDT_RSP_BUSY);
10791103
rlen = 1;
10801104
}
10811105
break;
@@ -1108,14 +1132,39 @@ static int tcpm_pd_svdm(struct tcpm_port *port, const __le32 *payload, int cnt,
11081132
svdm_consume_modes(port, payload, cnt);
11091133
modep->svid_index++;
11101134
if (modep->svid_index < modep->nsvids) {
1111-
svid = modep->svids[modep->svid_index];
1135+
u16 svid = modep->svids[modep->svid_index];
11121136
response[0] = VDO(svid, 1, CMD_DISCOVER_MODES);
11131137
rlen = 1;
11141138
} else {
1115-
/* enter alternate mode if/when implemented */
1139+
tcpm_register_partner_altmodes(port);
11161140
}
11171141
break;
11181142
case CMD_ENTER_MODE:
1143+
typec_altmode_update_active(pdev, true);
1144+
1145+
if (typec_altmode_vdm(adev, p[0], &p[1], cnt)) {
1146+
response[0] = VDO(adev->svid, 1, CMD_EXIT_MODE);
1147+
response[0] |= VDO_OPOS(adev->mode);
1148+
return 1;
1149+
}
1150+
return 0;
1151+
case CMD_EXIT_MODE:
1152+
typec_altmode_update_active(pdev, false);
1153+
1154+
/* Back to USB Operation */
1155+
WARN_ON(typec_altmode_notify(adev, TYPEC_STATE_USB,
1156+
NULL));
1157+
break;
1158+
default:
1159+
break;
1160+
}
1161+
break;
1162+
case CMDT_RSP_NAK:
1163+
switch (cmd) {
1164+
case CMD_ENTER_MODE:
1165+
/* Back to USB Operation */
1166+
WARN_ON(typec_altmode_notify(adev, TYPEC_STATE_USB,
1167+
NULL));
11191168
break;
11201169
default:
11211170
break;
@@ -1125,6 +1174,9 @@ static int tcpm_pd_svdm(struct tcpm_port *port, const __le32 *payload, int cnt,
11251174
break;
11261175
}
11271176

1177+
/* Informing the alternate mode drivers about everything */
1178+
typec_altmode_vdm(adev, p[0], &p[1], cnt);
1179+
11281180
return rlen;
11291181
}
11301182

@@ -1408,6 +1460,57 @@ static int tcpm_validate_caps(struct tcpm_port *port, const u32 *pdo,
14081460
return 0;
14091461
}
14101462

1463+
static int tcpm_altmode_enter(struct typec_altmode *altmode)
1464+
{
1465+
struct tcpm_port *port = typec_altmode_get_drvdata(altmode);
1466+
u32 header;
1467+
1468+
mutex_lock(&port->lock);
1469+
header = VDO(altmode->svid, 1, CMD_ENTER_MODE);
1470+
header |= VDO_OPOS(altmode->mode);
1471+
1472+
tcpm_queue_vdm(port, header, NULL, 0);
1473+
mod_delayed_work(port->wq, &port->vdm_state_machine, 0);
1474+
mutex_unlock(&port->lock);
1475+
1476+
return 0;
1477+
}
1478+
1479+
static int tcpm_altmode_exit(struct typec_altmode *altmode)
1480+
{
1481+
struct tcpm_port *port = typec_altmode_get_drvdata(altmode);
1482+
u32 header;
1483+
1484+
mutex_lock(&port->lock);
1485+
header = VDO(altmode->svid, 1, CMD_EXIT_MODE);
1486+
header |= VDO_OPOS(altmode->mode);
1487+
1488+
tcpm_queue_vdm(port, header, NULL, 0);
1489+
mod_delayed_work(port->wq, &port->vdm_state_machine, 0);
1490+
mutex_unlock(&port->lock);
1491+
1492+
return 0;
1493+
}
1494+
1495+
static int tcpm_altmode_vdm(struct typec_altmode *altmode,
1496+
u32 header, const u32 *data, int count)
1497+
{
1498+
struct tcpm_port *port = typec_altmode_get_drvdata(altmode);
1499+
1500+
mutex_lock(&port->lock);
1501+
tcpm_queue_vdm(port, header, data, count - 1);
1502+
mod_delayed_work(port->wq, &port->vdm_state_machine, 0);
1503+
mutex_unlock(&port->lock);
1504+
1505+
return 0;
1506+
}
1507+
1508+
static const struct typec_altmode_ops tcpm_altmode_ops = {
1509+
.enter = tcpm_altmode_enter,
1510+
.exit = tcpm_altmode_exit,
1511+
.vdm = tcpm_altmode_vdm,
1512+
};
1513+
14111514
/*
14121515
* PD (data, control) command handling functions
14131516
*/
@@ -2539,7 +2642,7 @@ static int tcpm_src_attach(struct tcpm_port *port)
25392642
out_disable_pd:
25402643
port->tcpc->set_pd_rx(port->tcpc, false);
25412644
out_disable_mux:
2542-
tcpm_mux_set(port, TYPEC_MUX_NONE, USB_ROLE_NONE,
2645+
tcpm_mux_set(port, TYPEC_STATE_SAFE, USB_ROLE_NONE,
25432646
TYPEC_ORIENTATION_NONE);
25442647
return ret;
25452648
}
@@ -2585,7 +2688,7 @@ static void tcpm_reset_port(struct tcpm_port *port)
25852688
tcpm_init_vconn(port);
25862689
tcpm_set_current_limit(port, 0, 0);
25872690
tcpm_set_polarity(port, TYPEC_POLARITY_CC1);
2588-
tcpm_mux_set(port, TYPEC_MUX_NONE, USB_ROLE_NONE,
2691+
tcpm_mux_set(port, TYPEC_STATE_SAFE, USB_ROLE_NONE,
25892692
TYPEC_ORIENTATION_NONE);
25902693
tcpm_set_attached_state(port, false);
25912694
port->try_src_count = 0;
@@ -4706,6 +4809,8 @@ struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc)
47064809
dev_name(dev), paltmode->svid);
47074810
break;
47084811
}
4812+
typec_altmode_set_drvdata(alt, port);
4813+
alt->ops = &tcpm_altmode_ops;
47094814
port->port_altmode[i] = alt;
47104815
i++;
47114816
paltmode++;

include/linux/usb/tcpm.h

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -98,15 +98,6 @@ struct tcpc_config {
9898
#define TCPC_MUX_DP_ENABLED BIT(1) /* DP enabled */
9999
#define TCPC_MUX_POLARITY_INVERTED BIT(2) /* Polarity inverted */
100100

101-
/* Mux modes, decoded to attributes */
102-
enum tcpc_mux_mode {
103-
TYPEC_MUX_NONE = 0, /* Open switch */
104-
TYPEC_MUX_USB = TCPC_MUX_USB_ENABLED, /* USB only */
105-
TYPEC_MUX_DP = TCPC_MUX_DP_ENABLED, /* DP only */
106-
TYPEC_MUX_DOCK = TCPC_MUX_USB_ENABLED | /* Both USB and DP */
107-
TCPC_MUX_DP_ENABLED,
108-
};
109-
110101
/**
111102
* struct tcpc_dev - Port configuration and callback functions
112103
* @config: Pointer to port configuration

0 commit comments

Comments
 (0)