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.patch | 452 |
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 */ |
