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; }; /** @@ -239,6 +241,8 @@ * @dcd_retries: The retry count used to track Data contact * detection process. * @edev: extcon device for notification registration + * @edev_self_allocated: no extcon phandle in DT, so the OTG state machine + * has no external role signal at all * @irq: muxed interrupt for single irq configuration * @phy_cfg: phy register configuration, assigned by driver data. * @ports: phy port instance. @@ -257,6 +261,7 @@ enum power_supply_type chg_type; u8 dcd_retries; struct extcon_dev *edev; + bool edev_self_allocated; int irq; const struct rockchip_usb2phy_cfg *phy_cfg; struct rockchip_usb2phy_port ports[USB2PHY_NUM_PORTS]; @@ -459,6 +464,14 @@ if (ret) return dev_err_probe(rphy->dev, ret, "failed to register extcon device\n"); + + /* + * Nothing external drives the role on this board. The extcon + * below is ours, and no code ever sets a cable state on it, so + * the OTG state machine's view of the world is permanently + * blank rather than merely stale. + */ + rphy->edev_self_allocated = true; } rphy->edev = edev; @@ -649,11 +662,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 +721,15 @@ unsigned long delay; bool vbus_attach, sch_work, notify_charger; + /* + * This machine can only get the answer wrong here. In host role its + * verdict suspends a phy that is enumerating a device (#160); with a + * self-allocated extcon it has no role signal in EITHER direction, and + * in device role it strands the gadget the same way (#168). + */ + if (rport->host_mode || rphy->edev_self_allocated) + return; + vbus_attach = property_enabled(rphy->grf, &rport->port_cfg->utmi_bvalid); @@ -825,6 +888,15 @@ bool is_dcd, tmout, vout, vbus_attach; unsigned long delay; + /* + * Never probe the front end of a port that is hosting a device, and + * never at all on a board whose bvalid is pinned high by the VBUS + * bypass: charger detection cannot be meaningful there, and its DCP + * verdict powers the phy off underneath a working link (#160, #168). + */ + if (rport->host_mode || rphy->edev_self_allocated) + return; + vbus_attach = property_enabled(rphy->grf, &rport->port_cfg->utmi_bvalid); dev_dbg(&rport->phy->dev, "chg detection work state = %d\n",