usb: dwc2: gadget: stop current transfer on dequeue
authorMian Yousaf Kaukab <yousaf.kaukab@intel.com>
Tue, 29 Sep 2015 10:08:24 +0000 (12:08 +0200)
committerFelipe Balbi <balbi@ti.com>
Thu, 1 Oct 2015 17:40:25 +0000 (12:40 -0500)
If the request being dequeued is already started, disable endpoint
to stop the transfer and then call dwc2_hsotg_complete_request().
Endpoint will be re-enabled on next call to dwc2_hsotg_start_req().

Signed-off-by: Mian Yousaf Kaukab <yousaf.kaukab@intel.com>
Tested-by: Robert Baldyga <r.baldyga@samsung.com>
Tested-by: Dinh Nguyen <dinguyen@opensource.altera.com>
Tested-by: John Youn <johnyoun@synopsys.com>
Acked-by: John Youn <johnyoun@synopsys.com>
Signed-off-by: Felipe Balbi <balbi@ti.com>
drivers/usb/dwc2/gadget.c

index 9188991b9b726f185f3b96c89541ad9c1c36aaa6..38c29a8bdd11e2f5809cc7b5c9075673f6455ab0 100644 (file)
@@ -2826,6 +2826,79 @@ static bool on_list(struct dwc2_hsotg_ep *ep, struct dwc2_hsotg_req *test)
        return false;
 }
 
+static int dwc2_hsotg_wait_bit_set(struct dwc2_hsotg *hs_otg, u32 reg,
+                                                       u32 bit, u32 timeout)
+{
+       u32 i;
+
+       for (i = 0; i < timeout; i++) {
+               if (dwc2_readl(hs_otg->regs + reg) & bit)
+                       return 0;
+               udelay(1);
+       }
+
+       return -ETIMEDOUT;
+}
+
+static void dwc2_hsotg_ep_stop_xfr(struct dwc2_hsotg *hsotg,
+                                               struct dwc2_hsotg_ep *hs_ep)
+{
+       u32 epctrl_reg;
+       u32 epint_reg;
+
+       epctrl_reg = hs_ep->dir_in ? DIEPCTL(hs_ep->index) :
+               DOEPCTL(hs_ep->index);
+       epint_reg = hs_ep->dir_in ? DIEPINT(hs_ep->index) :
+               DOEPINT(hs_ep->index);
+
+       dev_dbg(hsotg->dev, "%s: stopping transfer on %s\n", __func__,
+                       hs_ep->name);
+       if (hs_ep->dir_in) {
+               __orr32(hsotg->regs + epctrl_reg, DXEPCTL_SNAK);
+               /* Wait for Nak effect */
+               if (dwc2_hsotg_wait_bit_set(hsotg, epint_reg,
+                                               DXEPINT_INEPNAKEFF, 100))
+                       dev_warn(hsotg->dev,
+                               "%s: timeout DIEPINT.NAKEFF\n", __func__);
+       } else {
+               /* Clear any pending nak effect interrupt */
+               dwc2_writel(GINTSTS_GINNAKEFF, hsotg->regs + GINTSTS);
+
+               __orr32(hsotg->regs + DCTL, DCTL_SGNPINNAK);
+
+               /* Wait for global nak to take effect */
+               if (dwc2_hsotg_wait_bit_set(hsotg, GINTSTS,
+                                               GINTSTS_GINNAKEFF, 100))
+                       dev_warn(hsotg->dev,
+                               "%s: timeout GINTSTS.GINNAKEFF\n", __func__);
+       }
+
+       /* Disable ep */
+       __orr32(hsotg->regs + epctrl_reg, DXEPCTL_EPDIS | DXEPCTL_SNAK);
+
+       /* Wait for ep to be disabled */
+       if (dwc2_hsotg_wait_bit_set(hsotg, epint_reg, DXEPINT_EPDISBLD, 100))
+               dev_warn(hsotg->dev,
+                       "%s: timeout DOEPCTL.EPDisable\n", __func__);
+
+       if (hs_ep->dir_in) {
+               if (hsotg->dedicated_fifos) {
+                       dwc2_writel(GRSTCTL_TXFNUM(hs_ep->fifo_index) |
+                               GRSTCTL_TXFFLSH, hsotg->regs + GRSTCTL);
+                       /* Wait for fifo flush */
+                       if (dwc2_hsotg_wait_bit_set(hsotg, GRSTCTL,
+                                                       GRSTCTL_TXFFLSH, 100))
+                               dev_warn(hsotg->dev,
+                                       "%s: timeout flushing fifos\n",
+                                       __func__);
+               }
+               /* TODO: Flush shared tx fifo */
+       } else {
+               /* Remove global NAKs */
+               __bic32(hsotg->regs + DCTL, DCTL_SGNPINNAK);
+       }
+}
+
 /**
  * dwc2_hsotg_ep_dequeue - dequeue given endpoint
  * @ep: The endpoint to dequeue.
@@ -2847,6 +2920,10 @@ static int dwc2_hsotg_ep_dequeue(struct usb_ep *ep, struct usb_request *req)
                return -EINVAL;
        }
 
+       /* Dequeue already started request */
+       if (req == &hs_ep->req->req)
+               dwc2_hsotg_ep_stop_xfr(hs, hs_ep);
+
        dwc2_hsotg_complete_request(hs, hs_ep, hs_req, -ECONNRESET);
        spin_unlock_irqrestore(&hs->lock, flags);