aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/usb/typec/tcpm/tcpm.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb/typec/tcpm/tcpm.c')
-rw-r--r--drivers/usb/typec/tcpm/tcpm.c138
1 files changed, 84 insertions, 54 deletions
diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c
index fb18264b702e..07db1f1a1f72 100644
--- a/drivers/usb/typec/tcpm/tcpm.c
+++ b/drivers/usb/typec/tcpm/tcpm.c
@@ -159,6 +159,14 @@ enum pd_msg_request {
PD_MSG_DATA_SOURCE_CAP,
};
+enum adev_actions {
+ ADEV_NONE = 0,
+ ADEV_NOTIFY_USB_AND_QUEUE_VDM,
+ ADEV_QUEUE_VDM,
+ ADEV_QUEUE_VDM_SEND_EXIT_MODE_ON_FAIL,
+ ADEV_ATTENTION,
+};
+
/* Events from low level driver */
#define TCPM_CC_EVENT BIT(0)
@@ -969,16 +977,15 @@ static void tcpm_queue_vdm(struct tcpm_port *port, const u32 header,
port->vdm_state = VDM_STATE_READY;
}
-static void svdm_consume_identity(struct tcpm_port *port, const __le32 *payload,
- int cnt)
+static void svdm_consume_identity(struct tcpm_port *port, const u32 *p, int cnt)
{
- u32 vdo = le32_to_cpu(payload[VDO_INDEX_IDH]);
- u32 product = le32_to_cpu(payload[VDO_INDEX_PRODUCT]);
+ u32 vdo = p[VDO_INDEX_IDH];
+ u32 product = p[VDO_INDEX_PRODUCT];
memset(&port->mode_data, 0, sizeof(port->mode_data));
port->partner_ident.id_header = vdo;
- port->partner_ident.cert_stat = le32_to_cpu(payload[VDO_INDEX_CSTAT]);
+ port->partner_ident.cert_stat = p[VDO_INDEX_CSTAT];
port->partner_ident.product = product;
typec_partner_set_identity(port->partner);
@@ -988,17 +995,15 @@ static void svdm_consume_identity(struct tcpm_port *port, const __le32 *payload,
PD_PRODUCT_PID(product), product & 0xffff);
}
-static bool svdm_consume_svids(struct tcpm_port *port, const __le32 *payload,
- int cnt)
+static bool svdm_consume_svids(struct tcpm_port *port, const u32 *p, int cnt)
{
struct pd_mode_data *pmdata = &port->mode_data;
int i;
for (i = 1; i < cnt; i++) {
- u32 p = le32_to_cpu(payload[i]);
u16 svid;
- svid = (p >> 16) & 0xffff;
+ svid = (p[i] >> 16) & 0xffff;
if (!svid)
return false;
@@ -1008,7 +1013,7 @@ static bool svdm_consume_svids(struct tcpm_port *port, const __le32 *payload,
pmdata->svids[pmdata->nsvids++] = svid;
tcpm_log(port, "SVID %d: 0x%x", pmdata->nsvids, svid);
- svid = p & 0xffff;
+ svid = p[i] & 0xffff;
if (!svid)
return false;
@@ -1018,14 +1023,27 @@ static bool svdm_consume_svids(struct tcpm_port *port, const __le32 *payload,
pmdata->svids[pmdata->nsvids++] = svid;
tcpm_log(port, "SVID %d: 0x%x", pmdata->nsvids, svid);
}
- return true;
+
+ /*
+ * PD3.0 Spec 6.4.4.3.2: The SVIDs are returned 2 per VDO (see Table
+ * 6-43), and can be returned maximum 6 VDOs per response (see Figure
+ * 6-19). If the Respondersupports 12 or more SVID then the Discover
+ * SVIDs Command Shall be executed multiple times until a Discover
+ * SVIDs VDO is returned ending either with a SVID value of 0x0000 in
+ * the last part of the last VDO or with a VDO containing two SVIDs
+ * with values of 0x0000.
+ *
+ * However, some odd dockers support SVIDs less than 12 but without
+ * 0x0000 in the last VDO, so we need to break the Discover SVIDs
+ * request and return false here.
+ */
+ return cnt == 7;
abort:
tcpm_log(port, "SVID_DISCOVERY_MAX(%d) too low!", SVID_DISCOVERY_MAX);
return false;
}
-static void svdm_consume_modes(struct tcpm_port *port, const __le32 *payload,
- int cnt)
+static void svdm_consume_modes(struct tcpm_port *port, const u32 *p, int cnt)
{
struct pd_mode_data *pmdata = &port->mode_data;
struct typec_altmode_desc *paltmode;
@@ -1042,7 +1060,7 @@ static void svdm_consume_modes(struct tcpm_port *port, const __le32 *payload,
paltmode->svid = pmdata->svids[pmdata->svid_index];
paltmode->mode = i;
- paltmode->vdo = le32_to_cpu(payload[i]);
+ paltmode->vdo = p[i];
tcpm_log(port, " Alternate mode %d: SVID 0x%04x, VDO %d: 0x%08x",
pmdata->altmodes, paltmode->svid,
@@ -1070,21 +1088,17 @@ static void tcpm_register_partner_altmodes(struct tcpm_port *port)
#define supports_modal(port) PD_IDH_MODAL_SUPP((port)->partner_ident.id_header)
-static int tcpm_pd_svdm(struct tcpm_port *port, const __le32 *payload, int cnt,
- u32 *response)
+static int tcpm_pd_svdm(struct tcpm_port *port, struct typec_altmode *adev,
+ const u32 *p, int cnt, u32 *response,
+ enum adev_actions *adev_action)
{
- struct typec_altmode *adev;
struct typec_altmode *pdev;
struct pd_mode_data *modep;
- u32 p[PD_MAX_PAYLOAD];
int rlen = 0;
int cmd_type;
int cmd;
int i;
- for (i = 0; i < cnt; i++)
- p[i] = le32_to_cpu(payload[i]);
-
cmd_type = PD_VDO_CMDT(p[0]);
cmd = PD_VDO_CMD(p[0]);
@@ -1093,9 +1107,6 @@ static int tcpm_pd_svdm(struct tcpm_port *port, const __le32 *payload, int cnt,
modep = &port->mode_data;
- adev = typec_match_altmode(port->port_altmode, ALTMODE_DISCOVERY_MAX,
- PD_VDO_VID(p[0]), PD_VDO_OPOS(p[0]));
-
pdev = typec_match_altmode(port->partner_altmode, ALTMODE_DISCOVERY_MAX,
PD_VDO_VID(p[0]), PD_VDO_OPOS(p[0]));
@@ -1121,8 +1132,7 @@ static int tcpm_pd_svdm(struct tcpm_port *port, const __le32 *payload, int cnt,
break;
case CMD_ATTENTION:
/* Attention command does not have response */
- if (adev)
- typec_altmode_attention(adev, p[1]);
+ *adev_action = ADEV_ATTENTION;
return 0;
default:
break;
@@ -1145,13 +1155,13 @@ static int tcpm_pd_svdm(struct tcpm_port *port, const __le32 *payload, int cnt,
switch (cmd) {
case CMD_DISCOVER_IDENT:
/* 6.4.4.3.1 */
- svdm_consume_identity(port, payload, cnt);
+ svdm_consume_identity(port, p, cnt);
response[0] = VDO(USB_SID_PD, 1, CMD_DISCOVER_SVID);
rlen = 1;
break;
case CMD_DISCOVER_SVID:
/* 6.4.4.3.2 */
- if (svdm_consume_svids(port, payload, cnt)) {
+ if (svdm_consume_svids(port, p, cnt)) {
response[0] = VDO(USB_SID_PD, 1,
CMD_DISCOVER_SVID);
rlen = 1;
@@ -1163,7 +1173,7 @@ static int tcpm_pd_svdm(struct tcpm_port *port, const __le32 *payload, int cnt,
break;
case CMD_DISCOVER_MODES:
/* 6.4.4.3.3 */
- svdm_consume_modes(port, payload, cnt);
+ svdm_consume_modes(port, p, cnt);
modep->svid_index++;
if (modep->svid_index < modep->nsvids) {
u16 svid = modep->svids[modep->svid_index];
@@ -1176,23 +1186,15 @@ static int tcpm_pd_svdm(struct tcpm_port *port, const __le32 *payload, int cnt,
case CMD_ENTER_MODE:
if (adev && pdev) {
typec_altmode_update_active(pdev, true);
-
- if (typec_altmode_vdm(adev, p[0], &p[1], cnt)) {
- response[0] = VDO(adev->svid, 1,
- CMD_EXIT_MODE);
- response[0] |= VDO_OPOS(adev->mode);
- return 1;
- }
+ *adev_action = ADEV_QUEUE_VDM_SEND_EXIT_MODE_ON_FAIL;
}
return 0;
case CMD_EXIT_MODE:
if (adev && pdev) {
typec_altmode_update_active(pdev, false);
-
/* Back to USB Operation */
- WARN_ON(typec_altmode_notify(adev,
- TYPEC_STATE_USB,
- NULL));
+ *adev_action = ADEV_NOTIFY_USB_AND_QUEUE_VDM;
+ return 0;
}
break;
default:
@@ -1203,11 +1205,8 @@ static int tcpm_pd_svdm(struct tcpm_port *port, const __le32 *payload, int cnt,
switch (cmd) {
case CMD_ENTER_MODE:
/* Back to USB Operation */
- if (adev)
- WARN_ON(typec_altmode_notify(adev,
- TYPEC_STATE_USB,
- NULL));
- break;
+ *adev_action = ADEV_NOTIFY_USB_AND_QUEUE_VDM;
+ return 0;
default:
break;
}
@@ -1217,24 +1216,30 @@ static int tcpm_pd_svdm(struct tcpm_port *port, const __le32 *payload, int cnt,
}
/* Informing the alternate mode drivers about everything */
- if (adev)
- typec_altmode_vdm(adev, p[0], &p[1], cnt);
-
+ *adev_action = ADEV_QUEUE_VDM;
return rlen;
}
static void tcpm_handle_vdm_request(struct tcpm_port *port,
const __le32 *payload, int cnt)
{
- int rlen = 0;
+ enum adev_actions adev_action = ADEV_NONE;
+ struct typec_altmode *adev;
+ u32 p[PD_MAX_PAYLOAD];
u32 response[8] = { };
- u32 p0 = le32_to_cpu(payload[0]);
+ int i, rlen = 0;
+
+ for (i = 0; i < cnt; i++)
+ p[i] = le32_to_cpu(payload[i]);
+
+ adev = typec_match_altmode(port->port_altmode, ALTMODE_DISCOVERY_MAX,
+ PD_VDO_VID(p[0]), PD_VDO_OPOS(p[0]));
if (port->vdm_state == VDM_STATE_BUSY) {
/* If UFP responded busy retry after timeout */
- if (PD_VDO_CMDT(p0) == CMDT_RSP_BUSY) {
+ if (PD_VDO_CMDT(p[0]) == CMDT_RSP_BUSY) {
port->vdm_state = VDM_STATE_WAIT_RSP_BUSY;
- port->vdo_retry = (p0 & ~VDO_CMDT_MASK) |
+ port->vdo_retry = (p[0] & ~VDO_CMDT_MASK) |
CMDT_INIT;
mod_delayed_work(port->wq, &port->vdm_state_machine,
msecs_to_jiffies(PD_T_VDM_BUSY));
@@ -1243,8 +1248,33 @@ static void tcpm_handle_vdm_request(struct tcpm_port *port,
port->vdm_state = VDM_STATE_DONE;
}
- if (PD_VDO_SVDM(p0))
- rlen = tcpm_pd_svdm(port, payload, cnt, response);
+ if (PD_VDO_SVDM(p[0]))
+ rlen = tcpm_pd_svdm(port, adev, p, cnt, response, &adev_action);
+
+ if (adev) {
+ switch (adev_action) {
+ case ADEV_NONE:
+ break;
+ case ADEV_NOTIFY_USB_AND_QUEUE_VDM:
+ WARN_ON(typec_altmode_notify(adev, TYPEC_STATE_USB, NULL));
+ typec_altmode_vdm(adev, p[0], &p[1], cnt);
+ break;
+ case ADEV_QUEUE_VDM:
+ typec_altmode_vdm(adev, p[0], &p[1], cnt);
+ break;
+ case ADEV_QUEUE_VDM_SEND_EXIT_MODE_ON_FAIL:
+ if (typec_altmode_vdm(adev, p[0], &p[1], cnt)) {
+ response[0] = VDO(adev->svid, 1, CMD_EXIT_MODE);
+ response[0] |= VDO_OPOS(adev->mode);
+ rlen = 1;
+ }
+ break;
+ case ADEV_ATTENTION:
+ if (typec_altmode_attention(adev, p[1]))
+ tcpm_log(port, "typec_altmode_attention no port partner altmode");
+ break;
+ }
+ }
if (rlen > 0) {
tcpm_queue_vdm(port, response[0], &response[1], rlen - 1);