summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--drivers/usb/dwc3/gadget.c115
1 files changed, 98 insertions, 17 deletions
diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c
index 0292b0617d72..ddc7a43592c0 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);