diff options
author | Vardan Mikayelyan <mvardan@synopsys.com> | 2016-05-26 01:07:17 (GMT) |
---|---|---|
committer | Felipe Balbi <felipe.balbi@linux.intel.com> | 2016-06-21 07:50:00 (GMT) |
commit | 381fc8f8228923026b3d75c8230fa2ee4d688f32 (patch) | |
tree | 539aa6bfd43d28496359f7c199ec85a8a22c1841 /drivers/usb | |
parent | 5321922cb6fa0f513fdd8ce73b281f4b9957886b (diff) | |
download | linux-381fc8f8228923026b3d75c8230fa2ee4d688f32.tar.xz |
usb: dwc2: gadget: Add Incomplete ISO IN/OUT Interrupt handlers
Incomplete ISO IN interrupt indicates one of the following conditions
occurred while transmitting an ISOC transaction.
- Corrupted IN Token for ISOC EP.
- Packet not complete in FIFO.
Incomplete ISO OUT indicates that there is at least one isochronous OUT
endpoint on which the transfer is not completed in the current
microframe.
The following actions will be taken:
In case of EP-IN
- Determine the EP
- Disable EP directly from this handler; when "Endpoint Disabled"
interrupt is received flush FIFO
In case of EP-OUT
- Determine the EP
- If target frame elapsed set DCTL_SGOUTNAK, unmask GOUTNAKEFF and
proceed as described in section 7.5.1 of DWC-HSOTG Programming Guide
Also added dwc2_gadget_target_frame_elapsed() helper function which
will be used in Incomplete ISO IN/OUT Interrupt handlers.
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>
Diffstat (limited to 'drivers/usb')
-rw-r--r-- | drivers/usb/dwc2/gadget.c | 173 |
1 files changed, 124 insertions, 49 deletions
diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index 256c929..61f913d 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -523,6 +523,23 @@ static unsigned get_ep_limit(struct dwc2_hsotg_ep *hs_ep) } /** +* dwc2_hsotg_read_frameno - read current frame number +* @hsotg: The device instance +* +* Return the current frame number +*/ +static u32 dwc2_hsotg_read_frameno(struct dwc2_hsotg *hsotg) +{ + u32 dsts; + + dsts = dwc2_readl(hsotg->regs + DSTS); + dsts &= DSTS_SOFFN_MASK; + dsts >>= DSTS_SOFFN_SHIFT; + + return dsts; +} + +/** * dwc2_hsotg_start_req - start a USB request from an endpoint's queue * @hsotg: The controller state. * @hs_ep: The endpoint to process a request for @@ -783,6 +800,30 @@ static void dwc2_hsotg_handle_unaligned_buf_complete(struct dwc2_hsotg *hsotg, hs_req->saved_req_buf = NULL; } +/** + * dwc2_gadget_target_frame_elapsed - Checks target frame + * @hs_ep: The driver endpoint to check + * + * Returns 1 if targeted frame elapsed. If returned 1 then we need to drop + * corresponding transfer. + */ +static bool dwc2_gadget_target_frame_elapsed(struct dwc2_hsotg_ep *hs_ep) +{ + struct dwc2_hsotg *hsotg = hs_ep->parent; + u32 target_frame = hs_ep->target_frame; + u32 current_frame = dwc2_hsotg_read_frameno(hsotg); + bool frame_overrun = hs_ep->frame_overrun; + + if (!frame_overrun && current_frame >= target_frame) + return true; + + if (frame_overrun && current_frame >= target_frame && + ((current_frame - target_frame) < DSTS_SOFFN_LIMIT / 2)) + return true; + + return false; +} + static int dwc2_hsotg_ep_queue(struct usb_ep *ep, struct usb_request *req, gfp_t gfp_flags) { @@ -1641,23 +1682,6 @@ static void dwc2_hsotg_handle_outdone(struct dwc2_hsotg *hsotg, int epnum) } /** - * dwc2_hsotg_read_frameno - read current frame number - * @hsotg: The device instance - * - * Return the current frame number - */ -static u32 dwc2_hsotg_read_frameno(struct dwc2_hsotg *hsotg) -{ - u32 dsts; - - dsts = dwc2_readl(hsotg->regs + DSTS); - dsts &= DSTS_SOFFN_MASK; - dsts >>= DSTS_SOFFN_SHIFT; - - return dsts; -} - -/** * dwc2_hsotg_handle_rx - RX FIFO has data * @hsotg: The device instance * @@ -2571,6 +2595,85 @@ void dwc2_hsotg_core_connect(struct dwc2_hsotg *hsotg) } /** + * dwc2_gadget_handle_incomplete_isoc_in - handle incomplete ISO IN Interrupt. + * @hsotg: The device state: + * + * This interrupt indicates one of the following conditions occurred while + * transmitting an ISOC transaction. + * - Corrupted IN Token for ISOC EP. + * - Packet not complete in FIFO. + * + * The following actions will be taken: + * - Determine the EP + * - Disable EP; when 'Endpoint Disabled' interrupt is received Flush FIFO + */ +static void dwc2_gadget_handle_incomplete_isoc_in(struct dwc2_hsotg *hsotg) +{ + struct dwc2_hsotg_ep *hs_ep; + u32 epctrl; + u32 idx; + + dev_dbg(hsotg->dev, "Incomplete isoc in interrupt received:\n"); + + for (idx = 1; idx <= hsotg->num_of_eps; idx++) { + hs_ep = hsotg->eps_in[idx]; + epctrl = dwc2_readl(hsotg->regs + DIEPCTL(idx)); + if ((epctrl & DXEPCTL_EPENA) && hs_ep->isochronous && + dwc2_gadget_target_frame_elapsed(hs_ep)) { + epctrl |= DXEPCTL_SNAK; + epctrl |= DXEPCTL_EPDIS; + dwc2_writel(epctrl, hsotg->regs + DIEPCTL(idx)); + } + } + + /* Clear interrupt */ + dwc2_writel(GINTSTS_INCOMPL_SOIN, hsotg->regs + GINTSTS); +} + +/** + * dwc2_gadget_handle_incomplete_isoc_out - handle incomplete ISO OUT Interrupt + * @hsotg: The device state: + * + * This interrupt indicates one of the following conditions occurred while + * transmitting an ISOC transaction. + * - Corrupted OUT Token for ISOC EP. + * - Packet not complete in FIFO. + * + * The following actions will be taken: + * - Determine the EP + * - Set DCTL_SGOUTNAK and unmask GOUTNAKEFF if target frame elapsed. + */ +static void dwc2_gadget_handle_incomplete_isoc_out(struct dwc2_hsotg *hsotg) +{ + u32 gintsts; + u32 gintmsk; + u32 epctrl; + struct dwc2_hsotg_ep *hs_ep; + int idx; + + dev_dbg(hsotg->dev, "%s: GINTSTS_INCOMPL_SOOUT\n", __func__); + + for (idx = 1; idx <= hsotg->num_of_eps; idx++) { + hs_ep = hsotg->eps_out[idx]; + epctrl = dwc2_readl(hsotg->regs + DOEPCTL(idx)); + if ((epctrl & DXEPCTL_EPENA) && hs_ep->isochronous && + dwc2_gadget_target_frame_elapsed(hs_ep)) { + /* Unmask GOUTNAKEFF interrupt */ + gintmsk = dwc2_readl(hsotg->regs + GINTMSK); + gintmsk |= GINTSTS_GOUTNAKEFF; + dwc2_writel(gintmsk, hsotg->regs + GINTMSK); + + gintsts = dwc2_readl(hsotg->regs + GINTSTS); + if (!(gintsts & GINTSTS_GOUTNAKEFF)) + __orr32(hsotg->regs + DCTL, DCTL_SGOUTNAK); + } + } + + /* Clear interrupt */ + dwc2_writel(GINTSTS_INCOMPL_SOOUT, hsotg->regs + GINTSTS); +} + +/** * dwc2_hsotg_irq - handle device interrupt * @irq: The IRQ number triggered * @pw: The pw value when registered the handler. @@ -2717,39 +2820,11 @@ irq_retry: dwc2_hsotg_dump(hsotg); } - if (gintsts & GINTSTS_INCOMPL_SOIN) { - u32 idx, epctl_reg; - struct dwc2_hsotg_ep *hs_ep; - - dev_dbg(hsotg->dev, "%s: GINTSTS_INCOMPL_SOIN\n", __func__); - for (idx = 1; idx < hsotg->num_of_eps; idx++) { - hs_ep = hsotg->eps_in[idx]; + if (gintsts & GINTSTS_INCOMPL_SOIN) + dwc2_gadget_handle_incomplete_isoc_in(hsotg); - if (!hs_ep->isochronous || hs_ep->has_correct_parity) - continue; - - epctl_reg = DIEPCTL(idx); - dwc2_hsotg_change_ep_iso_parity(hsotg, epctl_reg); - } - dwc2_writel(GINTSTS_INCOMPL_SOIN, hsotg->regs + GINTSTS); - } - - if (gintsts & GINTSTS_INCOMPL_SOOUT) { - u32 idx, epctl_reg; - struct dwc2_hsotg_ep *hs_ep; - - dev_dbg(hsotg->dev, "%s: GINTSTS_INCOMPL_SOOUT\n", __func__); - for (idx = 1; idx < hsotg->num_of_eps; idx++) { - hs_ep = hsotg->eps_out[idx]; - - if (!hs_ep->isochronous || hs_ep->has_correct_parity) - continue; - - epctl_reg = DOEPCTL(idx); - dwc2_hsotg_change_ep_iso_parity(hsotg, epctl_reg); - } - dwc2_writel(GINTSTS_INCOMPL_SOOUT, hsotg->regs + GINTSTS); - } + if (gintsts & GINTSTS_INCOMPL_SOOUT) + dwc2_gadget_handle_incomplete_isoc_out(hsotg); /* * if we've had fifo events, we should try and go around the |