diff options
Diffstat (limited to 'drivers/usb/dwc3/gadget.c')
-rw-r--r-- | drivers/usb/dwc3/gadget.c | 115 |
1 files changed, 98 insertions, 17 deletions
diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index 0292b0617d7..ddc7a43592c 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -65,6 +65,22 @@ void dwc3_map_buffer_to_dma(struct dwc3_request *req) return; } + if (req->request.num_sgs) { + int mapped; + + mapped = dma_map_sg(dwc->dev, req->request.sg, + req->request.num_sgs, + req->direction ? DMA_TO_DEVICE + : DMA_FROM_DEVICE); + if (mapped < 0) { + dev_err(dwc->dev, "failed to map SGs\n"); + return; + } + + req->request.num_mapped_sgs = mapped; + return; + } + if (req->request.dma == DMA_ADDR_INVALID) { req->request.dma = dma_map_single(dwc->dev, req->request.buf, req->request.length, req->direction @@ -82,6 +98,17 @@ void dwc3_unmap_buffer_from_dma(struct dwc3_request *req) return; } + if (req->request.num_mapped_sgs) { + req->request.dma = DMA_ADDR_INVALID; + dma_unmap_sg(dwc->dev, req->request.sg, + req->request.num_sgs, + req->direction ? DMA_TO_DEVICE + : DMA_FROM_DEVICE); + + req->request.num_mapped_sgs = 0; + return; + } + if (req->mapped) { dma_unmap_single(dwc->dev, req->request.dma, req->request.length, req->direction @@ -97,7 +124,11 @@ void dwc3_gadget_giveback(struct dwc3_ep *dep, struct dwc3_request *req, struct dwc3 *dwc = dep->dwc; if (req->queued) { - dep->busy_slot++; + if (req->request.num_mapped_sgs) + dep->busy_slot += req->request.num_mapped_sgs; + else + 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 @@ -108,6 +139,7 @@ void dwc3_gadget_giveback(struct dwc3_ep *dep, struct dwc3_request *req, dep->busy_slot++; } list_del(&req->list); + req->trb = NULL; if (req->request.status == -EINPROGRESS) req->request.status = status; @@ -545,13 +577,20 @@ static void dwc3_gadget_ep_free_request(struct usb_ep *ep, * @req: dwc3_request pointer */ static void dwc3_prepare_one_trb(struct dwc3_ep *dep, - struct dwc3_request *req, unsigned last) + struct dwc3_request *req, dma_addr_t dma, + unsigned length, unsigned last, unsigned chain) { + struct dwc3 *dwc = dep->dwc; struct dwc3_trb_hw *trb_hw; 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_hw = &dep->trb_pool[dep->free_slot & DWC3_TRB_MASK]; cur_slot = dep->free_slot; dep->free_slot++; @@ -561,15 +600,18 @@ static void dwc3_prepare_one_trb(struct dwc3_ep *dep, usb_endpoint_xfer_isoc(dep->desc)) return; - dwc3_gadget_move_request_queued(req); memset(&trb, 0, sizeof(trb)); - - req->trb = trb_hw; + if (!req->trb) { + dwc3_gadget_move_request_queued(req); + req->trb = trb_hw; + req->trb_dma = dwc3_trb_dma_offset(dep, trb_hw); + } if (usb_endpoint_xfer_isoc(dep->desc)) { trb.isp_imi = true; trb.csp = true; } else { + trb.chn = chain; trb.lst = last; } @@ -601,12 +643,11 @@ static void dwc3_prepare_one_trb(struct dwc3_ep *dep, BUG(); } - trb.length = req->request.length; - trb.bplh = req->request.dma; + trb.length = length; + trb.bplh = dma; trb.hwo = true; dwc3_trb_to_hw(&trb, trb_hw); - req->trb_dma = dwc3_trb_dma_offset(dep, trb_hw); } /* @@ -663,19 +704,58 @@ static void dwc3_prepare_trbs(struct dwc3_ep *dep, bool starting) return; list_for_each_entry_safe(req, n, &dep->request_list, list) { - trbs_left--; + unsigned length; + dma_addr_t dma; - if (!trbs_left) - last_one = 1; + if (req->request.num_mapped_sgs > 0) { + struct usb_request *request = &req->request; + struct scatterlist *sg = request->sg; + struct scatterlist *s; + int i; - /* Is this the last request? */ - if (list_empty(&dep->request_list)) - last_one = 1; + for_each_sg(sg, s, request->num_mapped_sgs, i) { + unsigned chain = true; - dwc3_prepare_one_trb(dep, req, last_one); + length = sg_dma_len(s); + dma = sg_dma_address(s); - if (last_one) - break; + if (i == (request->num_mapped_sgs - 1) + || sg_is_last(s)) { + last_one = true; + chain = false; + } + + trbs_left--; + if (!trbs_left) + last_one = true; + + if (last_one) + chain = false; + + dwc3_prepare_one_trb(dep, req, dma, length, + last_one, chain); + + if (last_one) + break; + } + } else { + dma = req->request.dma; + length = req->request.length; + trbs_left--; + + if (!trbs_left) + last_one = 1; + + /* Is this the last request? */ + if (list_is_last(&req->list, &dep->request_list)) + last_one = 1; + + dwc3_prepare_one_trb(dep, req, dma, length, + last_one, false); + + if (last_one) + break; + } } } @@ -1989,6 +2069,7 @@ int __devinit dwc3_gadget_init(struct dwc3 *dwc) dwc->gadget.max_speed = USB_SPEED_SUPER; dwc->gadget.speed = USB_SPEED_UNKNOWN; dwc->gadget.dev.parent = dwc->dev; + dwc->gadget.sg_supported = true; dma_set_coherent_mask(&dwc->gadget.dev, dwc->dev->coherent_dma_mask); |