diff options
author | Marek Vasut <marex@denx.de> | 2013-07-10 03:16:43 +0200 |
---|---|---|
committer | Marek Vasut <marex@denx.de> | 2013-07-29 23:01:33 +0200 |
commit | 6dd30af0fa5c2f509f6f789789151268bdfcde0c (patch) | |
tree | 3478096a1ef9c17cb2ce09f0d3e55849afe34ecc /drivers | |
parent | f19a343e732f4fd7ca0b0e067a1bfba930621553 (diff) |
usb: mv_udc: Add bounce buffer
The requests sent to the controller are not properly cache aligned
most of the time, thus implement a simple bounce buffer to avoid
problem with cache.
Signed-off-by: Marek Vasut <marex@denx.de>
Cc: Fabio Estevam <fabio.estevam@freescale.com>
Cc: Lei Wen <leiwen@marvell.com>
Cc: Otavio Salvador <otavio@ossystems.com.br>
Cc: Stefano Babic <sbabic@denx.de>
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/usb/gadget/mv_udc.c | 84 |
1 files changed, 77 insertions, 7 deletions
diff --git a/drivers/usb/gadget/mv_udc.c b/drivers/usb/gadget/mv_udc.c index 8a5593ae0a..7574e31a8f 100644 --- a/drivers/usb/gadget/mv_udc.c +++ b/drivers/usb/gadget/mv_udc.c @@ -247,6 +247,71 @@ static int mv_ep_disable(struct usb_ep *ep) return 0; } +static int mv_bounce(struct mv_ep *ep) +{ + uint32_t addr = (uint32_t)ep->req.buf; + uint32_t ba; + + /* Input buffer address is not aligned. */ + if (addr & (ARCH_DMA_MINALIGN - 1)) + goto align; + + /* Input buffer length is not aligned. */ + if (ep->req.length & (ARCH_DMA_MINALIGN - 1)) + goto align; + + /* The buffer is well aligned, only flush cache. */ + ep->b_len = ep->req.length; + ep->b_buf = ep->req.buf; + goto flush; + +align: + /* Use internal buffer for small payloads. */ + if (ep->req.length <= 64) { + ep->b_len = 64; + ep->b_buf = ep->b_fast; + } else { + ep->b_len = roundup(ep->req.length, ARCH_DMA_MINALIGN); + ep->b_buf = memalign(ARCH_DMA_MINALIGN, ep->b_len); + if (!ep->b_buf) + return -ENOMEM; + } + + memcpy(ep->b_buf, ep->req.buf, ep->req.length); + +flush: + ba = (uint32_t)ep->b_buf; + flush_dcache_range(ba, ba + ep->b_len); + + return 0; +} + +static void mv_debounce(struct mv_ep *ep) +{ + uint32_t addr = (uint32_t)ep->req.buf; + uint32_t ba = (uint32_t)ep->b_buf; + + invalidate_dcache_range(ba, ba + ep->b_len); + + /* Input buffer address is not aligned. */ + if (addr & (ARCH_DMA_MINALIGN - 1)) + goto copy; + + /* Input buffer length is not aligned. */ + if (ep->req.length & (ARCH_DMA_MINALIGN - 1)) + goto copy; + + /* The buffer is well aligned, only invalidate cache. */ + return; + +copy: + memcpy(ep->req.buf, ep->b_buf, ep->req.length); + + /* Large payloads use allocated buffer, free it. */ + if (ep->req.length > 64) + free(ep->b_buf); +} + static int mv_ep_queue(struct usb_ep *ep, struct usb_request *req, gfp_t gfp_flags) { @@ -254,25 +319,27 @@ static int mv_ep_queue(struct usb_ep *ep, struct mv_udc *udc = (struct mv_udc *)controller.ctrl->hcor; struct ept_queue_item *item; struct ept_queue_head *head; - unsigned phys; - int bit, num, len, in; + int bit, num, len, in, ret; num = mv_ep->desc->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK; in = (mv_ep->desc->bEndpointAddress & USB_DIR_IN) != 0; item = mv_get_qtd(num, in); head = mv_get_qh(num, in); - phys = (unsigned)req->buf; len = req->length; + ret = mv_bounce(mv_ep); + if (ret) + return ret; + item->next = TERMINATE; item->info = INFO_BYTES(len) | INFO_IOC | INFO_ACTIVE; - item->page0 = phys; - item->page1 = (phys & 0xfffff000) + 0x1000; + item->page0 = (uint32_t)mv_ep->b_buf; + item->page1 = ((uint32_t)mv_ep->b_buf & 0xfffff000) + 0x1000; head->next = (unsigned) item; head->info = 0; - DBG("ept%d %s queue len %x, buffer %x\n", - num, in ? "in" : "out", len, phys); + DBG("ept%d %s queue len %x, buffer %p\n", + num, in ? "in" : "out", len, mv_ep->b_buf); if (in) bit = EPT_TX(num); @@ -303,6 +370,9 @@ static void handle_ep_complete(struct mv_ep *ep) num, in ? "in" : "out", item->info, item->page0); len = (item->info >> 16) & 0x7fff; + + mv_debounce(ep); + ep->req.length -= len; DBG("ept%d %s complete %x\n", num, in ? "in" : "out", len); |