[RFC] ARM i.MX21 Host Controller Driver

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



From: Martin Fuzzey <mfuzzey@xxxxxxxxx>

This RFC patch adds basic USB host controller support for the iMX21.
It is based on code from http://coglinux.cvs.sourceforge.net/viewvc/coglinux/coglinux/coglinux-2.6.16/

The diff is against the mxc-master branch at git://git.pengutronix.de/git/imx/linux-2.6.git
which contains mxc platform work scheduled for the mainline.

What works:
	Control transfers
	Bulk transfers
	Interrupt transfers

What doesn't work
	Isochronous transfers

What isn't implemented yet
	Configuration of ports to use via platform data.
	Shutdown.
	Power management.
	Graceful handing of ETD or data memory exhaustion.

Only basic functional tests (mounting flash drive, mouse, ..) have been done.

Signed-off-by: Martin Fuzzey <mfuzzey@xxxxxxxxx>

---
It has already been posted on the arm mailing list and deemed to be "overall
quite good" by Sascha Hauer (the mxc platform maintainer). This version includes
some modifications suggested by Sascha.

There are still a few checkpatch warnings (all about long lines)

Sparse gives a warning about locking context but the calls are correct:
	drivers/usb/host/imx21-hcd.c:620:11: warning: context problem in 'noniso_etd_done': 'urb_done' expected different context
	drivers/usb/host/imx21-hcd.c:620:11:    context 'lock': wanted >= 1, got 0
(this function is only called from process_etd_done which holds the spinlock


I'm struggling a bit to understand why the iso transfers don't work.
I've tested this with a phillips webcam (pwc driver) and get one of the folowing
behaviours (seemingly randomly)

1) etd done interrupts occur correctly (with the correct frame numbers) but
always zero bytes transferred.
2) etd done interrupts occured with status code 0x8 = data overrun and 196 bytes

Comparing with the behaviour on a intel system I see that there are also around
100 empty transfers before the camera starts sending data. Then I get 192 byte
packets. In both cases the maxpacket size and buffer size is 196 bytes.

Unfortunately I don't have a USB analyser to see what's on the wire.

Case 2) seems very strange to me - how can more data be sent than expected?
I have run usbmon traces on both the mx21 and intel systems and the camera
setup commands are the same.

I also wonder how I should handle the case where iso scheduling arrives too late
currently I just log a message - is this an error condition or should I
recalculate the frame numbers?


diff --git a/arch/arm/mach-mx2/clock_imx21.c b/arch/arm/mach-mx2/clock_imx21.c
index 2dee5c8..e92dce9 100644
--- a/arch/arm/mach-mx2/clock_imx21.c
+++ b/arch/arm/mach-mx2/clock_imx21.c
@@ -48,6 +48,25 @@ static void _clk_disable(struct clk *clk)
 	__raw_writel(reg, clk->enable_reg);
 }

+static unsigned long _clk_generic_round_rate(struct clk *clk,
+			unsigned long rate,
+			u32 max_divisor)
+{
+	u32 div;
+	unsigned long parent_rate;
+
+	parent_rate = clk_get_rate(clk->parent);
+
+	div = parent_rate / rate;
+	if (parent_rate % rate)
+		div++;
+
+	if (div > max_divisor)
+		div = max_divisor;
+
+	return parent_rate / div;
+}
+
 static int _clk_spll_enable(struct clk *clk)
 {
 	u32 reg;
@@ -78,19 +97,7 @@ static void _clk_spll_disable(struct clk *clk)
 static unsigned long _clk_perclkx_round_rate(struct clk *clk,
 					     unsigned long rate)
 {
-	u32 div;
-	unsigned long parent_rate;
-
-	parent_rate = clk_get_rate(clk->parent);
-
-	div = parent_rate / rate;
-	if (parent_rate % rate)
-		div++;
-
-	if (div > 64)
-		div = 64;
-
-	return parent_rate / div;
+	return _clk_generic_round_rate(clk, rate, 64);
 }

 static int _clk_perclkx_set_rate(struct clk *clk, unsigned long rate)
@@ -130,6 +137,33 @@ static unsigned long _clk_usb_recalc(struct clk *clk)
 	return parent_rate / (usb_pdf + 1U);
 }

+static unsigned long _clk_usb_round_rate(struct clk *clk,
+					     unsigned long rate)
+{
+	return _clk_generic_round_rate(clk, rate, 8);
+}
+
+static int _clk_usb_set_rate(struct clk *clk, unsigned long rate)
+{
+	u32 reg;
+	u32 div;
+	unsigned long parent_rate;
+
+	parent_rate = clk_get_rate(clk->parent);
+
+	div = parent_rate / rate;
+
+	if (div > 8 || div < 1 || ((parent_rate / div) != rate))
+		return -EINVAL;
+	div--;
+
+	reg = CSCR() & ~CCM_CSCR_USB_MASK;
+	reg |= div << CCM_CSCR_USB_OFFSET;
+	__raw_writel(reg, CCM_CSCR);
+
+	return 0;
+}
+
 static unsigned long _clk_ssix_recalc(struct clk *clk, unsigned long pdf)
 {
 	unsigned long parent_rate;
@@ -595,11 +629,14 @@ static struct clk csi_clk[] = {
 static struct clk usb_clk[] = {
 	{
 		.parent = &spll_clk,
+		.secondary = &usb_clk[1],
 		.get_rate = _clk_usb_recalc,
 		.enable = _clk_enable,
 		.enable_reg = CCM_PCCR_USBOTG_REG,
 		.enable_shift = CCM_PCCR_USBOTG_OFFSET,
 		.disable = _clk_disable,
+		.round_rate = _clk_usb_round_rate,
+		.set_rate = _clk_usb_set_rate,
 	}, {
 		.parent = &hclk_clk,
 		.enable = _clk_enable,
@@ -768,18 +805,7 @@ static struct clk rtc_clk = {

 static unsigned long _clk_clko_round_rate(struct clk *clk, unsigned long rate)
 {
-	u32 div;
-	unsigned long parent_rate;
-
-	parent_rate = clk_get_rate(clk->parent);
-	div = parent_rate / rate;
-	if (parent_rate % rate)
-		div++;
-
-	if (div > 8)
-		div = 8;
-
-	return parent_rate / div;
+	return _clk_generic_round_rate(clk, rate, 8);
 }

 static int _clk_clko_set_rate(struct clk *clk, unsigned long rate)
@@ -921,7 +947,7 @@ static struct clk_lookup lookups[] __initdata = {
 	_REGISTER_CLOCK(NULL, "cspi3", cspi_clk[2])
 	_REGISTER_CLOCK(NULL, "lcdc", lcdc_clk[0])
 	_REGISTER_CLOCK(NULL, "csi", csi_clk[0])
-	_REGISTER_CLOCK(NULL, "usb", usb_clk[0])
+	_REGISTER_CLOCK("imx21-hc.0", NULL, usb_clk[0])
 	_REGISTER_CLOCK(NULL, "ssi1", ssi_clk[0])
 	_REGISTER_CLOCK(NULL, "ssi2", ssi_clk[1])
 	_REGISTER_CLOCK(NULL, "nfc", nfc_clk)
diff --git a/arch/arm/mach-mx2/devices.c b/arch/arm/mach-mx2/devices.c
index 7d12c2c..824edc1 100644
--- a/arch/arm/mach-mx2/devices.c
+++ b/arch/arm/mach-mx2/devices.c
@@ -382,3 +382,32 @@ int __init mxc_register_gpios(void)
 {
 	return mxc_gpio_init(imx_gpio_ports, ARRAY_SIZE(imx_gpio_ports));
 }
+
+
+#ifdef CONFIG_USB_IMX21_HCD
+static struct resource mx21_usbhc_resources[] = {
+	[0] = {
+		.start	= USBOTG_BASE_ADDR,
+		.end	= USBOTG_BASE_ADDR + 0x1FFF,
+		.flags	= IORESOURCE_MEM,
+	},
+	[1] = {
+		.start		= MXC_INT_USBHOST,
+		.end		= MXC_INT_USBHOST,
+		.flags		= IORESOURCE_IRQ,
+	},
+};
+
+struct platform_device mx21_usbhc_device = {
+	.name		= "imx21-hc",
+	.id		= 0,
+	.dev		= {
+		.dma_mask = &mx21_usbhc_device.dev.coherent_dma_mask,
+		.coherent_dma_mask = 0xffffffff,
+	},
+	.num_resources	= ARRAY_SIZE(mx21_usbhc_resources),
+	.resource	= mx21_usbhc_resources,
+};
+
+#endif
+
diff --git a/arch/arm/mach-mx2/devices.h b/arch/arm/mach-mx2/devices.h
index 271ab1e..c84b58f 100644
--- a/arch/arm/mach-mx2/devices.h
+++ b/arch/arm/mach-mx2/devices.h
@@ -18,3 +18,4 @@ extern struct platform_device mxc_fec_device;
 extern struct platform_device mxc_pwm_device;
 extern struct platform_device mxc_i2c_device0;
 extern struct platform_device mxc_i2c_device1;
+extern struct platform_device mx21_usbhc_device;
diff --git a/drivers/usb/Makefile b/drivers/usb/Makefile
index b2ceb4a..11c5da3 100644
--- a/drivers/usb/Makefile
+++ b/drivers/usb/Makefile
@@ -19,6 +19,7 @@ obj-$(CONFIG_USB_SL811_HCD)	+= host/
 obj-$(CONFIG_USB_U132_HCD)	+= host/
 obj-$(CONFIG_USB_R8A66597_HCD)	+= host/
 obj-$(CONFIG_USB_HWA_HCD)	+= host/
+obj-$(CONFIG_USB_IMX21_HCD) += host/

 obj-$(CONFIG_USB_C67X00_HCD)	+= c67x00/

diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig
index 2c63bfb..fff85ad 100644
--- a/drivers/usb/host/Kconfig
+++ b/drivers/usb/host/Kconfig
@@ -352,3 +352,15 @@ config USB_HWA_HCD

 	  To compile this driver a module, choose M here: the module
 	  will be called "hwa-hc".
+
+config USB_IMX21_HCD
+	tristate "iMX21 HCD support (EXPERIMENTAL)"
+	depends on EXPERIMENTAL
+	depends on USB && ARM && MACH_MX21
+	help
+	  This driver enables support for the on-chip USB host in the
+	  iMX21 processor.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called imx21-hcd.
+
diff --git a/drivers/usb/host/Makefile b/drivers/usb/host/Makefile
index f163571..e7466e1 100644
--- a/drivers/usb/host/Makefile
+++ b/drivers/usb/host/Makefile
@@ -29,3 +29,5 @@ obj-$(CONFIG_USB_U132_HCD)	+= u132-hcd.o
 obj-$(CONFIG_USB_R8A66597_HCD)	+= r8a66597-hcd.o
 obj-$(CONFIG_USB_ISP1760_HCD)	+= isp1760.o
 obj-$(CONFIG_USB_HWA_HCD)	+= hwa-hc.o
+obj-$(CONFIG_USB_IMX21_HCD)	+= imx21-hcd.o
+
diff --git a/drivers/usb/host/imx21-hcd.c b/drivers/usb/host/imx21-hcd.c
new file mode 100644
index 0000000..f5ef299
--- /dev/null
+++ b/drivers/usb/host/imx21-hcd.c
@@ -0,0 +1,1518 @@
+/*
+ * USB Host Controller Driver for IMX21
+ *
+ * Copyright (C) 2006 Loping Dog Embedded Systems
+ * Copyright (C) 2009 Martin Fuzzey
+ * Originally written by Jay Monkman <jtm@xxxxxxxxxxxxx>
+ * Ported to 2.6.29 by Martin Fuzzey
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+ /* MF TODO
+ Make ISO work!!
+ Look at hub stuff
+ Examine queueing for failed submissions due to no etd memory.
+ Port flags in platform device
+ */
+
+#include <linux/platform_device.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/kernel.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/ioport.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/timer.h>
+#include <linux/list.h>
+#include <linux/interrupt.h>
+#include <linux/usb.h>
+#include <linux/dma-mapping.h>
+#include <linux/clk.h>
+
+#include <mach/hardware.h>
+
+#include "../core/hcd.h"
+#include "imx21-hcd.h"
+
+
+#define DMEM_SIZE   4096
+
+
+static const char hcd_name[] = "imx21-hc";
+
+static inline void set_register_bits(struct imx21 *imx21, u32 offset, u32 mask)
+{
+	void __iomem *reg = imx21->regs + offset;
+	writel(readl(reg) | mask, reg);
+}
+
+static inline void clear_register_bits(struct imx21 *imx21, u32 offset, u32 mask)
+{
+	void __iomem *reg = imx21->regs + offset;
+	writel(readl(reg) & ~mask, reg);
+}
+
+static inline void clear_toggle_bit(struct imx21 *imx21, u32 offset, u32 mask)
+{
+	void __iomem *reg = imx21->regs + offset;
+
+	if (readl(reg) & mask)
+		writel(mask, reg);
+}
+
+static inline void set_toggle_bit(struct imx21 *imx21, u32 offset, u32 mask)
+{
+	void __iomem *reg = imx21->regs + offset;
+
+	if (!(readl(reg) & mask))
+		writel(mask, reg);
+}
+
+static void etd_writel(struct imx21 *imx21, int etd_num, int dword, u32 value)
+{
+	writel(value, imx21->regs + USB_ETD_DWORD(etd_num, dword));
+}
+
+static u32 etd_readl(struct imx21 *imx21, int etd_num, int dword)
+{
+	return readl(imx21->regs + USB_ETD_DWORD(etd_num, dword));
+}
+
+static int imx21_hc_get_frame(struct usb_hcd *hcd)
+{
+	struct imx21 *imx21 = hcd_to_imx21(hcd);
+
+	return readl(imx21->regs + USBH_FRMNUB) & 0xFFFF;
+}
+
+
+static int alloc_etd(struct imx21 *imx21)
+{
+	int i;
+
+	for (i = 0; i < USB_NUM_ETD; i++) {
+		if (imx21->etd[i].alloc == 0) {
+			imx21->etd[i].alloc = 1;
+			return i;
+		}
+	}
+	dev_err(imx21->dev, "All ETDs are busy!\n");
+	return -ENODEV;
+}
+
+static void free_etd(struct imx21 *imx21, int num)
+{
+	struct etd_priv *etd;
+	int etd_mask = (1 << num);
+	int i;
+
+	if (num < 0)
+		return;
+
+	if (num >= USB_NUM_ETD) {
+		dev_err(imx21->dev, "BAD etd=%d!\n", num);
+		return;
+	}
+	if (imx21->etd[num].alloc == 0) {
+		dev_err(imx21->dev, "ETD %d already free!\n", num);
+		return;
+	}
+
+	etd = imx21->etd + num;
+	writel(etd_mask, imx21->regs + USBH_ETDENCLR);
+	clear_register_bits(imx21, USBH_ETDDONEEN, etd_mask);
+	writel(etd_mask, imx21->regs + USB_ETDDMACHANLCLR);
+	writel(0, imx21->regs + USB_ETDSMSA(num));
+	for (i = 0; i < 4; i++)
+		etd_writel(imx21, num, i, 0);
+	memset(etd, 0, sizeof(struct etd_priv));
+}
+
+static int alloc_dmem(struct imx21 *imx21, unsigned int size,
+		      struct usb_host_endpoint *ep)
+{
+	unsigned int offset = 0;
+	struct imx21_dmem_area *area;
+	struct imx21_dmem_area *tmp;
+
+	size += (~size + 1) & 0x3; /* Round to 4 byte multiple */
+
+	if (size > DMEM_SIZE) {
+		dev_err(imx21->dev, "size=%d > DMEM_SIZE(%d)\n",
+			size, DMEM_SIZE);
+		return -ENOMEM;
+	}
+
+	list_for_each_entry(tmp, &imx21->dmem_list.list, list) {
+		if ((size + offset) < offset)
+			goto fail;
+		if ((size + offset) <= tmp->offset)
+			break;
+		offset = tmp->size + tmp->offset;
+		if ((offset + size) > DMEM_SIZE)
+			goto fail;
+	}
+
+	area = kmalloc(sizeof(struct imx21_dmem_area), GFP_ATOMIC);
+	if (area == NULL)
+		return -ENOMEM;
+
+	area->ep = ep;
+	area->offset = offset;
+	area->size = size;
+	list_add_tail(&area->list, &tmp->list);
+	return offset;
+
+fail:
+	dev_err(imx21->dev, "no DMEM memory!\n");
+	return -ENOMEM;
+}
+
+static void free_dmem(struct imx21 *imx21, int offset)
+{
+	struct imx21_dmem_area *tmp;
+
+	list_for_each_entry(tmp, &imx21->dmem_list.list, list) {
+		if (tmp->offset == offset) {
+			list_del(&tmp->list);
+			kfree(tmp);
+			return;
+		}
+	}
+	dev_err(imx21->dev, "Trying to free unallocated DMEM %d\n", offset);
+}
+
+static void free_epdmem(struct imx21 *imx21, struct usb_host_endpoint *ep)
+{
+	struct imx21_dmem_area *tmp, *tmp1;
+
+	list_for_each_entry_safe(tmp, tmp1, &imx21->dmem_list.list, list) {
+		if (tmp->ep == ep) {
+			dev_err(imx21->dev,
+				"Active DMEM %d for disabled ep=%p\n",
+				tmp->offset, ep);
+			list_del(&tmp->list);
+			kfree(tmp);
+		}
+	}
+}
+
+static int get_hub_descriptor(struct usb_hcd *hcd,
+			      struct usb_hub_descriptor *desc)
+{
+	struct imx21 *imx21 = hcd_to_imx21(hcd);
+	desc->bDescriptorType = 0x29;	/* HUB descriptor */
+	desc->bHubContrCurrent = 0;
+
+	desc->bNbrPorts = readl(imx21->regs + USBH_ROOTHUBA) & USBH_ROOTHUBA_NDNSTMPRT_MASK;
+	desc->bDescLength = 9;
+	desc->bPwrOn2PwrGood = 0;
+	desc->wHubCharacteristics = (__force __u16) cpu_to_le16(
+		0x0002 |	/* No power switching */
+		0x0010 |	/* No over current protection */
+		0);
+
+	desc->bitmap[0] = 1 << 1;
+	desc->bitmap[1] = ~0;
+	return 0;
+}
+
+static void urb_done(struct usb_hcd *hcd, struct urb *urb, int status)
+__releases(imx21->lock)
+__acquires(imx21->lock)
+{
+	struct imx21 *imx21 = hcd_to_imx21(hcd);
+
+	dev_dbg(imx21->dev, "urb %p done %d\n", urb, status);
+	kfree(urb->hcpriv);
+	urb->hcpriv = NULL;
+	usb_hcd_unlink_urb_from_ep(hcd, urb);
+	spin_unlock(&imx21->lock);
+	usb_hcd_giveback_urb(hcd, urb, status);
+	spin_lock(&imx21->lock);
+}
+
+static void schedule_iso_etds(struct usb_hcd *hcd, struct usb_host_endpoint *ep)
+{
+	struct imx21 *imx21 = hcd_to_imx21(hcd);
+	struct ep_priv *ep_priv = ep->hcpriv;
+	struct etd_priv *etd;
+	int etd_num;
+	int etd_mask;
+	struct td *td;
+	int i;
+	int maxpacket;
+	struct urb_priv *urb_priv;
+	int cur_frame;
+
+	for (i = 0; i < NUM_ISO_ETDS; i++) {
+		if (list_empty(&ep_priv->td_list))
+			break;
+
+		etd_num = ep_priv->etd[i];
+		if (etd_num < 0)
+			break;
+
+		etd = &imx21->etd[etd_num];
+		if (etd->busy)
+			continue;
+
+		td = list_entry(ep_priv->td_list.next, struct td, list);
+		maxpacket = usb_maxpacket(td->urb->dev, td->urb->pipe,
+					  usb_pipeout(td->urb->pipe));
+		td->buf_addr = alloc_dmem(imx21, maxpacket, ep);
+		if (td->buf_addr < 0)
+			return;
+
+		list_del(&td->list);
+		urb_priv = td->urb->hcpriv;
+		if (!urb_priv) {
+			dev_err(imx21->dev,
+				"urb_priv NULL for iso urb=%p ep=%p ep_priv=%p td=%p!\n",
+				td->urb, ep, ep_priv, td);
+			continue;
+		}
+		urb_priv->active = 1;
+
+		etd->busy = 1;
+		etd->td = td;
+		etd->ep = td->ep;
+		etd->urb = td->urb;
+		etd->len = td->len;
+
+		cur_frame = imx21_hc_get_frame(hcd);
+		if (td->frame == cur_frame)
+			dev_dbg(imx21->dev, "submit iso same frame! subUrb=%d\n",
+				imx21->dbg_iso_urb_cnt);
+		if (td->frame < cur_frame)
+			dev_dbg(imx21->dev, "submit iso past frame %d > %d subUrb=%d\n",
+				cur_frame, td->frame,
+				imx21->dbg_iso_urb_cnt);
+
+		etd_mask = 1 << etd_num;
+		etd_writel(imx21, etd_num, 0,
+			((u32) usb_pipedevice(td->urb->pipe)) |
+			((u32) usb_pipeendpoint(td->urb->pipe) << 7) |
+			((u32) (usb_pipeout(td->urb->pipe) ? TD_DIR_OUT : TD_DIR_IN) << 11) |
+			((u32) ((td->urb->dev->speed == USB_SPEED_LOW) ? 1 : 0) << 13) |
+			((u32)  IMX_FMT_ISO << 14) |
+			((u32) maxpacket << 16));
+
+		etd_writel(imx21, etd_num, 1, td->buf_addr); /* Only X buf  */
+		etd_writel(imx21, etd_num, 2,
+			(0xf << 28) |	/* clear completion code bits */
+			((td->frame & 0xFFFF) << 0));
+		etd_writel(imx21, etd_num, 3,
+			(0xf << 12) |	/* clear completion code bits */
+			(td->len << 0));
+
+
+		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);
+		set_register_bits(imx21, USB_ETDDMACHANLCLR, etd_mask);
+		if (usb_pipeout(td->urb->pipe)) {
+			set_toggle_bit(imx21, USBH_XFILLSTAT, etd_mask);
+			set_toggle_bit(imx21, USBH_YFILLSTAT, etd_mask);
+		}
+		clear_toggle_bit(imx21, USBH_XBUFSTAT, etd_mask);
+		clear_toggle_bit(imx21, USBH_YBUFSTAT, etd_mask);
+
+		writel(td->data, imx21->regs + USB_ETDSMSA(etd_num));
+		set_register_bits(imx21, USB_ETDDMAEN, etd_mask);
+		set_register_bits(imx21, USBH_ETDENSET, etd_mask);
+	}
+}
+
+/***************************************************************************
+ * Setup a bulk, control or interrupt ETD. Configure DMA if necessary.
+ * Trigger the processing of the ETD by the host controller. Return error
+ * and not do anything in case there are not enough resources.
+ **************************************************************************/
+static int schedule_noniso_etd(struct imx21 *imx21, struct urb *urb, int state)
+{
+	unsigned int pipe = urb->pipe;
+	struct urb_priv *urb_priv = urb->hcpriv;
+	struct ep_priv *ep_priv = urb_priv->ep->hcpriv;
+	int etd_num = ep_priv->etd_num;
+	struct etd_priv *etd;
+	u32 etd_mask;
+	int dmem_offset;
+	u16 buf_size;
+	u16 etd_buf_size;
+	u32 count;
+	u8 dir;
+	u8 bufround;
+	u8 datatoggle;
+	u16 maxpacket;
+	u8 interval = 0;
+	u8 relpolpos = 0;
+
+	if (etd_num < 0) {
+		dev_err(imx21->dev, "No valid ETD\n");
+		return etd_num;
+	}
+
+	etd = &imx21->etd[etd_num];
+	maxpacket = usb_maxpacket(urb->dev, pipe, usb_pipeout(pipe));
+	if (!maxpacket)
+		maxpacket = 8;
+
+	if (usb_pipecontrol(pipe) && (state != US_CTRL_DATA)) {
+		if (state == US_CTRL_SETUP) {
+			dir = TD_DIR_SETUP;
+			etd->dma_handle = urb->setup_dma;
+			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;
+		}
+	} else {
+		dir = usb_pipeout(pipe) ? TD_DIR_OUT : TD_DIR_IN;
+		bufround = (dir == TD_DIR_IN) ? 1 : 0;
+		etd->dma_handle = urb->transfer_dma;
+		if (usb_pipebulk(pipe) && (state == US_BULK0))
+			count = 0;
+		else
+			count = urb->transfer_buffer_length;
+
+		if (usb_pipecontrol(pipe)) {
+			datatoggle = TD_TOGGLE_DATA1;
+		} else {
+			if (usb_gettoggle(
+					urb->dev,
+					usb_pipeendpoint(urb->pipe),
+					usb_pipeout(urb->pipe)))
+				datatoggle = TD_TOGGLE_DATA1;
+			else
+				datatoggle = TD_TOGGLE_DATA0;
+		}
+	}
+
+	if (count > maxpacket)
+		buf_size = maxpacket * 2;
+	else
+		buf_size = maxpacket;	/*MF: count? */
+
+	/* allocate x and y buffer space at once */
+	dmem_offset = alloc_dmem(imx21, buf_size, urb_priv->ep);
+	if (dmem_offset < 0) {
+		dev_err(imx21->dev, "Cannot allocate DMEM for non-iso\n");
+		return dmem_offset;
+	}
+
+	/* cannot fail from here: commit etd to urb/ep */
+	urb_priv->active = 1;
+	etd->busy = 1;
+	etd->urb = urb;
+	etd->ep = urb_priv->ep;
+	etd->len = count;
+
+	if (usb_pipeint(pipe)) {
+		interval = urb->interval;
+		relpolpos = (readl(imx21->regs + USBH_FRMNUB) + 1) & 0xff;
+	}
+
+	/* Write ETD to device memory */
+	etd_writel(imx21, etd_num, 0,
+		(u32) usb_pipedevice(pipe) |
+		((u32) usb_pipeendpoint(pipe) << 7) |
+		((u32) dir << 11) |
+		((u32) ((urb->dev->speed == USB_SPEED_LOW) ? 1 : 0) << 13) |
+		((u32) fmt_urb_to_etd[usb_pipetype(pipe)] << 14) |
+		((u32) maxpacket << 16));
+
+	etd_writel(imx21, etd_num, 1,
+	    (((u32) dmem_offset + (u32) maxpacket) << 16) | (u32) dmem_offset);
+
+	/*delay interrupt option is disabled (bit 19-21 == 0) */
+	etd_writel(imx21, etd_num, 2,
+		(u32) interval |
+		((u32) relpolpos << 8) |
+		((u32) dir << 16) |
+		((u32) bufround << 18) |
+		((u32) datatoggle << 22) |
+		((u32) 0xf << 28)); /*reset completion code */
+
+	/* DMA will always transfer buffer size even if TOBYCNT in DWORD3
+	   is smaller. Make sure we don't overrun the buffer!
+	 */
+	if (count && count < maxpacket)
+		etd_buf_size = count;
+	else
+		etd_buf_size = maxpacket;
+
+	etd_writel(imx21, etd_num, 3,
+		((u32) (etd_buf_size - 1) << 21) | (u32) count);
+
+	etd_mask = 1 << etd_num;
+
+	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 (count == 0) {
+		if (dir != TD_DIR_IN) {
+			/*need to set it even if count == 0 */
+			set_toggle_bit(imx21, USBH_XFILLSTAT, etd_mask);
+			set_toggle_bit(imx21, USBH_YFILLSTAT, etd_mask);
+		}
+		writel(0, imx21->regs + USB_ETDSMSA(etd_num));
+	} else {
+		/* enable DMA */
+		set_toggle_bit(imx21, USB_ETDDMACHANLCLR, etd_mask);
+		clear_toggle_bit(imx21, USBH_XBUFSTAT, etd_mask);
+		clear_toggle_bit(imx21, USBH_YBUFSTAT, etd_mask);
+		writel(etd->dma_handle, imx21->regs + USB_ETDSMSA(etd_num));
+		set_register_bits(imx21, USB_ETDDMAEN, etd_mask);
+
+	}
+
+	/* enable the ETD to kick off transfer */
+	dev_dbg(imx21->dev, "Activating etd %d for %d bytes %s\n",
+		etd_num, count, dir != TD_DIR_IN ? "out" : "in");
+	writel(etd_mask, imx21->regs + USBH_ETDENSET);
+	return 0;
+}
+
+/***************************************************************************
+ * If the ETD is done, parse all status data.
+ * Report number of bytes actually transferred and completion code
+ **************************************************************************/
+static void noniso_etd_done(struct usb_hcd *hcd, struct urb *urb, int etd_num)
+{
+	struct imx21 *imx21 = hcd_to_imx21(hcd);
+	struct etd_priv *etd = &imx21->etd[etd_num];
+	u32 etd_mask = 1 << etd_num;
+	struct urb_priv *urb_priv = urb->hcpriv;
+	int dir;
+	u16 xbufaddr;
+	int cc;
+	u32 bytes_xfrd;
+	int etd_done;
+
+	writel(etd_mask, imx21->regs + USBH_ETDENCLR);
+	clear_toggle_bit(imx21, USBH_ETDDONESTAT, etd_mask);
+	set_toggle_bit(imx21, USB_ETDDMACHANLCLR, etd_mask);
+
+
+	dir = (etd_readl(imx21, etd_num, 0) >> 11) & 0x3;
+	xbufaddr = etd_readl(imx21, etd_num, 1) & 0xffff;
+	cc = (etd_readl(imx21, etd_num, 2) >> 28) & 0xf;
+	bytes_xfrd = etd->len - (etd_readl(imx21, etd_num, 3) & 0x1fffff);
+
+	/* save toggle carry */
+	usb_settoggle(urb->dev, usb_pipeendpoint(urb->pipe),
+		      usb_pipeout(urb->pipe),
+		      (etd_readl(imx21, etd_num, 0) >> 28) & 0x1);
+
+	if (dir == TD_DIR_IN) {
+		clear_toggle_bit(imx21, USBH_XFILLSTAT, etd_mask);
+		clear_toggle_bit(imx21, USBH_YFILLSTAT, etd_mask);
+	}
+
+	free_dmem(imx21, xbufaddr);
+	etd->urb = NULL;
+
+	urb->error_count = 0;
+	if (!(urb->transfer_flags & URB_SHORT_NOT_OK) && (cc == TD_DATAUNDERRUN))
+		cc = TD_CC_NOERROR;
+	if (cc != 0)
+		dev_dbg(imx21->dev, "cc is 0x%x\n", cc);
+
+	etd_done = (cc_to_error[cc] != 0);	/*stop if error */
+
+	switch (usb_pipetype(urb->pipe)) {
+	case PIPE_CONTROL:
+		switch (urb_priv->state) {
+		case US_CTRL_SETUP:
+			if (urb->transfer_buffer_length > 0)
+				urb_priv->state = US_CTRL_DATA;
+			else
+				urb_priv->state = US_CTRL_ACK;
+			break;
+		case US_CTRL_DATA:
+			urb->actual_length += bytes_xfrd;
+			urb_priv->state = US_CTRL_ACK;
+			break;
+		case US_CTRL_ACK:
+			etd_done = 1;
+			break;
+		default:
+			dev_err(imx21->dev,
+				"Invalid pipe state %d\n", urb_priv->state);
+			etd_done = 1;
+			break;
+		}
+		break;
+
+	case PIPE_BULK:
+		urb->actual_length += bytes_xfrd;
+		if ((urb_priv->state == US_BULK)
+		    && (urb->transfer_flags & URB_ZERO_PACKET)
+		    && urb->transfer_buffer_length > 0
+		    && ((urb->transfer_buffer_length %
+			 usb_maxpacket(urb->dev, urb->pipe,
+				       usb_pipeout(urb->pipe))) == 0)) {
+			/*need a 0-packet */
+			urb_priv->state = US_BULK0;
+		} else {
+			etd_done = 1;
+		}
+		break;
+
+	case PIPE_INTERRUPT:
+		urb->actual_length += bytes_xfrd;
+		etd_done = 1;
+		break;
+	}
+
+	if (!etd_done) {
+		dev_dbg(imx21->dev, "Continue ETD state=%d\n", urb_priv->state);
+		if (schedule_noniso_etd(imx21, urb, urb_priv->state) < 0) {
+			/*requeue in case of res. shortage MF: todo look at this
+			// list_add(&urb_priv->list, &imx21->ctrl_list); */
+		}
+	} else {
+		struct usb_host_endpoint *ep = urb_priv->ep;
+		struct ep_priv *ep_priv;
+
+		if (!ep) {
+			dev_err(imx21->dev,
+				"MF: no ep for urb %p urb_priv=%p\n",
+				urb, urb_priv);
+			return;
+		}
+		ep_priv = ep->hcpriv;
+
+		dev_dbg(imx21->dev, "etd completed\n");
+		urb_done(hcd, urb, cc_to_error[cc]);
+
+		if (list_empty(&ep->urb_list)) {
+			dev_dbg(imx21->dev, "no more urbs for ep\n");
+			free_etd(imx21, ep_priv->etd_num);
+			ep_priv->etd_num = -1;
+		} else {
+			/* Process next URB */
+			dev_dbg(imx21->dev, "more urbs for ep\n");
+			urb =
+			    list_entry(ep->urb_list.next, struct urb, urb_list);
+			urb_priv = urb->hcpriv;
+			if (urb_priv != NULL) {
+				dev_dbg(imx21->dev, "Next URB\n");
+				schedule_noniso_etd(imx21, urb, urb_priv->state);
+			} else {
+				dev_warn(imx21->dev, "free etd no urb_priv\n");
+				free_etd(imx21, ep_priv->etd_num);
+				ep_priv->etd_num = -1;
+			}
+		}
+	}
+}
+
+static void iso_etd_done(struct usb_hcd *hcd, struct urb *urb, 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 td *td = etd->td;
+	struct usb_host_endpoint *ep = etd->ep;
+	int iso_index = td->iso_index;
+	unsigned int pipe = urb->pipe;
+	int dir_in = usb_pipein(pipe);
+	int cc;
+	int bytes_xfrd;
+
+	writel(etd_mask, imx21->regs + USBH_ETDENCLR);
+	clear_toggle_bit(imx21, USBH_ETDDONESTAT, etd_mask);
+	set_toggle_bit(imx21, USB_ETDDMACHANLCLR, etd_mask);
+
+	cc = (etd_readl(imx21, etd_num, 3) >> 12) & 0xf;
+	bytes_xfrd = etd_readl(imx21, etd_num, 3) & 0x3ff;
+
+	/* Input doesn't always fill the buffer, don't generate an error
+	 * when this happens.
+	 */
+	if (dir_in && (cc == TD_DATAUNDERRUN))
+		cc = TD_CC_NOERROR;
+
+	imx21->dbg_iso_done_cnt++;
+	if (bytes_xfrd)
+		dev_dbg(imx21->dev, "iso%d done xferd=%d f=0x%X ef=0x%X cc=%d idx=%d cnt=%d\n",
+			etd_num,
+			bytes_xfrd,
+			imx21_hc_get_frame(hcd),
+			td->frame,
+			cc,
+			td->iso_index,
+			imx21->dbg_iso_done_cnt
+		);
+
+	if (cc) {
+		dev_dbg(imx21->dev,
+			"bad iso cc=0x%X cnt=%d urb=%p etd=%d index=%d td=%p\n",
+			cc, bytes_xfrd, urb, etd_num, iso_index, td);
+	}
+
+	if (dir_in)
+		clear_toggle_bit(imx21, USBH_XFILLSTAT, etd_mask);
+
+	urb->actual_length += bytes_xfrd;
+	urb->iso_frame_desc[iso_index].actual_length = bytes_xfrd;
+	urb->iso_frame_desc[iso_index].status = cc_to_error[cc];
+
+	free_dmem(imx21, td->buf_addr);
+	etd->busy = 0;
+	etd->td = NULL;
+	etd->urb = NULL;
+	etd->ep = NULL;
+
+	kfree(td);
+	if (--urb_priv->iso_remaining == 0)
+		urb_done(hcd, urb, cc_to_error[cc]);
+
+	schedule_iso_etds(hcd, ep);
+}
+
+/***************************************************************************
+ * Part of the Host Controller interrupt handler.
+ * Scan through all ETD done states. Submit another URB transaction if
+ * necessary, or report URB completion.
+ **************************************************************************/
+static void process_etd_done(struct usb_hcd *hcd, struct imx21 *imx21)
+{
+	int etd_num;
+	unsigned long flags;
+
+	spin_lock_irqsave(&imx21->lock, flags);
+	for (etd_num = 0; etd_num < USB_NUM_ETD; etd_num++) {
+		u32 etd_mask = 1 << etd_num;
+		struct urb *urb = imx21->etd[etd_num].urb;
+
+		if (!(readl(imx21->regs + USBH_ETDDONESTAT) & etd_mask))
+			continue;
+
+		if (imx21->etd[etd_num].ep == NULL || urb == NULL) {
+			dev_warn(imx21->dev,
+				"Interrupt for unexpected etd %d\n", etd_num);
+			writel(etd_mask, imx21->regs + USBH_ETDENCLR);
+			clear_register_bits(imx21, USBH_ETDDONEEN, etd_mask);
+			clear_toggle_bit(imx21, USBH_ETDDONESTAT, etd_mask);
+			set_toggle_bit(imx21, USB_ETDDMACHANLCLR, etd_mask);
+			continue;
+		}
+		if (usb_pipeisoc(urb->pipe))
+			iso_etd_done(hcd, urb, etd_num);
+		else
+			noniso_etd_done(hcd, urb, etd_num);
+	}
+	spin_unlock_irqrestore(&imx21->lock, flags);
+}
+
+static irqreturn_t imx21_irq(struct usb_hcd *hcd)
+{
+	struct imx21 *imx21 = hcd_to_imx21(hcd);
+	u32 ints = readl(imx21->regs + USBH_SYSISR);
+
+	if (ints & USBH_SYSISR_DONEINT)
+		process_etd_done(hcd, imx21);
+	writel(ints, imx21->regs + USBH_SYSISR);
+	return IRQ_HANDLED;
+}
+
+static void imx21_hc_stop(struct usb_hcd *hcd)
+{
+	struct imx21 *imx21 = hcd_to_imx21(hcd);
+
+	dev_err(imx21->dev, "%s NOT IMPLEMENTED YET\n", __func__);
+}
+
+static int imx21_hc_reset(struct usb_hcd *hcd)
+{
+	struct imx21 *imx21 = hcd_to_imx21(hcd);
+
+	dev_err(imx21->dev, "%s NOT IMPLEMENTED YET\n", __func__);
+	return 0;
+}
+
+static int __devinit imx21_hc_start(struct usb_hcd *hcd)
+{
+	struct usb_device *udev;
+	struct imx21 *imx21 = hcd_to_imx21(hcd);
+
+	udev = usb_alloc_dev(NULL, &hcd->self, 0);
+	if (!udev)
+		return -ENOMEM;
+
+	udev->speed = USB_SPEED_FULL;
+	hcd->state = HC_STATE_RUNNING;
+
+	/* Enable host controller interrupts */
+	set_register_bits(imx21, USBOTG_CINT_STEN, USBOTG_HCINT);
+
+	return 0;
+}
+
+static struct ep_priv *alloc_isoc_ep(
+	struct imx21 *imx21, struct usb_host_endpoint *ep, gfp_t mem_flags)
+{
+	struct ep_priv *ep_priv;
+	int i;
+	unsigned long flags;
+
+	ep_priv = kzalloc(sizeof(struct ep_priv), mem_flags);
+	if (ep_priv == NULL)
+		return NULL;
+
+	dev_dbg(imx21->dev, "ep=%p, ep->hcpriv=%p, new ep_priv=%p\n",
+		ep, ep->hcpriv, ep_priv);
+
+	/* Allocate the ETDs */
+	spin_lock_irqsave(&imx21->lock, flags);
+	for (i = 0; i < NUM_ISO_ETDS; i++) {
+		ep_priv->etd[i] = alloc_etd(imx21);
+		if (ep_priv->etd[i] < 0) {
+			int j;
+			dev_err(imx21->dev, "isoc: Couldn't allocate etd\n");
+			for (j = 0; j < i; j++)
+				free_etd(imx21, ep_priv->etd[j]);
+			goto alloc_etd_failed;
+		}
+		imx21->etd[ep_priv->etd[i]].busy = 0;
+		imx21->etd[ep_priv->etd[i]].ep = ep;
+		imx21->etd[ep_priv->etd[i]].urb = NULL;
+		imx21->etd[ep_priv->etd[i]].td = NULL;
+	}
+	spin_unlock_irqrestore(&imx21->lock, flags);
+
+	ep_priv->etd_num = -1;
+	INIT_LIST_HEAD(&ep_priv->td_list);
+	ep_priv->hep = ep;
+	ep->hcpriv = ep_priv;
+	return ep_priv;
+
+alloc_etd_failed:
+	kfree(ep_priv);
+	spin_unlock_irqrestore(&imx21->lock, flags);
+	return NULL;
+}
+
+static int imx21_hc_urb_enqueue_isoc(struct usb_hcd *hcd,
+				     struct usb_host_endpoint *ep,
+				     struct urb *urb, gfp_t mem_flags)
+{
+	struct imx21 *imx21 = hcd_to_imx21(hcd);
+	struct urb_priv *urb_priv;
+	unsigned long flags;
+	struct ep_priv *ep_priv;
+	struct td *td = NULL;
+	struct td *tmp = NULL;
+	LIST_HEAD(new_tds);
+	int i;
+	int ret;
+
+	urb_priv = kzalloc(sizeof(struct urb_priv), mem_flags);
+	if (urb_priv == NULL)
+		return -ENOMEM;
+
+	if (ep->hcpriv == NULL) {
+		ep_priv = alloc_isoc_ep(imx21, ep, mem_flags);
+		if (ep_priv == NULL) {
+			ret = -ENOMEM;
+			goto alloc_ep_failed;
+		}
+	} else {
+		ep_priv = ep->hcpriv;
+	}
+
+	spin_lock_irqsave(&imx21->lock, flags);
+	ret = usb_hcd_link_urb_to_ep(hcd, urb);
+	if (ret)
+		goto link_failed;
+
+	urb->status = -EINPROGRESS;
+	urb->actual_length = 0;
+	urb->error_count = 0;
+	urb->hcpriv = urb_priv;
+	urb_priv->ep = ep;
+
+	if (urb->transfer_flags & URB_ISO_ASAP) {
+		int cur_frame = imx21_hc_get_frame(hcd);
+		if (list_empty(&ep_priv->td_list))
+			urb->start_frame = cur_frame + 1;
+		else
+			urb->start_frame = list_entry(
+				ep_priv->td_list.prev,
+				struct td, list)->frame + urb->interval;
+		if (urb->start_frame < cur_frame) {
+			dev_dbg(imx21->dev, "Adjusting iso start frame %d (cur=%d) submission=%d\n",
+				urb->start_frame, cur_frame, imx21->dbg_iso_urb_cnt);
+			urb->start_frame = cur_frame + 1;
+		}
+	}
+
+	/* set up transfers */
+	for (i = 0; i < urb->number_of_packets; i++) {
+		td = kzalloc(sizeof(struct td), GFP_ATOMIC);
+		if (td == NULL) {
+			ret = -ENOMEM;
+			goto alloc_td_failed;
+		}
+
+		td->ep = ep;
+		td->urb = urb;
+		td->len = urb->iso_frame_desc[i].length;
+		td->iso_index = i;
+		td->frame = urb->start_frame + urb->interval * i;
+		td->data = urb->transfer_dma + urb->iso_frame_desc[i].offset;
+		list_add_tail(&td->list, &new_tds);
+	}
+	list_splice_tail(&new_tds, &ep_priv->td_list);
+	urb_priv->iso_remaining = urb->number_of_packets;
+	/*dev_dbg(imx21->dev, "setup %d packets for iso submission %d frame %d->%d\n",
+		urb->number_of_packets, imx21->dbg_iso_urb_cnt, urb->start_frame, td->frame);*/
+
+	schedule_iso_etds(hcd, ep);
+
+	/* Enable ETD interrupts */
+	set_register_bits(imx21, USBH_SYSIEN, USBH_SYSIEN_DONEINT);
+	imx21->dbg_iso_urb_cnt++;
+	spin_unlock_irqrestore(&imx21->lock, flags);
+	return 0;
+
+alloc_td_failed:
+	list_for_each_entry_safe(td, tmp, &new_tds, list) {
+		list_del(&tmp->list);
+		kfree(tmp);
+	}
+link_failed:
+	spin_unlock_irqrestore(&imx21->lock, flags);
+
+alloc_ep_failed:
+	kfree(urb_priv);
+	return ret;
+
+}
+
+
+static struct ep_priv *alloc_ep(gfp_t mem_flags)
+{
+	int i;
+	struct ep_priv *ep_priv;
+
+	ep_priv = kzalloc(sizeof(struct ep_priv), mem_flags);
+	if (!ep_priv)
+		return NULL;
+
+	for (i = 0; i < NUM_ISO_ETDS; ++i)
+		ep_priv->etd[i] = -1;
+
+	ep_priv->etd_num = -1;
+	return ep_priv;
+}
+
+static int imx21_hc_urb_enqueue(struct usb_hcd *hcd,
+				struct urb *urb, gfp_t mem_flags)
+{
+	struct imx21 *imx21 = hcd_to_imx21(hcd);
+	struct usb_host_endpoint *ep = urb->ep;
+	struct urb_priv *urb_priv;
+	unsigned long flags;
+	struct ep_priv *ep_priv;
+	int ret;
+
+	dev_dbg(imx21->dev,
+		"enqueue urb=%p len=%d buffer=%p dma=%08X setupBuf=%p setupDma=%08X\n",
+		urb,
+		urb->transfer_buffer_length,
+		urb->transfer_buffer, urb->transfer_dma,
+		urb->setup_packet, urb->setup_dma);
+
+	if (usb_pipeisoc(urb->pipe))
+		return imx21_hc_urb_enqueue_isoc(hcd, ep, urb, mem_flags);
+
+	urb_priv = kzalloc(sizeof(struct urb_priv), mem_flags);
+	if (!urb_priv)
+		return -ENOMEM;
+
+	ep_priv = ep->hcpriv;
+	if (ep_priv == NULL) {
+		ep_priv = alloc_ep(mem_flags);
+		if (!ep_priv) {
+			ret = -ENOMEM;
+			goto failed_alloc_ep;
+		}
+		ep->hcpriv = ep_priv;
+	}
+
+	spin_lock_irqsave(&imx21->lock, flags);
+	ret = usb_hcd_link_urb_to_ep(hcd, urb);
+	if (ret) {
+		dev_err(imx21->dev, "Error linking urb to ep\n");
+		goto failed_link;
+	}
+
+	urb->status = -EINPROGRESS;
+	urb->actual_length = 0;
+	urb->error_count = 0;
+	urb->hcpriv = urb_priv;
+	urb_priv->ep = ep;
+
+	switch (usb_pipetype(urb->pipe)) {
+	case PIPE_CONTROL:
+		urb_priv->state = US_CTRL_SETUP;
+		break;
+	case PIPE_BULK:
+		urb_priv->state = US_BULK;
+		break;
+	}
+
+	if (ep_priv->etd_num < 0) {
+		ep_priv->etd_num = alloc_etd(imx21);
+		if (ep_priv->etd_num < 0) {
+			/* FIXME: need to queue up these URBs/EPs */
+			dev_err(imx21->dev, "Unable to enqueue URB %p\n", urb);
+			ret = -ENXIO;
+			goto failed_alloc_etd;
+		}
+	}
+
+	if (imx21->etd[ep_priv->etd_num].busy == 0)
+		schedule_noniso_etd(imx21, urb, urb_priv->state);
+
+	/* Enable ETD interrupts */
+	set_register_bits(imx21, USBH_SYSIEN, USBH_SYSIEN_DONEINT);
+	spin_unlock_irqrestore(&imx21->lock, flags);
+	return 0;
+
+failed_alloc_etd:
+	usb_hcd_unlink_urb_from_ep(hcd, urb);
+
+failed_link:
+	kfree(ep_priv);
+	ep->hcpriv = NULL;
+	spin_unlock_irqrestore(&imx21->lock, flags);
+
+failed_alloc_ep:
+	kfree(urb_priv);
+	return ret;
+
+}
+
+static void dequeue_isoc_urb(struct imx21 *imx21,
+	struct urb *urb, struct ep_priv *ep_priv)
+{
+	struct urb_priv *urb_priv = urb->hcpriv;
+	struct td *td, *tmp;
+	int i;
+
+	if (urb_priv->active) {
+		for (i = 0; i < NUM_ISO_ETDS; i++) {
+			int etd_num = ep_priv->etd[i];
+			if (etd_num != -1 && imx21->etd[etd_num].urb == urb) {
+				u32 etd_mask = 1 << etd_num;
+				struct etd_priv *etd = imx21->etd + etd_num;
+
+				writel(etd_mask, imx21->regs + USBH_ETDENCLR);
+				clear_register_bits(imx21, USBH_ETDDONEEN, etd_mask);
+				clear_toggle_bit(imx21, USBH_ETDDONESTAT, etd_mask);
+				clear_toggle_bit(imx21, USB_ETDDMAEN, etd_mask);
+
+				td = etd->td;
+				etd->td = NULL;
+				etd->urb = NULL;
+				etd->ep = NULL;
+				etd->busy = 0;
+				free_dmem(imx21, td->buf_addr);
+				kfree(td);
+			}
+		}
+	}
+
+	list_for_each_entry_safe(td, tmp, &ep_priv->td_list, list) {
+		if (td->urb == urb) {
+			dev_dbg(imx21->dev, "removing td %p\n", td);
+			list_del(&td->list);
+			kfree(td);
+		}
+	}
+}
+
+static int imx21_hc_urb_dequeue(struct usb_hcd *hcd, struct urb *urb,
+				int status)
+{
+	struct imx21 *imx21 = hcd_to_imx21(hcd);
+	unsigned long flags;
+	struct usb_host_endpoint *ep;
+	struct ep_priv *ep_priv;
+	struct urb_priv *urb_priv = urb->hcpriv;
+
+	dev_dbg(imx21->dev, "dequeue urb=%p iso=%d status=%d\n",
+		urb, usb_pipeisoc(urb->pipe), status);
+
+	spin_lock_irqsave(&imx21->lock, flags);
+	if (urb_priv == NULL) {
+		dev_dbg(imx21->dev, "urb=%p, urb_priv == NULL\n", urb);
+		goto fail;
+	}
+
+	ep = urb_priv->ep;
+	if (ep == NULL) {
+		dev_dbg(imx21->dev, "urb=%p, ep == NULL\n", urb);
+		goto fail;
+	}
+
+	ep_priv = ep->hcpriv;
+	if (ep_priv == NULL) {
+		dev_dbg(imx21->dev, "urb=%p, ep_priv == NULL\n", urb);
+		goto fail;
+	}
+
+	if (usb_pipeisoc(urb->pipe)) {
+		dequeue_isoc_urb(imx21, urb, ep_priv);
+		schedule_iso_etds(hcd, ep);
+	} else if (urb_priv->active) {
+		int etd_num = ep_priv->etd_num;
+		if (etd_num != -1) {
+			u32 etd_mask = 1 << etd_num;
+			u16 xbufaddr;
+
+			writel(etd_mask, imx21->regs + USBH_ETDENCLR);
+			clear_register_bits(imx21, USBH_ETDDONEEN, etd_mask);
+
+			xbufaddr = etd_readl(imx21, etd_num, 1) & 0xffff;
+			free_dmem(imx21, xbufaddr);
+			imx21->etd[etd_num].urb = NULL;
+		}
+	}
+
+	urb_done(hcd, urb, status);
+
+	if ((!usb_pipeisoc(urb->pipe)) && list_empty(&ep->urb_list)) {
+		free_etd(imx21, ep_priv->etd_num);
+		ep_priv->etd_num = -1;
+	}
+
+	spin_unlock_irqrestore(&imx21->lock, flags);
+	return 0;
+
+fail:
+	dev_err(imx21->dev, "Invalid URB=%p\n", urb);
+	spin_unlock_irqrestore(&imx21->lock, flags);
+	return -EINVAL;
+}
+
+static void imx21_hc_endpoint_disable(struct usb_hcd *hcd,
+				      struct usb_host_endpoint *ep)
+{
+	struct imx21 *imx21 = hcd_to_imx21(hcd);
+	unsigned long flags;
+	struct ep_priv *ep_priv;
+	int i;
+
+	if (ep == NULL)
+		return;
+
+	spin_lock_irqsave(&imx21->lock, flags);
+	ep_priv = ep->hcpriv;
+	dev_dbg(imx21->dev, "disable ep=%p, ep->hcpriv=%p\n", ep, ep_priv);
+
+	if (!list_empty(&ep->urb_list))
+		dev_err(imx21->dev, "ep's URB list is not empty\n");
+
+	if (ep_priv != NULL) {
+		for (i = 0; i < NUM_ISO_ETDS; i++)
+			free_etd(imx21, ep_priv->etd[i]);
+		free_etd(imx21, ep_priv->etd_num);
+		kfree(ep_priv);
+		ep->hcpriv = NULL;
+	}
+
+	for (i = 0; i < USB_NUM_ETD; i++) {
+		if (imx21->etd[i].alloc && imx21->etd[i].ep == ep) {
+			dev_err(imx21->dev, "Active etd %d for disabled ep=%p!\n", i, ep);
+			free_etd(imx21, i);
+		}
+	}
+	free_epdmem(imx21, ep);
+	spin_unlock_irqrestore(&imx21->lock, flags);
+
+}
+
+/*
+ * Checks the root hub's status for changes.
+ *
+ * If a port has changed, then the appropriate bit in buf is set.
+ * The function returns the number of ports that have changed.
+ *
+ */
+static int imx21_hc_hub_status_data(struct usb_hcd *hcd, char *buf)
+{
+	struct imx21 *imx21 = hcd_to_imx21(hcd);
+	int ports;
+	int changed = 0;
+	int i;
+	unsigned long flags;
+
+	spin_lock_irqsave(&imx21->lock, flags);
+	ports = readl(imx21->regs + USBH_ROOTHUBA) & USBH_ROOTHUBA_NDNSTMPRT_MASK;
+	if (ports > 7) {
+		ports = 7;
+		dev_err(imx21->dev, "ports %d > 7\n", ports);
+	}
+	for (i = 0; i < ports; i++) {
+		if (readl(imx21->regs + USBH_PORTSTAT(i)) &
+			(USBH_PORTSTAT_CONNECTSC |
+			USBH_PORTSTAT_PRTENBLSC |
+			USBH_PORTSTAT_PRTSTATSC |
+			USBH_PORTSTAT_OVRCURIC |
+			USBH_PORTSTAT_PRTRSTSC)) {
+
+			changed = 1;
+			buf[0] |= 1 << (i + 1);
+		}
+	}
+	spin_unlock_irqrestore(&imx21->lock, flags);
+
+	if (changed)
+		dev_info(imx21->dev, "Hub status changed\n");
+	return changed;
+}
+
+static int imx21_hc_hub_control(struct usb_hcd *hcd,
+				u16 typeReq,
+				u16 wValue, u16 wIndex, char *buf, u16 wLength)
+{
+	struct imx21 *imx21 = hcd_to_imx21(hcd);
+	int rc = 0;
+	u32 status_write = 0;
+
+	switch (typeReq) {
+	case ClearHubFeature:
+		dev_dbg(imx21->dev, "ClearHubFeature\n");
+		switch (wValue) {
+		case C_HUB_OVER_CURRENT:
+			dev_dbg(imx21->dev, "    C_HUB_OVER_CURRENT\n");
+			break;
+		case C_HUB_LOCAL_POWER:
+			dev_dbg(imx21->dev, "    C_HUB_LOCAL_POWER\n");
+			break;
+		default:
+			dev_dbg(imx21->dev, "    unknown\n");
+			rc = -EINVAL;
+			break;
+		}
+		break;
+
+	case ClearPortFeature:
+		dev_dbg(imx21->dev, "ClearPortFeature\n");
+		switch (wValue) {
+		case USB_PORT_FEAT_ENABLE:
+			dev_dbg(imx21->dev, "    USB_PORT_FEAT_ENABLE\n");
+			status_write = USBH_PORTSTAT_CURCONST;
+			break;
+		case USB_PORT_FEAT_SUSPEND:
+			dev_dbg(imx21->dev, "    USB_PORT_FEAT_SUSPEND\n");
+			status_write = USBH_PORTSTAT_PRTOVRCURI;
+			break;
+		case USB_PORT_FEAT_POWER:
+			dev_dbg(imx21->dev, "    USB_PORT_FEAT_POWER\n");
+			status_write = USBH_PORTSTAT_LSDEVCON;
+			break;
+		case USB_PORT_FEAT_C_ENABLE:
+			dev_dbg(imx21->dev, "    USB_PORT_FEAT_C_ENABLE\n");
+			status_write = USBH_PORTSTAT_PRTENBLSC;
+			break;
+		case USB_PORT_FEAT_C_SUSPEND:
+			dev_dbg(imx21->dev, "    USB_PORT_FEAT_C_SUSPEND\n");
+			status_write = USBH_PORTSTAT_PRTSTATSC;
+			break;
+		case USB_PORT_FEAT_C_CONNECTION:
+			dev_dbg(imx21->dev, "    USB_PORT_FEAT_C_CONNECTION\n");
+			status_write = USBH_PORTSTAT_CONNECTSC;
+			break;
+		case USB_PORT_FEAT_C_OVER_CURRENT:
+			dev_dbg(imx21->dev, "    USB_PORT_FEAT_C_OVER_CURRENT\n");
+			status_write = USBH_PORTSTAT_OVRCURIC;
+			break;
+		case USB_PORT_FEAT_C_RESET:
+			dev_dbg(imx21->dev, "    USB_PORT_FEAT_C_RESET\n");
+			status_write = USBH_PORTSTAT_PRTRSTSC;
+			break;
+		default:
+			dev_dbg(imx21->dev, "    unknown\n");
+			rc = -EINVAL;
+			break;
+		}
+
+		break;
+
+	case GetHubDescriptor:
+		dev_dbg(imx21->dev, "GetHubDescriptor\n");
+		rc = get_hub_descriptor(hcd, (void *)buf);
+		break;
+
+	case GetHubStatus:
+		dev_dbg(imx21->dev, "  GetHubStatus\n");
+		*(__le32 *) buf = 0;
+		break;
+
+	case GetPortStatus:
+		dev_dbg(imx21->dev, "GetPortStatus: port: %d, 0x%x\n",
+		    wIndex, USBH_PORTSTAT(wIndex - 1));
+		*(__le32 *) buf = readl(imx21->regs + USBH_PORTSTAT(wIndex - 1));
+		break;
+
+	case SetHubFeature:
+		dev_dbg(imx21->dev, "SetHubFeature\n");
+		switch (wValue) {
+		case C_HUB_OVER_CURRENT:
+			dev_dbg(imx21->dev, "    C_HUB_OVER_CURRENT\n");
+			break;
+
+		case C_HUB_LOCAL_POWER:
+			dev_dbg(imx21->dev, "    C_HUB_LOCAL_POWER\n");
+			break;
+		default:
+			dev_dbg(imx21->dev, "    unknown\n");
+			rc = -EINVAL;
+			break;
+		}
+
+		break;
+
+	case SetPortFeature:
+		dev_dbg(imx21->dev, "SetPortFeature\n");
+		switch (wValue) {
+		case USB_PORT_FEAT_SUSPEND:
+			dev_dbg(imx21->dev, "    USB_PORT_FEAT_SUSPEND\n");
+			status_write = USBH_PORTSTAT_PRTSUSPST;
+			break;
+		case USB_PORT_FEAT_POWER:
+			dev_dbg(imx21->dev, "    USB_PORT_FEAT_POWER\n");
+			status_write = USBH_PORTSTAT_PRTPWRST;
+			break;
+		case USB_PORT_FEAT_RESET:
+			dev_dbg(imx21->dev, "    USB_PORT_FEAT_RESET\n");
+			status_write = USBH_PORTSTAT_PRTRSTST;
+			break;
+		default:
+			dev_dbg(imx21->dev, "    unknown\n");
+			rc = -EINVAL;
+			break;
+		}
+		break;
+
+	default:
+		dev_dbg(imx21->dev, "  unknown\n");
+		rc = -EINVAL;
+		break;
+	}
+
+	if (status_write)
+		writel(status_write, imx21->regs + USBH_PORTSTAT(wIndex - 1));
+	return rc;
+}
+
+static struct hc_driver imx21_hc_driver = {
+	.description = hcd_name,
+	.product_desc = "IMX21 USB Host Controller",
+	.hcd_priv_size = sizeof(struct imx21),
+
+	.flags = HCD_USB11,	/* USB 1.1 */
+	.irq = imx21_irq,
+
+	.reset = imx21_hc_reset,
+	.start = imx21_hc_start,
+	.stop = imx21_hc_stop,
+
+	/* I/O requests */
+	.urb_enqueue = imx21_hc_urb_enqueue,
+	.urb_dequeue = imx21_hc_urb_dequeue,
+	.endpoint_disable = imx21_hc_endpoint_disable,
+
+	/* scheduling support */
+	.get_frame_number = imx21_hc_get_frame,
+
+	/* Root hub support */
+	.hub_status_data = imx21_hc_hub_status_data,
+	.hub_control = imx21_hc_hub_control,
+/* For PM
+   .hub_suspend = ;
+   .hub_resume = ;
+*/
+};
+
+static int imx21_remove(struct platform_device *pdev)
+{
+	struct usb_hcd *hcd = platform_get_drvdata(pdev);
+	struct imx21 *imx21 = hcd_to_imx21(hcd);
+	struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+
+	usb_remove_hcd(hcd);
+
+	if (res != NULL) {
+		clk_disable(imx21->clk);
+		clk_put(imx21->clk);
+		iounmap(imx21->regs);
+		release_mem_region(res->start, res->end - res->start);
+	}
+	return 0;
+}
+
+static int imx21_probe(struct platform_device *pdev)
+{
+	struct usb_hcd *hcd;
+	struct imx21 *imx21;
+	struct resource *res;
+	int ret;
+	int irq;
+	int i, j;
+
+	printk(KERN_INFO "%s\n", imx21_hc_driver.product_desc);
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!res)
+		return -ENODEV;
+	irq = platform_get_irq(pdev, 0);
+	if (irq < 0)
+		return -ENXIO;
+
+	hcd = usb_create_hcd(&imx21_hc_driver, &pdev->dev, pdev->dev.bus_id);
+	if (hcd == NULL) {
+		dev_err(&pdev->dev, "Cannot create hcd (bus_id=%s)\n",
+		    pdev->dev.bus_id);
+		return -ENOMEM;
+	}
+
+	imx21 = hcd_to_imx21(hcd);
+	memset(imx21, 0, sizeof(struct imx21));
+	imx21->dev = &pdev->dev;
+	imx21->board = pdev->dev.platform_data;
+	spin_lock_init(&imx21->lock);
+	INIT_LIST_HEAD(&imx21->dmem_list.list);
+
+	res = request_mem_region(res->start, resource_size(res), hcd_name);
+	if (!res) {
+		ret = -EBUSY;
+		goto failed_request_mem;
+	}
+
+	imx21->regs = ioremap(res->start, resource_size(res));
+	if (imx21->regs == NULL) {
+		dev_err(imx21->dev, "Cannot map registers\n");
+		ret = -ENOMEM;
+		goto failed_ioremap;
+	}
+
+	/* Enable clocks source */
+	imx21->clk = clk_get(imx21->dev, NULL);
+	if (IS_ERR(imx21->clk)) {
+		dev_err(imx21->dev, "no clock found\n");
+		ret = PTR_ERR(imx21->clk);
+		goto failed_clock_get;
+	}
+
+	ret = clk_set_rate(imx21->clk, clk_round_rate(imx21->clk, 48000000));
+	if (ret)
+		goto failed_clock_set;
+	ret = clk_enable(imx21->clk);
+	if (ret)
+		goto failed_clock_enable;
+
+	/* Reset the USB OTG module */
+	writel(USBOTG_RST_RSTCTRL | USBOTG_RST_RSTFC |
+			USBOTG_RST_RSTFSKE | USBOTG_RST_RSTRH |
+			USBOTG_RST_RSTHSIE | USBOTG_RST_RSTHC,
+			imx21->regs + USBOTG_RST_CTRL);
+
+	/* Wait for reset to finish */
+	while (readl(imx21->regs + USBOTG_RST_CTRL) != 0) /* MF: guard this */
+		schedule_timeout(1);
+
+	writel((USBOTG_CLK_CTRL_HST | USBOTG_CLK_CTRL_MAIN),
+		imx21->regs + USBOTG_CLK_CTRL);
+	writel(USBOTG_HWMODE_CRECFG_HOST, imx21->regs + USBOTG_HWMODE);
+
+	/* Clear the ETDs */
+	for (i = 0; i < USB_NUM_ETD; i++)
+		for (j = 0; j < 4; j++)
+			etd_writel(imx21, i, j, 0);
+
+	/* Take the HC out of reset */
+	writel(USBH_HOST_CTRL_HCUSBSTE_OPERATIONAL | USBH_HOST_CTRL_CTLBLKSR_1,
+		imx21->regs + USBH_HOST_CTRL);
+
+	for (i = 0; i < 3; i++) /*MF: TODO configure ports in platform_data */
+		writel(USBH_PORTSTAT_PRTPWRST | USBH_PORTSTAT_PRTENABST,
+			imx21->regs + USBH_PORTSTAT(0));
+
+
+	ret = usb_add_hcd(hcd, irq, IRQF_DISABLED);
+	if (ret != 0) {
+		dev_err(imx21->dev, "usb_add_hcd() returned %d\n", ret);
+		goto failed_add_hcd;
+	}
+
+	return 0;
+
+failed_add_hcd:
+	clk_disable(imx21->clk);
+failed_clock_enable:
+failed_clock_set:
+	clk_put(imx21->clk);
+failed_clock_get:
+	iounmap(imx21->regs);
+failed_ioremap:
+	release_mem_region(res->start, res->end - res->start);
+failed_request_mem:
+	usb_put_hcd(hcd);
+	return ret;
+}
+
+static struct platform_driver imx21_hcd_driver = {
+	.driver = {
+		   .name = (char *)hcd_name,
+		   },
+	.probe = imx21_probe,
+	.remove = imx21_remove,
+	.suspend = NULL,
+	.resume = NULL,
+};
+
+static int __init imx21_hcd_init(void)
+{
+	return platform_driver_register(&imx21_hcd_driver);
+}
+
+static void __exit imx21_hcd_cleanup(void)
+{
+	platform_driver_unregister(&imx21_hcd_driver);
+}
+
+module_init(imx21_hcd_init);
+module_exit(imx21_hcd_cleanup);
diff --git a/drivers/usb/host/imx21-hcd.h b/drivers/usb/host/imx21-hcd.h
new file mode 100644
index 0000000..db99a32
--- /dev/null
+++ b/drivers/usb/host/imx21-hcd.h
@@ -0,0 +1,347 @@
+/*
+ * Macros and prototypes for i.MX21
+ *
+ * Copyright (C) 2006 Loping Dog Embedded Systems
+ * Written by Jay Monkman <jtm@xxxxxxxxxxxxx>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE.	 See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+#ifndef __LINUX_IMX21_HCD_H__
+#define __LINUX_IMX21_HCD_H__
+
+#define NUM_ISO_ETDS 	2
+#define USB_NUM_ETD	32
+
+/* Register definitions */
+#define USBOTG_HWMODE		0x00
+#define USBOTG_HWMODE_ANASDBEN		(1 << 14)
+#define USBOTG_HWMODE_OTGXCVR_MASK	(3 << 6)
+#define USBOTG_HWMODE_OTGXCVR_TD_RD	(0 << 6)
+#define USBOTG_HWMODE_OTGXCVR_TS_RD	(2 << 6)
+#define USBOTG_HWMODE_OTGXCVR_TD_RS	(1 << 6)
+#define USBOTG_HWMODE_OTGXCVR_TS_RS	(3 << 6)
+#define USBOTG_HWMODE_HOSTXCVR_MASK	(3 << 4)
+#define USBOTG_HWMODE_HOSTXCVR_TD_RD	(0 << 4)
+#define USBOTG_HWMODE_HOSTXCVR_TS_RD	(2 << 4)
+#define USBOTG_HWMODE_HOSTXCVR_TD_RS	(1 << 4)
+#define USBOTG_HWMODE_HOSTXCVR_TS_RS	(3 << 4)
+#define USBOTG_HWMODE_CRECFG_MASK	(3 << 0)
+#define USBOTG_HWMODE_CRECFG_HOST	(1 << 0)
+#define USBOTG_HWMODE_CRECFG_FUNC	(2 << 0)
+#define USBOTG_HWMODE_CRECFG_HNP	(3 << 0)
+
+#define USBOTG_CINT_STAT	0x04
+#define USBOTG_CINT_STEN	0x08
+#define USBOTG_ASHNPINT			(1 << 5)
+#define USBOTG_ASFCINT			(1 << 4)
+#define USBOTG_ASHCINT			(1 << 3)
+#define USBOTG_SHNPINT			(1 << 2)
+#define USBOTG_FCINT			(1 << 1)
+#define USBOTG_HCINT			(1 << 0)
+
+#define USBOTG_CLK_CTRL		0x0c
+#define USBOTG_CLK_CTRL_FUNC		(1 << 2)
+#define USBOTG_CLK_CTRL_HST		(1 << 1)
+#define USBOTG_CLK_CTRL_MAIN		(1 << 0)
+
+#define USBOTG_RST_CTRL		0x10
+#define USBOTG_RST_RSTI2C		(1 << 15)
+#define USBOTG_RST_RSTCTRL		(1 << 5)
+#define USBOTG_RST_RSTFC		(1 << 4)
+#define USBOTG_RST_RSTFSKE		(1 << 3)
+#define USBOTG_RST_RSTRH		(1 << 2)
+#define USBOTG_RST_RSTHSIE		(1 << 1)
+#define USBOTG_RST_RSTHC		(1 << 0)
+
+#define USBOTG_FRM_INTVL    	0x14
+#define USBOTG_FRM_REMAIN   	0x18
+#define USBOTG_HNP_CSR	    	0x1c
+#define USBOTG_HNP_ISR	    	0x2c
+#define USBOTG_HNP_IEN	    	0x30
+
+#define USBOTG_I2C_TXCVR_REG(x)	     (0x100 + (x))
+#define USBOTG_I2C_XCVR_DEVAD		0x118
+#define USBOTG_I2C_SEQ_OP_REG		0x119
+#define USBOTG_I2C_SEQ_RD_STARTAD	0x11a
+#define USBOTG_I2C_OP_CTRL_REG	     	0x11b
+#define USBOTG_I2C_SCLK_TO_SCK_HPER  	0x11e
+#define USBOTG_I2C_MASTER_INT_REG    	0x11f
+
+#define USBH_HOST_CTRL		0x80
+#define USBH_HOST_CTRL_HCRESET			(1 << 31)
+#define USBH_HOST_CTRL_SCHDOVR(x)		((x) << 16)
+#define USBH_HOST_CTRL_RMTWUEN			(1 << 4)
+#define USBH_HOST_CTRL_HCUSBSTE_RESET		(0 << 2)
+#define USBH_HOST_CTRL_HCUSBSTE_RESUME		(1 << 2)
+#define USBH_HOST_CTRL_HCUSBSTE_OPERATIONAL	(2 << 2)
+#define USBH_HOST_CTRL_HCUSBSTE_SUSPEND		(3 << 2)
+#define USBH_HOST_CTRL_CTLBLKSR_1		(0 << 0)
+#define USBH_HOST_CTRL_CTLBLKSR_2		(1 << 0)
+#define USBH_HOST_CTRL_CTLBLKSR_3		(2 << 0)
+#define USBH_HOST_CTRL_CTLBLKSR_4		(3 << 0)
+
+#define USBH_SYSISR		0x88
+#define USBH_SYSISR_PSCINT		(1 << 6)
+#define USBH_SYSISR_FMOFINT		(1 << 5)
+#define USBH_SYSISR_HERRINT		(1 << 4)
+#define USBH_SYSISR_RESDETINT		(1 << 3)
+#define USBH_SYSISR_SOFINT		(1 << 2)
+#define USBH_SYSISR_DONEINT		(1 << 1)
+#define USBH_SYSISR_SORINT		(1 << 0)
+
+#define USBH_SYSIEN	    	0x8c
+#define USBH_SYSIEN_PSCINT		(1 << 6)
+#define USBH_SYSIEN_FMOFINT		(1 << 5)
+#define USBH_SYSIEN_HERRINT		(1 << 4)
+#define USBH_SYSIEN_RESDETINT		(1 << 3)
+#define USBH_SYSIEN_SOFINT		(1 << 2)
+#define USBH_SYSIEN_DONEINT		(1 << 1)
+#define USBH_SYSIEN_SORINT		(1 << 0)
+
+#define USBH_XBUFSTAT	    	0x98
+#define USBH_YBUFSTAT	    	0x9c
+#define USBH_XYINTEN	    	0xa0
+#define USBH_XFILLSTAT	    	0xa8
+#define USBH_YFILLSTAT	    	0xac
+#define USBH_ETDENSET	    	0xc0
+#define USBH_ETDENCLR	    	0xc4
+#define USBH_IMMEDINT	    	0xcc
+#define USBH_ETDDONESTAT    	0xd0
+#define USBH_ETDDONEEN	    	0xd4
+#define USBH_FRMNUB	    	0xe0
+#define USBH_LSTHRESH	    	0xe4
+
+#define USBH_ROOTHUBA	    	0xe8
+#define USBH_ROOTHUBA_PWRTOGOOD_MASK	(0xff)
+#define USBH_ROOTHUBA_PWRTOGOOD_SHIFT	(24)
+#define USBH_ROOTHUBA_NOOVRCURP		(1 << 12)
+#define USBH_ROOTHUBA_OVRCURPM		(1 << 11)
+#define USBH_ROOTHUBA_DEVTYPE		(1 << 10)
+#define USBH_ROOTHUBA_PWRSWTMD		(1 << 9)
+#define USBH_ROOTHUBA_NOPWRSWT		(1 << 8)
+#define USBH_ROOTHUBA_NDNSTMPRT_MASK	(0xff)
+
+#define USBH_ROOTHUBB		0xec
+#define USBH_ROOTHUBB_PRTPWRCM(x)	(1 << ((x) + 16))
+#define USBH_ROOTHUBB_DEVREMOVE(x)	(1 << (x))
+
+#define USBH_ROOTSTAT		0xf0
+#define USBH_ROOTSTAT_CLRRMTWUE		(1 << 31)
+#define USBH_ROOTSTAT_OVRCURCHG		(1 << 17)
+#define USBH_ROOTSTAT_DEVCONWUE		(1 << 15)
+#define USBH_ROOTSTAT_OVRCURI		(1 << 1)
+#define USBH_ROOTSTAT_LOCPWRS		(1 << 0)
+
+#define USBH_PORTSTAT(x)	(0xf4 + ((x) * 4))
+#define USBH_PORTSTAT_PRTRSTSC		(1 << 20)
+#define USBH_PORTSTAT_OVRCURIC		(1 << 19)
+#define USBH_PORTSTAT_PRTSTATSC		(1 << 18)
+#define USBH_PORTSTAT_PRTENBLSC		(1 << 17)
+#define USBH_PORTSTAT_CONNECTSC		(1 << 16)
+#define USBH_PORTSTAT_LSDEVCON		(1 << 9)
+#define USBH_PORTSTAT_PRTPWRST		(1 << 8)
+#define USBH_PORTSTAT_PRTRSTST		(1 << 4)
+#define USBH_PORTSTAT_PRTOVRCURI	(1 << 3)
+#define USBH_PORTSTAT_PRTSUSPST		(1 << 2)
+#define USBH_PORTSTAT_PRTENABST		(1 << 1)
+#define USBH_PORTSTAT_CURCONST		(1 << 0)
+
+#define USB_DMAREV		0x800
+#define USB_DMAINTSTAT	    	0x804
+#define USB_DMAINTSTAT_EPERR		(1 << 1)
+#define USB_DMAINTSTAT_ETDERR		(1 << 0)
+
+#define USB_DMAINTEN	    	0x808
+#define USB_DMAINTEN_EPERRINTEN		(1 << 1)
+#define USB_DMAINTEN_ETDERRINTEN	(1 << 0)
+
+#define USB_ETDDMAERSTAT    	0x80c
+#define USB_EPDMAERSTAT	    	0x810
+#define USB_ETDDMAEN	    	0x820
+#define USB_EPDMAEN	    	0x824
+#define USB_ETDDMAXTEN	    	0x828
+#define USB_EPDMAXTEN	    	0x82c
+#define USB_ETDDMAENXYT	    	0x830
+#define USB_EPDMAENXYT	    	0x834
+#define USB_ETDDMABST4EN    	0x838
+#define USB_EPDMABST4EN	    	0x83c
+
+#define USB_MISCCONTROL	    	0x840
+#define USB_MISCCONTROL_ISOPREVFRM	(1 << 3)
+#define USB_MISCCONTROL_SKPRTRY		(1 << 2)
+#define USB_MISCCONTROL_ARBMODE		(1 << 1)
+#define USB_MISCCONTROL_FILTCC		(1 << 0)
+
+#define USB_ETDDMACHANLCLR  	0x848
+#define USB_EPDMACHANLCLR   	0x84c
+#define USB_ETDSMSA(x)	    	(0x900 + ((x) * 4))
+#define USB_EPSMSA(x)	    	(0x980 + ((x) * 4))
+#define USB_ETDDMABUFPTR(x) 	(0xa00 + ((x) * 4))
+#define USB_EPDMABUFPTR(x)  	(0xa80 + ((x) * 4))
+
+#define USB_ETD_DWORD(x, w)	    	(0x200 + ((x) * 16) + ((w) * 4))
+
+#define USBCTRL		    	0x600
+#define USBCTRL_I2C_WU_INT_STAT		(1 << 27)
+#define USBCTRL_OTG_WU_INT_STAT		(1 << 26)
+#define USBCTRL_HOST_WU_INT_STAT	(1 << 25)
+#define USBCTRL_FNT_WU_INT_STAT		(1 << 24)
+#define USBCTRL_I2C_WU_INT_EN		(1 << 19)
+#define USBCTRL_OTG_WU_INT_EN		(1 << 18)
+#define USBCTRL_HOST_WU_INT_EN		(1 << 17)
+#define USBCTRL_FNT_WU_INT_EN		(1 << 16)
+#define USBCTRL_OTC_RCV_RXDP		(1 << 13)
+#define USBCTRL_HOST1_BYP_TLL		(1 << 12)
+#define USBCTRL_OTG_BYP_VAL(x)		((x) << 10)
+#define USBCTRL_HOST1_BYP_VAL(x)	((x) << 8)
+#define USBCTRL_OTG_PWR_MASK		(1 << 6)
+#define USBCTRL_HOST1_PWR_MASK		(1 << 5)
+#define USBCTRL_HOST2_PWR_MASK		(1 << 4)
+#define USBCTRL_USB_BYP			(1 << 2)
+#define USBCTRL_HOST1_TXEN_OE		(1 << 1)
+
+#define USB_DMEM		0x1000
+
+/* Values in TD blocks */
+#define TD_DIR_SETUP	    0
+#define TD_DIR_OUT	    1
+#define TD_DIR_IN	    2
+#define TD_FORMAT_CONTROL   0
+#define TD_FORMAT_ISO	    1
+#define TD_FORMAT_BULK	    2
+#define TD_FORMAT_INT	    3
+#define TD_TOGGLE_CARRY	    0
+#define TD_TOGGLE_DATA0	    2
+#define TD_TOGGLE_DATA1	    3
+
+/* control transfer states */
+#define US_CTRL_SETUP	2
+#define US_CTRL_DATA	1
+#define US_CTRL_ACK	0
+
+/* bulk transfer main state and 0-length packet */
+#define US_BULK		1
+#define US_BULK0	0
+
+/*ETD format description*/
+#define IMX_FMT_CTRL   0x0
+#define IMX_FMT_ISO    0x1
+#define IMX_FMT_BULK   0x2
+#define IMX_FMT_INT    0x3
+
+static char fmt_urb_to_etd[4] = {
+/*PIPE_ISOCHRONOUS*/ IMX_FMT_ISO,
+/*PIPE_INTERRUPT*/ IMX_FMT_INT,
+/*PIPE_CONTROL*/ IMX_FMT_CTRL,
+/*PIPE_BULK*/ IMX_FMT_BULK
+};
+
+/* condition (error) CC codes and mapping (OHCI like) */
+
+#define TD_CC_NOERROR		0x00
+#define TD_CC_CRC		0x01
+#define TD_CC_BITSTUFFING	0x02
+#define TD_CC_DATATOGGLEM	0x03
+#define TD_CC_STALL		0x04
+#define TD_DEVNOTRESP		0x05
+#define TD_PIDCHECKFAIL		0x06
+/*#define TD_UNEXPECTEDPID	0x07 - reserved, not active on MX2*/
+#define TD_DATAOVERRUN		0x08
+#define TD_DATAUNDERRUN		0x09
+#define TD_BUFFEROVERRUN	0x0C
+#define TD_BUFFERUNDERRUN	0x0D
+#define TD_NOTACCESSED		0x0F
+
+struct td {
+	struct list_head list;
+	struct urb *urb;
+	dma_addr_t data;
+	int len;
+	struct usb_host_endpoint *ep;
+	unsigned long buf_addr;
+	int frame;
+	int iso_index;
+};
+
+struct urb_priv {
+	struct urb *urb;
+	struct usb_host_endpoint *ep;
+	int active;
+	int state;
+	int iso_remaining;
+};
+
+struct ep_priv {
+	struct usb_host_endpoint *hep;
+	int etd_num;
+	struct list_head td_list;
+	int etd[NUM_ISO_ETDS];
+};
+
+static const int cc_to_error[16] = {
+	/* No  Error  */ 0,
+	/* CRC Error  */ -EILSEQ,
+	/* Bit Stuff  */ -EPROTO,
+	/* Data Togg  */ -EILSEQ,
+	/* Stall      */ -EPIPE,
+	/* DevNotResp */ -ETIMEDOUT,
+	/* PIDCheck   */ -EPROTO,
+	/* UnExpPID   */ -EPROTO,
+	/* DataOver   */ -EOVERFLOW,
+	/* DataUnder  */ -EREMOTEIO,
+	/* (for hw)   */ -EIO,
+	/* (for hw)   */ -EIO,
+	/* BufferOver */ -ECOMM,
+	/* BuffUnder  */ -ENOSR,
+	/* (for HCD)  */ -EALREADY,
+	/* (for HCD)  */ -EALREADY
+};
+
+struct imx21_dmem_area {
+	unsigned int offset;
+	unsigned int size;
+	struct usb_host_endpoint *ep;
+	struct list_head list;
+};
+
+struct etd_priv {
+	int alloc;
+	struct usb_host_endpoint *ep;
+	struct urb *urb;
+	int len;
+	int busy;
+	dma_addr_t dma_handle;
+	struct td *td;
+};
+
+struct imx21 {
+	spinlock_t lock;
+	struct device *dev;
+	struct imx21_dmem_area dmem_list;
+	struct etd_priv etd[USB_NUM_ETD];
+	struct imx21_usb_platform_data *board;
+	void __iomem *regs;
+	struct clk *clk;
+	int dbg_iso_urb_cnt;
+	int dbg_iso_done_cnt;
+};
+
+static inline struct imx21 *hcd_to_imx21(struct usb_hcd *hcd)
+{
+	return (struct imx21 *)hcd->hcd_priv;
+}
+
+
+#endif


--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html

[Index of Archives]     [Linux Media]     [Linux Input]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Old Linux USB Devel Archive]

  Powered by Linux