usb: dwc3: Add remote wakeup handling
[linux-block.git] / drivers / usb / dwc3 / gadget.c
index 29f8f9311d77d87c9af3f3ccd00d6e7efc88491f..7ceff08a1b8cf977de3e68fd35508b00b0be5898 100644 (file)
@@ -258,7 +258,7 @@ int dwc3_send_gadget_generic_command(struct dwc3 *dwc, unsigned int cmd,
        return ret;
 }
 
-static int __dwc3_gadget_wakeup(struct dwc3 *dwc);
+static int __dwc3_gadget_wakeup(struct dwc3 *dwc, bool async);
 
 /**
  * dwc3_send_gadget_ep_cmd - issue an endpoint command
@@ -325,7 +325,7 @@ int dwc3_send_gadget_ep_cmd(struct dwc3_ep *dep, unsigned int cmd,
 
                        fallthrough;
                case DWC3_LINK_STATE_U3:
-                       ret = __dwc3_gadget_wakeup(dwc);
+                       ret = __dwc3_gadget_wakeup(dwc, false);
                        dev_WARN_ONCE(dwc->dev, ret, "wakeup failed --> %d\n",
                                        ret);
                        break;
@@ -2273,6 +2273,22 @@ static const struct usb_ep_ops dwc3_gadget_ep_ops = {
 
 /* -------------------------------------------------------------------------- */
 
+static void dwc3_gadget_enable_linksts_evts(struct dwc3 *dwc, bool set)
+{
+       u32 reg;
+
+       if (DWC3_VER_IS_PRIOR(DWC3, 250A))
+               return;
+
+       reg = dwc3_readl(dwc->regs, DWC3_DEVTEN);
+       if (set)
+               reg |= DWC3_DEVTEN_ULSTCNGEN;
+       else
+               reg &= ~DWC3_DEVTEN_ULSTCNGEN;
+
+       dwc3_writel(dwc->regs, DWC3_DEVTEN, reg);
+}
+
 static int dwc3_gadget_get_frame(struct usb_gadget *g)
 {
        struct dwc3             *dwc = gadget_to_dwc(g);
@@ -2280,7 +2296,7 @@ static int dwc3_gadget_get_frame(struct usb_gadget *g)
        return __dwc3_gadget_get_frame(dwc);
 }
 
-static int __dwc3_gadget_wakeup(struct dwc3 *dwc)
+static int __dwc3_gadget_wakeup(struct dwc3 *dwc, bool async)
 {
        int                     retries;
 
@@ -2311,9 +2327,13 @@ static int __dwc3_gadget_wakeup(struct dwc3 *dwc)
                return -EINVAL;
        }
 
+       if (async)
+               dwc3_gadget_enable_linksts_evts(dwc, true);
+
        ret = dwc3_gadget_set_link_state(dwc, DWC3_LINK_STATE_RECOV);
        if (ret < 0) {
                dev_err(dwc->dev, "failed to put link in Recovery\n");
+               dwc3_gadget_enable_linksts_evts(dwc, false);
                return ret;
        }
 
@@ -2325,6 +2345,13 @@ static int __dwc3_gadget_wakeup(struct dwc3 *dwc)
                dwc3_writel(dwc->regs, DWC3_DCTL, reg);
        }
 
+       /*
+        * Since link status change events are enabled we will receive
+        * an U0 event when wakeup is successful. So bail out.
+        */
+       if (async)
+               return 0;
+
        /* poll until Link State changes to ON */
        retries = 20000;
 
@@ -2350,13 +2377,36 @@ static int dwc3_gadget_wakeup(struct usb_gadget *g)
        unsigned long           flags;
        int                     ret;
 
+       if (!dwc->wakeup_configured) {
+               dev_err(dwc->dev, "remote wakeup not configured\n");
+               return -EINVAL;
+       }
+
        spin_lock_irqsave(&dwc->lock, flags);
-       ret = __dwc3_gadget_wakeup(dwc);
+       if (!dwc->gadget->wakeup_armed) {
+               dev_err(dwc->dev, "not armed for remote wakeup\n");
+               spin_unlock_irqrestore(&dwc->lock, flags);
+               return -EINVAL;
+       }
+       ret = __dwc3_gadget_wakeup(dwc, true);
+
        spin_unlock_irqrestore(&dwc->lock, flags);
 
        return ret;
 }
 
+static int dwc3_gadget_set_remote_wakeup(struct usb_gadget *g, int set)
+{
+       struct dwc3             *dwc = gadget_to_dwc(g);
+       unsigned long           flags;
+
+       spin_lock_irqsave(&dwc->lock, flags);
+       dwc->wakeup_configured = !!set;
+       spin_unlock_irqrestore(&dwc->lock, flags);
+
+       return 0;
+}
+
 static int dwc3_gadget_set_selfpowered(struct usb_gadget *g,
                int is_selfpowered)
 {
@@ -2982,6 +3032,7 @@ static void dwc3_gadget_async_callbacks(struct usb_gadget *g, bool enable)
 static const struct usb_gadget_ops dwc3_gadget_ops = {
        .get_frame              = dwc3_gadget_get_frame,
        .wakeup                 = dwc3_gadget_wakeup,
+       .set_remote_wakeup      = dwc3_gadget_set_remote_wakeup,
        .set_selfpowered        = dwc3_gadget_set_selfpowered,
        .pullup                 = dwc3_gadget_pullup,
        .udc_start              = dwc3_gadget_start,
@@ -3827,6 +3878,8 @@ static void dwc3_gadget_disconnect_interrupt(struct dwc3 *dwc)
 
        dwc->gadget->speed = USB_SPEED_UNKNOWN;
        dwc->setup_packet_pending = false;
+       dwc->gadget->wakeup_armed = false;
+       dwc3_gadget_enable_linksts_evts(dwc, false);
        usb_gadget_set_state(dwc->gadget, USB_STATE_NOTATTACHED);
 
        if (dwc->ep0state != EP0_SETUP_PHASE) {
@@ -3920,6 +3973,8 @@ static void dwc3_gadget_reset_interrupt(struct dwc3 *dwc)
        reg &= ~DWC3_DCTL_TSTCTRL_MASK;
        dwc3_gadget_dctl_write_safe(dwc, reg);
        dwc->test_mode = false;
+       dwc->gadget->wakeup_armed = false;
+       dwc3_gadget_enable_linksts_evts(dwc, false);
        dwc3_clear_stall_all_ep(dwc);
 
        /* Reset device address to zero */
@@ -4072,7 +4127,7 @@ static void dwc3_gadget_conndone_interrupt(struct dwc3 *dwc)
         */
 }
 
-static void dwc3_gadget_wakeup_interrupt(struct dwc3 *dwc)
+static void dwc3_gadget_wakeup_interrupt(struct dwc3 *dwc, unsigned int evtinfo)
 {
        /*
         * TODO take core out of low power mode when that's
@@ -4084,6 +4139,8 @@ static void dwc3_gadget_wakeup_interrupt(struct dwc3 *dwc)
                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,
@@ -4165,6 +4222,12 @@ static void dwc3_gadget_linksts_change_interrupt(struct dwc3 *dwc,
        }
 
        switch (next) {
+       case DWC3_LINK_STATE_U0:
+               if (dwc->gadget->wakeup_armed) {
+                       dwc3_gadget_enable_linksts_evts(dwc, false);
+                       dwc3_resume_gadget(dwc);
+               }
+               break;
        case DWC3_LINK_STATE_U1:
                if (dwc->speed == USB_SPEED_SUPER)
                        dwc3_suspend_gadget(dwc);
@@ -4233,7 +4296,7 @@ static void dwc3_gadget_interrupt(struct dwc3 *dwc,
                dwc3_gadget_conndone_interrupt(dwc);
                break;
        case DWC3_DEVICE_EVENT_WAKEUP:
-               dwc3_gadget_wakeup_interrupt(dwc);
+               dwc3_gadget_wakeup_interrupt(dwc, event->event_info);
                break;
        case DWC3_DEVICE_EVENT_HIBER_REQ:
                if (dev_WARN_ONCE(dwc->dev, !dwc->has_hibernation,
@@ -4481,6 +4544,7 @@ int dwc3_gadget_init(struct dwc3 *dwc)
        dwc->gadget->sg_supported       = true;
        dwc->gadget->name               = "dwc3-gadget";
        dwc->gadget->lpm_capable        = !dwc->usb2_gadget_lpm_disable;
+       dwc->gadget->wakeup_capable     = true;
 
        /*
         * FIXME We might be setting max_speed to <SUPER, however versions