When operating in High-Speed, it is observed that DSTS[USBLNKST] doesn't update link state immediately after receiving the wakeup interrupt. Since wakeup event handler calls the resume callbacks, there is a chance that function drivers can perform an ep queue. Which in turn tries to perform remote wakeup from send_gadget_ep_cmd(), this happens because DSTS[[21:18] wasn't updated to U0 yet. It is observed that the latency of DSTS can be in order of milli-seconds. Hence update the dwc->link_state from evtinfo, and use this variable to prevent calling remote wakup unnecessarily.
Fixes: ecba9bc9946b ("usb: dwc3: gadget: Check for L1/L2/U3 for Start Transfer") Cc: stable@vger.kernel.org Signed-off-by: Prashanth K quic_prashk@quicinc.com --- drivers/usb/dwc3/gadget.c | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-)
diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index 89fc690fdf34..3b55285118b0 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -328,7 +328,8 @@ int dwc3_send_gadget_ep_cmd(struct dwc3_ep *dep, unsigned int cmd, }
if (DWC3_DEPCMD_CMD(cmd) == DWC3_DEPCMD_STARTTRANSFER) { - int link_state; + int link_state; + bool remote_wakeup = false;
/* * Initiate remote wakeup if the link state is in U3 when @@ -339,15 +340,26 @@ int dwc3_send_gadget_ep_cmd(struct dwc3_ep *dep, unsigned int cmd, link_state = dwc3_gadget_get_link_state(dwc); switch (link_state) { case DWC3_LINK_STATE_U2: - if (dwc->gadget->speed >= USB_SPEED_SUPER) + if (dwc->gadget->speed < USB_SPEED_SUPER) + remote_wakeup = true; + break; + case DWC3_LINK_STATE_U3: + /* + * In HS, DSTS can take few milliseconds to update linkstate bits, + * so rely on dwc->link_state to identify whether gadget woke up. + * Don't issue remote wakuep again if link is already in U0. + */ + if (dwc->link_state == DWC3_LINK_STATE_U0) break;
- fallthrough; - case DWC3_LINK_STATE_U3: + remote_wakeup = true; + break; + } + + if (remote_wakeup) { ret = __dwc3_gadget_wakeup(dwc, false); dev_WARN_ONCE(dwc->dev, ret, "wakeup failed --> %d\n", ret); - break; } }
@@ -4214,6 +4226,7 @@ static void dwc3_gadget_conndone_interrupt(struct dwc3 *dwc) static void dwc3_gadget_wakeup_interrupt(struct dwc3 *dwc, unsigned int evtinfo) { dwc->suspended = false; + dwc->link_state = evtinfo & DWC3_LINK_STATE_MASK;
/* * TODO take core out of low power mode when that's @@ -4225,8 +4238,6 @@ static void dwc3_gadget_wakeup_interrupt(struct dwc3 *dwc, unsigned int evtinfo) dwc->gadget_driver->resume(dwc->gadget); spin_lock(&dwc->lock); } - - dwc->link_state = evtinfo & DWC3_LINK_STATE_MASK; }
static void dwc3_gadget_linksts_change_interrupt(struct dwc3 *dwc,