[PATCH] imx: add udc driver

Matt Reimer <[email protected]>
Newsgroups gmane.linux.usb.devel
Message-ID <[email protected]>
From: Matt Reimer <[email protected]>

Add an i.MX UDC driver, written by Mike Lee with patches by
Torsten Koschorrek and me.

Signed-off-by: Matt Reimer <[email protected]>
---
 arch/arm/mach-imx/generic.c         |   76 ++
 drivers/usb/gadget/Kconfig          |   21 +
 drivers/usb/gadget/Makefile         |    1 +
 drivers/usb/gadget/ether.c          |    4 +
 drivers/usb/gadget/imx_udc.c        | 1980 +++++++++++++++++++++++++++++++++++
 drivers/usb/gadget/imx_udc.h        |  281 +++++
 include/asm-arm/arch-imx/hardware.h |    3 +
 include/asm-arm/arch-imx/udc.h      |   14 +
 8 files changed, 2380 insertions(+), 0 deletions(-)
 create mode 100644 drivers/usb/gadget/imx_udc.c
 create mode 100644 drivers/usb/gadget/imx_udc.h
 create mode 100644 include/asm-arm/arch-imx/udc.h

diff --git a/arch/arm/mach-imx/generic.c b/arch/arm/mach-imx/generic.c
index efef69f..7382c54 100644
--- a/arch/arm/mach-imx/generic.c
+++ b/arch/arm/mach-imx/generic.c
@@ -36,6 +36,7 @@
 #include <asm/mach/map.h>
 #include <asm/arch/mmc.h>
 #include <asm/arch/gpio.h>
+#include <asm/arch/udc.h>
 
 /* rate in Hz of the external clock source on pin EXTAL16M
  *
@@ -302,6 +303,16 @@ unsigned int imx_get_hclk(void)
 }
 EXPORT_SYMBOL(imx_get_hclk);
 
+/*
+ *  get usb_clk
+ */
+unsigned int imx_get_usb_clk(void)
+{
+	return imx_get_system_clk() / (((CSCR>>26) & 0x7)+1);
+}
+EXPORT_SYMBOL(imx_get_usb_clk);
+
+
 static struct resource imx_mmc_resources[] = {
 	[0] = {
 		.start	= 0x00214000,
@@ -367,9 +378,74 @@ static struct platform_device imxfb_device = {
 	.resource	= imxfb_resources,
 };
 
+static struct imx_udc_mach_info imx_udc_info;
+
+void __init imx_set_udc_info(struct imx_udc_mach_info *info)
+{
+	memcpy(&imx_udc_info, info, sizeof *info);
+}
+
+static struct resource imx_udc_resources[] = {
+	[0] = {
+		.start	= 0x00212000,
+		.end	= 0x00212FFF,
+		.flags	= IORESOURCE_MEM,
+	},
+	[1] = {
+		.start	= USBD_INT0,
+		.end	= USBD_INT0,
+		.flags	= IORESOURCE_IRQ,
+	},
+	[2] = {
+		.start	= USBD_INT1,
+		.end	= USBD_INT1,
+		.flags	= IORESOURCE_IRQ,
+	},
+        [3] = {
+		.start	= USBD_INT2,
+		.end	= USBD_INT2,
+		.flags	= IORESOURCE_IRQ,
+	},
+	[4] = {
+		.start	= USBD_INT3,
+		.end	= USBD_INT3,
+		.flags	= IORESOURCE_IRQ,
+	},
+	[5] = {
+		.start	= USBD_INT4,
+		.end	= USBD_INT4,
+		.flags	= IORESOURCE_IRQ,
+	},
+	[6] = {
+		.start	= USBD_INT5,
+		.end	= USBD_INT5,
+		.flags	= IORESOURCE_IRQ,
+	},
+	[7] = {
+		.start	= USBD_INT6,
+		.end	= USBD_INT6,
+		.flags	= IORESOURCE_IRQ,
+	},
+};
+
+static u64 udc_dma_mask = ~(u32)0;
+
+static struct platform_device imx_udc_device = {
+	.name		= "imx_udc",
+	.id		= -1,
+	.num_resources	= ARRAY_SIZE(imx_udc_resources),
+	.resource	= imx_udc_resources,
+	.dev		= {
+		.platform_data  = &imx_udc_info,
+		.dma_mask       = &udc_dma_mask,
+	}
+};
+
+
 static struct platform_device *devices[] __initdata = {
 	&imx_mmc_device,
 	&imxfb_device,
+	&imx_udc_device,
 };
 
 static struct map_desc imx_io_desc[] __initdata = {
diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig
index 767aed5..be46d41 100644
--- a/drivers/usb/gadget/Kconfig
+++ b/drivers/usb/gadget/Kconfig
@@ -300,6 +300,27 @@ config USB_AT91
 	depends on USB_GADGET_AT91
 	default USB_GADGET
 
+config USB_GADGET_IMX
+	boolean "IMX: MX1, MXL"
+	depends on ARCH_IMX
+	help
+	  Freescale's IMX series include an integrated full speed
+	  USB 1.1 device controller.  The controller in the IMX series
+	  is register-compatible.
+
+	  It has six fixed-function endpoints, as well as endpoint
+	  zero (for control transfers).
+
+	  Say "y" to link the driver statically, or "m" to build a
+	  dynamically linked module called "imx_udc" and force all
+	  gadget drivers to also be dynamically linked.
+
+config USB_IMX
+	tristate
+	depends on USB_GADGET_IMX
+	default USB_GADGET
+	select USB_GADGET_SELECTED
+
 config USB_GADGET_DUMMY_HCD
 	boolean "Dummy HCD (DEVELOPMENT)"
 	depends on (USB=y || (USB=m && USB_GADGET=m)) && EXPERIMENTAL
diff --git a/drivers/usb/gadget/Makefile b/drivers/usb/gadget/Makefile
index 1bc0f03..6e383ad 100644
--- a/drivers/usb/gadget/Makefile
+++ b/drivers/usb/gadget/Makefile
@@ -16,6 +16,7 @@ obj-$(CONFIG_USB_S3C2410)	+= s3c2410_udc.o
 obj-$(CONFIG_USB_AT91)		+= at91_udc.o
 obj-$(CONFIG_USB_FSL_USB2)	+= fsl_usb2_udc.o
 obj-$(CONFIG_USB_M66592)	+= m66592-udc.o
+obj-$(CONFIG_USB_IMX)		+= imx_udc.o
 
 #
 # USB gadget drivers
diff --git a/drivers/usb/gadget/ether.c b/drivers/usb/gadget/ether.c
index 593e235..51ac3c1 100644
--- a/drivers/usb/gadget/ether.c
+++ b/drivers/usb/gadget/ether.c
@@ -285,6 +285,10 @@ MODULE_PARM_DESC(host_addr, "Host Ethernet Address");
 #define DEV_CONFIG_CDC
 #endif
 
+#ifdef CONFIG_USB_GADGET_IMX
+#define DEV_CONFIG_CDC
+#endif
+
 /* For CDC-incapable hardware, choose the simple cdc subset.
  * Anything that talks bulk (without notable bugs) can do this.
  */
diff --git a/drivers/usb/gadget/imx_udc.c b/drivers/usb/gadget/imx_udc.c
new file mode 100644
index 0000000..999dceb
--- /dev/null
+++ b/drivers/usb/gadget/imx_udc.c
@@ -0,0 +1,1980 @@
+/*
+ *	driver/usb/gadget/imx_udc.c
+ *
+ *	Copyright (C) 2005 Mike Lee([email protected])
+ *	
+ *	This udc driver is now under testing and code is based on pxa2xx_udc.c
+ *	Please use it with your own risk!
+ *
+ *	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.
+ */
+
+/*
+ * support control, bulk, int transfer
+ * no power management, double buffering, dma
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/ioport.h>
+#include <linux/types.h>
+#include <linux/version.h>
+#include <linux/errno.h>
+#include <linux/delay.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/timer.h>
+#include <linux/list.h>
+#include <linux/interrupt.h>
+
+#include <linux/proc_fs.h>
+#include <linux/mm.h>
+#include <linux/platform_device.h>
+#include <linux/dma-mapping.h>
+
+#include <asm/byteorder.h>
+#include <asm/dma.h>
+#include <asm/io.h>
+#include <asm/irq.h>
+#include <asm/system.h>
+#include <asm/mach-types.h>
+#include <asm/unaligned.h>
+#include <asm/hardware.h>
+#include <asm/arch/imx-regs.h>
+#include <asm/arch/udc.h>
+
+#include <linux/usb/ch9.h>
+#include <linux/usb_gadget.h>
+
+#define	DRIVER_VERSION	"0.5"
+#define	DRIVER_DESC	"IMX USB Device Controller driver"
+
+
+static const char driver_name [] = "imx_udc";
+
+static const char ep0name [] = "ep0";
+
+
+#ifdef CONFIG_ARCH_IMX
+#undef USE_DMA
+#else
+#error "Only support on IMX platform"
+#endif
+
+#include "imx_udc.h"
+
+static struct imx_udc memory;
+static inline void ep0_idle (const char* label,struct imx_udc *dev);
+
+/*
+ * Hardware related function
+ */
+
+void imx_usb_disable(struct imx_udc *dev)
+{
+	USB_CTRL &= ~(USB_FE_ENA | USB_AFE_ENA); 
+
+	ep0_idle (__FUNCTION__,dev);
+	dev->gadget.speed = USB_SPEED_UNKNOWN;
+}
+
+void imx_usb_gpio(void)
+{
+	imx_gpio_mode(PB20_PF_USBD_AFE);
+	imx_gpio_mode(PB21_PF_USBD_OE);
+	imx_gpio_mode(PB22_PF_USBD_RCV);
+	imx_gpio_mode(PB23_PF_USBD_SUSPND);
+	imx_gpio_mode(PB24_PF_USBD_VP);
+	imx_gpio_mode(PB25_PF_USBD_VM);
+	imx_gpio_mode(PB26_PF_USBD_VPO);
+	imx_gpio_mode(PB27_PF_USBD_VMO);
+}
+
+void imx_usb_reset(struct imx_udc *dev)
+{
+	/* set RST bit */
+	USB_ENAB |= USB_RST;
+
+	/* wait RST bit to clear */
+	do {} while (USB_ENAB & USB_RST);
+
+	/* wait CFG bit to assert */
+	do {} while (!(USB_DADR & USB_CFG));
+
+	/* udc module is now full reset */
+}
+
+int imx_download_conf(struct imx_udc *dev)
+{
+	u8 ep_conf[5];
+	u8 i,j,intf_alt;
+	struct imx_ep *ep;
+
+	/* wait CFG bit to assert */
+	do {} while (!(USB_DADR & USB_CFG));
+
+	/* Download the endpoint buffer for endpoint 0. */
+	for (j=0;j<5;j++) {
+		USB_DDAT = (u32)(j==2?dev->ep[0].wMaxPacketSize:0x00);
+		do {} while(USB_DADR & USB_BSY);
+	} 
+
+	/* Download the endpoint buffers for endpoints 1-5.
+	 * We specify one configuration, two interfaces:
+	 *     interface 0, alternate 0
+	 *     interface 1, alternate 0 (no endpoints)
+	 *     interface 1, alternate 1
+	 */
+	for (intf_alt = 0; intf_alt <= 1; intf_alt++) {
+		for (i = 1; i < IMX_USB_NB_EP; i++){
+			ep = &dev->ep[i];
+			/* EP no | configuration no | Interface no */
+			ep_conf[0] = ((EP_NO(ep) << 4) | 1 << 2 | intf_alt) & 0xFF;
+			/* Alt setting | type | Direction | max pkt size(partly)*/
+			ep_conf[1] = ((intf_alt << 5) |
+				      (ep->bmAttributes << 3) |
+				      (EP_DIR(ep) << 2) | (0)) & 0xFF;
+			/* max pkt size(partly) */
+			ep_conf[2] = ep->wMaxPacketSize & 0xFF;
+			/* TRXTYP */
+			ep_conf[3] = 0xc0;
+			/* FIFO no */
+			ep_conf[4] = i;
+			D3("ep_conf[%d] : [%02x-%02x-%02x-%02x-%02x]\n",i,
+			   ep_conf[0],ep_conf[1],ep_conf[2],ep_conf[3],ep_conf[4]);
+
+			for (j=0;j<5;j++) {
+				USB_DDAT = (u8)ep_conf[j];
+				do {} while(USB_DADR & USB_BSY);
+			} 
+		}
+	}
+
+	/* Wait for the config download to complete. */
+	do {} while (USB_DADR & USB_CFG);
+
+	return (USB_DADR & USB_CFG) ? -1 : 0;
+}
+
+void imx_init_intr(void)
+{
+	int i;
+
+	/* block all irqs */
+	USB_MASK = 0xFFFFFFFF;
+	for (i=0;i<IMX_USB_NB_EP;i++){
+		USB_EP_MASK(i) = 0xFFFFFFFF;
+	}
+
+	USB_MASK = USB_MSOF | USB_SOF | USB_FRAME_MATCH;
+	USB_INTR = 0x800000ff;
+
+	USB_EP_MASK(0) = 0x1FF & ~(USB_DEVREQ | USB_EOT | USB_EOF);
+	USB_EP_INTR(0) = 0x1FF;
+
+	for (i=1;i<IMX_USB_NB_EP;i++){
+		USB_EP_MASK(i) = 0x1FF & ~(USB_EOT | USB_EOF);
+		USB_EP_INTR(i) = 0x1FF;
+	}
+}
+
+void imx_init_ep(struct imx_udc *dev)
+{
+	int i,max;
+	struct imx_ep *ep;
+	for (i=0;i<IMX_USB_NB_EP;i++){
+		ep = &dev->ep[i];
+		switch (ep->wMaxPacketSize) {
+		case 8: max=0;break;
+		default:
+		case 16: max=1;break;
+		case 32: max=2;break;
+		case 64: max=3;break;
+		}
+		USB_EP_STAT(i) = (EP_DIR(ep) << 7) | 
+			(max << 5) |
+			(ep->bmAttributes << 3) |
+			(0);
+		USB_EP_STAT(i) = USB_EP_STAT(i) | USB_FLUSH;
+		D3("ep%d_stat %08x\n",i,USB_EP_STAT(i));		
+	}
+}
+
+void imx_init_fifo(struct imx_udc *dev)
+{
+	int i;
+	struct imx_ep *ep;
+	for (i=0;i<IMX_USB_NB_EP;i++){
+		ep = &dev->ep[i];
+		USB_EP_FCTRL(i) = EP_DIR(ep) == EP_DIR_IN ? 0x0B000000 : 0x0F000000;
+		D3("ep%d_fctrl %08x\n",i,USB_EP_FCTRL(i));		
+		// alarm set when w/wo max pkt size data
+		USB_EP_FALRM(i) = (i == 0 ? 0 : ep->wMaxPacketSize - 1);
+		D3("ep%d_falrm %08x\n",i,USB_EP_FALRM(i));		
+	}
+}
+
+void imx_usb_enable(struct imx_udc *dev)
+{
+	USB_CTRL |= USB_FE_ENA | USB_AFE_ENA; 
+}
+
+int imx_ep_empty(struct imx_ep *ep)
+{
+	return (USB_EP_FSTAT(EP_NO(ep)) & USB_EMPTY);
+}
+
+unsigned imx_fifo_bcount(struct imx_ep *ep)
+{
+	return ((USB_EP_STAT(EP_NO(ep)) & USB_FIFO_BCOUNT) >> 16);
+}
+
+void imx_flush(struct imx_ep *ep)
+{
+	USB_EP_STAT(EP_NO(ep)) |= USB_FLUSH;
+}
+
+void imx_force_stall(struct imx_ep *ep)
+{
+ 	unsigned i;
+
+	imx_flush(ep);
+
+	USB_EP_STAT(EP_NO(ep)) |= USB_STALL;
+
+ 	for (i = 0; i < 1000; i += 20) {
+ 		if (!(USB_EP_STAT(EP_NO(ep)) & USB_STALL))
+ 			break;
+ 		udelay(20);
+ 	}
+	if (i == 1000)
+		D3("Non finished stall on %s\n",ep->ep.name);
+}
+
+void ep0_chg_stat(const char* label, struct imx_udc *dev, enum ep0_state stat)
+{
+	if (dev->ep0state == stat)
+		return;
+
+	dev->ep0state = stat;
+
+	D(label,"FROM %15s TO %15s\n",state_name[old],state_name[stat]);
+} 
+
+
+/* ---------------------------------------------------------------------------
+ * 	endpoint related parts of the api to the usb controller hardware,
+ *	used by gadget driver; and the inner talker-to-hardware core.
+ * ---------------------------------------------------------------------------
+ */
+
+static void imx_ep_fifo_flush (struct usb_ep *ep);
+static void nuke (struct imx_ep *, int status);
+
+/*
+ * endpoint enable/disable
+ *
+ * we need to verify the descriptors used to enable endpoints.  since imx
+ * endpoint configurations are fixed, and are pretty much always enabled,
+ * there's not a lot to manage here.
+ *
+ */
+static int imx_ep_enable (struct usb_ep *_ep,
+			  const struct usb_endpoint_descriptor *desc)
+{
+	struct imx_ep	*ep = container_of(_ep, struct imx_ep, ep);
+	struct imx_udc       *dev;
+	unsigned long flags;
+
+	if (!_ep || !desc || ep->desc || _ep->name == ep0name
+	    || desc->bDescriptorType != USB_DT_ENDPOINT
+	    || ep->bEndpointAddress != desc->bEndpointAddress
+	    || ep->wMaxPacketSize < le16_to_cpu
+	    (desc->wMaxPacketSize)) {
+		D1("%s, bad ep or descriptor\n", __FUNCTION__);
+		return -EINVAL;
+	}
+
+	/* xfer types must match, except that interrupt ~= bulk */
+	if (ep->bmAttributes != desc->bmAttributes
+	    && ep->bmAttributes != USB_ENDPOINT_XFER_BULK
+	    && desc->bmAttributes != USB_ENDPOINT_XFER_INT) {
+		D1("%s, %s type mismatch\n", __FUNCTION__, _ep->name);
+		return -EINVAL;
+	}
+
+	/* hardware _could_ do smaller, but driver doesn't */
+	if ((desc->bmAttributes == USB_ENDPOINT_XFER_BULK
+	     && le16_to_cpu (desc->wMaxPacketSize)
+	     != BULK_MAX_SIZE)
+	    || !desc->wMaxPacketSize) {
+		D1("%s, bad %s maxpacket\n", __FUNCTION__, _ep->name);
+		return -ERANGE;
+	}
+
+	dev = ep->dev;
+	if (!dev->driver || dev->gadget.speed == USB_SPEED_UNKNOWN) {
+		D1("%s, bogus device state\n", __FUNCTION__);
+		return -ESHUTDOWN;
+	}
+
+	local_irq_save(flags);
+
+	ep->desc = desc;
+	ep->stopped = 0;
+	ep->irqs = 0;
+	ep->ep.maxpacket = le16_to_cpu (desc->wMaxPacketSize);
+
+	/* flush fifo (mostly for OUT buffers) */
+	imx_flush(ep);
+
+	local_irq_restore(flags);
+
+	D1("enabled %s\n", _ep->name);
+	return 0;
+}
+
+static int imx_ep_disable (struct usb_ep *_ep)
+{
+	struct imx_ep	*ep = container_of(_ep, struct imx_ep, ep);
+	unsigned long flags;
+
+	if (!_ep || !ep->desc) {
+		D1("%s, %s not enabled\n", __FUNCTION__,
+		   _ep ? ep->ep.name : NULL);
+		return -EINVAL;
+	}
+	nuke (ep, -ESHUTDOWN);
+
+	local_irq_save(flags);
+
+	/* flush fifo (mostly for IN buffers) */
+	imx_flush(ep);
+
+	ep->desc = 0;
+	ep->stopped = 1;
+
+	local_irq_restore(flags);
+
+	D1("%s disabled\n", _ep->name);
+	return 0;
+}
+
+/*-------------------------------------------------------------------------*/
+
+/*
+ * 	imx_ep_alloc_request - allocate a request data structure
+ */
+static struct usb_request *
+imx_ep_alloc_request (struct usb_ep *_ep, gfp_t gfp_flags)
+{
+	struct imx_request *req;
+
+	req = kmalloc (sizeof *req, gfp_flags);
+	if (!req)
+		return 0;
+
+	memset (req, 0, sizeof *req);
+	INIT_LIST_HEAD (&req->queue);
+	return &req->req;
+}
+
+
+/*
+ * 	imx_ep_free_request - deallocate a request data structure
+ */
+static void
+imx_ep_free_request (struct usb_ep *_ep, struct usb_request *_req)
+{
+	struct imx_request	*req;
+
+	req = container_of (_req, struct imx_request, req);
+	if (!req) {
+		D1("req doesn't exist\n");
+		return;
+	}
+	WARN_ON (!list_empty (&req->queue));
+	kfree(req);
+}
+
+/*-------------------------------------------------------------------------*/
+
+/*
+ *	done - retire a request; caller blocked irqs
+ */
+static void done(struct imx_ep *ep, struct imx_request *req, int status)
+{
+	unsigned		stopped = ep->stopped;
+
+	list_del_init(&req->queue);
+
+	if (likely (req->req.status == -EINPROGRESS))
+		req->req.status = status;
+	else
+		status = req->req.status;
+
+	if (status && status != -ESHUTDOWN) 
+		D3("complete %s req %p stat %d len %u/%u\n",
+		   ep->ep.name, &req->req, status,
+		   req->req.actual, req->req.length);
+
+	/* don't modify queue heads during completion callback */
+	ep->stopped = 1;
+	req->req.complete(&ep->ep, &req->req);
+	ep->stopped = stopped;
+}
+
+
+static inline void ep0_idle (const char* label, struct imx_udc *dev)
+{
+	ep0_chg_stat(label, dev, EP0_IDLE);
+}
+
+static int
+write_packet(struct imx_ep *ep, struct imx_request *req)
+{
+	u8		*buf,tmp;
+	unsigned	length, count;
+
+	buf = req->req.buf + req->req.actual;
+	prefetch(buf);
+
+	/* how big will this packet be? */
+	length = min(req->req.length - req->req.actual, ep->wMaxPacketSize);
+
+	if (imx_fifo_bcount(ep)+length > ep->fifosize) {
+		D4("packet overfill %s fifo, wait.. \n",ep->ep.name);
+		return -1;
+	}
+
+	req->req.actual += length;
+
+	count = length;
+	D4("count<%d>, fifo remain<%d>\n",count,
+	   ep->fifosize - imx_fifo_bcount(ep));
+	
+	if (!count && req->req.zero) {/* zero byte */ 
+		USB_EP_STAT(EP_NO(ep)) |= USB_ZLPS;
+		D3("zero packet\n");
+		return 0;
+	}
+
+//FIXME: find better solution
+#if 0
+	u32		*tmp1;
+	while (count >> 2) {
+		if (count == 4) {
+			USB_EP_FCTRL(EP_NO(ep)) = USB_EP_FCTRL(EP_NO(ep)) | USB_WFR;
+			//D4("end of frame marked\n");
+		}
+		tmp1 = (u32*)buf;
+		USB_EP_FDAT(EP_NO(ep)) = cpu_to_be32(*tmp1);
+		count -= 4;
+		buf += 4;
+		D4("byte wrote<%08x>;fifo count <%d>\n",
+		   *tmp1,imx_fifo_bcount(ep));
+	}
+#endif
+	while (count--){
+		if (imx_fifo_bcount(ep)==ep->fifosize)
+			D3("fifo reach limit\n");
+
+		if (count == 0/* last byte */) {
+			USB_EP_FCTRL(EP_NO(ep)) |= USB_WFR;
+			//D4("end of frame marked\n");
+		}
+		tmp = *buf++;
+		USB_EP_FDAT0(EP_NO(ep)) = tmp; //byte write action
+		D4("byte written<%02x>;fstat<%08x>;fifo count <%d>\n",
+		   tmp,USB_EP_FSTAT(EP_NO(ep)),imx_fifo_bcount(ep));
+	}
+
+	return length;
+}
+
+static int write_fifo (struct imx_ep *ep, struct imx_request *req)
+{
+	int	count;
+	int		is_short;
+
+	is_short = 0;
+	D3("write %s req %08x, fifo stat<%08x>\n",
+	   ep->ep.name,req,USB_EP_FSTAT(EP_NO(ep)));
+	while(!is_short)
+	{
+		D4("fifostat<%08x> fifoctrl<%08x> fcount<%d>\n",
+		   USB_EP_FSTAT(EP_NO(ep)),
+		   USB_EP_FCTRL(EP_NO(ep)),
+		   imx_fifo_bcount(ep));
+
+		count = write_packet(ep, req);
+		if (count < 0) return is_short; /* busy */
+		//if (count < 0) continue; /* busy */
+		ep->dev->stats.write.bytes += count;
+
+		/* last packet "must be" short (or a zlp) */
+		is_short = (count != ep->wMaxPacketSize);
+
+		D4("%s %d bytes %d left\n",ep->ep.name,count,
+		   req->req.length - req->req.actual);
+	}
+
+	/* return whenever short packet sent, or fifo full*/
+	return is_short;
+}
+
+static inline void ep0start(struct imx_udc *dev, u32 flags, const char *tag)
+{
+	USB_EP_STAT(0) = USB_EP_STAT(0) | flags;
+	D2("ep0 change status to %s\n",tag);
+}
+
+static int write_ep0_fifo (struct imx_ep *ep, struct imx_request *req)
+{
+	int	count;
+	int		is_short;
+
+	is_short = 0;
+	while(!is_short)
+	{
+		D4("fifostat<%08x> fifoctrl<%08x> fcount<%d>\n",
+		   USB_EP_FSTAT(EP_NO(ep)),
+		   USB_EP_FCTRL(EP_NO(ep)),
+		   imx_fifo_bcount(ep));
+
+		count = write_packet(ep, req);
+		if (count < 0) break;
+		ep->dev->stats.write.bytes += count;
+
+		/* last packet "must be" short (or a zlp) */
+		is_short = (count != ep->wMaxPacketSize);
+
+		D4("ep0in %d bytes %d left %p\n", count,
+		   req->req.length - req->req.actual, req);
+
+		if (is_short) {
+			done (ep, req, 0);
+			//dump_ep_stat(__FUNCTION__,ep);
+			if (EP_NO(ep) == 0)
+				ep0_idle(__FUNCTION__,ep->dev);
+		}
+	}
+
+	/* return whenever short packet sent, or fifo full*/
+	return is_short;
+}
+
+
+static int
+read_fifo (struct imx_ep *ep, struct imx_request *req)
+{
+	u32 tmp;
+	int i,last;
+
+	D3("read %s fifo, fifo count<%d> fifo stat<%08x>\n",
+		ep->ep.name,imx_fifo_bcount(ep),USB_EP_FSTAT(EP_NO(ep)));
+	for (;;)
+	{
+		u8		*buf,byte;
+		unsigned	bufferspace, count, is_short;
+
+		if (!(USB_EP_FSTAT(EP_NO(ep)) & USB_FR)) {
+			/* 
+			   special care! I.MX seem to ignore some EOF, so
+			   ensure there is no data in fifo and then out
+			*/	
+			if (imx_fifo_bcount(ep)) continue;
+			D3("no frame ready, fstat<%08x>\n",
+			   USB_EP_FSTAT(EP_NO(ep)));
+
+			return 0;
+		}
+		buf = req->req.buf + req->req.actual;
+		prefetchw(buf);
+		bufferspace = req->req.length - req->req.actual;
+
+		/* read all bytes from this packet */
+		if (!(USB_EP_FSTAT(EP_NO(ep)) & USB_EMPTY)) {
+			//check for available data
+			count = min(imx_fifo_bcount(ep),ep->wMaxPacketSize);
+			req->req.actual += min (count, bufferspace);
+		} else {/* zlp */
+			count = 0;
+		}
+		is_short = (count < ep->wMaxPacketSize);
+		D4("read %s , %d bytes%s req %p %d/%d\n",
+		   ep->ep.name, count,
+		   is_short ? "/S" : "",
+		   req, req->req.actual, req->req.length);
+		i=0;
+		last=0;
+		while (count>0) {
+#if 0
+			if (!(i%4))
+				//tmp = be32_to_cpu(USB_EP_FDAT(EP_NO(ep)));
+				tmp = (USB_EP_FDAT(EP_NO(ep)));
+#else
+			if (last && i) {
+				/* 
+				   IMX seem not clear the frame bit if it does
+				   not perform a dump read so if it is the
+				   start of frame, it will be misunderstood
+				*/
+				//D2("host sent frame smaller than the req ,"
+				//	"req remain %d\n",count);
+				//is_short = 1;
+				//break;
+			}
+			if ((USB_EP_FSTAT(EP_NO(ep)) & USB_FRAME_STAT) ==
+			    0x08000000) {
+				last=1;
+			}
+#if 0
+			int t=imx_fifo_bcount(ep);
+#endif
+			D4("frame boundary stat <%08x>\n",
+			   USB_EP_FSTAT(EP_NO(ep)));
+			tmp = (USB_EP_FDAT0(EP_NO(ep)));
+#if 0
+			if (imx_fifo_bcount(ep)-t > 1) 
+				D1("byte read but fifo count drop %d bytes\n",
+				   t);
+#endif
+#endif
+
+			if (unlikely (bufferspace == 0)) {
+				/* this happens when the driver's buffer
+				 * is smaller than what the host sent.
+				 * discard the extra data.
+				 */
+				if (req->req.status != -EOVERFLOW)
+					D1("%s overflow %d\n",
+					   ep->ep.name, count);
+				req->req.status = -EOVERFLOW;
+			} else {
+				byte = (u8) tmp&0xFF;
+				*buf++ = byte;
+				bufferspace--;
+				tmp = tmp >> 8;
+			}
+			i++;
+			count--;
+		}
+
+		/* completion */
+		if (is_short || req->req.actual == req->req.length) {
+			if (imx_fifo_bcount(ep) > 0)
+				D3("complete read but fifo byte count %d\n",
+				   imx_fifo_bcount(ep));
+			return 1;
+		}
+
+		/* finished that packet.  the next one may be waiting... */
+	}
+
+	return 0;
+}
+
+#if 0
+static int
+read_ep0_fifo (struct imx_ep *ep, struct imx_request *req)
+{
+	u8		*buf, byte;
+	unsigned	bufferspace;
+
+	POSITION;
+	buf = req->req.buf + req->req.actual;
+	bufferspace = req->req.length - req->req.actual;
+
+	if (USB_EP_FSTAT(EP_NO(ep)) & USB_EMPTY) {
+		return -1;
+	}
+	{
+		//FIXME: check frame boundary
+		//if ((USB_EP_FSTAT(EP_NO(ep))&USB_FRAME_STAT)== 0xF)	
+	
+		byte = USB_EP_FDAT3(EP_NO(ep));
+		if (bufferspace == 0) {
+			/* this happens when the driver's buffer
+			 * is smaller than what the host sent.
+			 * discard the extra data.
+			 */
+			if (req->req.status != -EOVERFLOW)
+				D1("%s overflow\n", ep->ep.name);
+			req->req.status = -EOVERFLOW;
+		} else {
+			*buf++ = byte;
+			req->req.actual++;
+			bufferspace--;
+		}
+	}
+
+	/* completion */
+	if (req->req.actual >= req->req.length)
+		return 1;
+
+	/* finished that packet.  the next one may be waiting... */
+	return 0;
+}
+#endif
+
+/*-------------------------------------------------------------------------*/
+
+
+
+static int
+imx_ep_queue(struct usb_ep *_ep, struct usb_request *_req, gfp_t gfp_flags)
+{ 
+	struct imx_ep	*ep = container_of(_ep, struct imx_ep, ep);
+	struct imx_request	*req =
+		container_of(_req, struct imx_request, req);
+	struct imx_udc		*dev;
+	unsigned long		flags;
+
+	if (ep->dev->set_config && EP_NO(ep) == 0){
+		/*
+		  Special care on IMX udc.
+		  Ignore enqueue when after set configuration from the
+		  host. This assume all gadget drivers reply set
+		  configuration with the next ep0 req enqueue.
+		*/
+		local_irq_save(flags);
+		ep->dev->set_config = 0;
+		local_irq_restore(flags);
+		D2("gadget driver reply set configuration\n");
+			return 0;
+	}
+	if (!_req || !_req->complete || !_req->buf
+	    || !list_empty(&req->queue)
+		) {
+		D1("%s, bad params\n", __FUNCTION__);
+		return -EINVAL;
+	}
+
+	if (unlikely (!_ep || (!ep->desc && ep->ep.name != ep0name))) {
+		D1("%s, bad ep\n", __FUNCTION__);
+		return -EINVAL;
+	}
+
+	dev = ep->dev;
+	if (!dev->driver || dev->gadget.speed == USB_SPEED_UNKNOWN) {
+		D1("%s, bogus device state\n", __FUNCTION__);
+		return -ESHUTDOWN;
+	}
+
+//	D3( "%s queue req %p, len %d buf %p\n",
+//	     _ep->name, _req, _req->length, _req->buf);
+
+	if ((ep->dev->ep0state==EP0_IN_DATA_PHASE && EP_NO(ep) == 0)
+	    || (EP_NO(ep) != 0 && EP_DIR(ep) == EP_DIR_IN))
+		dump_req(_req);
+
+	local_irq_save(flags);
+
+	_req->status = -EINPROGRESS;
+	_req->actual = 0;
+
+	/* kickstart this i/o queue? */
+	if (list_empty(&ep->queue) && !ep->stopped)
+	{
+		if (EP_NO(ep) == 0 /* ep0 */) {
+			unsigned	length = _req->length;
+
+			switch (dev->ep0state) {
+			case EP0_IN_DATA_PHASE:
+				dev->stats.write.ops++;
+				if (write_ep0_fifo(ep, req)) {
+					req = 0;
+					/* Enable SOF for setting CMD_OVER. */
+					USB_MASK &= ~USB_SOF;
+				}
+				break;
+
+			case EP0_OUT_DATA_PHASE:
+				dev->stats.read.ops++;
+				/* 
+				   read_ep0 should handle read zero packet
+				   instead serve here
+				*/
+				if (length == 0 || read_fifo(ep, req)) {
+					ep0_idle(__FUNCTION__,dev);
+					done(ep, req, 0);
+					req = 0;
+				}
+				break;
+
+			default:
+				D1("ep0 i/o, odd state %d\n", dev->ep0state);
+				local_irq_restore (flags);
+				return -EL2HLT;
+			}
+		}
+		else if (EP_DIR(ep) == EP_DIR_IN
+			 /* can the FIFO satisfy the request immediately? */
+			 && write_fifo(ep, req)) {
+//			D3("%s req<%08x> completed\n",ep->ep.name,req);
+			done (ep, req, 0);
+			req = 0;
+			//dump_ep_stat(__FUNCTION__,ep);
+		} else if (EP_DIR(ep) == EP_DIR_OUT && (read_fifo(ep, req))) {
+//			D3("%s req<%08x> completed\n",ep->ep.name,req);
+			done (ep, req, 0);
+			req = 0;
+			//dump_ep_stat(__FUNCTION__,ep);
+		}
+	}
+
+	if (req != 0) {
+		list_add_tail(&req->queue, &ep->queue);
+//		D3("%s not finish req %p %d bytes %d left\n",ep->ep.name,req,
+//			req->req.actual,req->req.length - req->req.actual);
+	}
+//	D3("End of enqueue\n");
+
+	local_irq_restore(flags);
+
+	return 0;
+}
+
+
+/*
+ * 	nuke - dequeue ALL requests
+ */
+static void nuke(struct imx_ep *ep, int status)
+{
+	struct imx_request *req;
+	while (!list_empty(&ep->queue)) {
+		req = list_entry(ep->queue.next, struct imx_request, queue);
+		done(ep, req, status);
+	}
+}
+
+/*
+ *	dequeue JUST ONE request
+ */
+static int imx_ep_dequeue(struct usb_ep *_ep, struct usb_request *_req)
+{
+
+	struct imx_ep	*ep = container_of(_ep, struct imx_ep, ep);
+	struct imx_request	*req;
+	unsigned long		flags;
+
+	if (!_ep || ep->ep.name == ep0name)
+		return -EINVAL;
+
+	local_irq_save(flags);
+
+	/* make sure it's actually queued on this endpoint */
+	list_for_each_entry (req, &ep->queue, queue) {
+		if (&req->req == _req)
+			break;
+	}
+	if (&req->req != _req) {
+		local_irq_restore(flags);
+		return -EINVAL;
+	}
+
+	done(ep, req, -ECONNRESET);
+
+	local_irq_restore(flags);
+	return 0;
+
+}
+
+/*-------------------------------------------------------------------------*/
+
+static int imx_ep_set_halt(struct usb_ep *_ep, int value)
+{
+	struct imx_ep	*ep = container_of(_ep, struct imx_ep, ep);
+	unsigned long		flags;
+
+	if (unlikely (!_ep
+		      || (!ep->desc && ep->ep.name != ep0name))
+	    || ep->bmAttributes == USB_ENDPOINT_XFER_ISOC) {
+		D1("%s, bad ep\n", __FUNCTION__);
+		return -EINVAL;
+	}
+
+	local_irq_save(flags);
+
+	if ((ep->bEndpointAddress & USB_DIR_IN) != 0
+	    && (!list_empty(&ep->queue))) {
+		local_irq_restore(flags);
+		return -EAGAIN;
+	}
+
+	//TODO: flush ep fifo
+
+	/* ep0 needs special care */
+	if (EP_NO(ep) == 0) {
+		/* XXX Force protocol stall? */
+		ep0_chg_stat(__FUNCTION__,ep->dev,EP0_STALL);
+		/* and bulk/intr endpoints like dropping stalls too */
+ 	} else {
+		imx_force_stall(ep);
+  	}
+	local_irq_restore(flags);
+
+	D1("%s halt\n", _ep->name);
+	return 0;
+}
+
+static int imx_ep_fifo_status(struct usb_ep *_ep)
+{
+	struct imx_ep	*ep = container_of(_ep, struct imx_ep, ep);
+
+	if (!_ep) {
+		D1("%s, bad ep\n", __FUNCTION__);
+		return -ENODEV;
+	}
+	if (ep->dev->gadget.speed == USB_SPEED_UNKNOWN)
+		return 0;
+	else
+		return imx_fifo_bcount(ep);
+}
+
+static void imx_ep_fifo_flush(struct usb_ep *_ep)
+{
+	struct imx_ep	*ep = container_of(_ep, struct imx_ep, ep);
+
+	if (!_ep || ep->ep.name == ep0name || !list_empty(&ep->queue)) {
+		D1("%s, bad ep\n", __FUNCTION__);
+		return;
+	}
+
+	/* toggle and halt bits stay unchanged */
+	imx_flush(ep);
+}
+
+static struct usb_ep_ops imx_ep_ops = {
+	.enable		= imx_ep_enable,
+	.disable	= imx_ep_disable,
+
+	.alloc_request	= imx_ep_alloc_request,
+	.free_request	= imx_ep_free_request,
+
+	.queue		= imx_ep_queue,
+	.dequeue	= imx_ep_dequeue,
+
+	.set_halt	= imx_ep_set_halt,
+	.fifo_status	= imx_ep_fifo_status,
+	.fifo_flush	= imx_ep_fifo_flush,
+};
+
+static int imx_udc_get_frame(struct usb_gadget *_gadget)
+{
+	return 0;
+}
+
+static int imx_udc_wakeup(struct usb_gadget *_gadget)
+{
+	return 0;
+}
+
+static const struct usb_gadget_ops imx_udc_ops = {
+	.get_frame	 = imx_udc_get_frame,
+	.wakeup		 = imx_udc_wakeup,
+	// I.MX udc must always be self-powered
+};
+
+/*-------------------------------------------------------------------------*/
+
+/*
+ * "function" sysfs attribute
+ */
+static ssize_t show_function (struct device *_dev,
+			      struct device_attribute *attr, char *buf)
+{
+	struct imx_udc	*dev = dev_get_drvdata (_dev);
+
+	if (!dev->driver
+	    || !dev->driver->function
+	    || strlen (dev->driver->function) > PAGE_SIZE)
+		return 0;
+	return scnprintf (buf, PAGE_SIZE, "%s\n", dev->driver->function);
+}
+static DEVICE_ATTR (function, S_IRUGO, show_function, NULL);
+
+/*-------------------------------------------------------------------------*/
+
+/*
+ * 	udc_disable - disable USB device controller
+ */
+static void udc_disable(struct imx_udc *dev)
+{
+	imx_usb_disable(dev);
+	ep0_idle (__FUNCTION__,dev);
+	dev->gadget.speed = USB_SPEED_UNKNOWN;
+}
+
+/*
+ * 	udc_reinit - initialize software state
+ */
+static void udc_reinit(struct imx_udc *dev)
+{
+	u32	i;
+
+	/* device/ep0 records init */
+	INIT_LIST_HEAD (&dev->gadget.ep_list);
+	INIT_LIST_HEAD (&dev->gadget.ep0->ep_list);
+	ep0_chg_stat(__FUNCTION__,dev,EP0_IDLE);
+
+	/* basic endpoint records init */
+	for (i = 0; i < IMX_USB_NB_EP; i++) {
+		struct imx_ep *ep = &dev->ep[i];
+
+		if (i != 0)
+			list_add_tail (&ep->ep.ep_list, &dev->gadget.ep_list);
+
+		ep->desc = 0;
+		ep->stopped = 0;
+		INIT_LIST_HEAD (&ep->queue);
+		ep->irqs = 0;
+	}
+}
+
+static void udc_hardinit(struct imx_udc *dev)
+{
+	int ret;
+	imx_usb_gpio();
+
+	imx_usb_reset(dev);
+	
+	ret = imx_download_conf(dev);
+	if (ret)
+		D1("download conf return <%d>\n",ret);
+	
+	/* Set desired interrupt mask */
+	imx_init_intr();
+
+	/* Set EP status: direction , type, max ps */
+	imx_init_ep(dev);
+	
+	/* Set fifo register: frame mode, granularity, alarm level */
+	imx_init_fifo(dev);
+
+	/* Wait for reset signalling to stop. */
+	do {} while (USB_INTR & USB_RESET_STOP);
+}
+
+/*
+ * until it's enabled, this UDC should be completely invisible
+ * to any USB host.
+ */
+static void udc_enable (struct imx_udc *dev)
+{
+	imx_usb_enable(dev);
+	dev->gadget.speed = USB_SPEED_FULL;
+}
+
+
+/* when a driver is successfully registered, it will receive
+ * control requests including set_configuration(), which enables
+ * non-control requests.  then usb traffic follows until a
+ * disconnect is reported.  then a host may connect again, or
+ * the driver might get unbound.
+ */
+int usb_gadget_register_driver(struct usb_gadget_driver *driver)
+{
+	struct imx_udc	*dev = the_controller;
+	int			retval;
+
+	if (!driver
+	    || driver->speed != USB_SPEED_FULL
+	    || !driver->bind
+	    || !driver->unbind
+	    || !driver->disconnect
+	    || !driver->setup)
+		return -EINVAL;
+	if (!dev)
+		return -ENODEV;
+	if (dev->driver)
+		return -EBUSY;
+
+	/* first hook up the driver ... */
+	dev->driver = driver;
+	dev->gadget.dev.driver = &driver->driver;
+
+	device_add (&dev->gadget.dev);
+	retval = driver->bind(&dev->gadget);
+	if (retval) {
+		D1("bind to driver %s --> error %d\n",
+		   driver->driver.name, retval);
+		device_del (&dev->gadget.dev);
+
+		dev->driver = 0;
+		dev->gadget.dev.driver = 0;
+		return retval;
+	}
+	device_create_file(dev->dev, &dev_attr_function);
+
+	/* ... then enable host detection and ep0; and we're ready
+	 * for set_configuration as well as eventual disconnect.
+	 * NOTE:  this shouldn't power up until later.
+	 */
+	D1("registered gadget driver '%s'\n", driver->driver.name);
+	udc_disable(dev);
+	udc_reinit(dev);
+	udc_enable(dev);
+	dev->dev_config = 0;
+	dev->dev_interface = 0;
+	dev->dev_altsetting = 0;
+	dump_state(__FUNCTION__);
+	return 0;
+}
+EXPORT_SYMBOL(usb_gadget_register_driver);
+
+static void
+stop_activity(struct imx_udc *dev, struct usb_gadget_driver *driver)
+{
+	int i;
+	if (dev->gadget.speed == USB_SPEED_UNKNOWN)
+		driver = 0;
+	/* prevent new request submissions, kill any outstanding requests  */
+	for (i = 0; i < IMX_USB_NB_EP; i++) {
+		struct imx_ep *ep = &dev->ep[i];
+
+		imx_flush(ep);
+		ep->stopped = 1;
+		nuke(ep, -ESHUTDOWN);
+	}
+	dev->gadget.speed = USB_SPEED_UNKNOWN;
+	if (driver) {
+		dev->dev_config = 0;
+		dev->dev_interface = 0;
+		dev->dev_altsetting = 0;
+		driver->disconnect(&dev->gadget);
+	}
+
+	/* re-init driver-visible data structures */
+	udc_reinit(dev);
+}
+
+int usb_gadget_unregister_driver(struct usb_gadget_driver *driver)
+{
+	struct imx_udc	*dev = the_controller;
+
+	if (!dev)
+		return -ENODEV;
+	if (!driver || driver != dev->driver)
+		return -EINVAL;
+
+	local_irq_disable();
+	udc_disable(dev);
+	stop_activity(dev, driver);
+	local_irq_enable();
+
+	driver->unbind(&dev->gadget);
+	dev->driver = 0;
+
+	device_del (&dev->gadget.dev);
+	device_remove_file(dev->dev, &dev_attr_function);
+
+	D1("unregistered gadget driver '%s'\n", driver->driver.name);
+	dump_state(__FUNCTION__);
+	return 0;
+}
+EXPORT_SYMBOL(usb_gadget_unregister_driver);
+
+
+/*-------------------------------------------------------------------------*/
+
+static int handle_ep0 (struct imx_udc *dev)
+{
+	int i;
+	struct imx_ep	*ep = &dev->ep [0];
+	struct imx_request	*req=0;
+	union {
+		struct usb_ctrlrequest	r;
+		u8			raw [8];
+		u32			word [2];
+	} u;
+
+	if (!list_empty(&ep->queue))
+		req = list_entry(ep->queue.next, struct imx_request, queue);
+
+	/* previous request unfinished?  non-error iff back-to-back ... */
+	if ((USB_EP_INTR(EP_NO(ep)) & (USB_DEVREQ | USB_MDEVREQ)) &&
+	    dev->ep0state != EP0_IDLE) {
+		nuke(ep, 0);
+		ep0_idle(__FUNCTION__,dev);
+	}
+
+	switch (dev->ep0state) {
+	case EP0_STALL:	//stall is clear by hardware auto
+		ep0_chg_stat(__FUNCTION__,dev,EP0_IDLE);
+		//dev->ep0state = EP0_IDLE;
+	case EP0_IDLE:
+		/* start control request? */
+
+		/* Discard the first setup packet if a second is queued. */
+		if (USB_EP_INTR(EP_NO(ep)) & USB_MDEVREQ) {
+		    u.word[0] = USB_EP_FDAT(EP_NO(ep));
+		    u.word[1] = USB_EP_FDAT(EP_NO(ep));
+		    D2("discarding SETUP %02x.%02x v%04x i%04x l%04x\n",
+		       u.r.bRequestType, u.r.bRequest, u.r.wValue, u.r.wIndex,
+		       u.r.wLength);
+		}
+
+		if (!(USB_EP_INTR(EP_NO(ep)) & USB_DEVREQ))
+			break;
+
+		nuke (ep, -EPROTO);
+		/* read SETUP packet */
+		for (i = 0; i < 2; i++) {
+			if (imx_ep_empty(ep)) {
+			bad_setup:
+				D1("SETUP %d!\n", i);
+
+				le16_to_cpus (&u.r.wValue);
+				le16_to_cpus (&u.r.wIndex);
+				le16_to_cpus (&u.r.wLength);
+				D2("SETUP %02x.%02x v%04x i%04x l%04x\n",
+				   u.r.bRequestType, u.r.bRequest,
+				   u.r.wValue, u.r.wIndex, u.r.wLength);
+				goto stall;
+			}
+			u.word[i] = USB_EP_FDAT(EP_NO(ep));
+			ep->dev->stats.read.bytes += 4;
+			//u.word[i] = be32_to_cpu(tmp);
+		}
+		if (!imx_ep_empty(ep))
+			goto bad_setup;
+
+		le16_to_cpus (&u.r.wValue);
+		le16_to_cpus (&u.r.wIndex);
+		le16_to_cpus (&u.r.wLength);
+		ep->dev->stats.read.ops++;
+
+		USB_INTR = USB_DEVREQ | USB_EOF;
+
+		D2("SETUP %02x.%02x v%04x i%04x l%04x\n",
+		   u.r.bRequestType, u.r.bRequest,
+		   u.r.wValue, u.r.wIndex, u.r.wLength);
+
+		if (dev->set_config) {
+			/* if set config is pending , we must NACK the host
+			   by using CMDOVER
+			*/
+			USB_CTRL = USB_CTRL | USB_CMDOVER;
+			D2("set config req is pending,NACK the host\n");
+			return 0;
+		}
+
+		if (u.r.bRequestType & USB_DIR_IN)
+			ep0_chg_stat(__FUNCTION__, dev, EP0_IN_DATA_PHASE);
+		else
+			ep0_chg_stat(__FUNCTION__, dev, EP0_OUT_DATA_PHASE);
+
+		if (!dev->driver) 
+			i = -1;
+		else
+			i = dev->driver->setup(&dev->gadget, &u.r);
+		if (i < 0) {
+			D1("protocol STALL, "
+			   "ep0 err %d\n", i);
+		stall:
+			/* Force a protocol stall. */
+			USB_CTRL |= USB_CMDOVER | USB_CMDERROR;
+			do { } while (USB_CTRL & USB_CMDOVER);
+			USB_CTRL &= ~USB_CMDERROR;
+
+			ep0_chg_stat(__FUNCTION__,dev,EP0_STALL);
+			//dump_ep_stat(__FUNCTION__,ep);
+		}
+
+		break;
+
+	case EP0_IN_DATA_PHASE:			/* GET_DESCRIPTOR etc */
+		if (req) {
+			/* this IN packet might finish the request */
+			if (write_ep0_fifo(ep, req)) {
+				/* Enable SOF for setting CMD_OVER. */
+				USB_MASK &= ~USB_SOF;
+				//done(ep,req,0);
+				//ep0_idle(__FUNCTION__,dev);
+			}
+		} /* else IN token before response was written */
+		break;
+	case EP0_OUT_DATA_PHASE:		/* SET_DESCRIPTOR etc */
+		if (req) {
+			/* this OUT packet might finish the request */
+			if (read_fifo(ep, req)) {
+				done(ep, req, 0);
+				ep0_idle(__FUNCTION__,dev);
+			}
+			/* else more OUT packets expected */
+		} /* else OUT token before read was issued */
+		break;
+	}
+	return 0;
+}
+
+/* XXX What's the point of the return value? Only returns 0. */
+static int handle_ep(struct imx_ep *ep)
+{
+	struct imx_request	*req;
+	int			completed;
+
+	do {
+		completed = 0;
+		if (!list_empty(&ep->queue))
+			req = list_entry(ep->queue.next,
+					 struct imx_request, queue);
+		else {
+			D3("no request on %s\n",ep->ep.name);
+			return 0;
+		}
+
+		if (EP_DIR(ep) == EP_DIR_IN) {	/* to host */
+			completed = write_fifo(ep, req);
+#if 0
+			//write fifo need to wait for available of the fifo,
+			//so if not completed, just go out and wait eof/eot
+			if (!completed)
+				return 0;
+#endif
+
+		} else {	/* to device */
+			/* fifos can hold packets, ready for reading... */
+			completed = read_fifo(ep, req);
+		}
+		D3("%s req<%08x> %s\n",ep->ep.name,req,
+		   completed?"completed":"not completed");
+		if (completed) {
+			done (ep, req, 0);
+			//dump_ep_stat(__FUNCTION__,ep);
+			if (EP_NO(ep) == 0)
+				ep0_idle(__FUNCTION__,ep->dev);
+		}
+		//} while (1);
+	} while (completed);
+	return 0;
+
+}
+
+/*
+ *	imx_udc_irq - interrupt handler
+ *
+ * avoid delays in ep0 processing. the control handshaking isn't always
+ * under software control 
+ */
+static irqreturn_t
+imx_udc_irq(int irq, void *_dev)
+{
+	struct imx_udc	*dev = _dev;
+	u32 intr = USB_INTR;
+
+	dev->stats.irqs++;
+
+	if (!dev->driver) {
+		udc_disable(dev);
+		USB_INTR = intr;
+
+		return IRQ_HANDLED;
+	}
+
+	do {
+#ifdef DEBUG
+		if ((intr != USB_SOF) //reduce dumping sof-only intr
+		    && (intr != (USB_SOF | USB_FRAME_MATCH))
+		    && (intr != (USB_SOF | USB_MSOF))) {
+			dump_intr(__FUNCTION__);
+		}
+#endif
+
+		if (intr & USB_CFG_CHG){
+			struct usb_ctrlrequest u;
+			unsigned long flags;
+			int cfg, intf, alt;
+
+			/* Clear the CFG_CHG interrupt right away,
+			 * so we don't NAK the next requests. */
+			USB_INTR = USB_CFG_CHG;
+
+			D2("host set config req, USB_STAT<%08x>\n",USB_STAT);
+
+			cfg  = (USB_STAT & USB_STAT_CFG) >> 5;
+			intf = (USB_STAT & USB_STAT_INTF) >> 3;
+			alt  =  USB_STAT & USB_STAT_ALTSET;
+
+			D2("orig config #%d/%d/%d , req config #%d/%d/%d\n",
+			   dev->dev_config, dev->dev_interface,
+			   dev->dev_altsetting, cfg, intf, alt);
+
+			if (cfg != 1 && cfg != 2)
+				break; /* can not happen */
+
+			local_irq_save(flags);
+
+			/* should make it aware to gadget driver, but 
+			   some gadget drivers will perform an async on 
+			   set config req, so 2 solutions:
+			   1) disable all gadget configuration response
+			   in the gadget driver
+			   2) neglect any set_config response before enqueue 
+
+			   I choose to use (2) which don't need to change
+			   gadget driver
+			*/
+
+			dev->set_config = 0;
+
+			/* Fake a SET_CONFIGURATION request if the interface
+			 * or altsetting changed. */
+			if (dev->dev_config != cfg) {
+
+				u.bRequest = USB_REQ_SET_CONFIGURATION;
+				u.bRequestType = (USB_DIR_OUT |
+						  USB_TYPE_STANDARD |
+						  USB_RECIP_DEVICE);
+				u.wValue = cfg;
+				u.wIndex = 0;
+				u.wLength = 0;
+
+				dev->dev_config = cfg;
+				dev->set_config = 1;
+				dev->driver->setup(&dev->gadget,&u);
+			}
+
+			/* Fake a SET_INTERFACE request if the interface or
+			 * altsetting changed. */
+			if ((dev->dev_interface != intf) ||
+			    (dev->dev_altsetting != alt)) {
+
+				u.bRequest = USB_REQ_SET_INTERFACE;
+				u.bRequestType = (USB_DIR_OUT |
+						  USB_TYPE_STANDARD |
+						  USB_RECIP_INTERFACE);
+				u.wValue = alt;
+				u.wIndex = intf;
+				u.wLength = 0;
+
+				dev->dev_interface = intf;
+				dev->dev_altsetting = alt;
+				dev->set_config = 1;
+				dev->driver->setup(&dev->gadget,&u);
+			}
+
+			local_irq_restore(flags);
+			break;
+			
+		}
+
+		if (intr & USB_SOF) {
+			/* copy from motorola bsp.
+			   We must enable SOF intr and signal CMDOVER.
+			   Datasheet don't specifiy this action, but it
+			   is done in motorola bsp, so just copy it
+			*/
+			if (dev->ep0state==EP0_IDLE)
+			{
+				USB_CTRL |= USB_CMDOVER;
+				//ep0_idle(__FUNCTION__,dev);
+			}
+			USB_MASK |= USB_SOF;
+		}
+
+		if (intr & USB_WAKEUP) {
+			if (dev->gadget.speed == USB_SPEED_UNKNOWN
+			    && dev->driver
+			    && dev->driver->resume)
+				dev->driver->resume(&dev->gadget);
+			/* ignore all other interrupt */
+			USB_INTR = intr;
+			dev->set_config = 0;
+			dev->gadget.speed = USB_SPEED_FULL;
+		}
+		
+		if (intr & USB_SUSP) {
+//TODO: suspend handle
+			//stop_activity (dev, dev->driver);
+			//udc_disable(dev);
+			if (dev->gadget.speed != USB_SPEED_UNKNOWN
+			    && dev->driver
+			    && dev->driver->suspend)
+				dev->driver->suspend(&dev->gadget);
+			dev->gadget.speed = USB_SPEED_UNKNOWN;
+		}
+
+		if (intr & USB_RES){
+//TODO: resume handle
+			//udc_reinit(dev);
+			//udc_enable(dev);
+
+		}
+
+		if (intr & USB_RESET_START){
+//TODO: reset handle
+#if 1
+			stop_activity (dev, dev->driver);
+			dev->set_config = 0;
+			/* ignore all other interrupt */
+			USB_INTR = intr;
+			dev->gadget.speed = USB_SPEED_FULL;
+#endif
+		}
+
+		if (intr & USB_RESET_STOP) {
+#if 1
+			dev->gadget.speed = USB_SPEED_FULL;
+			//dev->driver->resume(&dev->gadget);
+#endif
+		}
+
+		break; /* XXX This bypasses the following while(1). */
+
+	} while (1);
+	USB_INTR= intr;
+
+	return IRQ_HANDLED;
+}
+
+/*
+ *	imx_udc_ctrl_irq - interrupt handler
+ */
+static irqreturn_t imx_udc_ctrl_irq(int irq, void *_dev)
+{
+	struct imx_udc	*dev = _dev;
+	u32 intr;
+	//unsigned long flags;
+
+	dev->stats.irqs++;
+
+	if (!dev->driver) {
+		udc_disable(dev);
+		USB_EP_INTR(0)= USB_EP_INTR(0);
+
+		return IRQ_HANDLED;
+	}
+	
+	//local_irq_save(flags);
+	intr = USB_EP_INTR(0);
+	if (intr & (USB_DEVREQ | USB_MDEVREQ)){
+		if (handle_ep0(dev) == -1) /* XXX handle_ep0() always returns 0. */
+			imx_force_stall(&dev->ep[0]);
+	} else if ((intr & USB_EOF) && (dev->ep0state != EP0_IDLE)) {
+		if (handle_ep0(dev)==-1)
+			imx_force_stall(&dev->ep[0]);
+	}
+	//local_irq_restore(flags);
+	USB_EP_INTR(0)= intr;
+	return IRQ_HANDLED;
+}
+
+/*
+ *	imx_udc_bulk_irq - interrupt handler
+ *
+ */
+static irqreturn_t imx_udc_bulk_irq(int irq, void *_dev)
+{
+	struct imx_udc	*dev = _dev;
+	struct imx_ep	*ep = &dev->ep[irq-USBD_INT0];
+	unsigned long	flags;
+	u32 ep_intr;
+
+	ep_intr = USB_EP_INTR(EP_NO(ep));
+	dev->stats.irqs++;
+
+	if (!dev->driver) {
+		udc_disable(dev);
+		USB_EP_INTR(EP_NO(ep)) = ep_intr;
+
+		return IRQ_HANDLED;
+	}
+	local_irq_save(flags);
+	if (handle_ep(ep)== -1)
+		imx_force_stall(ep);
+	local_irq_restore(flags);
+	USB_EP_INTR(EP_NO(ep)) = ep_intr;
+	
+	return IRQ_HANDLED;
+}
+
+static irqreturn_t imx_udc_int_irq(int irq, void *_dev)
+{
+	imx_udc_bulk_irq(irq,_dev);
+
+	return IRQ_HANDLED;
+}
+
+/*-------------------------------------------------------------------------*/
+
+static void nop_release (struct device *dev)
+{
+	D1("%s %s\n", __FUNCTION__, dev->bus_id);
+}
+
+/* this uses load-time allocation and initialization (instead of
+ * doing it at run-time) to save code, eliminate fault paths, and
+ * be more obviously correct.
+ */
+static struct imx_udc memory = {
+	.gadget = {
+		.ops		= &imx_udc_ops,
+		.ep0		= &memory.ep[0].ep,
+		.name		= driver_name,
+		.dev = {
+			 .bus_id		= "gadget",
+			 .release	= nop_release,
+		 },
+	},
+
+	/* control endpoint */
+	.ep[0] = {
+		 .ep = {
+			 .name		= ep0name,
+			 .ops		= &imx_ep_ops,
+			 .maxpacket	= EP0_MAX_SIZE,
+		 },
+		 .dev		= &memory,
+		 .wMaxPacketSize	= EP0_MAX_SIZE,
+		 .fifosize	= 32,
+		 .bEndpointAddress = 0,
+		 .bmAttributes	= USB_ENDPOINT_XFER_CONTROL,
+	 },
+
+	/* first group of endpoints */
+	.ep[1] = {
+		 .ep = {
+			 .name		= "ep1in-bulk",
+			 .ops		= &imx_ep_ops,
+			 .maxpacket	= BULK_MAX_SIZE,
+		 },
+		 .dev		= &memory,
+		 .wMaxPacketSize	= BULK_MAX_SIZE,
+		 .fifosize	= 64,
+		 .bEndpointAddress = USB_DIR_IN | 1,
+		 .bmAttributes	= USB_ENDPOINT_XFER_BULK,
+	 },
+	.ep[2] = {
+		 .ep = {
+			 .name		= "ep2out-bulk",
+			 .ops		= &imx_ep_ops,
+			 .maxpacket	= BULK_MAX_SIZE,
+		 },
+		 .dev		= &memory,
+		 .wMaxPacketSize	= BULK_MAX_SIZE,
+		 .fifosize	= 64,
+		 .bEndpointAddress = 2,
+		 .bmAttributes	= USB_ENDPOINT_XFER_BULK,
+	 },
+#ifndef CONFIG_USB_IMX_SMALL
+	.ep[3] = {
+		 .ep = {
+			 .name		= "ep3out-bulk",
+			 .ops		= &imx_ep_ops,
+			 .maxpacket	= BULK_MAX_SIZE/2,
+		 },
+		 .dev		= &memory,
+		 .wMaxPacketSize	= BULK_MAX_SIZE/2,
+		 .fifosize	= 32,
+		 .bEndpointAddress = 3,
+		 .bmAttributes	= USB_ENDPOINT_XFER_BULK,
+	 },
+	.ep[4] = {
+		 .ep = {
+			 .name		= "ep4in-int",
+			 .ops		= &imx_ep_ops,
+			 .maxpacket	= INT_MAX_SIZE,
+		 },
+		 .dev		= &memory,
+		 .wMaxPacketSize	= INT_MAX_SIZE,
+		 .fifosize	= 32,
+		 .bEndpointAddress = USB_DIR_IN | 4,
+		 .bmAttributes	= USB_ENDPOINT_XFER_INT,
+	 },
+	.ep[5] = {
+		 .ep = {
+			 .name		= "ep5out-int",
+			 .ops		= &imx_ep_ops,
+			 .maxpacket	= INT_MAX_SIZE,
+		 },
+		 .dev		= &memory,
+		 .wMaxPacketSize	= INT_MAX_SIZE,
+		 .fifosize	= 32,
+		 .bEndpointAddress = 5,
+		 .bmAttributes	= USB_ENDPOINT_XFER_INT,
+	 },
+
+#endif /* !CONFIG_USB_IMX_SMALL */
+};
+
+/*-------------------------------------------------------------------------*/
+
+#ifdef CONFIG_USB_GADGET_DEBUG_FILES
+
+static const char proc_node_name [] = "driver/udc";
+
+static int
+udc_proc_read(char *page, char **start, off_t off, int count,
+	      int *eof, void *_dev)
+{
+	char			*buf = page;
+	struct imx_udc	*dev = _dev;
+	char			*next = buf;
+	unsigned		size = count;
+	unsigned long		flags;
+	int			i, t;
+
+	if (off != 0)
+		return 0;
+
+	local_irq_save(flags);
+
+	/* basic device status */
+	t = scnprintf(next, size, DRIVER_DESC "\n"
+		      "%s version: %s\nGadget driver: %s\nHost %s\n",
+		      driver_name, DRIVER_VERSION,
+		      dev->driver ? dev->driver->driver.name : "(none)",
+		      is_usb_connected() ? "full speed" : "disconnected");
+	size -= t;
+	next += t;
+
+	/* registers for device and ep0 */
+	t = scnprintf(next, size,
+		      "USB_CTRL %08x, USB_STAT %08x, USB_ENAB %08x "
+		      "USB_FRAME %08x, USB_INTR %08x, USB_MASK %08x\n",
+		      USB_CTRL,USB_STAT,USB_ENAB,USB_FRAME,USB_INTR,USB_MASK);
+	size -= t;
+	next += t;
+
+	if (!is_usb_connected() || !dev->driver)
+		goto done;
+
+	t = scnprintf(next, size, "ep0 IN %lu/%lu, OUT %lu/%lu\nirqs %lu\n\n",
+		      dev->stats.write.bytes, dev->stats.write.ops,
+		      dev->stats.read.bytes, dev->stats.read.ops,
+		      dev->stats.irqs);
+	size -= t;
+	next += t;
+
+	/* dump endpoint queues */
+	for (i = 0; i < IMX_USB_NB_EP; i++) {
+		struct imx_ep	*ep = &dev->ep [i];
+		struct imx_request	*req;
+
+		if (i != 0) {
+			const struct usb_endpoint_descriptor	*d;
+
+			d = ep->desc;
+			if (!d)
+				continue;
+			t = scnprintf(next, size, "%s\n", ep->ep.name);
+		} else /* ep0 should only have one transfer queued */
+			t = scnprintf(next, size, "ep0 irqs %lu\n",
+				      ep->irqs);
+		if (t <= 0 || t > size)
+			goto done;
+		size -= t;
+		next += t;
+
+		if (list_empty(&ep->queue)) {
+			t = scnprintf(next, size, "\t(nothing queued)\n");
+			if (t <= 0 || t > size)
+				goto done;
+			size -= t;
+			next += t;
+			continue;
+		}
+		list_for_each_entry(req, &ep->queue, queue) {
+			t = scnprintf(next, size,
+				      "\treq %p len %d/%d buf %p\n",
+				      &req->req, req->req.actual,
+				      req->req.length, req->req.buf);
+			if (t <= 0 || t > size)
+				goto done;
+			size -= t;
+			next += t;
+		}
+	}
+
+	t = scnprintf(next, size,
+		      "USB_EP1_FCTRL %08x, USB_EP1_STAT %08x, USB_EP1_FSTAT %08x "
+		      "USB_EP1_INTR %08x, USB_EP1_MASK %08x, USB_EP1_FALRM %08x\n",
+		      USB_EP_FCTRL(1),USB_EP_STAT(1),USB_EP_FSTAT(1),USB_EP_INTR(1),
+		      USB_EP_MASK(1),USB_EP_FALRM(1));
+	size -= t;
+	next += t;
+
+	t = scnprintf(next, size,
+		      "USB_EP2_FCTRL %08x, USB_EP2_STAT %08x, USB_EP2_FSTAT %08x "
+		      "USB_EP2_INTR %08x, USB_EP2_MASK %08x, USB_EP2_FALRM %08x\n",
+		      USB_EP_FCTRL(2),USB_EP_STAT(2),USB_EP_FSTAT(2),USB_EP_INTR(2),
+		      USB_EP_MASK(2),USB_EP_FALRM(2));
+	size -= t;
+	next += t;
+
+//	t = scnprintf(next, size,"position flags <%s>; loop flags <%s>\n",p_flags,l_flags);
+//	size -= t;
+//	next += t;
+
+done:
+	local_irq_restore(flags);
+	*eof = 1;
+	return count - size;
+}
+
+#define create_proc_files()						\
+	create_proc_read_entry(proc_node_name, 0, NULL, udc_proc_read, dev)
+#define remove_proc_files()			\
+	remove_proc_entry(proc_node_name, NULL)
+
+#else	/* !CONFIG_USB_GADGET_DEBUG_FILES */
+
+#define create_proc_files() do {} while (0)
+#define remove_proc_files() do {} while (0)
+
+#endif	/* CONFIG_USB_GADGET_DEBUG_FILES */
+
+irqreturn_t(*intr_handler(int i))(int, void *) {
+	switch (i) {
+	case 0:
+		return imx_udc_ctrl_irq;
+	case 1:
+	case 2:
+		return imx_udc_bulk_irq;
+	case 3:
+	case 4:
+	case 5:
+		return imx_udc_int_irq;
+	case 6:
+		return imx_udc_irq;
+	}
+	return imx_udc_irq;
+}
+
+/*
+ * 	probe - binds to the platform device
+ */
+static int __init imx_udc_probe(struct platform_device *pdev)
+{
+	struct imx_udc *dev = &memory;
+	int retval = 0;
+	int i;
+
+	/* TODO: probe for IMX cpu */
+	
+	if (!imx_get_usb_clk()) {
+		WARN("USB clock should be 48MHz, but is not\n");
+		return -EIO;
+	} else
+		D2("USB clock %uHz detected\n",imx_get_usb_clk());
+	
+	/* 
+	 * USB general purpose IRQ:  ep0, state changes, dma, etc
+	 * irq setup after old hardware state is cleaned up
+	 */
+	for (i = 0; i < IMX_USB_NB_EP + 1; i++) {
+		retval = request_irq(USBD_INT0 + i, intr_handler(i),
+				     IRQF_DISABLED, driver_name, dev);
+		if (retval != 0) {
+			printk(KERN_ERR "%s: can't get irq %i, err %d\n",
+			       driver_name, USBD_INT0 + i, retval);
+			break;
+		}
+	}
+	if (retval) {
+		for (--i; i >= 0; i--)
+			free_irq(USBD_INT0 + i, dev);
+		return -EBUSY;
+	}
+
+	dev->dev = &pdev->dev;
+	dev->mach = pdev->dev.platform_data;
+
+	device_initialize(&dev->gadget.dev);
+	dev->gadget.dev.parent = &pdev->dev;
+	dev->gadget.dev.dma_mask = pdev->dev.dma_mask;
+
+	the_controller = dev;
+	platform_set_drvdata(pdev, dev);
+
+	udc_disable(dev);
+	udc_hardinit(dev);
+	udc_reinit(dev);
+
+	create_proc_files();
+
+	return 0;
+}
+
+static int __exit imx_udc_remove(struct platform_device *pdev)
+{
+	struct imx_udc *dev = platform_get_drvdata(pdev);
+
+	udc_disable(dev);
+	remove_proc_files();
+	usb_gadget_unregister_driver(dev->driver);
+
+	free_irq(USBD_INT6, dev);
+	free_irq(USBD_INT5, dev);
+	free_irq(USBD_INT4, dev);
+	free_irq(USBD_INT3, dev);
+	free_irq(USBD_INT2, dev);
+	free_irq(USBD_INT1, dev);
+	free_irq(USBD_INT0, dev);
+	platform_set_drvdata(pdev, NULL);
+	the_controller = NULL;
+	return 0;
+}
+
+/*-------------------------------------------------------------------------*/
+
+#ifdef	CONFIG_PM
+int imx_udc_suspend(struct platform_device *junk, pm_message_t state)
+{
+	USB_CTRL &= ~(USB_FE_ENA | USB_AFE_ENA);
+	return 0;
+}
+int imx_udc_resume(struct platform_device *junk)
+{
+	USB_CTRL |= USB_FE_ENA | USB_AFE_ENA;
+	return 0;
+}
+#else
+#define	imx_udc_suspend	NULL
+#define	imx_udc_resume	NULL
+#endif
+
+/*-------------------------------------------------------------------------*/
+
+static struct platform_driver udc_driver = {
+	.probe	    = imx_udc_probe,
+	.remove	    = __exit_p(imx_udc_remove),
+	.suspend    = imx_udc_suspend,
+	.resume	    = imx_udc_resume,
+	.driver	    = {
+		.owner	= THIS_MODULE,
+		.name	= "imx_udc",
+	},
+};
+
+
+static int __init udc_init(void)
+{
+	INFO("%s version: %s\n", driver_name, DRIVER_VERSION);
+	return platform_driver_register(&udc_driver);
+}
+module_init(udc_init);
+
+static void __exit udc_exit(void)
+{
+	platform_driver_unregister(&udc_driver);
+}
+module_exit(udc_exit);
+
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_AUTHOR("Mike Lee, Torsten Koschorrek");
+MODULE_LICENSE("GPL");
+
diff --git a/drivers/usb/gadget/imx_udc.h b/drivers/usb/gadget/imx_udc.h
new file mode 100644
index 0000000..07641e8
--- /dev/null
+++ b/drivers/usb/gadget/imx_udc.h
@@ -0,0 +1,281 @@
+/*
+ *	driver/usb/gadget/imx_udc.h
+ *
+ *	Copyright (C) 2005 Mike Lee([email protected])
+ *	
+ *	This udc driver is now under testing and code is based on pxa2xx_udc.h
+ *	Please use it with your own risk!
+ *
+ *	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.
+ */
+
+#ifndef __LINUX_USB_GADGET_IMX_H
+#define __LINUX_USB_GADGET_IMX_H
+
+#include <linux/types.h>
+
+/*-------------------------------------------------------------------------*/
+
+struct imx_udc;
+
+#define EP_NO(ep)	((ep->bEndpointAddress) & ~USB_DIR_IN)
+
+#define EP_DIR_OUT	0
+#define EP_DIR_IN	1
+#define EP_DIR(ep)	((ep->bEndpointAddress) & USB_DIR_IN ? EP_DIR_IN : EP_DIR_OUT)
+
+struct imx_ep {
+	struct usb_ep				ep;
+	struct imx_udc				*dev;
+
+	const struct usb_endpoint_descriptor	*desc;
+	struct list_head			queue;
+	unsigned long				irqs;
+
+	unsigned				wMaxPacketSize;
+	unsigned				fifosize;
+	u8					bEndpointAddress;
+	u8					bmAttributes;
+
+	unsigned				stopped : 1;
+};
+
+struct imx_request {
+	struct usb_request			req;
+	struct list_head			queue;
+};
+
+enum ep0_state { 
+	EP0_IDLE,
+	EP0_IN_DATA_PHASE,
+	EP0_OUT_DATA_PHASE,
+	EP0_STALL,
+};
+
+/*
+ * not yeah finish double buffering
+ * so use full fifo size be the max packet size
+ */
+#define EP0_MAX_SIZE	((unsigned)8)
+#define BULK_MAX_SIZE	((unsigned)64)
+#define ISO_MAX_SIZE	((unsigned)64)
+#define INT_MAX_SIZE	((unsigned)32)
+
+#define IMX_USB_NB_EP	6
+struct udc_stats {
+	struct ep0stats {
+		unsigned long		ops;
+		unsigned long		bytes;
+	} read, write;
+	unsigned long			irqs;
+};
+
+
+struct imx_udc {
+	struct usb_gadget			gadget;
+	struct usb_gadget_driver		*driver;
+
+	spinlock_t				lock;
+	enum ep0_state				ep0state;
+	struct udc_stats			stats;
+	unsigned				got_irq : 1,
+						set_config : 1;
+	int					dev_config;
+	int					dev_interface;
+	int					dev_altsetting;
+
+
+	struct device				*dev;
+	struct imx_udc_mach_info		*mach;
+	struct imx_ep			ep[IMX_USB_NB_EP];
+};
+
+/*-------------------------------------------------------------------------*/
+
+static struct imx_udc *the_controller;
+
+static inline int is_usb_connected(void)
+{
+	if (!the_controller->mach->udc_is_connected)
+		return 1;
+	return the_controller->mach->udc_is_connected();
+}
+
+static inline void make_usb_disappear(void)
+{
+	USB_CTRL = USB_CTRL | USB_FE_ENA;
+}
+
+static inline void let_usb_appear(void)
+{
+	USB_CTRL = USB_CTRL & ~USB_FE_ENA;
+}
+
+#define irq_to_ep(irq)	(irq >= USBD_INT0) || (irq <= USBD_INT6) ? (irq - USBD_INT0) \
+			: (USBD_INT6) /*should not happen*/
+#define ep_to_irq(ep)	EP_NO(ep) + USBD_INT0
+
+/*-------------------------------------------------------------------------*/
+
+//#define DEBUG
+
+#ifdef DEBUG
+//#define LV4
+//#define LV3
+#define LV2
+#define LV1
+#define LV0
+
+#ifdef LV0
+#define D(label,fmt,args...) printk("udc    (%20s) " fmt,label,## args)
+#else /* LV0 */
+#define D(fmt,args...) do{}while(0)
+#endif /* LV0 */
+
+#ifdef LV1
+#define D1(fmt,args...) printk("udc lv1(%20s) " fmt,__FUNCTION__,## args)
+#else /* LV1 */
+#define D1(fmt,args...) do{}while(0)
+#endif /* LV1 */
+
+#ifdef LV2
+#define D2(fmt,args...) printk("udc lv2(%20s) " fmt,__FUNCTION__,## args)
+#else /* LV2 */
+#define D2(fmt,args...) do{}while(0)
+#endif /* LV2 */
+
+#ifdef LV3
+#define D3(fmt,args...) printk("udc lv3(%20s) " fmt,__FUNCTION__,## args)
+#else /* LV3 */
+#define D3(fmt,args...) do{}while(0)
+#endif /* LV3 */
+
+#ifdef LV4
+#define D4(fmt,args...) printk("udc lv4(%20s) " fmt,__FUNCTION__,## args)
+#else /* LV4 */
+#define D4(fmt,args...) do{}while(0)
+#endif /* LV4 */
+
+static const char *state_name[] = {
+	"EP0_IDLE",
+	"EP0_IN_DATA_PHASE", "EP0_OUT_DATA_PHASE",
+	"EP0_STALL"
+};
+
+static void __attribute__ ((__unused__))
+dump_ep_stat(const char *label,struct imx_ep *ep)
+{
+	int nb = EP_NO(ep);
+	D(label,"ep0[%s] ep%d_stat<%08x>=[%s%s%s%s%s]\n",
+		state_name[the_controller->ep0state], nb,
+		USB_EP_STAT(nb),
+		(USB_EP_STAT(nb) & USB_SIP) ? " sip" : "",
+		(USB_EP_STAT(nb) & USB_DIR) ? " in" : "",
+		(USB_EP_STAT(nb) & USB_ZLPS) ? " zlp" : "",
+		(USB_EP_STAT(nb) & USB_FLUSH) ? " fsh" : "",
+		(USB_EP_STAT(nb) & USB_STALL) ? " stall" : "");
+}
+static void __attribute__ ((__unused__))
+dump_ep_intr(const char *label,struct imx_ep *ep)
+{
+	int nb = EP_NO(ep);
+	D(label,"ep%d_intr<%08x>=[%s%s%s%s%s%s%s%s%s]\n",
+		nb,USB_EP_INTR(nb),
+		(USB_EP_INTR(nb) & USB_FIFO_FULL) ? " full" : "",
+		(USB_EP_INTR(nb) & USB_FIFO_EMPTY) ? " fempty" : "",
+		(USB_EP_INTR(nb) & USB_FIFO_ERROR) ? " ferr" : "",
+		(USB_EP_INTR(nb) & USB_FIFO_HIGH) ? " fhigh" : "",
+		(USB_EP_INTR(nb) & USB_FIFO_LOW) ? " flow" : "",
+		(USB_EP_INTR(nb) & USB_MDEVREQ) ? " mreq" : "",
+		(USB_EP_INTR(nb) & USB_EOF) ? " eof" : "",
+		(USB_EP_INTR(nb) & USB_DEVREQ) ? " req" : "",
+		(USB_EP_INTR(nb) & USB_EOT) ? " eot" : ""
+		);
+}
+static void __attribute__ ((__unused__))
+dump_intr(const char *label)
+{
+	D(label,"usb_intr<%08x>=[%s%s%s%s%s%s%s%s%s]\n",
+		USB_INTR,
+		(USB_INTR & USB_WAKEUP) ? " wak" : "",
+		(USB_INTR & USB_MSOF) ? " msof" : "",
+		(USB_INTR & USB_SOF) ? " sof" : "",
+		(USB_INTR & USB_RES) ? " res" : "",
+		(USB_INTR & USB_SUSP) ? " sus" : "",
+		(USB_INTR & USB_RESET_STOP) ? " res_stop" : "",
+		(USB_INTR & USB_RESET_START) ? " res_start" : "",
+		(USB_INTR & USB_FRAME_MATCH) ? " f_match" : "",
+		(USB_INTR & USB_CFG_CHG) ? " cfg" : ""
+		);
+}
+
+static void __attribute__ ((__unused__))
+dump_ep_fstat(const char *label,struct imx_ep *ep)
+{
+	int nb = EP_NO(ep);
+	D(label,"%s %08X =framebit[%04x],[%s%s%s%s%s%s%s]\n",
+		state_name[the_controller->ep0state], USB_EP_FSTAT(nb),
+		(USB_EP_FSTAT(nb) & USB_FRAME_STAT) >> 24,
+		(USB_EP_FSTAT(nb) & USB_ERR) ? " err" : "",
+		(USB_EP_FSTAT(nb) & USB_UF) ? " uf" : "",
+		(USB_EP_FSTAT(nb) & USB_OF) ? " of" : "",
+		(USB_EP_FSTAT(nb) & USB_FR) ? " fr" : "",
+		(USB_EP_FSTAT(nb) & USB_FULL) ? " full" : "",
+		(USB_EP_FSTAT(nb) & USB_ALRM) ? " alrm" : "",
+		(USB_EP_FSTAT(nb) & USB_EMPTY) ? " empty" : "");
+}
+static void __attribute__ ((__unused__))
+dump_state(const char *label)
+{
+}
+
+static void __attribute__ ((__unused__))
+dump_req(struct usb_request *req){
+#ifdef LV4
+	if (!req || !req->buf) {
+		D(__FUNCTION__, "req or req buf is free\n");
+		return;
+	}
+
+	int i=0;
+	printk("dump req <");
+	for (;i<req->length;i++){
+		printk("%02x-",*((u8*)req->buf+i));
+	}
+	printk(">\n");
+#endif /* LV4 */
+}
+
+
+#else /* DEBUG */
+
+#define D(label,fmt,args...) do{}while(0)
+#define D1(fmt,args...) do{}while(0)
+#define D2(fmt,args...) do{}while(0)
+#define D3(fmt,args...) do{}while(0)
+#define D4(fmt,args...) do{}while(0)
+
+#define	dump_ep_stat(x,y) 	do{}while(0)
+#define	dump_ep_fstat(x,y)	do{}while(0)
+#define dump_ep_intr(x,y)	do{}while(0)
+#define dump_intr(x)		do{}while(0)
+#define dump_ep_fstat(x,y)	do{}while(0)
+#define dump_req(req)		do{}while(0)
+#define dump_state(x)		do{}while(0)
+
+#endif /* DEBUG */
+
+#define WARN(stuff...) printk(KERN_WARNING "usb: " stuff)
+#define INFO(stuff...) printk(KERN_INFO "usb: " stuff)
+
+
+#endif /* __LINUX_USB_GADGET_IMX_H */
+
diff --git a/include/asm-arm/arch-imx/hardware.h b/include/asm-arm/arch-imx/hardware.h
index 944b577..5cecb58 100644
--- a/include/asm-arm/arch-imx/hardware.h
+++ b/include/asm-arm/arch-imx/hardware.h
@@ -28,6 +28,8 @@
 
 # define __REG2(x,y)        (*(volatile u32 *)((u32)&__REG(x) + (y)))
 
+# define __REG8(x)	(*((volatile u8 *)IO_ADDRESS(x)))
+
 #else
 # define __REG(x)	(IO_ADDRESS(x))
 #endif
@@ -84,6 +86,7 @@ extern unsigned int imx_get_perclk2(void); /* LCD, SD, SPI[12]         */
 extern unsigned int imx_get_perclk3(void); /* SSI                      */
 extern unsigned int imx_get_hclk(void);    /* SDRAM, CSI, Memory Stick,*/
                                            /* I2C, DMA                 */
+extern unsigned int imx_get_usb_clk(void); /* USB                      */
 #endif
 
 #define MAXIRQNUM                       62
diff --git a/include/asm-arm/arch-imx/udc.h b/include/asm-arm/arch-imx/udc.h
new file mode 100644
index 0000000..479a1ec
--- /dev/null
+++ b/include/asm-arm/arch-imx/udc.h
@@ -0,0 +1,14 @@
+/*
+ * linux/include/asm-arm/arch-imx/udc.h
+ *
+ * This supports machine-specific differences in how the i.MX
+ * USB Device Controller (UDC) is wired.
+ *
+ * It is set in linux/arch/arm/mach-imx/<machine>.c and used in
+ * the probe routine of linux/drivers/usb/gadget/imx_udc.c
+ */
+struct imx_udc_mach_info {
+	int  (*udc_is_connected)(void);		/* do we see the host? */
+};
+
+extern void imx_set_udc_info(struct imx_udc_mach_info *info);
-- 
1.5.3.2


-------------------------------------------------------------------------
This SF.net email is sponsored by: Microsoft
Defy all challenges. Microsoft(R) Visual Studio 2005.
http://clk.atdmt.com/MRT/go/vse0120000070mrt/direct/01/
_______________________________________________
[email protected]
To unsubscribe, use the last form field at:
https://lists.sourceforge.net/lists/listinfo/linux-usb-devel
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.