usb: dwc3: gadget: add support for SG lists
authorFelipe Balbi <balbi@ti.com>
Mon, 28 Nov 2011 10:46:59 +0000 (12:46 +0200)
committerFelipe Balbi <balbi@ti.com>
Wed, 21 Dec 2011 11:24:46 +0000 (13:24 +0200)
add support for SG lists on dwc3 driver. With
this we can e.g. use VFS layer's SG lists on
storage gadgets so that we can start bigger
transfers and improve throughput.

Signed-off-by: Felipe Balbi <balbi@ti.com>
drivers/usb/dwc3/gadget.c

index 0292b0617d72e3cc72f61d5ba25c8818b01abcd6..ddc7a43592c08164ed19f2416c3e30b785883f1c 100644 (file)
@@ -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);