OSDN Git Service

dwc3: gadget: Replace polling mechnism to go into U0 state
authorMayank Rana <mrana@codeaurora.org>
Tue, 25 Nov 2014 23:29:58 +0000 (15:29 -0800)
committerDavid Keitel <dkeitel@codeaurora.org>
Tue, 22 Mar 2016 18:07:06 +0000 (11:07 -0700)
Moving into U0 state is being confirmed by polling for 100ms after
performing remote wakeup from device. In some of cases where host is
taking more time to respond, remote wakeup is failing. Also USB
specification does not define any limit for the host response time.
Hence this change replaces polling mechnism by using LINK status
change event notification with core and increase host response time
from 100ms to 3 seconds. It also makes sure that composite_resume()
is being called after remote wakeup is completed succesfully.

It removes some of flag used to avoid race between bus suspend/resume
and fuction suspend/resume as those are serialize and not required
anymore.

CRs-Fixed: 712681
Change-Id: I71285daf117282c738e139e9a05ead6ef16dd202
Signed-off-by: Mayank Rana <mrana@codeaurora.org>
drivers/usb/dwc3/core.c
drivers/usb/dwc3/core.h
drivers/usb/dwc3/gadget.c

index 038999c..67f058c 100644 (file)
@@ -1084,6 +1084,7 @@ static int dwc3_probe(struct platform_device *pdev)
        dwc->hird_threshold = hird_threshold
                | (dwc->is_utmi_l1_suspend << 4);
 
+       init_waitqueue_head(&dwc->wait_linkstate);
        platform_set_drvdata(pdev, dwc);
        dwc3_cache_hwparams(dwc);
 
index 6cc8cef..f2006c0 100644 (file)
@@ -27,6 +27,7 @@
 #include <linux/mm.h>
 #include <linux/debugfs.h>
 #include <linux/workqueue.h>
+#include <linux/wait.h>
 
 #include <linux/usb/ch9.h>
 #include <linux/usb/gadget.h>
@@ -815,6 +816,7 @@ struct dwc3_scratchpad_array {
  * @bh_completion_time: time taken for taklet completion
  * @bh_handled_evt_cnt: no. of events handled by tasklet per interrupt
  * @bh_dbg_index: index for capturing bh_completion_time and bh_handled_evt_cnt
+ * @wait_linkstate: waitqueue for waiting LINK to move into required state
  */
 struct dwc3 {
        struct usb_ctrlrequest  *ctrl_req;
@@ -987,6 +989,8 @@ struct dwc3 {
        unsigned                irq_completion_time[MAX_INTR_STATS];
        unsigned                irq_event_count[MAX_INTR_STATS];
        unsigned                irq_dbg_index;
+
+       wait_queue_head_t       wait_linkstate;
 };
 
 /* -------------------------------------------------------------------------- */
index 3f97d64..f9ded17 100644 (file)
@@ -38,7 +38,7 @@
 #include "debug.h"
 #include "io.h"
 
-static void dwc3_gadget_wakeup_interrupt(struct dwc3 *dwc);
+static void dwc3_gadget_wakeup_interrupt(struct dwc3 *dwc, bool remote_wakeup);
 static int dwc3_gadget_wakeup_int(struct dwc3 *dwc);
 
 /**
@@ -1508,19 +1508,14 @@ static int dwc3_gadget_get_frame(struct usb_gadget *g)
 static void dwc3_gadget_wakeup_work(struct work_struct *w)
 {
        struct dwc3             *dwc;
-       unsigned long           flags;
        int                     ret;
 
        dwc = container_of(w, struct dwc3, wakeup_work);
 
-       spin_lock_irqsave(&dwc->lock, flags);
-
        if (atomic_read(&dwc->in_lpm)) {
-               spin_unlock_irqrestore(&dwc->lock, flags);
                pm_runtime_get_sync(dwc->dev);
                dbg_event(0xFF, "Gdgwake gsyn",
                        atomic_read(&dwc->dev->power.usage_count));
-               spin_lock_irqsave(&dwc->lock, flags);
        }
 
        ret = dwc3_gadget_wakeup_int(dwc);
@@ -1529,19 +1524,20 @@ static void dwc3_gadget_wakeup_work(struct work_struct *w)
                pr_err("Remote wakeup failed. ret = %d.\n", ret);
        else
                pr_debug("Remote wakeup succeeded.\n");
-
-       spin_unlock_irqrestore(&dwc->lock, flags);
 }
 
 static int dwc3_gadget_wakeup_int(struct dwc3 *dwc)
 {
-       u32                     timeout = 0;
        bool                    link_recover_only = false;
 
        u32                     reg;
        int                     ret = 0;
        u8                      link_state;
+       unsigned long           flags;
 
+       pr_debug("%s(): Entry\n", __func__);
+       disable_irq(dwc->irq);
+       spin_lock_irqsave(&dwc->lock, flags);
        /*
         * According to the Databook Remote wakeup request should
         * be issued only when the device is in early suspend state.
@@ -1567,9 +1563,25 @@ static int dwc3_gadget_wakeup_int(struct dwc3 *dwc)
                goto out;
        }
 
+       /* Enable LINK STATUS change event */
+       reg = dwc3_readl(dwc->regs, DWC3_DEVTEN);
+       reg |= DWC3_DEVTEN_ULSTCNGEN;
+       dwc3_writel(dwc->regs, DWC3_DEVTEN, reg);
+       /*
+        * memory barrier is required to make sure that required events
+        * with core is enabled before performing RECOVERY mechnism.
+        */
+       mb();
+
        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");
+               /* Disable LINK STATUS change */
+               reg = dwc3_readl(dwc->regs, DWC3_DEVTEN);
+               reg &= ~DWC3_DEVTEN_ULSTCNGEN;
+               dwc3_writel(dwc->regs, DWC3_DEVTEN, reg);
+               /* Required to complete this operation before returning */
+               mb();
                goto out;
        }
 
@@ -1581,21 +1593,39 @@ static int dwc3_gadget_wakeup_int(struct dwc3 *dwc)
                dwc3_writel(dwc->regs, DWC3_DCTL, reg);
        }
 
-       /* poll until Link State changes to ON */
-       do {
-               reg = dwc3_readl(dwc->regs, DWC3_DSTS);
+       spin_unlock_irqrestore(&dwc->lock, flags);
+       enable_irq(dwc->irq);
+       ret = wait_event_interruptible_timeout(dwc->wait_linkstate,
+                       (dwc->link_state < DWC3_LINK_STATE_U3) ||
+                       (dwc->link_state == DWC3_LINK_STATE_SS_DIS),
+                       msecs_to_jiffies(3000)); /* 3 seconds */
 
-               /* in HS, means ON */
-               if (DWC3_DSTS_USBLNKST(reg) == DWC3_LINK_STATE_U0)
-                       break;
-               udelay(10);
-               timeout++;
-       } while (timeout < 10000);
+       spin_lock_irqsave(&dwc->lock, flags);
+       /* Disable link status change event */
+       reg = dwc3_readl(dwc->regs, DWC3_DEVTEN);
+       reg &= ~DWC3_DEVTEN_ULSTCNGEN;
+       dwc3_writel(dwc->regs, DWC3_DEVTEN, reg);
+       /*
+        * Complete this write before we go ahead and perform resume
+        * as we don't need link status change notificaiton anymore.
+        */
+       mb();
 
-       if (DWC3_DSTS_USBLNKST(reg) != DWC3_LINK_STATE_U0) {
-               dev_err(dwc->dev, "failed to send remote wakeup\n");
+       if (!ret) {
+               dev_dbg(dwc->dev, "Timeout moving into state(%d)\n",
+                                                       dwc->link_state);
                ret = -EINVAL;
-               goto out;
+               spin_unlock_irqrestore(&dwc->lock, flags);
+               goto out1;
+       } else {
+               ret = 0;
+               /*
+                * If USB is disconnected OR received RESET from host,
+                * don't perform resume
+                */
+               if (dwc->link_state == DWC3_LINK_STATE_SS_DIS ||
+                               dwc->gadget.state == USB_STATE_DEFAULT)
+                       link_recover_only = true;
        }
 
        /*
@@ -1606,8 +1636,17 @@ static int dwc3_gadget_wakeup_int(struct dwc3 *dwc)
         * the resume sequence is initiated by SW.
         */
        if (!link_recover_only)
-               dwc3_gadget_wakeup_interrupt(dwc);
+               dwc3_gadget_wakeup_interrupt(dwc, true);
+
+       spin_unlock_irqrestore(&dwc->lock, flags);
+       pr_debug("%s: Exit\n", __func__);
+       return ret;
+
 out:
+       spin_unlock_irqrestore(&dwc->lock, flags);
+       enable_irq(dwc->irq);
+
+out1:
        return ret;
 }
 
@@ -2525,6 +2564,7 @@ static void dwc3_gadget_disconnect_interrupt(struct dwc3 *dwc)
        dwc->setup_packet_pending = false;
        dwc->link_state = DWC3_LINK_STATE_SS_DIS;
        usb_gadget_set_state(&dwc->gadget, USB_STATE_NOTATTACHED);
+       wake_up_interruptible(&dwc->wait_linkstate);
 }
 
 static void dwc3_gadget_reset_interrupt(struct dwc3 *dwc)
@@ -2588,6 +2628,7 @@ static void dwc3_gadget_reset_interrupt(struct dwc3 *dwc)
        reg &= ~(DWC3_DCFG_DEVADDR_MASK);
        dwc3_writel(dwc->regs, DWC3_DCFG, reg);
        dwc->link_state = DWC3_LINK_STATE_U0;
+       wake_up_interruptible(&dwc->wait_linkstate);
 }
 
 static void dwc3_update_ram_clk_sel(struct dwc3 *dwc, u32 speed)
@@ -2734,12 +2775,23 @@ 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, bool remote_wakeup)
 {
+       bool perform_resume = true;
+
        dev_dbg(dwc->dev, "%s\n", __func__);
 
+       /*
+        * Identify if it is called from wakeup_interrupt() context for bus
+        * resume or as part of remote wakeup. And based on that check for
+        * U3 state. as we need to handle case of L1 resume i.e. where we
+        * don't want to perform resume.
+        */
+       if (!remote_wakeup && dwc->link_state != DWC3_LINK_STATE_U3)
+               perform_resume = false;
+
        /* Only perform resume from L2 or Early Suspend states */
-       if (dwc->link_state == DWC3_LINK_STATE_U3) {
+       if (perform_resume) {
                dbg_event(0xFF, "WAKEUP", 0);
 
                /*
@@ -2860,7 +2912,9 @@ static void dwc3_gadget_linksts_change_interrupt(struct dwc3 *dwc,
                break;
        }
 
+       dev_dbg(dwc->dev, "Going from (%d)--->(%d)\n", dwc->link_state, next);
        dwc->link_state = next;
+       wake_up_interruptible(&dwc->wait_linkstate);
 }
 
 static void dwc3_gadget_hibernation_interrupt(struct dwc3 *dwc,
@@ -2961,7 +3015,7 @@ static void dwc3_gadget_interrupt(struct dwc3 *dwc,
                dwc->dbg_gadget_events.connect++;
                break;
        case DWC3_DEVICE_EVENT_WAKEUP:
-               dwc3_gadget_wakeup_interrupt(dwc);
+               dwc3_gadget_wakeup_interrupt(dwc, false);
                dwc->dbg_gadget_events.wakeup++;
                break;
        case DWC3_DEVICE_EVENT_HIBER_REQ: