On Radxa ROCK 5B I managed to get U-Boot into an endless loop of printing fusb302 usb-typec@22: TCPM: data role mismatch, initiating error recovery
messages by changing the data role in Linux and then rebooting the system. This is happening because the external device (A cheap USB-C hub powered through a USB-C PD power-supply) kept its state and the error recovery path for non self-powered devices is not enough to change it. Avoid this by swapping our own data role when the error recovery stage is reached for a port, which is not self-powered. Right now data support is limited anyways and once proper support is added we can use the data role swap request to get the desired data direction after the initial negotiation completed. Fixes: 1db4c0ac77e3 ("usb: tcpm: add core framework") Signed-off-by: Sebastian Reichel <sebastian.reic...@collabora.com> --- drivers/usb/tcpm/tcpm.c | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/drivers/usb/tcpm/tcpm.c b/drivers/usb/tcpm/tcpm.c index 909fe2ef4fcb..12d66d470f9a 100644 --- a/drivers/usb/tcpm/tcpm.c +++ b/drivers/usb/tcpm/tcpm.c @@ -833,6 +833,28 @@ static void tcpm_pd_ctrl_request(struct udevice *dev, } } +static void tcpm_recover_data_role_mismatch(struct udevice *dev) +{ + struct tcpm_port *port = dev_get_uclass_plat(dev); + + dev_err(dev, "TCPM: data role mismatch, initiating error recovery\n"); + if (port->self_powered) { + tcpm_set_state(dev, ERROR_RECOVERY, 0); + return; + } + + /* + * The error recovery will not help for devices, which are not + * self-powered because the error recovery avoids killing the board + * power. Since this can happen early on sending + * a DR_SWAP request is not sensible. Instead let's change our own + * data role. It can be swapped back once USB-PD reached the ready + * state. + */ + tcpm_set_roles(dev, true, port->pwr_role, + port->data_role == TYPEC_HOST ? TYPEC_DEVICE : TYPEC_HOST); +} + static void tcpm_pd_rx_handler(struct udevice *dev, const struct pd_message *msg) { @@ -867,9 +889,11 @@ static void tcpm_pd_rx_handler(struct udevice *dev, remote_is_host = !!(le16_to_cpu(msg->header) & PD_HEADER_DATA_ROLE); local_is_host = port->data_role == TYPEC_HOST; if (remote_is_host == local_is_host) { - dev_err(dev, "TCPM: data role mismatch, initiating error recovery\n"); - tcpm_set_state(dev, ERROR_RECOVERY, 0); - } else { + tcpm_recover_data_role_mismatch(dev); + local_is_host = port->data_role == TYPEC_HOST; + } + + if (remote_is_host != local_is_host) { if (cnt) tcpm_pd_data_request(dev, msg); else -- 2.47.2