diff --git a/drivers/phy/rockchip/phy-rockchip-inno-usb2.c b/drivers/phy/rockchip/phy-rockchip-inno-usb2.c --- a/drivers/phy/rockchip/phy-rockchip-inno-usb2.c +++ b/drivers/phy/rockchip/phy-rockchip-inno-usb2.c @@ -203,6 +203,7 @@ * @event_nb: hold event notification callback. * @state: define OTG enumeration states before device reset. * @mode: the dr_mode of the controller. + * @host_mode: the port is currently acting as a USB host. */ struct rockchip_usb2phy_port { struct phy *phy; @@ -222,6 +223,7 @@ struct notifier_block event_nb; enum usb_otg_state state; enum usb_dr_mode mode; + bool host_mode; }; /** @@ -649,11 +651,52 @@ return 0; } +/* + * The OTG port's state machines only make sense while the port is a + * peripheral. In host role their inputs are meaningless on a board with no + * ID source: charger detection drives the analog front end of a port that is + * already enumerating a device, and its DCP verdict ends in + * rockchip_usb2phy_power_off(), which suspends the phy and drops the 480M + * clkout that the host controller's core runs on. The controller then stops + * clocking with its registers still readable, so it reports itself healthy + * while no transfer ever completes. + * + * dwc3 already calls phy_set_mode() on every role change, but this driver + * implemented no .set_mode, so the phy never learned the role. Track it and + * keep both state machines out of host role. + */ +static int rockchip_usb2phy_set_mode(struct phy *phy, enum phy_mode mode, + int submode) +{ + struct rockchip_usb2phy_port *rport = phy_get_drvdata(phy); + + if (rport->port_id != USB2PHY_PORT_OTG) + return 0; + + switch (mode) { + case PHY_MODE_USB_HOST: + if (!rport->host_mode) { + rport->host_mode = true; + cancel_delayed_work_sync(&rport->otg_sm_work); + cancel_delayed_work_sync(&rport->chg_work); + } + break; + case PHY_MODE_USB_DEVICE: + rport->host_mode = false; + break; + default: + break; + } + + return 0; +} + static const struct phy_ops rockchip_usb2phy_ops = { .init = rockchip_usb2phy_init, .exit = rockchip_usb2phy_exit, .power_on = rockchip_usb2phy_power_on, .power_off = rockchip_usb2phy_power_off, + .set_mode = rockchip_usb2phy_set_mode, .owner = THIS_MODULE, }; @@ -667,6 +710,10 @@ unsigned long delay; bool vbus_attach, sch_work, notify_charger; + /* In host role this machine can only get the answer wrong. */ + if (rport->host_mode) + return; + vbus_attach = property_enabled(rphy->grf, &rport->port_cfg->utmi_bvalid); @@ -825,6 +872,10 @@ bool is_dcd, tmout, vout, vbus_attach; unsigned long delay; + /* Never probe the front end of a port that is hosting a device. */ + if (rport->host_mode) + return; + vbus_attach = property_enabled(rphy->grf, &rport->port_cfg->utmi_bvalid); dev_dbg(&rport->phy->dev, "chg detection work state = %d\n",