]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
usb: dwc3: gadget: Make gadget_wakeup asynchronous
authorPrashanth K <prashanth.k@oss.qualcomm.com>
Tue, 22 Apr 2025 10:32:31 +0000 (16:02 +0530)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Thu, 1 May 2025 15:38:55 +0000 (17:38 +0200)
Currently gadget_wakeup() waits for U0 synchronously if it was
called from func_wakeup(), this is because we need to send the
function wakeup command soon after the link is active. And the
call is made synchronous by polling DSTS continuosly for 20000
times in __dwc3_gadget_wakeup(). But it observed that sometimes
the link is not active even after polling 20K times, leading to
remote wakeup failures. Adding a small delay between each poll
helps, but that won't guarantee resolution in future. Hence make
the gadget_wakeup completely asynchronous.

Since multiple interfaces can issue a function wakeup at once,
add a new variable wakeup_pending_funcs which will indicate the
functions that has issued func_wakup, this is represented in a
bitmap format. If the link is in U3, dwc3_gadget_func_wakeup()
will set the bit corresponding to interface_id and bail out.
Once link comes back to U0, linksts_change irq is triggered,
where the function wakeup command is sent based on bitmap.

Cc: stable <stable@kernel.org>
Fixes: 92c08a84b53e ("usb: dwc3: Add function suspend and function wakeup support")
Signed-off-by: Prashanth K <prashanth.k@oss.qualcomm.com>
Acked-by: Thinh Nguyen <Thinh.Nguyen@synopsys.com>
Link: https://lore.kernel.org/r/20250422103231.1954387-4-prashanth.k@oss.qualcomm.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/usb/dwc3/core.h
drivers/usb/dwc3/gadget.c

index aaa39e663f60a5f4f638c91d9d369a5913d1b967..27eae4cf223dfd92f3045cb1b2a5d0421966cfe1 100644 (file)
@@ -1164,6 +1164,9 @@ struct dwc3_scratchpad_array {
  * @gsbuscfg0_reqinfo: store GSBUSCFG0.DATRDREQINFO, DESRDREQINFO,
  *                    DATWRREQINFO, and DESWRREQINFO value passed from
  *                    glue driver.
+ * @wakeup_pending_funcs: Indicates whether any interface has requested for
+ *                      function wakeup in bitmap format where bit position
+ *                      represents interface_id.
  */
 struct dwc3 {
        struct work_struct      drd_work;
@@ -1394,6 +1397,7 @@ struct dwc3 {
        int                     num_ep_resized;
        struct dentry           *debug_root;
        u32                     gsbuscfg0_reqinfo;
+       u32                     wakeup_pending_funcs;
 };
 
 #define INCRX_BURST_MODE 0
index 8c30d86cc4e3a00ef624621f13063e2c37330029..321361288935db4b773cd06235a16670a6adda1a 100644 (file)
@@ -276,8 +276,6 @@ int dwc3_send_gadget_generic_command(struct dwc3 *dwc, unsigned int cmd,
        return ret;
 }
 
-static int __dwc3_gadget_wakeup(struct dwc3 *dwc, bool async);
-
 /**
  * dwc3_send_gadget_ep_cmd - issue an endpoint command
  * @dep: the endpoint to which the command is going to be issued
@@ -2359,10 +2357,8 @@ static int dwc3_gadget_get_frame(struct usb_gadget *g)
        return __dwc3_gadget_get_frame(dwc);
 }
 
-static int __dwc3_gadget_wakeup(struct dwc3 *dwc, bool async)
+static int __dwc3_gadget_wakeup(struct dwc3 *dwc)
 {
-       int                     retries;
-
        int                     ret;
        u32                     reg;
 
@@ -2390,8 +2386,7 @@ static int __dwc3_gadget_wakeup(struct dwc3 *dwc, bool async)
                return -EINVAL;
        }
 
-       if (async)
-               dwc3_gadget_enable_linksts_evts(dwc, true);
+       dwc3_gadget_enable_linksts_evts(dwc, true);
 
        ret = dwc3_gadget_set_link_state(dwc, DWC3_LINK_STATE_RECOV);
        if (ret < 0) {
@@ -2410,27 +2405,8 @@ static int __dwc3_gadget_wakeup(struct dwc3 *dwc, bool async)
 
        /*
         * Since link status change events are enabled we will receive
-        * an U0 event when wakeup is successful. So bail out.
+        * an U0 event when wakeup is successful.
         */
-       if (async)
-               return 0;
-
-       /* poll until Link State changes to ON */
-       retries = 20000;
-
-       while (retries--) {
-               reg = dwc3_readl(dwc->regs, DWC3_DSTS);
-
-               /* in HS, means ON */
-               if (DWC3_DSTS_USBLNKST(reg) == DWC3_LINK_STATE_U0)
-                       break;
-       }
-
-       if (DWC3_DSTS_USBLNKST(reg) != DWC3_LINK_STATE_U0) {
-               dev_err(dwc->dev, "failed to send remote wakeup\n");
-               return -EINVAL;
-       }
-
        return 0;
 }
 
@@ -2451,7 +2427,7 @@ static int dwc3_gadget_wakeup(struct usb_gadget *g)
                spin_unlock_irqrestore(&dwc->lock, flags);
                return -EINVAL;
        }
-       ret = __dwc3_gadget_wakeup(dwc, true);
+       ret = __dwc3_gadget_wakeup(dwc);
 
        spin_unlock_irqrestore(&dwc->lock, flags);
 
@@ -2479,14 +2455,10 @@ static int dwc3_gadget_func_wakeup(struct usb_gadget *g, int intf_id)
         */
        link_state = dwc3_gadget_get_link_state(dwc);
        if (link_state == DWC3_LINK_STATE_U3) {
-               ret = __dwc3_gadget_wakeup(dwc, false);
-               if (ret) {
-                       spin_unlock_irqrestore(&dwc->lock, flags);
-                       return -EINVAL;
-               }
-               dwc3_resume_gadget(dwc);
-               dwc->suspended = false;
-               dwc->link_state = DWC3_LINK_STATE_U0;
+               dwc->wakeup_pending_funcs |= BIT(intf_id);
+               ret = __dwc3_gadget_wakeup(dwc);
+               spin_unlock_irqrestore(&dwc->lock, flags);
+               return ret;
        }
 
        ret = dwc3_send_gadget_generic_command(dwc, DWC3_DGCMD_DEV_NOTIFICATION,
@@ -4353,6 +4325,8 @@ static void dwc3_gadget_linksts_change_interrupt(struct dwc3 *dwc,
 {
        enum dwc3_link_state    next = evtinfo & DWC3_LINK_STATE_MASK;
        unsigned int            pwropt;
+       int                     ret;
+       int                     intf_id;
 
        /*
         * WORKAROUND: DWC3 < 2.50a have an issue when configured without
@@ -4428,7 +4402,7 @@ static void dwc3_gadget_linksts_change_interrupt(struct dwc3 *dwc,
 
        switch (next) {
        case DWC3_LINK_STATE_U0:
-               if (dwc->gadget->wakeup_armed) {
+               if (dwc->gadget->wakeup_armed || dwc->wakeup_pending_funcs) {
                        dwc3_gadget_enable_linksts_evts(dwc, false);
                        dwc3_resume_gadget(dwc);
                        dwc->suspended = false;
@@ -4451,6 +4425,18 @@ static void dwc3_gadget_linksts_change_interrupt(struct dwc3 *dwc,
        }
 
        dwc->link_state = next;
+
+       /* Proceed with func wakeup if any interfaces that has requested */
+       while (dwc->wakeup_pending_funcs && (next == DWC3_LINK_STATE_U0)) {
+               intf_id = ffs(dwc->wakeup_pending_funcs) - 1;
+               ret = dwc3_send_gadget_generic_command(dwc, DWC3_DGCMD_DEV_NOTIFICATION,
+                                                      DWC3_DGCMDPAR_DN_FUNC_WAKE |
+                                                      DWC3_DGCMDPAR_INTF_SEL(intf_id));
+               if (ret)
+                       dev_err(dwc->dev, "Failed to send DN wake for intf %d\n", intf_id);
+
+               dwc->wakeup_pending_funcs &= ~BIT(intf_id);
+       }
 }
 
 static void dwc3_gadget_suspend_interrupt(struct dwc3 *dwc,