aboutsummaryrefslogtreecommitdiffstats
path: root/usb/usb-imx21-hcd-accept-arbitary-transfer-buffer-alignement.patch
diff options
Diffstat (limited to 'usb/usb-imx21-hcd-accept-arbitary-transfer-buffer-alignement.patch')
-rw-r--r--usb/usb-imx21-hcd-accept-arbitary-transfer-buffer-alignement.patch452
1 files changed, 452 insertions, 0 deletions
diff --git a/usb/usb-imx21-hcd-accept-arbitary-transfer-buffer-alignement.patch b/usb/usb-imx21-hcd-accept-arbitary-transfer-buffer-alignement.patch
new file mode 100644
index 00000000000000..d2ec1eb74c239a
--- /dev/null
+++ b/usb/usb-imx21-hcd-accept-arbitary-transfer-buffer-alignement.patch
@@ -0,0 +1,452 @@
+From mfuzzey@gmail.com Tue Oct 5 15:56:17 2010
+From: Martin Fuzzey <mfuzzey@gmail.com>
+Subject: USB: imx21-hcd accept arbitary transfer buffer alignement.
+To: Greg KH <greg@kroah.com>, linux-usb@vger.kernel.org
+Date: Fri, 01 Oct 2010 00:21:59 +0200
+Message-ID: <20100930222159.15137.87053.stgit@srv002.fuzzey.net>
+
+The hardware can only do DMA to 4 byte aligned addresses.
+When this requirement is not met use PIO or a bounce buffer.
+
+PIO is used when the buffer is small enough to directly
+use the hardware data memory (2*maxpacket).
+
+A bounce buffer is used for larger transfers.
+
+Signed-off-by: Martin Fuzzey <mfuzzey@gmail.com>
+Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
+
+
+---
+ drivers/usb/host/imx21-hcd.c | 196 +++++++++++++++++++++++++++++++++++--------
+ drivers/usb/host/imx21-hcd.h | 8 +
+ 2 files changed, 169 insertions(+), 35 deletions(-)
+
+--- a/drivers/usb/host/imx21-hcd.c
++++ b/drivers/usb/host/imx21-hcd.c
+@@ -57,6 +57,7 @@
+ #include <linux/slab.h>
+ #include <linux/usb.h>
+ #include <linux/usb/hcd.h>
++#include <linux/dma-mapping.h>
+
+ #include "imx21-hcd.h"
+
+@@ -136,9 +137,18 @@ static int imx21_hc_get_frame(struct usb
+ return wrap_frame(readl(imx21->regs + USBH_FRMNUB));
+ }
+
++static inline bool unsuitable_for_dma(dma_addr_t addr)
++{
++ return (addr & 3) != 0;
++}
+
+ #include "imx21-dbg.c"
+
++static void nonisoc_urb_completed_for_etd(
++ struct imx21 *imx21, struct etd_priv *etd, int status);
++static void schedule_nonisoc_etd(struct imx21 *imx21, struct urb *urb);
++static void free_dmem(struct imx21 *imx21, struct etd_priv *etd);
++
+ /* =========================================== */
+ /* ETD management */
+ /* =========================================== */
+@@ -185,7 +195,8 @@ static void reset_etd(struct imx21 *imx2
+ etd_writel(imx21, num, i, 0);
+ etd->urb = NULL;
+ etd->ep = NULL;
+- etd->td = NULL;;
++ etd->td = NULL;
++ etd->bounce_buffer = NULL;
+ }
+
+ static void free_etd(struct imx21 *imx21, int num)
+@@ -221,26 +232,94 @@ static void setup_etd_dword0(struct imx2
+ ((u32) maxpacket << DW0_MAXPKTSIZ));
+ }
+
+-static void activate_etd(struct imx21 *imx21,
+- int etd_num, dma_addr_t dma, u8 dir)
++/**
++ * Copy buffer to data controller data memory.
++ * We cannot use memcpy_toio() because the hardware requires 32bit writes
++ */
++static void copy_to_dmem(
++ struct imx21 *imx21, int dmem_offset, void *src, int count)
++{
++ void __iomem *dmem = imx21->regs + USBOTG_DMEM + dmem_offset;
++ u32 word = 0;
++ u8 *p = src;
++ int byte = 0;
++ int i;
++
++ for (i = 0; i < count; i++) {
++ byte = i % 4;
++ word += (*p++ << (byte * 8));
++ if (byte == 3) {
++ writel(word, dmem);
++ dmem += 4;
++ word = 0;
++ }
++ }
++
++ if (count && byte != 3)
++ writel(word, dmem);
++}
++
++static void activate_etd(struct imx21 *imx21, int etd_num, u8 dir)
+ {
+ u32 etd_mask = 1 << etd_num;
+ struct etd_priv *etd = &imx21->etd[etd_num];
+
++ if (etd->dma_handle && unsuitable_for_dma(etd->dma_handle)) {
++ /* For non aligned isoc the condition below is always true */
++ if (etd->len <= etd->dmem_size) {
++ /* Fits into data memory, use PIO */
++ if (dir != TD_DIR_IN) {
++ copy_to_dmem(imx21,
++ etd->dmem_offset,
++ etd->cpu_buffer, etd->len);
++ }
++ etd->dma_handle = 0;
++
++ } else {
++ /* Too big for data memory, use bounce buffer */
++ enum dma_data_direction dmadir;
++
++ if (dir == TD_DIR_IN) {
++ dmadir = DMA_FROM_DEVICE;
++ etd->bounce_buffer = kmalloc(etd->len,
++ GFP_ATOMIC);
++ } else {
++ dmadir = DMA_TO_DEVICE;
++ etd->bounce_buffer = kmemdup(etd->cpu_buffer,
++ etd->len,
++ GFP_ATOMIC);
++ }
++ if (!etd->bounce_buffer) {
++ dev_err(imx21->dev, "failed bounce alloc\n");
++ goto err_bounce_alloc;
++ }
++
++ etd->dma_handle =
++ dma_map_single(imx21->dev,
++ etd->bounce_buffer,
++ etd->len,
++ dmadir);
++ if (dma_mapping_error(imx21->dev, etd->dma_handle)) {
++ dev_err(imx21->dev, "failed bounce map\n");
++ goto err_bounce_map;
++ }
++ }
++ }
++
+ clear_toggle_bit(imx21, USBH_ETDDONESTAT, etd_mask);
+ set_register_bits(imx21, USBH_ETDDONEEN, etd_mask);
+ clear_toggle_bit(imx21, USBH_XFILLSTAT, etd_mask);
+ clear_toggle_bit(imx21, USBH_YFILLSTAT, etd_mask);
+
+- if (dma) {
++ if (etd->dma_handle) {
+ set_register_bits(imx21, USB_ETDDMACHANLCLR, etd_mask);
+ clear_toggle_bit(imx21, USBH_XBUFSTAT, etd_mask);
+ clear_toggle_bit(imx21, USBH_YBUFSTAT, etd_mask);
+- writel(dma, imx21->regs + USB_ETDSMSA(etd_num));
++ writel(etd->dma_handle, imx21->regs + USB_ETDSMSA(etd_num));
+ set_register_bits(imx21, USB_ETDDMAEN, etd_mask);
+ } else {
+ if (dir != TD_DIR_IN) {
+- /* need to set for ZLP */
++ /* need to set for ZLP and PIO */
+ set_toggle_bit(imx21, USBH_XFILLSTAT, etd_mask);
+ set_toggle_bit(imx21, USBH_YFILLSTAT, etd_mask);
+ }
+@@ -263,6 +342,14 @@ static void activate_etd(struct imx21 *i
+
+ etd->active_count = 1;
+ writel(etd_mask, imx21->regs + USBH_ETDENSET);
++ return;
++
++err_bounce_map:
++ kfree(etd->bounce_buffer);
++
++err_bounce_alloc:
++ free_dmem(imx21, etd);
++ nonisoc_urb_completed_for_etd(imx21, etd, -ENOMEM);
+ }
+
+ /* =========================================== */
+@@ -325,7 +412,7 @@ static void activate_queued_etd(struct i
+
+ etd->dmem_offset = dmem_offset;
+ urb_priv->active = 1;
+- activate_etd(imx21, etd_num, etd->dma_handle, dir);
++ activate_etd(imx21, etd_num, dir);
+ }
+
+ static void free_dmem(struct imx21 *imx21, struct etd_priv *etd)
+@@ -385,7 +472,6 @@ static void free_epdmem(struct imx21 *im
+ /* =========================================== */
+ /* End handling */
+ /* =========================================== */
+-static void schedule_nonisoc_etd(struct imx21 *imx21, struct urb *urb);
+
+ /* Endpoint now idle - release it's ETD(s) or asssign to queued request */
+ static void ep_idle(struct imx21 *imx21, struct ep_priv *ep_priv)
+@@ -448,6 +534,24 @@ __acquires(imx21->lock)
+ ep_idle(imx21, ep_priv);
+ }
+
++static void nonisoc_urb_completed_for_etd(
++ struct imx21 *imx21, struct etd_priv *etd, int status)
++{
++ struct usb_host_endpoint *ep = etd->ep;
++
++ urb_done(imx21->hcd, etd->urb, status);
++ etd->urb = NULL;
++
++ if (!list_empty(&ep->urb_list)) {
++ struct urb *urb = list_first_entry(
++ &ep->urb_list, struct urb, urb_list);
++
++ dev_vdbg(imx21->dev, "next URB %p\n", urb);
++ schedule_nonisoc_etd(imx21, urb);
++ }
++}
++
++
+ /* =========================================== */
+ /* ISOC Handling ... */
+ /* =========================================== */
+@@ -500,6 +604,8 @@ too_late:
+ etd->ep = td->ep;
+ etd->urb = td->urb;
+ etd->len = td->len;
++ etd->dma_handle = td->dma_handle;
++ etd->cpu_buffer = td->cpu_buffer;
+
+ debug_isoc_submitted(imx21, cur_frame, td);
+
+@@ -513,16 +619,17 @@ too_late:
+ (TD_NOTACCESSED << DW3_COMPCODE0) |
+ (td->len << DW3_PKTLEN0));
+
+- activate_etd(imx21, etd_num, td->data, dir);
++ activate_etd(imx21, etd_num, dir);
+ }
+ }
+
+-static void isoc_etd_done(struct usb_hcd *hcd, struct urb *urb, int etd_num)
++static void isoc_etd_done(struct usb_hcd *hcd, int etd_num)
+ {
+ struct imx21 *imx21 = hcd_to_imx21(hcd);
+ int etd_mask = 1 << etd_num;
+- struct urb_priv *urb_priv = urb->hcpriv;
+ struct etd_priv *etd = imx21->etd + etd_num;
++ struct urb *urb = etd->urb;
++ struct urb_priv *urb_priv = urb->hcpriv;
+ struct td *td = etd->td;
+ struct usb_host_endpoint *ep = etd->ep;
+ int isoc_index = td->isoc_index;
+@@ -556,8 +663,13 @@ static void isoc_etd_done(struct usb_hcd
+ bytes_xfrd, td->len, urb, etd_num, isoc_index);
+ }
+
+- if (dir_in)
++ if (dir_in) {
+ clear_toggle_bit(imx21, USBH_XFILLSTAT, etd_mask);
++ if (!etd->dma_handle)
++ memcpy_fromio(etd->cpu_buffer,
++ imx21->regs + USBOTG_DMEM + etd->dmem_offset,
++ bytes_xfrd);
++ }
+
+ urb->actual_length += bytes_xfrd;
+ urb->iso_frame_desc[isoc_index].actual_length = bytes_xfrd;
+@@ -716,12 +828,14 @@ static int imx21_hc_urb_enqueue_isoc(str
+ /* set up transfers */
+ td = urb_priv->isoc_td;
+ for (i = 0; i < urb->number_of_packets; i++, td++) {
++ unsigned int offset = urb->iso_frame_desc[i].offset;
+ td->ep = ep;
+ td->urb = urb;
+ td->len = urb->iso_frame_desc[i].length;
+ td->isoc_index = i;
+ td->frame = wrap_frame(urb->start_frame + urb->interval * i);
+- td->data = urb->transfer_dma + urb->iso_frame_desc[i].offset;
++ td->dma_handle = urb->transfer_dma + offset;
++ td->cpu_buffer = urb->transfer_buffer + offset;
+ list_add_tail(&td->list, &ep_priv->td_list);
+ }
+
+@@ -812,13 +926,15 @@ static void schedule_nonisoc_etd(struct
+ if (usb_pipecontrol(pipe) && (state != US_CTRL_DATA)) {
+ if (state == US_CTRL_SETUP) {
+ dir = TD_DIR_SETUP;
++ if (unsuitable_for_dma(urb->setup_dma))
++ unmap_urb_setup_for_dma(imx21->hcd, urb);
+ etd->dma_handle = urb->setup_dma;
++ etd->cpu_buffer = urb->setup_packet;
+ bufround = 0;
+ count = 8;
+ datatoggle = TD_TOGGLE_DATA0;
+ } else { /* US_CTRL_ACK */
+ dir = usb_pipeout(pipe) ? TD_DIR_IN : TD_DIR_OUT;
+- etd->dma_handle = urb->transfer_dma;
+ bufround = 0;
+ count = 0;
+ datatoggle = TD_TOGGLE_DATA1;
+@@ -826,7 +942,11 @@ static void schedule_nonisoc_etd(struct
+ } else {
+ dir = usb_pipeout(pipe) ? TD_DIR_OUT : TD_DIR_IN;
+ bufround = (dir == TD_DIR_IN) ? 1 : 0;
++ if (unsuitable_for_dma(urb->transfer_dma))
++ unmap_urb_for_dma(imx21->hcd, urb);
++
+ etd->dma_handle = urb->transfer_dma;
++ etd->cpu_buffer = urb->transfer_buffer;
+ if (usb_pipebulk(pipe) && (state == US_BULK0))
+ count = 0;
+ else
+@@ -901,14 +1021,15 @@ static void schedule_nonisoc_etd(struct
+ /* enable the ETD to kick off transfer */
+ dev_vdbg(imx21->dev, "Activating etd %d for %d bytes %s\n",
+ etd_num, count, dir != TD_DIR_IN ? "out" : "in");
+- activate_etd(imx21, etd_num, etd->dma_handle, dir);
++ activate_etd(imx21, etd_num, dir);
+
+ }
+
+-static void nonisoc_etd_done(struct usb_hcd *hcd, struct urb *urb, int etd_num)
++static void nonisoc_etd_done(struct usb_hcd *hcd, int etd_num)
+ {
+ struct imx21 *imx21 = hcd_to_imx21(hcd);
+ struct etd_priv *etd = &imx21->etd[etd_num];
++ struct urb *urb = etd->urb;
+ u32 etd_mask = 1 << etd_num;
+ struct urb_priv *urb_priv = urb->hcpriv;
+ int dir;
+@@ -930,7 +1051,20 @@ static void nonisoc_etd_done(struct usb_
+ if (dir == TD_DIR_IN) {
+ clear_toggle_bit(imx21, USBH_XFILLSTAT, etd_mask);
+ clear_toggle_bit(imx21, USBH_YFILLSTAT, etd_mask);
++
++ if (etd->bounce_buffer) {
++ memcpy(etd->cpu_buffer, etd->bounce_buffer, bytes_xfrd);
++ dma_unmap_single(imx21->dev,
++ etd->dma_handle, etd->len, DMA_FROM_DEVICE);
++ } else if (!etd->dma_handle && bytes_xfrd) {/* PIO */
++ memcpy_fromio(etd->cpu_buffer,
++ imx21->regs + USBOTG_DMEM + etd->dmem_offset,
++ bytes_xfrd);
++ }
+ }
++
++ kfree(etd->bounce_buffer);
++ etd->bounce_buffer = NULL;
+ free_dmem(imx21, etd);
+
+ urb->error_count = 0;
+@@ -988,24 +1122,15 @@ static void nonisoc_etd_done(struct usb_
+ break;
+ }
+
+- if (!etd_done) {
++ if (etd_done)
++ nonisoc_urb_completed_for_etd(imx21, etd, cc_to_error[cc]);
++ else {
+ dev_vdbg(imx21->dev, "next state=%d\n", urb_priv->state);
+ schedule_nonisoc_etd(imx21, urb);
+- } else {
+- struct usb_host_endpoint *ep = urb->ep;
+-
+- urb_done(hcd, urb, cc_to_error[cc]);
+- etd->urb = NULL;
+-
+- if (!list_empty(&ep->urb_list)) {
+- urb = list_first_entry(&ep->urb_list,
+- struct urb, urb_list);
+- dev_vdbg(imx21->dev, "next URB %p\n", urb);
+- schedule_nonisoc_etd(imx21, urb);
+- }
+ }
+ }
+
++
+ static struct ep_priv *alloc_ep(void)
+ {
+ int i;
+@@ -1146,9 +1271,13 @@ static int imx21_hc_urb_dequeue(struct u
+ } else if (urb_priv->active) {
+ int etd_num = ep_priv->etd[0];
+ if (etd_num != -1) {
++ struct etd_priv *etd = &imx21->etd[etd_num];
++
+ disactivate_etd(imx21, etd_num);
+- free_dmem(imx21, &imx21->etd[etd_num]);
+- imx21->etd[etd_num].urb = NULL;
++ free_dmem(imx21, etd);
++ etd->urb = NULL;
++ kfree(etd->bounce_buffer);
++ etd->bounce_buffer = NULL;
+ }
+ }
+
+@@ -1248,9 +1377,9 @@ static void process_etds(struct usb_hcd
+ }
+
+ if (usb_pipeisoc(etd->urb->pipe))
+- isoc_etd_done(hcd, etd->urb, etd_num);
++ isoc_etd_done(hcd, etd_num);
+ else
+- nonisoc_etd_done(hcd, etd->urb, etd_num);
++ nonisoc_etd_done(hcd, etd_num);
+ }
+
+ /* only enable SOF interrupt if it may be needed for the kludge */
+@@ -1718,6 +1847,7 @@ static int imx21_probe(struct platform_d
+ }
+
+ imx21 = hcd_to_imx21(hcd);
++ imx21->hcd = hcd;
+ imx21->dev = &pdev->dev;
+ imx21->pdata = pdev->dev.platform_data;
+ if (!imx21->pdata)
+--- a/drivers/usb/host/imx21-hcd.h
++++ b/drivers/usb/host/imx21-hcd.h
+@@ -250,6 +250,7 @@
+ #define USBCTRL_USB_BYP (1 << 2)
+ #define USBCTRL_HOST1_TXEN_OE (1 << 1)
+
++#define USBOTG_DMEM 0x1000
+
+ /* Values in TD blocks */
+ #define TD_DIR_SETUP 0
+@@ -346,8 +347,8 @@ struct td {
+ struct list_head list;
+ struct urb *urb;
+ struct usb_host_endpoint *ep;
+- dma_addr_t data;
+- unsigned long buf_addr;
++ dma_addr_t dma_handle;
++ void *cpu_buffer;
+ int len;
+ int frame;
+ int isoc_index;
+@@ -360,6 +361,8 @@ struct etd_priv {
+ struct td *td;
+ struct list_head queue;
+ dma_addr_t dma_handle;
++ void *cpu_buffer;
++ void *bounce_buffer;
+ int alloc;
+ int len;
+ int dmem_size;
+@@ -412,6 +415,7 @@ struct debug_isoc_trace {
+ struct imx21 {
+ spinlock_t lock;
+ struct device *dev;
++ struct usb_hcd *hcd;
+ struct mx21_usbh_platform_data *pdata;
+ struct list_head dmem_list;
+ struct list_head queue_for_etd; /* eps queued due to etd shortage */