diff options
Diffstat (limited to 'drivers/platform')
| -rw-r--r-- | drivers/platform/chrome/cros_ec_typec.c | 94 |
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; } |
