usb: dwc2: gadget: Add OUTTKNEPDIS and NAKINTRPT handlers
authorVardan Mikayelyan <mvardan@synopsys.com>
Thu, 26 May 2016 01:07:14 +0000 (18:07 -0700)
committerFelipe Balbi <felipe.balbi@linux.intel.com>
Tue, 21 Jun 2016 07:49:54 +0000 (10:49 +0300)
NAKINTRPT interrupt is starting point for isoc-in transfer,
synchronization done with first in token received from host,
core asserts this interrupt when responds with 0 length data
to in token, received from host.

The first IN token is asynchronous for device - device does not
know when first one token will arrive from host. On first token
arrival HW generates 2 interrupts: 'in token received while FIFO
empty' and 'NAK'. NAK interrupt for ISOC in means that token has
arrived and ZLP was sent in response to that as there was no data
in FIFO. SW is basing on this interrupt to obtain frame in which
token has come and then based on the interval calculates next
frame for transfer.

OUTTKNEPDIS interrupt is starting point for isoc-out transfer,
synchronization done with first out token received from host
while corresponding ep is disabled.

For OUTs the reason is same - device does not know initial frame
in which out token will come. For this HW generates OUTTKNEPDIS
- out token is received while EP is disabled. Upon getting this
interrupt SW starts calculation for next transfer frame.

Tested-by: John Keeping <john@metanate.com>
Signed-off-by: Vardan Mikayelyan <mvardan@synopsys.com>
Signed-off-by: John Youn <johnyoun@synopsys.com>
Signed-off-by: Felipe Balbi <felipe.balbi@linux.intel.com>
drivers/usb/dwc2/gadget.c

index 7db9f9fa40de7c87de21ff5cca0a3445858c5f6e..256c929fece9366b305764590c3467412c16ad62 100644 (file)
@@ -1999,6 +1999,94 @@ static u32 dwc2_gadget_read_ep_interrupts(struct dwc2_hsotg *hsotg,
        return ints;
 }
 
+/**
+ * dwc2_gadget_handle_out_token_ep_disabled - handle DXEPINT_OUTTKNEPDIS
+ * @hs_ep: The endpoint on which interrupt is asserted.
+ *
+ * This is starting point for ISOC-OUT transfer, synchronization done with
+ * first out token received from host while corresponding EP is disabled.
+ *
+ * Device does not know initial frame in which out token will come. For this
+ * HW generates OUTTKNEPDIS - out token is received while EP is disabled. Upon
+ * getting this interrupt SW starts calculation for next transfer frame.
+ */
+static void dwc2_gadget_handle_out_token_ep_disabled(struct dwc2_hsotg_ep *ep)
+{
+       struct dwc2_hsotg *hsotg = ep->parent;
+       int dir_in = ep->dir_in;
+       u32 doepmsk;
+
+       if (dir_in || !ep->isochronous)
+               return;
+
+       dwc2_hsotg_complete_request(hsotg, ep, get_ep_head(ep), -ENODATA);
+
+       if (ep->interval > 1 &&
+           ep->target_frame == TARGET_FRAME_INITIAL) {
+               u32 dsts;
+               u32 ctrl;
+
+               dsts = dwc2_readl(hsotg->regs + DSTS);
+               ep->target_frame = dwc2_hsotg_read_frameno(hsotg);
+               dwc2_gadget_incr_frame_num(ep);
+
+               ctrl = dwc2_readl(hsotg->regs + DOEPCTL(ep->index));
+               if (ep->target_frame & 0x1)
+                       ctrl |= DXEPCTL_SETODDFR;
+               else
+                       ctrl |= DXEPCTL_SETEVENFR;
+
+               dwc2_writel(ctrl, hsotg->regs + DOEPCTL(ep->index));
+       }
+
+       dwc2_gadget_start_next_request(ep);
+       doepmsk = dwc2_readl(hsotg->regs + DOEPMSK);
+       doepmsk &= ~DOEPMSK_OUTTKNEPDISMSK;
+       dwc2_writel(doepmsk, hsotg->regs + DOEPMSK);
+}
+
+/**
+* dwc2_gadget_handle_nak - handle NAK interrupt
+* @hs_ep: The endpoint on which interrupt is asserted.
+*
+* This is starting point for ISOC-IN transfer, synchronization done with
+* first IN token received from host while corresponding EP is disabled.
+*
+* Device does not know when first one token will arrive from host. On first
+* token arrival HW generates 2 interrupts: 'in token received while FIFO empty'
+* and 'NAK'. NAK interrupt for ISOC-IN means that token has arrived and ZLP was
+* sent in response to that as there was no data in FIFO. SW is basing on this
+* interrupt to obtain frame in which token has come and then based on the
+* interval calculates next frame for transfer.
+*/
+static void dwc2_gadget_handle_nak(struct dwc2_hsotg_ep *hs_ep)
+{
+       struct dwc2_hsotg *hsotg = hs_ep->parent;
+       int dir_in = hs_ep->dir_in;
+
+       if (!dir_in || !hs_ep->isochronous)
+               return;
+
+       if (hs_ep->target_frame == TARGET_FRAME_INITIAL) {
+               hs_ep->target_frame = dwc2_hsotg_read_frameno(hsotg);
+               if (hs_ep->interval > 1) {
+                       u32 ctrl = dwc2_readl(hsotg->regs +
+                                             DIEPCTL(hs_ep->index));
+                       if (hs_ep->target_frame & 0x1)
+                               ctrl |= DXEPCTL_SETODDFR;
+                       else
+                               ctrl |= DXEPCTL_SETEVENFR;
+
+                       dwc2_writel(ctrl, hsotg->regs + DIEPCTL(hs_ep->index));
+               }
+
+               dwc2_hsotg_complete_request(hsotg, hs_ep,
+                                           get_ep_head(hs_ep), 0);
+       }
+
+       dwc2_gadget_incr_frame_num(hs_ep);
+}
+
 /**
  * dwc2_hsotg_epint - handle an in/out endpoint interrupt
  * @hsotg: The driver state
@@ -2083,6 +2171,12 @@ static void dwc2_hsotg_epint(struct dwc2_hsotg *hsotg, unsigned int idx,
                }
        }
 
+       if (ints & DXEPINT_OUTTKNEPDIS)
+               dwc2_gadget_handle_out_token_ep_disabled(hs_ep);
+
+       if (ints & DXEPINT_NAKINTRPT)
+               dwc2_gadget_handle_nak(hs_ep);
+
        if (ints & DXEPINT_AHBERR)
                dev_dbg(hsotg->dev, "%s: AHBErr\n", __func__);