diff options
Diffstat (limited to 'drivers/usb/dwc3/gadget.c')
-rw-r--r-- | drivers/usb/dwc3/gadget.c | 292 |
1 files changed, 179 insertions, 113 deletions
diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index 2fdd767f8fe8..a04342f6cbfa 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -241,21 +241,23 @@ void dwc3_gadget_giveback(struct dwc3_ep *dep, struct dwc3_request *req, int status) { struct dwc3 *dwc = dep->dwc; + int i; if (req->queued) { - if (req->request.num_mapped_sgs) - dep->busy_slot += req->request.num_mapped_sgs; - else + i = 0; + do { dep->busy_slot++; - - /* - * Skip LINK TRB. We can't use req->trb and check for - * DWC3_TRBCTL_LINK_TRB because it points the TRB we just - * completed (not the LINK TRB). - */ - if (((dep->busy_slot & DWC3_TRB_MASK) == DWC3_TRB_NUM - 1) && + /* + * Skip LINK TRB. We can't use req->trb and check for + * DWC3_TRBCTL_LINK_TRB because it points the TRB we + * just completed (not the LINK TRB). + */ + if (((dep->busy_slot & DWC3_TRB_MASK) == + DWC3_TRB_NUM- 1) && usb_endpoint_xfer_isoc(dep->endpoint.desc)) - dep->busy_slot++; + dep->busy_slot++; + } while(++i < req->request.num_mapped_sgs); + req->queued = false; } list_del(&req->list); req->trb = NULL; @@ -749,33 +751,32 @@ static void dwc3_gadget_ep_free_request(struct usb_ep *ep, */ static void dwc3_prepare_one_trb(struct dwc3_ep *dep, struct dwc3_request *req, dma_addr_t dma, - unsigned length, unsigned last, unsigned chain) + unsigned length, unsigned last, unsigned chain, unsigned node) { struct dwc3 *dwc = dep->dwc; struct dwc3_trb *trb; - unsigned int cur_slot; - dev_vdbg(dwc->dev, "%s: req %p dma %08llx length %d%s%s\n", dep->name, req, (unsigned long long) dma, length, last ? " last" : "", chain ? " chain" : ""); - trb = &dep->trb_pool[dep->free_slot & DWC3_TRB_MASK]; - cur_slot = dep->free_slot; - dep->free_slot++; - /* Skip the LINK-TRB on ISOC */ - if (((cur_slot & DWC3_TRB_MASK) == DWC3_TRB_NUM - 1) && + if (((dep->free_slot & DWC3_TRB_MASK) == DWC3_TRB_NUM - 1) && usb_endpoint_xfer_isoc(dep->endpoint.desc)) - return; + dep->free_slot++; + + trb = &dep->trb_pool[dep->free_slot & DWC3_TRB_MASK]; if (!req->trb) { dwc3_gadget_move_request_queued(req); req->trb = trb; req->trb_dma = dwc3_trb_dma_offset(dep, trb); + req->start_slot = dep->free_slot & DWC3_TRB_MASK; } + dep->free_slot++; + trb->size = DWC3_TRB_SIZE_LENGTH(length); trb->bpl = lower_32_bits(dma); trb->bph = upper_32_bits(dma); @@ -786,9 +787,12 @@ static void dwc3_prepare_one_trb(struct dwc3_ep *dep, break; case USB_ENDPOINT_XFER_ISOC: - trb->ctrl = DWC3_TRBCTL_ISOCHRONOUS_FIRST; + if (!node) + trb->ctrl = DWC3_TRBCTL_ISOCHRONOUS_FIRST; + else + trb->ctrl = DWC3_TRBCTL_ISOCHRONOUS; - if (!req->request.no_interrupt) + if (!req->request.no_interrupt && !chain) trb->ctrl |= DWC3_TRB_CTRL_IOC; break; @@ -807,14 +811,13 @@ static void dwc3_prepare_one_trb(struct dwc3_ep *dep, if (usb_endpoint_xfer_isoc(dep->endpoint.desc)) { trb->ctrl |= DWC3_TRB_CTRL_ISP_IMI; trb->ctrl |= DWC3_TRB_CTRL_CSP; - } else { - if (chain) - trb->ctrl |= DWC3_TRB_CTRL_CHN; - - if (last) - trb->ctrl |= DWC3_TRB_CTRL_LST; + } else if (last) { + trb->ctrl |= DWC3_TRB_CTRL_LST; } + if (chain) + trb->ctrl |= DWC3_TRB_CTRL_CHN; + if (usb_endpoint_xfer_bulk(dep->endpoint.desc) && dep->stream_capable) trb->ctrl |= DWC3_TRB_CTRL_SID_SOFN(req->request.stream_id); @@ -885,6 +888,7 @@ static void dwc3_prepare_trbs(struct dwc3_ep *dep, bool starting) list_for_each_entry_safe(req, n, &dep->request_list, list) { unsigned length; dma_addr_t dma; + last_one = false; if (req->request.num_mapped_sgs > 0) { struct usb_request *request = &req->request; @@ -900,7 +904,9 @@ static void dwc3_prepare_trbs(struct dwc3_ep *dep, bool starting) if (i == (request->num_mapped_sgs - 1) || sg_is_last(s)) { - last_one = true; + if (list_is_last(&req->list, + &dep->request_list)) + last_one = true; chain = false; } @@ -912,7 +918,7 @@ static void dwc3_prepare_trbs(struct dwc3_ep *dep, bool starting) chain = false; dwc3_prepare_one_trb(dep, req, dma, length, - last_one, chain); + last_one, chain, i); if (last_one) break; @@ -930,7 +936,7 @@ static void dwc3_prepare_trbs(struct dwc3_ep *dep, bool starting) last_one = 1; dwc3_prepare_one_trb(dep, req, dma, length, - last_one, false); + last_one, false, 0); if (last_one) break; @@ -977,13 +983,14 @@ static int __dwc3_gadget_kick_transfer(struct dwc3_ep *dep, u16 cmd_param, } memset(¶ms, 0, sizeof(params)); - params.param0 = upper_32_bits(req->trb_dma); - params.param1 = lower_32_bits(req->trb_dma); - if (start_new) + if (start_new) { + params.param0 = upper_32_bits(req->trb_dma); + params.param1 = lower_32_bits(req->trb_dma); cmd = DWC3_DEPCMD_STARTTRANSFER; - else + } else { cmd = DWC3_DEPCMD_UPDATETRANSFER; + } cmd |= DWC3_DEPCMD_PARAM(cmd_param); ret = dwc3_send_gadget_ep_cmd(dwc, dep->number, cmd, ¶ms); @@ -1082,8 +1089,6 @@ static int __dwc3_gadget_ep_queue(struct dwc3_ep *dep, struct dwc3_request *req) * */ if (dep->flags & DWC3_EP_PENDING_REQUEST) { - int ret; - /* * If xfernotready is already elapsed and it is a case * of isoc transfer, then issue END TRANSFER, so that @@ -1091,7 +1096,10 @@ static int __dwc3_gadget_ep_queue(struct dwc3_ep *dep, struct dwc3_request *req) * notion of current microframe. */ if (usb_endpoint_xfer_isoc(dep->endpoint.desc)) { - dwc3_stop_active_transfer(dwc, dep->number); + if (list_empty(&dep->req_queued)) { + dwc3_stop_active_transfer(dwc, dep->number); + dep->flags = DWC3_EP_ENABLED; + } return 0; } @@ -1099,6 +1107,7 @@ static int __dwc3_gadget_ep_queue(struct dwc3_ep *dep, struct dwc3_request *req) if (ret && ret != -EBUSY) dev_dbg(dwc->dev, "%s: failed to kick transfers\n", dep->name); + return ret; } /* @@ -1115,16 +1124,7 @@ static int __dwc3_gadget_ep_queue(struct dwc3_ep *dep, struct dwc3_request *req) if (ret && ret != -EBUSY) dev_dbg(dwc->dev, "%s: failed to kick transfers\n", dep->name); - } - - /* - * 3. Missed ISOC Handling. We need to start isoc transfer on the saved - * uframe number. - */ - if (usb_endpoint_xfer_isoc(dep->endpoint.desc) && - (dep->flags & DWC3_EP_MISSED_ISOC)) { - __dwc3_gadget_start_isoc(dwc, dep, dep->current_uf); - dep->flags &= ~DWC3_EP_MISSED_ISOC; + return ret; } return 0; @@ -1652,76 +1652,134 @@ static void dwc3_gadget_release(struct device *dev) } /* -------------------------------------------------------------------------- */ -static int dwc3_cleanup_done_reqs(struct dwc3 *dwc, struct dwc3_ep *dep, +static int __dwc3_cleanup_done_trbs(struct dwc3 *dwc, struct dwc3_ep *dep, + struct dwc3_request *req, struct dwc3_trb *trb, const struct dwc3_event_depevt *event, int status) { - struct dwc3_request *req; - struct dwc3_trb *trb; unsigned int count; unsigned int s_pkt = 0; unsigned int trb_status; + if ((trb->ctrl & DWC3_TRB_CTRL_HWO) && status != -ESHUTDOWN) + /* + * We continue despite the error. There is not much we + * can do. If we don't clean it up we loop forever. If + * we skip the TRB then it gets overwritten after a + * while since we use them in a ring buffer. A BUG() + * would help. Lets hope that if this occurs, someone + * fixes the root cause instead of looking away :) + */ + dev_err(dwc->dev, "%s's TRB (%p) still owned by HW\n", + dep->name, trb); + count = trb->size & DWC3_TRB_SIZE_MASK; + + if (dep->direction) { + if (count) { + trb_status = DWC3_TRB_SIZE_TRBSTS(trb->size); + if (trb_status == DWC3_TRBSTS_MISSED_ISOC) { + dev_dbg(dwc->dev, "incomplete IN transfer %s\n", + dep->name); + /* + * If missed isoc occurred and there is + * no request queued then issue END + * TRANSFER, so that core generates + * next xfernotready and we will issue + * a fresh START TRANSFER. + * If there are still queued request + * then wait, do not issue either END + * or UPDATE TRANSFER, just attach next + * request in request_list during + * giveback.If any future queued request + * is successfully transferred then we + * will issue UPDATE TRANSFER for all + * request in the request_list. + */ + dep->flags |= DWC3_EP_MISSED_ISOC; + } else { + dev_err(dwc->dev, "incomplete IN transfer %s\n", + dep->name); + status = -ECONNRESET; + } + } else { + dep->flags &= ~DWC3_EP_MISSED_ISOC; + } + } else { + if (count && (event->status & DEPEVT_STATUS_SHORT)) + s_pkt = 1; + } + + /* + * We assume here we will always receive the entire data block + * which we should receive. Meaning, if we program RX to + * receive 4K but we receive only 2K, we assume that's all we + * should receive and we simply bounce the request back to the + * gadget driver for further processing. + */ + req->request.actual += req->request.length - count; + if (s_pkt) + return 1; + if ((event->status & DEPEVT_STATUS_LST) && + (trb->ctrl & (DWC3_TRB_CTRL_LST | + DWC3_TRB_CTRL_HWO))) + return 1; + if ((event->status & DEPEVT_STATUS_IOC) && + (trb->ctrl & DWC3_TRB_CTRL_IOC)) + return 1; + return 0; +} + +static int dwc3_cleanup_done_reqs(struct dwc3 *dwc, struct dwc3_ep *dep, + const struct dwc3_event_depevt *event, int status) +{ + struct dwc3_request *req; + struct dwc3_trb *trb; + unsigned int slot; + unsigned int i; + int ret; + do { req = next_request(&dep->req_queued); if (!req) { WARN_ON_ONCE(1); return 1; } + i = 0; + do { + slot = req->start_slot + i; + if ((slot == DWC3_TRB_NUM - 1) && + usb_endpoint_xfer_isoc(dep->endpoint.desc)) + slot++; + slot %= DWC3_TRB_NUM; + trb = &dep->trb_pool[slot]; - trb = req->trb; + ret = __dwc3_cleanup_done_trbs(dwc, dep, req, trb, + event, status); + if (ret) + break; + }while (++i < req->request.num_mapped_sgs); - if ((trb->ctrl & DWC3_TRB_CTRL_HWO) && status != -ESHUTDOWN) + dwc3_gadget_giveback(dep, req, status); + + if (ret) + break; + } while (1); + + if (usb_endpoint_xfer_isoc(dep->endpoint.desc) && + list_empty(&dep->req_queued)) { + if (list_empty(&dep->request_list)) { /* - * We continue despite the error. There is not much we - * can do. If we don't clean it up we loop forever. If - * we skip the TRB then it gets overwritten after a - * while since we use them in a ring buffer. A BUG() - * would help. Lets hope that if this occurs, someone - * fixes the root cause instead of looking away :) + * If there is no entry in request list then do + * not issue END TRANSFER now. Just set PENDING + * flag, so that END TRANSFER is issued when an + * entry is added into request list. */ - dev_err(dwc->dev, "%s's TRB (%p) still owned by HW\n", - dep->name, req->trb); - count = trb->size & DWC3_TRB_SIZE_MASK; - - if (dep->direction) { - if (count) { - trb_status = DWC3_TRB_SIZE_TRBSTS(trb->size); - if (trb_status == DWC3_TRBSTS_MISSED_ISOC) { - dev_dbg(dwc->dev, "incomplete IN transfer %s\n", - dep->name); - dep->current_uf = event->parameters & - ~(dep->interval - 1); - dep->flags |= DWC3_EP_MISSED_ISOC; - } else { - dev_err(dwc->dev, "incomplete IN transfer %s\n", - dep->name); - status = -ECONNRESET; - } - } + dep->flags = DWC3_EP_PENDING_REQUEST; } else { - if (count && (event->status & DEPEVT_STATUS_SHORT)) - s_pkt = 1; + dwc3_stop_active_transfer(dwc, dep->number); + dep->flags = DWC3_EP_ENABLED; } - - /* - * We assume here we will always receive the entire data block - * which we should receive. Meaning, if we program RX to - * receive 4K but we receive only 2K, we assume that's all we - * should receive and we simply bounce the request back to the - * gadget driver for further processing. - */ - req->request.actual += req->request.length - count; - dwc3_gadget_giveback(dep, req, status); - if (s_pkt) - break; - if ((event->status & DEPEVT_STATUS_LST) && - (trb->ctrl & (DWC3_TRB_CTRL_LST | - DWC3_TRB_CTRL_HWO))) - break; - if ((event->status & DEPEVT_STATUS_IOC) && - (trb->ctrl & DWC3_TRB_CTRL_IOC)) - break; - } while (1); + return 1; + } if ((event->status & DEPEVT_STATUS_IOC) && (trb->ctrl & DWC3_TRB_CTRL_IOC)) @@ -2157,6 +2215,26 @@ static void dwc3_gadget_conndone_interrupt(struct dwc3 *dwc) break; } + /* Enable USB2 LPM Capability */ + + if ((dwc->revision > DWC3_REVISION_194A) + && (speed != DWC3_DCFG_SUPERSPEED)) { + reg = dwc3_readl(dwc->regs, DWC3_DCFG); + reg |= DWC3_DCFG_LPM_CAP; + dwc3_writel(dwc->regs, DWC3_DCFG, reg); + + reg = dwc3_readl(dwc->regs, DWC3_DCTL); + reg &= ~(DWC3_DCTL_HIRD_THRES_MASK | DWC3_DCTL_L1_HIBER_EN); + + /* + * TODO: This should be configurable. For now using + * maximum allowed HIRD threshold value of 0b1100 + */ + reg |= DWC3_DCTL_HIRD_THRES(12); + + dwc3_writel(dwc->regs, DWC3_DCTL, reg); + } + /* Recent versions support automatic phy suspend and don't need this */ if (dwc->revision < DWC3_REVISION_194A) { /* Suspend unneeded PHY */ @@ -2463,20 +2541,8 @@ int dwc3_gadget_init(struct dwc3 *dwc) DWC3_DEVTEN_DISCONNEVTEN); dwc3_writel(dwc->regs, DWC3_DEVTEN, reg); - /* Enable USB2 LPM and automatic phy suspend only on recent versions */ + /* automatic phy suspend only on recent versions */ if (dwc->revision >= DWC3_REVISION_194A) { - reg = dwc3_readl(dwc->regs, DWC3_DCFG); - reg |= DWC3_DCFG_LPM_CAP; - dwc3_writel(dwc->regs, DWC3_DCFG, reg); - - reg = dwc3_readl(dwc->regs, DWC3_DCTL); - reg &= ~(DWC3_DCTL_HIRD_THRES_MASK | DWC3_DCTL_L1_HIBER_EN); - - /* TODO: This should be configurable */ - reg |= DWC3_DCTL_HIRD_THRES(28); - - dwc3_writel(dwc->regs, DWC3_DCTL, reg); - dwc3_gadget_usb2_phy_suspend(dwc, false); dwc3_gadget_usb3_phy_suspend(dwc, false); } |