summaryrefslogtreecommitdiff
path: root/drivers/platform
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/platform')
-rw-r--r--drivers/platform/chrome/cros_ec_typec.c94
1 files changed, 65 insertions, 29 deletions
diff --git a/drivers/platform/chrome/cros_ec_typec.c b/drivers/platform/chrome/cros_ec_typec.c
index 4427897eb3f5..5871fe031f14 100644
--- a/drivers/platform/chrome/cros_ec_typec.c
+++ b/drivers/platform/chrome/cros_ec_typec.c
@@ -8,6 +8,8 @@
#include <linux/acpi.h>
#include <linux/array_size.h>
+#include <linux/delay.h>
+#include <linux/jiffies.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/overflow.h>
@@ -24,6 +26,14 @@
#define DRV_NAME "cros-ec-typec"
+/*
+ * Mirrors PD_ROLE_SWAP_TIMEOUT used by the TCPM port manager: a PR_Swap may
+ * legitimately take seconds when the partner answers with Wait and the
+ * initiator retries.
+ */
+#define ROLE_SWAP_TIMEOUT_MS 10000
+#define ROLE_SWAP_DELAY_MS 20
+
#define DP_PORT_VDO (DP_CAP_DFP_D | DP_CAP_RECEPTACLE | \
DP_CONF_SET_PIN_ASSIGN(BIT(DP_PIN_ASSIGN_C) | \
BIT(DP_PIN_ASSIGN_D) | \
@@ -60,12 +70,33 @@ static int cros_typec_enter_usb_mode(struct typec_port *tc_port, enum usb_mode m
&req, sizeof(req), NULL, 0);
}
+/*
+ * Decode the role relevant to @swap_type out of an EC_CMD_USB_PD_CONTROL
+ * response.
+ *
+ * Returns a value comparable against the typec class target role
+ * (TYPEC_DEVICE/TYPEC_HOST or TYPEC_SINK/TYPEC_SOURCE), or -EOPNOTSUPP if
+ * @swap_type is not a role swap this driver knows how to track.
+ */
+static int cros_typec_resp_to_role(struct ec_response_usb_pd_control_v2 *resp, u8 swap_type)
+{
+ switch (swap_type) {
+ case USB_PD_CTRL_SWAP_DATA:
+ return (resp->role & PD_CTRL_RESP_ROLE_DATA) ? TYPEC_HOST : TYPEC_DEVICE;
+ case USB_PD_CTRL_SWAP_POWER:
+ return (resp->role & PD_CTRL_RESP_ROLE_POWER) ? TYPEC_SOURCE : TYPEC_SINK;
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
static int cros_typec_perform_role_swap(struct typec_port *tc_port, int target_role, u8 swap_type)
{
struct cros_typec_port *port = typec_get_drvdata(tc_port);
struct cros_typec_data *data = port->typec_data;
struct ec_response_usb_pd_control_v2 resp;
struct ec_params_usb_pd_control req;
+ unsigned long end;
int role, ret;
/* Must be at least v1 to support role swap. */
@@ -83,18 +114,10 @@ static int cros_typec_perform_role_swap(struct typec_port *tc_port, int target_r
if (ret < 0)
return ret;
- switch (swap_type) {
- case USB_PD_CTRL_SWAP_DATA:
- role = (resp.role & PD_CTRL_RESP_ROLE_DATA) ? TYPEC_HOST :
- TYPEC_DEVICE;
- break;
- case USB_PD_CTRL_SWAP_POWER:
- role = (resp.role & PD_CTRL_RESP_ROLE_POWER) ? TYPEC_SOURCE :
- TYPEC_SINK;
- break;
- default:
+ role = cros_typec_resp_to_role(&resp, swap_type);
+ if (role < 0) {
dev_warn(data->dev, "Unsupported role swap type %d\n", swap_type);
- return -EOPNOTSUPP;
+ return role;
}
if (role == target_role)
@@ -106,28 +129,41 @@ static int cros_typec_perform_role_swap(struct typec_port *tc_port, int target_r
if (ret < 0)
return ret;
- switch (swap_type) {
- case USB_PD_CTRL_SWAP_DATA:
- role = resp.role & PD_CTRL_RESP_ROLE_DATA ? TYPEC_HOST : TYPEC_DEVICE;
- if (role != target_role) {
- dev_err(data->dev, "Data role swap failed despite EC returning success\n");
- return -EIO;
+ /*
+ * Depending on the EC USB-C stack (TCPMv2 vs PDC), EC_CMD_USB_PD_CONTROL
+ * may either block until the swap completes or queue the request
+ * asynchronously and return the pre-swap role. Poll until the target
+ * role is reached while connected, or until the timeout expires.
+ */
+ req.swap = USB_PD_CTRL_SWAP_NONE;
+ end = jiffies + msecs_to_jiffies(ROLE_SWAP_TIMEOUT_MS);
+ for (;;) {
+ if (!(resp.enabled & PD_CTRL_RESP_ENABLED_CONNECTED)) {
+ dev_warn(data->dev, "Port disconnected during role swap\n");
+ return -ENOTCONN;
}
- typec_set_data_role(tc_port, target_role);
- break;
- case USB_PD_CTRL_SWAP_POWER:
- role = resp.role & PD_CTRL_RESP_ROLE_POWER ? TYPEC_SOURCE : TYPEC_SINK;
- if (role != target_role) {
- dev_err(data->dev, "Power role swap failed despite EC returning success\n");
- return -EIO;
+
+ if (cros_typec_resp_to_role(&resp, swap_type) == target_role)
+ break;
+
+ if (time_after_eq(jiffies, end)) {
+ dev_err(data->dev, "Timed out waiting for role swap to complete\n");
+ return -ETIMEDOUT;
}
- typec_set_pwr_role(tc_port, target_role);
- break;
- default:
- /* Should never execute */
- break;
+
+ msleep(ROLE_SWAP_DELAY_MS);
+ ret = cros_ec_cmd(data->ec, data->pd_ctrl_ver,
+ EC_CMD_USB_PD_CONTROL, &req, sizeof(req),
+ &resp, sizeof(resp));
+ if (ret < 0)
+ return ret;
}
+ if (swap_type == USB_PD_CTRL_SWAP_DATA)
+ typec_set_data_role(tc_port, target_role);
+ else
+ typec_set_pwr_role(tc_port, target_role);
+
return 0;
}