Widens the #160 fix, which was scoped too narrowly to host role, and fixes flare-edge#168. Device role was deliberately left alone on the reasoning that the state machine is only meaningless when the port is a host. That was wrong. On a board with no extcon phandle the machine has no external role signal in EITHER direction, and with bvalid pinned high by the VBUS bypass its charger detection cannot be meaningful at all. In device role its verdict suspends the phy underneath a bound gadget, so the USB recovery link dies. Reproduced deterministically on warden-c8a3 with a host attached: device role, host attached ccf=1 phy_sus=0x0 udc=configured works -> host role ccf=1 phy_sus=0x0 udc=(none) -> back to gadget ccf=0 phy_sus=0x1d1 udc=configured SUSPENDED -> +15s / +30s ccf=0 phy_sus=0x1d1 udc=configured never recovers and the 2207:0019 device disappears from the host for good. Nothing notices, because every obvious signal lies: udc state reads "configured", usb0 carrier reads 1, and DCTL RUN_STOP reads 1, so the gadget really is asserting its pullup. It asserts into a phy whose analog front end and 480M clock are off, so the SoC sees SE0 and the host sees no device at all, not even an enumeration error. It cannot self-heal either. bvalid is pinned high, so a host attaching later produces no VBUS edge for the machine to trigger on: it ran once, powered the phy off, and nothing re-evaluates. That is why a reboot appears to fix it and why this read as a cable fault for weeks. It is not, and the cables were fine. Gated on the extcon being self-allocated rather than on the board compatible, so it describes the actual precondition: no external role signal exists. Renamed from 29-usb2phy-no-otg-sm-in-host.patch, whose name now misstated the scope. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MVGTC78dgCGfRANNKjoPea
137 lines
4.4 KiB
Diff
137 lines
4.4 KiB
Diff
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",
|