From f12c49e8bf1ac056946bc3098c6c361d51891916 Mon Sep 17 00:00:00 2001
From: Henry Yuan <hang.yuan@intel.com>
Date: Thu, 6 May 2010 19:30:00 +0800
Subject: [PATCH] Moorestown USB-OTG drivers full patch 0.2 for MeeGo

This is a consolidated full patch against K2.6.33. It
includes USB-OTG client controller driver, transceiver
driver, still image gadget driver and fixing for sighting
3469616: OTG driver hangs in suspend function.

OTG host, client functions and role switch per cable
plugged are tested.
Known issue: HNP/SRP have problem.

Kernel config:
CONFIG_USB_LANGWELL_OTG = y
CONFIG_USB_OTG_WHITELIST = n
CONFIG_USB_GADGET = y
CONFIG_USB_GADGET_LANGWELL = y

CONFIG_USB_STILL_IMAGE = y
or select other gadget driver as needed.

Signed-off-by: Henry Yuan <hang.yuan@intel.com>
Patch-mainline: 2.6.34
---
 drivers/usb/gadget/Kconfig        |    8 +
 drivers/usb/gadget/Makefile       |    2 +
 drivers/usb/gadget/f_ecm.c        |   22 +
 drivers/usb/gadget/f_subset.c     |   22 +
 drivers/usb/gadget/langwell_udc.c |  582 ++++--
 drivers/usb/gadget/langwell_udc.h |   13 +-
 drivers/usb/gadget/still_image.c  | 4566 +++++++++++++++++++++++++++++++++++++
 drivers/usb/otg/Kconfig           |   14 +
 drivers/usb/otg/Makefile          |    1 +
 drivers/usb/otg/langwell_otg.c    | 2260 ++++++++++++++++++
 include/linux/usb/langwell_otg.h  |  201 ++
 include/linux/usb/langwell_udc.h  |   13 +
 12 files changed, 7516 insertions(+), 188 deletions(-)
 create mode 100644 drivers/usb/gadget/still_image.c
 create mode 100644 drivers/usb/otg/langwell_otg.c
 create mode 100644 include/linux/usb/langwell_otg.h

diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig
index ee41120..94cc94f 100644
--- a/drivers/usb/gadget/Kconfig
+++ b/drivers/usb/gadget/Kconfig
@@ -853,6 +853,14 @@ config USB_G_MULTI_CDC
 
 	  If unsure, say "y".
 
+config USB_STILL_IMAGE
+	tristate "Lite Still Image Gadget"
+	help
+	  The Lite Still Image Gadget implements object transfer based on
+	  spec PIMA 15740:2000.
+
+	  Say "y" to link the driver statically, or "m" to build a dynamically
+	  linked module called "g_still_image".
 
 # put drivers that need isochronous transfer support (for audio
 # or video class gadget drivers), or specific hardware, here.
diff --git a/drivers/usb/gadget/Makefile b/drivers/usb/gadget/Makefile
index 2e2c047..7ef974e 100644
--- a/drivers/usb/gadget/Makefile
+++ b/drivers/usb/gadget/Makefile
@@ -43,6 +43,7 @@ g_mass_storage-objs		:= mass_storage.o
 g_printer-objs			:= printer.o
 g_cdc-objs			:= cdc2.o
 g_multi-objs			:= multi.o
+g_still_image-objs		:= still_image.o
 
 obj-$(CONFIG_USB_ZERO)		+= g_zero.o
 obj-$(CONFIG_USB_AUDIO)		+= g_audio.o
@@ -55,4 +56,5 @@ obj-$(CONFIG_USB_G_PRINTER)	+= g_printer.o
 obj-$(CONFIG_USB_MIDI_GADGET)	+= g_midi.o
 obj-$(CONFIG_USB_CDC_COMPOSITE) += g_cdc.o
 obj-$(CONFIG_USB_G_MULTI)	+= g_multi.o
+obj-$(CONFIG_USB_STILL_IMAGE)	+= g_still_image.o
 
diff --git a/drivers/usb/gadget/f_ecm.c b/drivers/usb/gadget/f_ecm.c
index ecf5bdd..d004328 100644
--- a/drivers/usb/gadget/f_ecm.c
+++ b/drivers/usb/gadget/f_ecm.c
@@ -753,6 +753,26 @@ ecm_unbind(struct usb_configuration *c, struct usb_function *f)
 	kfree(ecm);
 }
 
+static void
+ecm_suspend(struct usb_function *f)
+{
+	struct f_ecm	*ecm = func_to_ecm(f);
+	struct eth_dev	*dev = ecm->port.ioport;
+
+	if (dev)
+		gether_disconnect(&ecm->port);
+}
+
+static void
+ecm_resume(struct usb_function *f)
+{
+	struct f_ecm	*ecm = func_to_ecm(f);
+	struct eth_dev	*dev = ecm->port.ioport;
+
+	if (!dev)
+		gether_connect(&ecm->port);
+}
+
 /**
  * ecm_bind_config - add CDC Ethernet network link to a configuration
  * @c: the configuration to support the network link
@@ -821,6 +841,8 @@ int __init ecm_bind_config(struct usb_configuration *c, u8 ethaddr[ETH_ALEN])
 	ecm->port.func.get_alt = ecm_get_alt;
 	ecm->port.func.setup = ecm_setup;
 	ecm->port.func.disable = ecm_disable;
+	ecm->port.func.suspend = ecm_suspend;
+	ecm->port.func.resume = ecm_resume;
 
 	status = usb_add_function(c, &ecm->port.func);
 	if (status) {
diff --git a/drivers/usb/gadget/f_subset.c b/drivers/usb/gadget/f_subset.c
index a9c98fd..893816d 100644
--- a/drivers/usb/gadget/f_subset.c
+++ b/drivers/usb/gadget/f_subset.c
@@ -353,6 +353,26 @@ geth_unbind(struct usb_configuration *c, struct usb_function *f)
 	kfree(func_to_geth(f));
 }
 
+static void
+geth_suspend(struct usb_function *f)
+{
+	struct f_gether	*geth = func_to_geth(f);
+	struct eth_dev	*dev = geth->port.ioport;
+
+	if (dev)
+		gether_disconnect(&geth->port);
+}
+
+static void
+geth_resume(struct usb_function *f)
+{
+	struct f_gether	*geth = func_to_geth(f);
+	struct eth_dev	*dev = geth->port.ioport;
+
+	if (!dev)
+		gether_connect(&geth->port);
+}
+
 /**
  * geth_bind_config - add CDC Subset network link to a configuration
  * @c: the configuration to support the network link
@@ -411,6 +431,8 @@ int __init geth_bind_config(struct usb_configuration *c, u8 ethaddr[ETH_ALEN])
 	geth->port.func.unbind = geth_unbind;
 	geth->port.func.set_alt = geth_set_alt;
 	geth->port.func.disable = geth_disable;
+	geth->port.func.resume = geth_resume;
+	geth->port.func.suspend = geth_suspend;
 
 	status = usb_add_function(c, &geth->port.func);
 	if (status) {
diff --git a/drivers/usb/gadget/langwell_udc.c b/drivers/usb/gadget/langwell_udc.c
index a391351..eb0e185 100644
--- a/drivers/usb/gadget/langwell_udc.c
+++ b/drivers/usb/gadget/langwell_udc.c
@@ -54,7 +54,7 @@
 
 
 #define	DRIVER_DESC		"Intel Langwell USB Device Controller driver"
-#define	DRIVER_VERSION		"16 May 2009"
+#define	DRIVER_VERSION		"Apr 30, 2010"
 
 static const char driver_name[] = "langwell_udc";
 static const char driver_desc[] = DRIVER_DESC;
@@ -73,7 +73,6 @@ langwell_ep0_desc = {
 	.wMaxPacketSize =	EP0_MAX_PKT_SIZE,
 };
 
-
 /*-------------------------------------------------------------------------*/
 /* debugging */
 
@@ -114,104 +113,76 @@ static inline void print_all_registers(struct langwell_udc *dev)
 	int	i;
 
 	/* Capability Registers */
-	printk(KERN_DEBUG "Capability Registers (offset: "
-			"0x%04x, length: 0x%08x)\n",
-			CAP_REG_OFFSET,
-			(u32)sizeof(struct langwell_cap_regs));
-	printk(KERN_DEBUG "caplength=0x%02x\n",
-			readb(&dev->cap_regs->caplength));
-	printk(KERN_DEBUG "hciversion=0x%04x\n",
-			readw(&dev->cap_regs->hciversion));
-	printk(KERN_DEBUG "hcsparams=0x%08x\n",
-			readl(&dev->cap_regs->hcsparams));
-	printk(KERN_DEBUG "hccparams=0x%08x\n",
-			readl(&dev->cap_regs->hccparams));
-	printk(KERN_DEBUG "dciversion=0x%04x\n",
-			readw(&dev->cap_regs->dciversion));
-	printk(KERN_DEBUG "dccparams=0x%08x\n",
-			readl(&dev->cap_regs->dccparams));
+	DBG(dev, "Capability Registers (offset: 0x%04x, length: 0x%08x)\n",
+			CAP_REG_OFFSET, (u32)sizeof(struct langwell_cap_regs));
+	DBG(dev, "caplength=0x%02x\n", readb(&dev->cap_regs->caplength));
+	DBG(dev, "hciversion=0x%04x\n", readw(&dev->cap_regs->hciversion));
+	DBG(dev, "hcsparams=0x%08x\n", readl(&dev->cap_regs->hcsparams));
+	DBG(dev, "hccparams=0x%08x\n", readl(&dev->cap_regs->hccparams));
+	DBG(dev, "dciversion=0x%04x\n", readw(&dev->cap_regs->dciversion));
+	DBG(dev, "dccparams=0x%08x\n", readl(&dev->cap_regs->dccparams));
 
 	/* Operational Registers */
-	printk(KERN_DEBUG "Operational Registers (offset: "
-			"0x%04x, length: 0x%08x)\n",
-			OP_REG_OFFSET,
-			(u32)sizeof(struct langwell_op_regs));
-	printk(KERN_DEBUG "extsts=0x%08x\n",
-			readl(&dev->op_regs->extsts));
-	printk(KERN_DEBUG "extintr=0x%08x\n",
-			readl(&dev->op_regs->extintr));
-	printk(KERN_DEBUG "usbcmd=0x%08x\n",
-			readl(&dev->op_regs->usbcmd));
-	printk(KERN_DEBUG "usbsts=0x%08x\n",
-			readl(&dev->op_regs->usbsts));
-	printk(KERN_DEBUG "usbintr=0x%08x\n",
-			readl(&dev->op_regs->usbintr));
-	printk(KERN_DEBUG "frindex=0x%08x\n",
-			readl(&dev->op_regs->frindex));
-	printk(KERN_DEBUG "ctrldssegment=0x%08x\n",
+	DBG(dev, "Operational Registers (offset: 0x%04x, length: 0x%08x)\n",
+			OP_REG_OFFSET, (u32)sizeof(struct langwell_op_regs));
+	DBG(dev, "extsts=0x%08x\n", readl(&dev->op_regs->extsts));
+	DBG(dev, "extintr=0x%08x\n", readl(&dev->op_regs->extintr));
+	DBG(dev, "usbcmd=0x%08x\n", readl(&dev->op_regs->usbcmd));
+	DBG(dev, "usbsts=0x%08x\n", readl(&dev->op_regs->usbsts));
+	DBG(dev, "usbintr=0x%08x\n", readl(&dev->op_regs->usbintr));
+	DBG(dev, "frindex=0x%08x\n", readl(&dev->op_regs->frindex));
+	DBG(dev, "ctrldssegment=0x%08x\n",
 			readl(&dev->op_regs->ctrldssegment));
-	printk(KERN_DEBUG "deviceaddr=0x%08x\n",
-			readl(&dev->op_regs->deviceaddr));
-	printk(KERN_DEBUG "endpointlistaddr=0x%08x\n",
+	DBG(dev, "deviceaddr=0x%08x\n", readl(&dev->op_regs->deviceaddr));
+	DBG(dev, "endpointlistaddr=0x%08x\n",
 			readl(&dev->op_regs->endpointlistaddr));
-	printk(KERN_DEBUG "ttctrl=0x%08x\n",
-			readl(&dev->op_regs->ttctrl));
-	printk(KERN_DEBUG "burstsize=0x%08x\n",
-			readl(&dev->op_regs->burstsize));
-	printk(KERN_DEBUG "txfilltuning=0x%08x\n",
-			readl(&dev->op_regs->txfilltuning));
-	printk(KERN_DEBUG "txttfilltuning=0x%08x\n",
+	DBG(dev, "ttctrl=0x%08x\n", readl(&dev->op_regs->ttctrl));
+	DBG(dev, "burstsize=0x%08x\n", readl(&dev->op_regs->burstsize));
+	DBG(dev, "txfilltuning=0x%08x\n", readl(&dev->op_regs->txfilltuning));
+	DBG(dev, "txttfilltuning=0x%08x\n",
 			readl(&dev->op_regs->txttfilltuning));
-	printk(KERN_DEBUG "ic_usb=0x%08x\n",
-			readl(&dev->op_regs->ic_usb));
-	printk(KERN_DEBUG "ulpi_viewport=0x%08x\n",
+	DBG(dev, "ic_usb=0x%08x\n", readl(&dev->op_regs->ic_usb));
+	DBG(dev, "ulpi_viewport=0x%08x\n",
 			readl(&dev->op_regs->ulpi_viewport));
-	printk(KERN_DEBUG "configflag=0x%08x\n",
-			readl(&dev->op_regs->configflag));
-	printk(KERN_DEBUG "portsc1=0x%08x\n",
-			readl(&dev->op_regs->portsc1));
-	printk(KERN_DEBUG "devlc=0x%08x\n",
-			readl(&dev->op_regs->devlc));
-	printk(KERN_DEBUG "otgsc=0x%08x\n",
-			readl(&dev->op_regs->otgsc));
-	printk(KERN_DEBUG "usbmode=0x%08x\n",
-			readl(&dev->op_regs->usbmode));
-	printk(KERN_DEBUG "endptnak=0x%08x\n",
-			readl(&dev->op_regs->endptnak));
-	printk(KERN_DEBUG "endptnaken=0x%08x\n",
-			readl(&dev->op_regs->endptnaken));
-	printk(KERN_DEBUG "endptsetupstat=0x%08x\n",
+	DBG(dev, "configflag=0x%08x\n", readl(&dev->op_regs->configflag));
+	DBG(dev, "portsc1=0x%08x\n", readl(&dev->op_regs->portsc1));
+	DBG(dev, "devlc=0x%08x\n", readl(&dev->op_regs->devlc));
+	DBG(dev, "otgsc=0x%08x\n", readl(&dev->op_regs->otgsc));
+	DBG(dev, "usbmode=0x%08x\n", readl(&dev->op_regs->usbmode));
+	DBG(dev, "endptnak=0x%08x\n", readl(&dev->op_regs->endptnak));
+	DBG(dev, "endptnaken=0x%08x\n", readl(&dev->op_regs->endptnaken));
+	DBG(dev, "endptsetupstat=0x%08x\n",
 			readl(&dev->op_regs->endptsetupstat));
-	printk(KERN_DEBUG "endptprime=0x%08x\n",
-			readl(&dev->op_regs->endptprime));
-	printk(KERN_DEBUG "endptflush=0x%08x\n",
-			readl(&dev->op_regs->endptflush));
-	printk(KERN_DEBUG "endptstat=0x%08x\n",
-			readl(&dev->op_regs->endptstat));
-	printk(KERN_DEBUG "endptcomplete=0x%08x\n",
+	DBG(dev, "endptprime=0x%08x\n", readl(&dev->op_regs->endptprime));
+	DBG(dev, "endptflush=0x%08x\n", readl(&dev->op_regs->endptflush));
+	DBG(dev, "endptstat=0x%08x\n", readl(&dev->op_regs->endptstat));
+	DBG(dev, "endptcomplete=0x%08x\n",
 			readl(&dev->op_regs->endptcomplete));
 
 	for (i = 0; i < dev->ep_max / 2; i++) {
-		printk(KERN_DEBUG "endptctrl[%d]=0x%08x\n",
+		DBG(dev, "endptctrl[%d]=0x%08x\n",
 				i, readl(&dev->op_regs->endptctrl[i]));
 	}
 }
+#else
+
+#define	print_all_registers(dev)	do { } while (0)
+
 #endif /* VERBOSE */
 
 
 /*-------------------------------------------------------------------------*/
 
-#define	DIR_STRING(bAddress)	(((bAddress) & USB_DIR_IN) ? "in" : "out")
+#define	is_in(ep)	(((ep)->ep_num == 0) ? ((ep)->dev->ep0_dir ==	\
+			USB_DIR_IN) : (usb_endpoint_dir_in((ep)->desc)))
 
-#define is_in(ep)	(((ep)->ep_num == 0) ? ((ep)->dev->ep0_dir == \
-			USB_DIR_IN) : ((ep)->desc->bEndpointAddress \
-			& USB_DIR_IN) == USB_DIR_IN)
+#define	DIR_STRING(ep)	(is_in(ep) ? "in" : "out")
 
 
 #ifdef	DEBUG
-static char *type_string(u8 bmAttributes)
+static char *type_string(const struct usb_endpoint_descriptor *desc)
 {
-	switch ((bmAttributes) & USB_ENDPOINT_XFERTYPE_MASK) {
+	switch (usb_endpoint_type(desc)) {
 	case USB_ENDPOINT_XFER_BULK:
 		return "bulk";
 	case USB_ENDPOINT_XFER_ISOC:
@@ -274,11 +245,13 @@ static void ep0_reset(struct langwell_udc *dev)
 		ep->dqh->dqh_ios = 1;
 		ep->dqh->dqh_mpl = EP0_MAX_PKT_SIZE;
 
-		/* FIXME: enable ep0-in HW zero length termination select */
+		/* enable ep0-in HW zero length termination select */
 		if (is_in(ep))
 			ep->dqh->dqh_zlt = 0;
 		ep->dqh->dqh_mult = 0;
 
+		ep->dqh->dtd_next = DTD_TERM;
+
 		/* configure ep0 control registers */
 		ep_reset(&dev->ep[0], 0, i, USB_ENDPOINT_XFER_CONTROL);
 	}
@@ -300,7 +273,7 @@ static int langwell_ep_enable(struct usb_ep *_ep,
 	struct langwell_ep	*ep;
 	u16			max = 0;
 	unsigned long		flags;
-	int			retval = 0;
+	int			i, retval = 0;
 	unsigned char		zlt, ios = 0, mult = 0;
 
 	ep = container_of(_ep, struct langwell_ep, ep);
@@ -326,7 +299,7 @@ static int langwell_ep_enable(struct usb_ep *_ep,
 	 * sanity check type, direction, address, and then
 	 * initialize the endpoint capabilities fields in dQH
 	 */
-	switch (desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) {
+	switch (usb_endpoint_type(desc)) {
 	case USB_ENDPOINT_XFER_CONTROL:
 		ios = 1;
 		break;
@@ -386,28 +359,31 @@ static int langwell_ep_enable(struct usb_ep *_ep,
 
 	spin_lock_irqsave(&dev->lock, flags);
 
-	/* configure endpoint capabilities in dQH */
-	ep->dqh->dqh_ios = ios;
-	ep->dqh->dqh_mpl = cpu_to_le16(max);
-	ep->dqh->dqh_zlt = zlt;
-	ep->dqh->dqh_mult = mult;
-
 	ep->ep.maxpacket = max;
 	ep->desc = desc;
 	ep->stopped = 0;
-	ep->ep_num = desc->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK;
+	ep->ep_num = usb_endpoint_num(desc);
 
 	/* ep_type */
-	ep->ep_type = desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK;
+	ep->ep_type = usb_endpoint_type(desc);
 
 	/* configure endpoint control registers */
 	ep_reset(ep, ep->ep_num, is_in(ep), ep->ep_type);
 
+	/* configure endpoint capabilities in dQH */
+	i = ep->ep_num * 2 + is_in(ep);
+	ep->dqh = &dev->ep_dqh[i];
+	ep->dqh->dqh_ios = ios;
+	ep->dqh->dqh_mpl = cpu_to_le16(max);
+	ep->dqh->dqh_zlt = zlt;
+	ep->dqh->dqh_mult = mult;
+	ep->dqh->dtd_next = DTD_TERM;
+
 	DBG(dev, "enabled %s (ep%d%s-%s), max %04x\n",
 			_ep->name,
 			ep->ep_num,
-			DIR_STRING(desc->bEndpointAddress),
-			type_string(desc->bmAttributes),
+			DIR_STRING(ep),
+			type_string(desc),
 			max);
 
 	spin_unlock_irqrestore(&dev->lock, flags);
@@ -617,7 +593,7 @@ static int queue_dtd(struct langwell_ep *ep, struct langwell_request *req)
 		VDBG(dev, "%s\n", ep->name);
 	else
 		/* ep0 */
-		VDBG(dev, "%s-%s\n", ep->name, is_in(ep) ? "in" : "out");
+		VDBG(dev, "%s-%s\n", ep->name, DIR_STRING(ep));
 
 	VDBG(dev, "ep_dqh[%d] addr: 0x%08x\n", i, (u32)&(dev->ep_dqh[i]));
 
@@ -667,6 +643,9 @@ static int queue_dtd(struct langwell_ep *ep, struct langwell_request *req)
 	dqh->dtd_status &= dtd_status;
 	VDBG(dev, "dqh->dtd_status = 0x%x\n", dqh->dtd_status);
 
+	/* ensure that updates to the dQH will occure before priming */
+	wmb();
+
 	/* write 1 to endptprime register to PRIME endpoint */
 	bit_mask = is_in(ep) ? (1 << (ep->ep_num + 16)) : (1 << ep->ep_num);
 	VDBG(dev, "endprime bit_mask = 0x%08x\n", bit_mask);
@@ -805,7 +784,7 @@ static int langwell_ep_queue(struct usb_ep *_ep, struct usb_request *_req,
 	req->ep = ep;
 	VDBG(dev, "---> %s()\n", __func__);
 
-	if (ep->desc->bmAttributes == USB_ENDPOINT_XFER_ISOC) {
+	if (usb_endpoint_xfer_isoc(ep->desc)) {
 		if (req->req.length > ep->ep.maxpacket)
 			return -EMSGSIZE;
 		is_iso = 1;
@@ -844,7 +823,7 @@ static int langwell_ep_queue(struct usb_ep *_ep, struct usb_request *_req,
 
 	DBG(dev, "%s queue req %p, len %u, buf %p, dma 0x%08x\n",
 			_ep->name,
-			_req, _req->length, _req->buf, _req->dma);
+			_req, _req->length, _req->buf, (int)_req->dma);
 
 	_req->status = -EINPROGRESS;
 	_req->actual = 0;
@@ -1024,8 +1003,7 @@ static int langwell_ep_set_halt(struct usb_ep *_ep, int value)
 	if (!dev->driver || dev->gadget.speed == USB_SPEED_UNKNOWN)
 		return -ESHUTDOWN;
 
-	if (ep->desc && (ep->desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK)
-			== USB_ENDPOINT_XFER_ISOC)
+	if (usb_endpoint_xfer_isoc(ep->desc))
 		return  -EOPNOTSUPP;
 
 	spin_lock_irqsave(&dev->lock, flags);
@@ -1094,7 +1072,7 @@ static void langwell_ep_fifo_flush(struct usb_ep *_ep)
 		return;
 	}
 
-	VDBG(dev, "%s-%s fifo flush\n", _ep->name, is_in(ep) ? "in" : "out");
+	VDBG(dev, "%s-%s fifo flush\n", _ep->name, DIR_STRING(ep));
 
 	/* flush endpoint buffer */
 	if (ep->ep_num == 0)
@@ -1181,6 +1159,7 @@ static int langwell_wakeup(struct usb_gadget *_gadget)
 {
 	struct langwell_udc	*dev;
 	u32 			portsc1, devlc;
+	u8			devlc_byte2;
 	unsigned long   	flags;
 
 	if (!_gadget)
@@ -1189,9 +1168,11 @@ static int langwell_wakeup(struct usb_gadget *_gadget)
 	dev = container_of(_gadget, struct langwell_udc, gadget);
 	VDBG(dev, "---> %s()\n", __func__);
 
-	/* Remote Wakeup feature not enabled by host */
-	if (!dev->remote_wakeup)
+	/* remote wakeup feature not enabled by host */
+	if (!dev->remote_wakeup) {
+		INFO(dev, "remote wakeup is disabled\n");
 		return -ENOTSUPP;
+	}
 
 	spin_lock_irqsave(&dev->lock, flags);
 
@@ -1201,23 +1182,25 @@ static int langwell_wakeup(struct usb_gadget *_gadget)
 		return 0;
 	}
 
-	/* LPM L1 to L0, remote wakeup */
-	if (dev->lpm && dev->lpm_state == LPM_L1) {
-		portsc1 |= PORTS_SLP;
-		writel(portsc1, &dev->op_regs->portsc1);
-	}
-
-	/* force port resume */
-	if (dev->usb_state == USB_STATE_SUSPENDED) {
-		portsc1 |= PORTS_FPR;
-		writel(portsc1, &dev->op_regs->portsc1);
-	}
+	/* LPM L1 to L0 or legacy remote wakeup */
+	if (dev->lpm && dev->lpm_state == LPM_L1)
+		INFO(dev, "LPM L1 to L0 remote wakeup\n");
+	else
+		INFO(dev, "device remote wakeup\n");
 
 	/* exit PHY low power suspend */
 	devlc = readl(&dev->op_regs->devlc);
 	VDBG(dev, "devlc = 0x%08x\n", devlc);
 	devlc &= ~LPM_PHCD;
-	writel(devlc, &dev->op_regs->devlc);
+	/* FIXME: workaround for Langwell A1/A2/A3 sighting */
+	devlc_byte2 = (devlc >> 16) & 0xff;
+	writeb(devlc_byte2, (u8 *)&dev->op_regs->devlc + 2);
+	devlc = readl(&dev->op_regs->devlc);
+	VDBG(dev, "exit PHY low power suspend, devlc = 0x%08x\n", devlc);
+
+	/* force port resume */
+	portsc1 |= PORTS_FPR;
+	writel(portsc1, &dev->op_regs->portsc1);
 
 	spin_unlock_irqrestore(&dev->lock, flags);
 
@@ -1346,6 +1329,7 @@ static const struct usb_gadget_ops langwell_ops = {
 static int langwell_udc_reset(struct langwell_udc *dev)
 {
 	u32		usbcmd, usbmode, devlc, endpointlistaddr;
+	u8		devlc_byte0, devlc_byte2;
 	unsigned long	timeout;
 
 	if (!dev)
@@ -1390,9 +1374,16 @@ static int langwell_udc_reset(struct langwell_udc *dev)
 	/* if support USB LPM, ACK all LPM token */
 	if (dev->lpm) {
 		devlc = readl(&dev->op_regs->devlc);
+		VDBG(dev, "devlc = 0x%08x\n", devlc);
+		/* FIXME: workaround for Langwell A1/A2/A3 sighting */
 		devlc &= ~LPM_STL;	/* don't STALL LPM token */
 		devlc &= ~LPM_NYT_ACK;	/* ACK LPM token */
-		writel(devlc, &dev->op_regs->devlc);
+		devlc_byte0 = devlc & 0xff;
+		devlc_byte2 = (devlc >> 16) & 0xff;
+		writeb(devlc_byte0, (u8 *)&dev->op_regs->devlc);
+		writeb(devlc_byte2, (u8 *)&dev->op_regs->devlc + 2);
+		devlc = readl(&dev->op_regs->devlc);
+		VDBG(dev, "ACK LPM token, devlc = 0x%08x\n", devlc);
 	}
 
 	/* fill endpointlistaddr register */
@@ -1449,8 +1440,6 @@ static int eps_reinit(struct langwell_udc *dev)
 
 		INIT_LIST_HEAD(&ep->queue);
 		list_add_tail(&ep->ep.ep_list, &dev->gadget.ep_list);
-
-		ep->dqh = &dev->ep_dqh[i];
 	}
 
 	VDBG(dev, "<--- %s()\n", __func__);
@@ -1539,21 +1528,6 @@ static void stop_activity(struct langwell_udc *dev,
 
 /*-------------------------------------------------------------------------*/
 
-/* device "function" sysfs attribute file */
-static ssize_t show_function(struct device *_dev,
-		struct device_attribute *attr, char *buf)
-{
-	struct langwell_udc	*dev = the_controller;
-
-	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);
-
-
 /* device "langwell_udc" sysfs attribute file */
 static ssize_t show_langwell_udc(struct device *_dev,
 		struct device_attribute *attr, char *buf)
@@ -1659,13 +1633,15 @@ static ssize_t show_langwell_udc(struct device *_dev,
 		"Over-current Change: %s\n"
 		"Port Enable/Disable Change: %s\n"
 		"Port Enabled/Disabled: %s\n"
-		"Current Connect Status: %s\n\n",
+		"Current Connect Status: %s\n"
+		"LPM Suspend Status: %s\n\n",
 		(tmp_reg & PORTS_PR) ? "Reset" : "Not Reset",
 		(tmp_reg & PORTS_SUSP) ? "Suspend " : "Not Suspend",
 		(tmp_reg & PORTS_OCC) ? "Detected" : "No",
 		(tmp_reg & PORTS_PEC) ? "Changed" : "Not Changed",
 		(tmp_reg & PORTS_PE) ? "Enable" : "Not Correct",
-		(tmp_reg & PORTS_CCS) ?  "Attached" : "Not Attached");
+		(tmp_reg & PORTS_CCS) ?  "Attached" : "Not Attached",
+		(tmp_reg & PORTS_SLP) ? "LPM L1" : "LPM L0");
 	size -= t;
 	next += t;
 
@@ -1676,7 +1652,7 @@ static ssize_t show_langwell_udc(struct device *_dev,
 		"Serial Transceiver : %d\n"
 		"Port Speed: %s\n"
 		"Port Force Full Speed Connenct: %s\n"
-		"PHY Low Power Suspend Clock Disable: %s\n"
+		"PHY Low Power Suspend Clock: %s\n"
 		"BmAttributes: %d\n\n",
 		LPM_PTS(tmp_reg),
 		(tmp_reg & LPM_STS) ? 1 : 0,
@@ -1797,6 +1773,40 @@ static ssize_t show_langwell_udc(struct device *_dev,
 static DEVICE_ATTR(langwell_udc, S_IRUGO, show_langwell_udc, NULL);
 
 
+/* device "remote_wakeup" sysfs attribute file */
+static ssize_t store_remote_wakeup(struct device *_dev,
+		struct device_attribute *attr, const char *buf, size_t count)
+{
+	struct langwell_udc	*dev = the_controller;
+#if defined(CONFIG_USB_DEBUG)
+	unsigned long		flags;
+#endif
+	ssize_t			rc = count;
+
+	if (count > 2)
+		return -EINVAL;
+
+	if (count > 0 && buf[count-1] == '\n')
+		((char *) buf)[count-1] = 0;
+
+	if (buf[0] != '1')
+		return -EINVAL;
+
+#if defined(CONFIG_USB_DEBUG)
+	/* force remote wakeup enabled in case gadget driver doesn't support */
+	spin_lock_irqsave(&dev->lock, flags);
+	dev->remote_wakeup = 1;
+	dev->dev_status |= (1 << USB_DEVICE_REMOTE_WAKEUP);
+	spin_unlock_irqrestore(&dev->lock, flags);
+#endif
+
+	langwell_wakeup(&dev->gadget);
+
+	return rc;
+}
+static DEVICE_ATTR(remote_wakeup, S_IWUSR, NULL, store_remote_wakeup);
+
+
 /*-------------------------------------------------------------------------*/
 
 /*
@@ -1818,6 +1828,9 @@ int usb_gadget_register_driver(struct usb_gadget_driver *driver)
 
 	DBG(dev, "---> %s()\n", __func__);
 
+	if (unlikely(!driver || !driver->bind))
+		return -EINVAL;
+
 	if (dev->driver)
 		return -EBUSY;
 
@@ -1839,34 +1852,24 @@ int usb_gadget_register_driver(struct usb_gadget_driver *driver)
 		return retval;
 	}
 
-	retval = device_create_file(&dev->pdev->dev, &dev_attr_function);
-	if (retval)
-		goto err_unbind;
-
 	dev->usb_state = USB_STATE_ATTACHED;
 	dev->ep0_state = WAIT_FOR_SETUP;
 	dev->ep0_dir = USB_DIR_OUT;
 
+	/* bind OTG transceiver */
+	if (dev->transceiver)
+		(void)otg_set_peripheral(dev->transceiver, &dev->gadget);
+
 	/* enable interrupt and set controller to run state */
 	if (dev->got_irq)
 		langwell_udc_start(dev);
 
 	VDBG(dev, "After langwell_udc_start(), print all registers:\n");
-#ifdef	VERBOSE
 	print_all_registers(dev);
-#endif
 
 	INFO(dev, "register driver: %s\n", driver->driver.name);
-	VDBG(dev, "<--- %s()\n", __func__);
-	return 0;
-
-err_unbind:
-	driver->unbind(&dev->gadget);
-	dev->gadget.dev.driver = NULL;
-	dev->driver = NULL;
-
 	DBG(dev, "<--- %s()\n", __func__);
-	return retval;
+	return 0;
 }
 EXPORT_SYMBOL(usb_gadget_register_driver);
 
@@ -1876,15 +1879,27 @@ int usb_gadget_unregister_driver(struct usb_gadget_driver *driver)
 {
 	struct langwell_udc	*dev = the_controller;
 	unsigned long		flags;
+	u32		        devlc;
+	u8	        	devlc_byte2;
 
 	if (!dev)
 		return -ENODEV;
 
 	DBG(dev, "---> %s()\n", __func__);
 
-	if (unlikely(!driver || !driver->bind || !driver->unbind))
+	if (unlikely(!driver || !driver->unbind || !driver->disconnect))
 		return -EINVAL;
 
+	/* exit PHY low power suspend */
+	devlc = readl(&dev->op_regs->devlc);
+	VDBG(dev, "devlc = 0x%08x\n", devlc);
+	devlc &= ~LPM_PHCD;
+	/* FIXME: workaround for Langwell A1/A2/A3 sighting */
+	devlc_byte2 = (devlc >> 16) & 0xff;
+	writeb(devlc_byte2, (u8 *)&dev->op_regs->devlc + 2);
+	devlc = readl(&dev->op_regs->devlc);
+	VDBG(dev, "exit PHY low power suspend, devlc = 0x%08x\n", devlc);
+
 	/* unbind OTG transceiver */
 	if (dev->transceiver)
 		(void)otg_set_peripheral(dev->transceiver, 0);
@@ -1908,8 +1923,6 @@ int usb_gadget_unregister_driver(struct usb_gadget_driver *driver)
 	dev->gadget.dev.driver = NULL;
 	dev->driver = NULL;
 
-	device_remove_file(&dev->pdev->dev, &dev_attr_function);
-
 	INFO(dev, "unregistered driver '%s'\n", driver->driver.name);
 	DBG(dev, "<--- %s()\n", __func__);
 	return 0;
@@ -1917,6 +1930,55 @@ int usb_gadget_unregister_driver(struct usb_gadget_driver *driver)
 EXPORT_SYMBOL(usb_gadget_unregister_driver);
 
 
+/* gets the maximum power consumption */
+int langwell_udc_maxpower(int *mA)
+{
+	struct langwell_udc	*dev = the_controller;
+	u32			usbmode, portsc1, usbcmd;
+
+	/* fatal error */
+	if (!dev) {
+		*mA = 0;
+		return -EOTGFAIL;
+	}
+
+	DBG(dev, "---> %s()\n", __func__);
+
+	/* contrller is not in device mode */
+	usbmode = readl(&dev->op_regs->usbmode);
+	if (MODE_CM(usbmode) != MODE_DEVICE) {
+		*mA = 0;
+		return -EOTGNODEVICE;
+	}
+
+	/* can't get maximum power */
+	usbcmd = readl(&dev->op_regs->usbcmd);
+	if (!(usbcmd & CMD_RUNSTOP)) {
+		*mA = 0;
+		return -EOTGCHARGER;
+	}
+
+	/* disconnect to USB host */
+	portsc1 = readl(&dev->op_regs->portsc1);
+	if (!(portsc1 & PORTS_CCS)) {
+		*mA = 0;
+		return -EOTGDISCONN;
+	}
+
+	/* set max power capability */
+	*mA = CONFIG_USB_GADGET_VBUS_DRAW;
+
+	if ((*mA < 8) || (*mA > 500)) {
+		*mA = 0;
+		return -EOTGINVAL;
+	}
+
+	DBG(dev, "<--- %s()\n", __func__);
+	return 0;
+}
+EXPORT_SYMBOL(langwell_udc_maxpower);
+
+
 /*-------------------------------------------------------------------------*/
 
 /*
@@ -2113,8 +2175,7 @@ static void get_status(struct langwell_udc *dev, u8 request_type, u16 value,
 
 	if ((request_type & USB_RECIP_MASK) == USB_RECIP_DEVICE) {
 		/* get device status */
-		status_data = 1 << USB_DEVICE_SELF_POWERED;
-		status_data |= dev->remote_wakeup << USB_DEVICE_REMOTE_WAKEUP;
+		status_data = dev->dev_status;
 	} else if ((request_type & USB_RECIP_MASK) == USB_RECIP_INTERFACE) {
 		/* get interface status */
 		status_data = 0;
@@ -2129,6 +2190,8 @@ static void get_status(struct langwell_udc *dev, u8 request_type, u16 value,
 		status_data = ep_is_stall(epn) << USB_ENDPOINT_HALT;
 	}
 
+	DBG(dev, "get status data: 0x%04x\n", status_data);
+
 	dev->ep0_dir = USB_DIR_IN;
 
 	/* borrow the per device status_req */
@@ -2247,22 +2310,37 @@ static void handle_setup_packet(struct langwell_udc *dev,
 		} else if ((setup->bRequestType & (USB_RECIP_MASK
 				| USB_TYPE_MASK)) == (USB_RECIP_DEVICE
 				| USB_TYPE_STANDARD)) {
-			if (!gadget_is_otg(&dev->gadget))
+			rc = 0;
+			switch (wValue) {
+			case USB_DEVICE_REMOTE_WAKEUP:
+				if (setup->bRequest == USB_REQ_SET_FEATURE) {
+					dev->remote_wakeup = 1;
+					dev->dev_status |= (1 << wValue);
+				} else {
+					dev->remote_wakeup = 0;
+					dev->dev_status &= ~(1 << wValue);
+				}
 				break;
-			else if (setup->bRequest == USB_DEVICE_B_HNP_ENABLE) {
+			case USB_DEVICE_B_HNP_ENABLE:
 				dev->gadget.b_hnp_enable = 1;
 #ifdef	OTG_TRANSCEIVER
 				if (!dev->lotg->otg.default_a)
 					dev->lotg->hsm.b_hnp_enable = 1;
 #endif
-			} else if (setup->bRequest == USB_DEVICE_A_HNP_SUPPORT)
+				dev->dev_status |= (1 << wValue);
+				break;
+			case USB_DEVICE_A_HNP_SUPPORT:
 				dev->gadget.a_hnp_support = 1;
-			else if (setup->bRequest ==
-					USB_DEVICE_A_ALT_HNP_SUPPORT)
+				dev->dev_status |= (1 << wValue);
+				break;
+			case USB_DEVICE_A_ALT_HNP_SUPPORT:
 				dev->gadget.a_alt_hnp_support = 1;
-			else
+				dev->dev_status |= (1 << wValue);
 				break;
-			rc = 0;
+			default:
+				rc = -EOPNOTSUPP;
+				break;
+			}
 		} else
 			break;
 
@@ -2387,7 +2465,7 @@ static int process_ep_req(struct langwell_udc *dev, int index,
 		} else {
 			/* transfers completed with errors */
 			if (dtd_status & DTD_STS_ACTIVE) {
-				DBG(dev, "request not completed\n");
+				DBG(dev, "dTD status ACTIVE dQH[%d]\n", index);
 				retval = 1;
 				return retval;
 			} else if (dtd_status & DTD_STS_HALTED) {
@@ -2586,18 +2664,14 @@ static void handle_port_change(struct langwell_udc *dev)
 	/* LPM L0 to L1 */
 	if (dev->lpm && dev->lpm_state == LPM_L0)
 		if (portsc1 & PORTS_SUSP && portsc1 & PORTS_SLP) {
-				INFO(dev, "LPM L0 to L1\n");
-				dev->lpm_state = LPM_L1;
+			INFO(dev, "LPM L0 to L1\n");
+			dev->lpm_state = LPM_L1;
 		}
 
 	/* LPM L1 to L0, force resume or remote wakeup finished */
 	if (dev->lpm && dev->lpm_state == LPM_L1)
 		if (!(portsc1 & PORTS_SUSP)) {
-			if (portsc1 & PORTS_SLP)
-				INFO(dev, "LPM L1 to L0, force resume\n");
-			else
-				INFO(dev, "LPM L1 to L0, remote wakeup\n");
-
+			INFO(dev, "LPM L1 to L0\n");
 			dev->lpm_state = LPM_L0;
 		}
 
@@ -2634,7 +2708,10 @@ static void handle_usb_reset(struct langwell_udc *dev)
 
 	dev->ep0_dir = USB_DIR_OUT;
 	dev->ep0_state = WAIT_FOR_SETUP;
-	dev->remote_wakeup = 0;		/* default to 0 on reset */
+
+	/* remote wakeup reset to 0 when the device is reset */
+	dev->remote_wakeup = 0;
+	dev->dev_status = 1 << USB_DEVICE_SELF_POWERED;
 	dev->gadget.b_hnp_enable = 0;
 	dev->gadget.a_hnp_support = 0;
 	dev->gadget.a_alt_hnp_support = 0;
@@ -2699,6 +2776,7 @@ static void handle_usb_reset(struct langwell_udc *dev)
 static void handle_bus_suspend(struct langwell_udc *dev)
 {
 	u32		devlc;
+	u8		devlc_byte2;
 	DBG(dev, "---> %s()\n", __func__);
 
 	dev->resume_state = dev->usb_state;
@@ -2706,7 +2784,8 @@ static void handle_bus_suspend(struct langwell_udc *dev)
 
 #ifdef	OTG_TRANSCEIVER
 	if (dev->lotg->otg.default_a) {
-		if (dev->lotg->hsm.b_bus_suspend_vld == 1) {
+		/* ignore host LPM capability checking during enumeration */
+		if (dev->lotg->hsm.b_bus_suspend_vld == 2) {
 			dev->lotg->hsm.b_bus_suspend = 1;
 			/* notify transceiver the state changes */
 			if (spin_trylock(&dev->lotg->wq_lock)) {
@@ -2741,7 +2820,11 @@ static void handle_bus_suspend(struct langwell_udc *dev)
 	devlc = readl(&dev->op_regs->devlc);
 	VDBG(dev, "devlc = 0x%08x\n", devlc);
 	devlc |= LPM_PHCD;
-	writel(devlc, &dev->op_regs->devlc);
+	/* FIXME: workaround for Langwell A1/A2/A3 sighting */
+	devlc_byte2 = (devlc >> 16) & 0xff;
+	writeb(devlc_byte2, (u8 *)&dev->op_regs->devlc + 2);
+	devlc = readl(&dev->op_regs->devlc);
+	VDBG(dev, "enter PHY low power suspend, devlc = 0x%08x\n", devlc);
 
 	DBG(dev, "<--- %s()\n", __func__);
 }
@@ -2750,6 +2833,7 @@ static void handle_bus_suspend(struct langwell_udc *dev)
 static void handle_bus_resume(struct langwell_udc *dev)
 {
 	u32		devlc;
+	u8		devlc_byte2;
 	DBG(dev, "---> %s()\n", __func__);
 
 	dev->usb_state = dev->resume_state;
@@ -2759,7 +2843,11 @@ static void handle_bus_resume(struct langwell_udc *dev)
 	devlc = readl(&dev->op_regs->devlc);
 	VDBG(dev, "devlc = 0x%08x\n", devlc);
 	devlc &= ~LPM_PHCD;
-	writel(devlc, &dev->op_regs->devlc);
+	/* FIXME: workaround for Langwell A1/A2/A3 sighting */
+	devlc_byte2 = (devlc >> 16) & 0xff;
+	writeb(devlc_byte2, (u8 *)&dev->op_regs->devlc + 2);
+	devlc = readl(&dev->op_regs->devlc);
+	VDBG(dev, "exit PHY low power suspend, devlc = 0x%08x\n", devlc);
 
 #ifdef	OTG_TRANSCEIVER
 	if (dev->lotg->otg.default_a == 0)
@@ -2898,6 +2986,50 @@ static void gadget_release(struct device *_dev)
 }
 
 
+/* enable SRAM caching if SRAM detected */
+static void sram_init(struct langwell_udc *dev)
+{
+	struct pci_dev		*pdev = dev->pdev;
+
+	DBG(dev, "---> %s()\n", __func__);
+
+	dev->sram_addr = pci_resource_start(pdev, 1);
+	dev->sram_size = pci_resource_len(pdev, 1);
+	INFO(dev, "Found private SRAM at %x size:%x\n",
+			dev->sram_addr, dev->sram_size);
+	dev->got_sram = 1;
+
+	if (pci_request_region(pdev, 1, kobject_name(&pdev->dev.kobj))) {
+		WARNING(dev, "SRAM request failed\n");
+		dev->got_sram = 0;
+	} else if (!dma_declare_coherent_memory(&pdev->dev, dev->sram_addr,
+			dev->sram_addr, dev->sram_size, DMA_MEMORY_MAP)) {
+		WARNING(dev, "SRAM DMA declare failed\n");
+		pci_release_region(pdev, 1);
+		dev->got_sram = 0;
+	}
+
+	DBG(dev, "<--- %s()\n", __func__);
+}
+
+
+/* release SRAM caching */
+static void sram_deinit(struct langwell_udc *dev)
+{
+	struct pci_dev *pdev = dev->pdev;
+
+	DBG(dev, "---> %s()\n", __func__);
+
+	dma_release_declared_memory(&pdev->dev);
+	pci_release_region(pdev, 1);
+
+	dev->got_sram = 0;
+
+	INFO(dev, "release SRAM caching\n");
+	DBG(dev, "<--- %s()\n", __func__);
+}
+
+
 /* tear down the binding between this driver and the pci device */
 static void langwell_udc_remove(struct pci_dev *pdev)
 {
@@ -2910,19 +3042,25 @@ static void langwell_udc_remove(struct pci_dev *pdev)
 
 	dev->done = &done;
 
-	/* free memory allocated in probe */
+#ifndef	OTG_TRANSCEIVER
+	/* free dTD dma_pool and dQH */
 	if (dev->dtd_pool)
 		dma_pool_destroy(dev->dtd_pool);
 
+	if (dev->ep_dqh)
+		dma_free_coherent(&pdev->dev, dev->ep_dqh_size,
+			dev->ep_dqh, dev->ep_dqh_dma);
+
+	/* release SRAM caching */
+	if (dev->has_sram && dev->got_sram)
+		sram_deinit(dev);
+#endif
+
 	if (dev->status_req) {
 		kfree(dev->status_req->req.buf);
 		kfree(dev->status_req);
 	}
 
-	if (dev->ep_dqh)
-		dma_free_coherent(&pdev->dev, dev->ep_dqh_size,
-			dev->ep_dqh, dev->ep_dqh_dma);
-
 	kfree(dev->ep);
 
 	/* diable IRQ handler */
@@ -2954,6 +3092,7 @@ static void langwell_udc_remove(struct pci_dev *pdev)
 
 	device_unregister(&dev->gadget.dev);
 	device_remove_file(&pdev->dev, &dev_attr_langwell_udc);
+	device_remove_file(&pdev->dev, &dev_attr_remote_wakeup);
 
 #ifndef	OTG_TRANSCEIVER
 	pci_set_drvdata(pdev, NULL);
@@ -2976,9 +3115,9 @@ static int langwell_udc_probe(struct pci_dev *pdev,
 	struct langwell_udc	*dev;
 #ifndef	OTG_TRANSCEIVER
 	unsigned long		resource, len;
+	size_t			size;
 #endif
 	void			__iomem *base = NULL;
-	size_t			size;
 	int			retval;
 
 	if (the_controller) {
@@ -3049,7 +3188,15 @@ static int langwell_udc_probe(struct pci_dev *pdev,
 		goto error;
 	}
 
+	dev->has_sram = 1;
+	dev->got_sram = 0;
+	VDBG(dev, "dev->has_sram: %d\n", dev->has_sram);
+
 #ifndef	OTG_TRANSCEIVER
+	/* enable SRAM caching if detected */
+	if (dev->has_sram && !dev->got_sram)
+		sram_init(dev);
+
 	INFO(dev, "irq %d, io mem: 0x%08lx, len: 0x%08lx, pci mem 0x%p\n",
 			pdev->irq, resource, len, base);
 	/* enables bus-mastering for device dev */
@@ -3094,6 +3241,7 @@ static int langwell_udc_probe(struct pci_dev *pdev,
 		goto error;
 	}
 
+#ifndef	OTG_TRANSCEIVER
 	/* allocate device dQH memory */
 	size = dev->ep_max * sizeof(struct langwell_dqh);
 	VDBG(dev, "orig size = %d\n", size);
@@ -3112,6 +3260,7 @@ static int langwell_udc_probe(struct pci_dev *pdev,
 	}
 	dev->ep_dqh_size = size;
 	VDBG(dev, "ep_dqh_size = %d\n", dev->ep_dqh_size);
+#endif
 
 	/* initialize ep0 status request structure */
 	dev->status_req = kzalloc(sizeof(struct langwell_request), GFP_KERNEL);
@@ -3129,7 +3278,10 @@ static int langwell_udc_probe(struct pci_dev *pdev,
 	dev->resume_state = USB_STATE_NOTATTACHED;
 	dev->usb_state = USB_STATE_POWERED;
 	dev->ep0_dir = USB_DIR_OUT;
-	dev->remote_wakeup = 0;	/* default to 0 on reset */
+
+	/* remote wakeup reset to 0 when the device is reset */
+	dev->remote_wakeup = 0;
+	dev->dev_status = 1 << USB_DEVICE_SELF_POWERED;
 
 #ifndef	OTG_TRANSCEIVER
 	/* reset device controller */
@@ -3159,7 +3311,6 @@ static int langwell_udc_probe(struct pci_dev *pdev,
 #ifndef	OTG_TRANSCEIVER
 	/* reset ep0 dQH and endptctrl */
 	ep0_reset(dev);
-#endif
 
 	/* create dTD dma_pool resource */
 	dev->dtd_pool = dma_pool_create("langwell_dtd",
@@ -3172,6 +3323,7 @@ static int langwell_udc_probe(struct pci_dev *pdev,
 		retval = -ENOMEM;
 		goto error;
 	}
+#endif
 
 	/* done */
 	INFO(dev, "%s\n", driver_desc);
@@ -3183,9 +3335,7 @@ static int langwell_udc_probe(struct pci_dev *pdev,
 	INFO(dev, "Support USB LPM: %s\n", dev->lpm ? "Yes" : "No");
 
 	VDBG(dev, "After langwell_udc_probe(), print all registers:\n");
-#ifdef	VERBOSE
 	print_all_registers(dev);
-#endif
 
 	the_controller = dev;
 
@@ -3197,9 +3347,15 @@ static int langwell_udc_probe(struct pci_dev *pdev,
 	if (retval)
 		goto error;
 
+	retval = device_create_file(&pdev->dev, &dev_attr_remote_wakeup);
+	if (retval)
+		goto error_attr1;
+
 	VDBG(dev, "<--- %s()\n", __func__);
 	return 0;
 
+error_attr1:
+	device_remove_file(&pdev->dev, &dev_attr_langwell_udc);
 error:
 	if (dev) {
 		DBG(dev, "<--- %s()\n", __func__);
@@ -3215,6 +3371,7 @@ static int langwell_udc_suspend(struct pci_dev *pdev, pm_message_t state)
 {
 	struct langwell_udc	*dev = the_controller;
 	u32			devlc;
+	u8			devlc_byte2;
 
 	DBG(dev, "---> %s()\n", __func__);
 
@@ -3226,10 +3383,21 @@ static int langwell_udc_suspend(struct pci_dev *pdev, pm_message_t state)
 		free_irq(pdev->irq, dev);
 	dev->got_irq = 0;
 
-
 	/* save PCI state */
 	pci_save_state(pdev);
 
+	/* free dTD dma_pool and dQH */
+	if (dev->dtd_pool)
+		dma_pool_destroy(dev->dtd_pool);
+
+	if (dev->ep_dqh)
+		dma_free_coherent(&pdev->dev, dev->ep_dqh_size,
+			dev->ep_dqh, dev->ep_dqh_dma);
+
+	/* release SRAM caching */
+	if (dev->has_sram && dev->got_sram)
+		sram_deinit(dev);
+
 	/* set device power state */
 	pci_set_power_state(pdev, PCI_D3hot);
 
@@ -3237,7 +3405,11 @@ static int langwell_udc_suspend(struct pci_dev *pdev, pm_message_t state)
 	devlc = readl(&dev->op_regs->devlc);
 	VDBG(dev, "devlc = 0x%08x\n", devlc);
 	devlc |= LPM_PHCD;
-	writel(devlc, &dev->op_regs->devlc);
+	/* FIXME: workaround for Langwell A1/A2/A3 sighting */
+	devlc_byte2 = (devlc >> 16) & 0xff;
+	writeb(devlc_byte2, (u8 *)&dev->op_regs->devlc + 2);
+	devlc = readl(&dev->op_regs->devlc);
+	VDBG(dev, "enter PHY low power suspend, devlc = 0x%08x\n", devlc);
 
 	DBG(dev, "<--- %s()\n", __func__);
 	return 0;
@@ -3249,6 +3421,8 @@ static int langwell_udc_resume(struct pci_dev *pdev)
 {
 	struct langwell_udc	*dev = the_controller;
 	u32			devlc;
+	u8			devlc_byte2;
+	size_t			size;
 
 	DBG(dev, "---> %s()\n", __func__);
 
@@ -3256,19 +3430,55 @@ static int langwell_udc_resume(struct pci_dev *pdev)
 	devlc = readl(&dev->op_regs->devlc);
 	VDBG(dev, "devlc = 0x%08x\n", devlc);
 	devlc &= ~LPM_PHCD;
-	writel(devlc, &dev->op_regs->devlc);
+	/* FIXME: workaround for Langwell A1/A2/A3 sighting */
+	devlc_byte2 = (devlc >> 16) & 0xff;
+	writeb(devlc_byte2, (u8 *)&dev->op_regs->devlc + 2);
+	devlc = readl(&dev->op_regs->devlc);
+	VDBG(dev, "exit PHY low power suspend, devlc = 0x%08x\n", devlc);
 
 	/* set device D0 power state */
 	pci_set_power_state(pdev, PCI_D0);
 
+	/* enable SRAM caching if detected */
+	if (dev->has_sram && !dev->got_sram)
+		sram_init(dev);
+
+	/* allocate device dQH memory */
+	size = dev->ep_max * sizeof(struct langwell_dqh);
+	VDBG(dev, "orig size = %d\n", size);
+	if (size < DQH_ALIGNMENT)
+		size = DQH_ALIGNMENT;
+	else if ((size % DQH_ALIGNMENT) != 0) {
+		size += DQH_ALIGNMENT + 1;
+		size &= ~(DQH_ALIGNMENT - 1);
+	}
+	dev->ep_dqh = dma_alloc_coherent(&pdev->dev, size,
+					&dev->ep_dqh_dma, GFP_KERNEL);
+	if (!dev->ep_dqh) {
+		ERROR(dev, "allocate dQH memory failed\n");
+		return -ENOMEM;
+	}
+	dev->ep_dqh_size = size;
+	VDBG(dev, "ep_dqh_size = %d\n", dev->ep_dqh_size);
+
+	/* create dTD dma_pool resource */
+	dev->dtd_pool = dma_pool_create("langwell_dtd",
+			&dev->pdev->dev,
+			sizeof(struct langwell_dtd),
+			DTD_ALIGNMENT,
+			DMA_BOUNDARY);
+
+	if (!dev->dtd_pool)
+		return -ENOMEM;
+
 	/* restore PCI state */
 	pci_restore_state(pdev);
 
 	/* enable IRQ handler */
-	if (request_irq(pdev->irq, langwell_irq, IRQF_SHARED, driver_name, dev)
-			!= 0) {
+	if (request_irq(pdev->irq, langwell_irq, IRQF_SHARED,
+				driver_name, dev) != 0) {
 		ERROR(dev, "request interrupt %d failed\n", pdev->irq);
-		return -1;
+		return -EBUSY;
 	}
 	dev->got_irq = 1;
 
diff --git a/drivers/usb/gadget/langwell_udc.h b/drivers/usb/gadget/langwell_udc.h
index 9719934..323c574 100644
--- a/drivers/usb/gadget/langwell_udc.h
+++ b/drivers/usb/gadget/langwell_udc.h
@@ -174,7 +174,7 @@ enum lpm_state {
 struct langwell_udc {
 	/* each pci device provides one gadget, several endpoints */
 	struct usb_gadget	gadget;
-	spinlock_t		lock;	/* device lock */
+	spinlock_t		lock;		/* device lock */
 	struct langwell_ep	*ep;
 	struct usb_gadget_driver	*driver;
 	struct otg_transceiver	*transceiver;
@@ -199,7 +199,9 @@ struct langwell_udc {
 				vbus_active:1,
 				suspended:1,
 				stopped:1,
-				lpm:1;	/* LPM capability */
+				lpm:1,		/* LPM capability */
+				has_sram:1,	/* SRAM caching */
+				got_sram:1;
 
 	/* pci state used to access those endpoints */
 	struct pci_dev		*pdev;
@@ -224,5 +226,12 @@ struct langwell_udc {
 
 	/* make sure release() is done */
 	struct completion	*done;
+
+	/* for private SRAM caching */
+	unsigned int		sram_addr;
+	unsigned int		sram_size;
+
+	/* device status data for get_status request */
+	u16			dev_status;
 };
 
diff --git a/drivers/usb/gadget/still_image.c b/drivers/usb/gadget/still_image.c
new file mode 100644
index 0000000..94c17ce
--- /dev/null
+++ b/drivers/usb/gadget/still_image.c
@@ -0,0 +1,4566 @@
+/*
+ * still_image.c -- Lite USB Still Image Capture Gadget, for USB development
+ * Copyright (C) 2009, Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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.,
+ * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+
+/*
+ * This code is partly based on:
+ * File-backed USB Storage Gadget driver, Copyright (C) 2003-2008 Alan Stern
+ *
+ *
+ * Refer to the USB Device Class Definition for Still Image Capture Device:
+ * http://www.usb.org/developers/devclass_docs/usb_still_img10.zip
+ *
+ *
+ * Supported PIMA 15740/PTP operations:
+ *	- GetDeviceInfo
+ *	- OpenSession
+ *	- CloseSession
+ *	- GetStorageIDs
+ *	- GetStorageInfo
+ *	- GetNumObjects
+ *	- GetObjectHandles
+ *	- GetObjectInfo
+ *	- GetObject
+ *	- DeleteObject
+ *	- SendObjectInfo
+ *	- SendObject
+ *	- CopyObject
+ *	- MoveObject
+ *
+ * Supported object formats:
+ *	- EXIF/JPEG, JFIF
+ *	- PNG
+ *	- TIFF, TIFF/IT, TIFF/EP
+ *	- BMP
+ *	- GIF
+ *	- Unknown image object
+ *	- Undefined non-image object
+ *
+ * Supported PIMA 15740/PTP events:
+ *	- N/A
+ *
+ * Storage filesystem type:
+ *	- Generic hierarchical
+ *
+ *
+ * Module options:
+ *	folder=foldername 	Default NULL, name of the backing folder
+ *	vendor=0xVVVV		Default 0x8087 (Intel), USB Vendor ID
+ *	product=0xPPPP		Default 0x811e, USB Product ID
+ *	release=0xRRRR		Override the USB release number (bcdDevice)
+ *	buflen=N		Default N=16384, buffer size used (will be
+ *					rounded down to a multiple of
+ *					PAGE_CACHE_SIZE)
+ *
+ * Sysfs attribute file:
+ * 	folder			read/write the name of the backing folder
+ *
+ */
+
+
+#define	VERBOSE_DEBUG
+
+#include <linux/blkdev.h>
+#include <linux/completion.h>
+#include <linux/dcache.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/fcntl.h>
+#include <linux/file.h>
+#include <linux/fs.h>
+#include <linux/vfs.h>
+#include <linux/namei.h>
+#include <linux/kref.h>
+#include <linux/kthread.h>
+#include <linux/limits.h>
+#include <linux/rwsem.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/string.h>
+#include <linux/freezer.h>
+#include <linux/utsname.h>
+#include <linux/sort.h>
+
+#include <linux/usb/ch9.h>
+#include <linux/usb/gadget.h>
+
+#include "gadget_chips.h"
+
+#include "usbstring.c"
+#include "config.c"
+#include "epautoconf.c"
+
+
+/*-------------------------------------------------------------------------*/
+
+#define	DRIVER_DESC		"Still Image Gadget"
+#define	DRIVER_NAME		"g_still_image"
+#define	DRIVER_VERSION		"Apr 30, 2010"
+
+
+static const char longname[] = DRIVER_DESC;
+static const char shortname[] = DRIVER_NAME;
+
+
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_AUTHOR("Xiaochen Shen <xiaochen.shen@intel.com>; "
+		"Hang Yuan <hang.yuan@intel.com>");
+MODULE_VERSION(DRIVER_VERSION);
+MODULE_LICENSE("GPL");
+
+
+/*
+ * Intel Corporation donates this product ID.
+ *
+ * DO NOT REUSE THESE IDs with any other driver
+ * instead:  allocate your own, using normal USB-IF procedures.
+ */
+#define	DRIVER_VENDOR_ID	0x8087
+#define	DRIVER_PRODUCT_ID	0x811e
+
+
+/*-------------------------------------------------------------------------*/
+
+#define	MDBG(fmt, args...) \
+	pr_debug(DRIVER_NAME ": " fmt, ## args)
+#define	MINFO(fmt, args...) \
+	pr_info(DRIVER_NAME ": " fmt, ## args)
+
+#ifdef	DEBUG
+#define	DBG(d, fmt, args...) \
+	dev_dbg(&(d)->gadget->dev, fmt, ## args)
+#else
+#define	DBG(dev, fmt, args...) \
+	do { } while (0)
+#endif	/* DEBUG */
+
+
+#ifndef	DEBUG
+#undef	VERBOSE_DEBUG
+#endif	/* !DEBUG */
+
+#ifdef	VERBOSE_DEBUG
+#define	VDBG	DBG
+#else
+#define	VDBG(sti, fmt, args...) \
+	do { } while (0)
+#endif /* VERBOSE_DEBUG */
+
+#define	ERROR(d, fmt, args...) \
+	dev_err(&(d)->gadget->dev, fmt, ## args)
+#define	WARNING(d, fmt, args...) \
+	dev_warn(&(d)->gadget->dev, fmt, ## args)
+#define	INFO(d, fmt, args...) \
+	dev_info(&(d)->gadget->dev, fmt, ## args)
+
+
+/*-------------------------------------------------------------------------*/
+
+/* encapsulate the module parameter settings */
+
+static struct {
+	char		*folder;
+	unsigned short	vendor;
+	unsigned short	product;
+	unsigned short	release;
+	unsigned int	buflen;
+} mod_data = {				/* default values */
+	.vendor		= DRIVER_VENDOR_ID,
+	.product	= DRIVER_PRODUCT_ID,
+	.release	= 0xffff,	/* use controller chip type */
+	.buflen		= 16384,
+};
+
+
+module_param_named(folder, mod_data.folder, charp, S_IRUGO);
+MODULE_PARM_DESC(folder, "name of the backing folder");
+
+module_param_named(vendor, mod_data.vendor, ushort, S_IRUGO);
+MODULE_PARM_DESC(vendor, "USB Vendor ID");
+
+module_param_named(product, mod_data.product, ushort, S_IRUGO);
+MODULE_PARM_DESC(product, "USB Product ID");
+
+module_param_named(release, mod_data.release, ushort, S_IRUGO);
+MODULE_PARM_DESC(release, "USB release number");
+
+module_param_named(buflen, mod_data.buflen, uint, S_IRUGO);
+MODULE_PARM_DESC(buflen, "I/O buffer size");
+
+
+/*-------------------------------------------------------------------------*/
+
+/*
+ * DESCRIPTORS ... most are static, but strings and (full) configuration
+ * descriptors are built on demand.  Also the (static) config and interface
+ * descriptors are adjusted during sti_bind().
+ */
+#define	STRING_MANUFACTURER	1
+#define	STRING_PRODUCT		2
+#define	STRING_SERIAL		3
+#define	STRING_CONFIG		4
+#define	STRING_INTERFACE	5
+
+
+/* only one configuration */
+#define	CONFIG_VALUE		1
+
+static struct usb_device_descriptor
+device_desc = {
+	.bLength =		sizeof device_desc,
+	.bDescriptorType =	USB_DT_DEVICE,
+
+	.bcdUSB =		cpu_to_le16(0x0200),
+	.bDeviceClass =		USB_CLASS_PER_INTERFACE,
+
+	/* the next three values can be overridden by module parameters */
+	.idVendor =		cpu_to_le16(DRIVER_VENDOR_ID),
+	.idProduct =		cpu_to_le16(DRIVER_PRODUCT_ID),
+	.bcdDevice =		cpu_to_le16(0xffff),
+
+	.iManufacturer =	STRING_MANUFACTURER,
+	.iProduct =		STRING_PRODUCT,
+	.iSerialNumber =	STRING_SERIAL,
+	.bNumConfigurations =	1,
+};
+
+static struct usb_config_descriptor
+config_desc = {
+	.bLength =		sizeof config_desc,
+	.bDescriptorType =	USB_DT_CONFIG,
+
+	/* wTotalLength computed by usb_gadget_config_buf() */
+	.bNumInterfaces =	1,
+	.bConfigurationValue =	CONFIG_VALUE,
+	.iConfiguration =	STRING_CONFIG,
+	.bmAttributes =		USB_CONFIG_ATT_ONE | USB_CONFIG_ATT_SELFPOWER,
+	.bMaxPower =		CONFIG_USB_GADGET_VBUS_DRAW / 2,
+};
+
+static struct usb_otg_descriptor
+otg_desc = {
+	.bLength =		sizeof(otg_desc),
+	.bDescriptorType =	USB_DT_OTG,
+
+	.bmAttributes =		USB_OTG_SRP,
+};
+
+
+/* one interface */
+static struct usb_interface_descriptor
+intf_desc = {
+	.bLength =		sizeof intf_desc,
+	.bDescriptorType =	USB_DT_INTERFACE,
+
+	.bNumEndpoints =	3,	/* adjusted during sti_bind() */
+	.bInterfaceClass =	USB_CLASS_STILL_IMAGE,
+	.bInterfaceSubClass =	0x01,	/* Still Image Capture device */
+	.bInterfaceProtocol =	0x01,	/* Bulk-only protocol */
+	.iInterface =		STRING_INTERFACE,
+};
+
+
+/* two full-speed endpoint descriptors: bulk-in, bulk-out */
+
+static struct usb_endpoint_descriptor
+fs_bulk_in_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+
+	.bEndpointAddress =	USB_DIR_IN,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+	/* wMaxPacketSize set by autoconfiguration */
+};
+
+static struct usb_endpoint_descriptor
+fs_bulk_out_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+
+	.bEndpointAddress =	USB_DIR_OUT,
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+	/* wMaxPacketSize set by autoconfiguration */
+};
+
+static struct usb_endpoint_descriptor
+fs_intr_in_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+
+	.bEndpointAddress =	USB_DIR_IN,
+	.bmAttributes =		USB_ENDPOINT_XFER_INT,
+	.wMaxPacketSize =	cpu_to_le16(2),
+	.bInterval =		32,	/* frames -> 32 ms */
+};
+
+static const struct usb_descriptor_header *fs_function[] = {
+	(struct usb_descriptor_header *) &otg_desc,
+	(struct usb_descriptor_header *) &intf_desc,
+	(struct usb_descriptor_header *) &fs_bulk_in_desc,
+	(struct usb_descriptor_header *) &fs_bulk_out_desc,
+	(struct usb_descriptor_header *) &fs_intr_in_desc,
+	NULL,
+};
+
+#define	FS_FUNCTION_PRE_EP_ENTRIES	2
+
+
+/*
+ * USB 2.0 devices need to expose both high speed and full speed
+ * descriptors, unless they only run at full speed.
+ *
+ * That means alternate endpoint descriptors (bigger packets)
+ * and a "device qualifier" ... plus more construction options
+ * for the config descriptor.
+ */
+static struct usb_qualifier_descriptor
+dev_qualifier = {
+	.bLength =		sizeof dev_qualifier,
+	.bDescriptorType =	USB_DT_DEVICE_QUALIFIER,
+
+	.bcdUSB =		cpu_to_le16(0x0200),
+	.bDeviceClass =		USB_CLASS_PER_INTERFACE,
+
+	.bNumConfigurations =	1,
+};
+
+static struct usb_endpoint_descriptor
+hs_bulk_in_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+
+	/* bEndpointAddress copied from fs_bulk_in_desc during sti_bind() */
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize =	cpu_to_le16(512),
+};
+
+static struct usb_endpoint_descriptor
+hs_bulk_out_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+
+	/* bEndpointAddress copied from fs_bulk_out_desc during sti_bind() */
+	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize =	cpu_to_le16(512),
+	.bInterval =		1,	/* NAK every 1 uframe */
+};
+
+static struct usb_endpoint_descriptor
+hs_intr_in_desc = {
+	.bLength =		USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =	USB_DT_ENDPOINT,
+
+	/* bEndpointAddress copied from fs_intr_in_desc during sti_bind() */
+	.bmAttributes =		USB_ENDPOINT_XFER_INT,
+	.wMaxPacketSize =	cpu_to_le16(2),
+	.bInterval =		9,	/* 2**(9-1) = 256 uframes -> 32 ms */
+};
+
+static const struct usb_descriptor_header *hs_function[] = {
+	(struct usb_descriptor_header *) &otg_desc,
+	(struct usb_descriptor_header *) &intf_desc,
+	(struct usb_descriptor_header *) &hs_bulk_in_desc,
+	(struct usb_descriptor_header *) &hs_bulk_out_desc,
+	(struct usb_descriptor_header *) &hs_intr_in_desc,
+	NULL,
+};
+
+#define	HS_FUNCTION_PRE_EP_ENTRIES	2
+
+
+/* maxpacket and other transfer characteristics vary by speed. */
+static struct usb_endpoint_descriptor *
+ep_desc(struct usb_gadget *g, struct usb_endpoint_descriptor *fs,
+		struct usb_endpoint_descriptor *hs)
+{
+	if (gadget_is_dualspeed(g) && g->speed == USB_SPEED_HIGH)
+		return hs;
+
+	return fs;
+}
+
+static char		manufacturer[64];
+static char		serial[13];
+
+/* static strings, in UTF-8 (for simplicity we use only ASCII characters) */
+static struct usb_string		strings[] = {
+	{STRING_MANUFACTURER,	manufacturer},
+	{STRING_PRODUCT,	longname},
+	{STRING_SERIAL,		serial},
+	{STRING_CONFIG,		"Self-powered"},
+	{STRING_INTERFACE,	"Still Image"},
+	{}
+};
+
+static struct usb_gadget_strings	stringtab = {
+	.language	= 0x0409,		/* en-us */
+	.strings	= strings,
+};
+
+
+/*-------------------------------------------------------------------------*/
+
+/* protocol, driver and device data structures */
+
+/* big enough to hold the biggest descriptor */
+#define	EP0_BUFSIZE		256
+
+#define	DELAYED_STATUS		(EP0_BUFSIZE + 999)
+
+/* number of buffers we will use.  2 is enough for double-buffering */
+#define	NUM_BUFFERS		2
+
+/* PIMA 15740, operation and response datasets have at most 5 parameters */
+#define	PARAM_NUM_MAX		5
+
+/* PIMA 15740 generic container head length */
+#define	PIMA15740_CONTAINER_LEN	12
+
+/* storage id, description */
+#define	STORAGE_ID		0x00010001
+#define	STORAGE_DESCRIPTION	"Built-in Storage"
+
+/* Still Image class-specific requests */
+#define	STI_CANCEL_REQUEST			0x64
+#define	STI_GET_EXTENDED_EVENT_DATA		0x65
+#define	STI_DEVICE_RESET_REQUEST		0x66
+#define	STI_GET_DEVICE_STATUS			0x67
+
+#define	STI_CANCEL_REQUEST_LENGTH		0x0006
+#define	STI_CANCEL_REQUEST_CODE			0x4001
+
+/* supported PIMA 15740 operations */
+#define	PIMA15740_OP_GET_DEVICE_INFO		0x1001
+#define	PIMA15740_OP_OPEN_SESSION		0x1002
+#define	PIMA15740_OP_CLOSE_SESSION		0x1003
+#define	PIMA15740_OP_GET_STORAGE_IDS		0x1004
+#define	PIMA15740_OP_GET_STORAGE_INFO		0x1005
+#define	PIMA15740_OP_GET_NUM_OBJECTS		0x1006
+#define	PIMA15740_OP_GET_OBJECT_HANDLES		0x1007
+#define	PIMA15740_OP_GET_OBJECT_INFO		0x1008
+#define	PIMA15740_OP_GET_OBJECT			0x1009
+#define	PIMA15740_OP_DELETE_OBJECT		0x100b
+#define	PIMA15740_OP_SEND_OBJECT_INFO		0x100c
+#define	PIMA15740_OP_SEND_OBJECT		0x100d
+#define	PIMA15740_OP_MOVE_OBJECT		0x1019
+#define	PIMA15740_OP_COPY_OBJECT		0x101a
+
+/* PIMA 15740 responses definition */
+#define	PIMA15740_RES_UNDEFINED			0x2000
+#define	PIMA15740_RES_OK			0x2001
+#define	PIMA15740_RES_GENERAL_ERROR		0x2002
+#define	PIMA15740_RES_SESSION_NOT_OPEN		0x2003
+#define	PIMA15740_RES_INVALID_TRANS_ID		0x2004
+#define	PIMA15740_RES_OPERATION_NOT_SUPPORTED	0x2005
+#define	PIMA15740_RES_PARAMETER_NOT_SUPPORTED	0x2006
+#define	PIMA15740_RES_INCOMPLETE_TRANSFER	0x2007
+#define	PIMA15740_RES_INVALID_STORAGE_ID	0x2008
+#define	PIMA15740_RES_INVALID_OBJECT_HANDLE	0x2009
+#define	PIMA15740_RES_DEVICE_PROP_NOT_SUPPORTED	0x200a
+#define	PIMA15740_RES_INVALID_OBJECT_FORMAT	0x200b
+#define	PIMA15740_RES_STORE_FULL		0x200c
+#define	PIMA15740_RES_OBJECT_WRITE_PROTECTED	0x200d
+#define	PIMA15740_RES_STORE_READ_ONLY		0x200e
+#define	PIMA15740_RES_ACCESS_DENIED		0x200f
+#define	PIMA15740_RES_NO_THUMBNAIL_PRESENT	0x2010
+#define	PIMA15740_RES_SELF_TEST_FAILED		0x2011
+#define	PIMA15740_RES_PARTIAL_DELETION		0x2012
+#define	PIMA15740_RES_STORE_NOT_AVAILABLE	0x2013
+#define	PIMA15740_RES_SPEC_BY_FORMAT_UNSUP	0x2014
+#define	PIMA15740_RES_NO_VALID_OBJECT_INFO	0x2015
+#define	PIMA15740_RES_INVALID_CODE_FORMAT	0x2016
+#define	PIMA15740_RES_UNKNOWN_VENDOR_CODE	0x2017
+#define	PIMA15740_RES_CAPTURE_ALREADY_TERM	0x2018
+#define	PIMA15740_RES_DEVICE_BUSY		0x2019
+#define	PIMA15740_RES_INVALID_PARENT_OBJECT	0x201a
+#define	PIMA15740_RES_INVALID_DEV_PROP_FORMAT	0x201b
+#define	PIMA15740_RES_INVALID_DEV_PROP_VALUE	0x201c
+#define	PIMA15740_RES_INVALID_PARAMETER		0x201d
+#define	PIMA15740_RES_SESSION_ALREADY_OPEN	0x201e
+#define	PIMA15740_RES_TRANSACTION_CANCELLED	0x201f
+#define	PIMA15740_RES_SPEC_OF_DESTINATION_UNSUP	0x2020
+
+/* PIMA 15740 functional mode */
+#define	PIMA15740_STANDARD_MODE			0x0000
+#define	PIMA15740_SLEEP_STATE_MODE		0x0001
+
+/* PIMA 15740 storage type */
+#define	PIMA15740_STOR_UNDEFINED		0x0000
+#define	PIMA15740_STOR_FIXED_ROM		0x0001
+#define	PIMA15740_STOR_REMOVABLE_ROM		0x0002
+#define	PIMA15740_STOR_FIXED_RAM		0x0003
+#define	PIMA15740_STOR_REMOVABLE_RAM		0x0004
+
+/* PIMA 15740 filesystem type */
+#define	PIMA15740_FS_UNDEFINED			0x0000
+#define	PIMA15740_FS_GENERIC_FLAT		0x0001
+#define	PIMA15740_FS_HIERARCHICAL		0x0002
+#define	PIMA15740_FS_DCF			0x0003
+
+/* PIMA 15740 access capability */
+#define	PIMA15740_ACCESS_CAP_RW			0x0000
+#define	PIMA15740_ACCESS_CAP_RO_WO_DELITION	0x0001
+#define	PIMA15740_ACCESS_CAP_RO_W_DELITION	0x0002
+
+/* PIMA 15740 object format codes */
+#define	PIMA15740_FMT_A_UNDEFINED		0x3000
+#define	PIMA15740_FMT_A_ASSOCIATION		0x3001
+#define	PIMA15740_FMT_I_UNDEFINED		0x3800
+#define	PIMA15740_FMT_I_EXIF_JPEG		0x3801
+#define	PIMA15740_FMT_I_TIFF_EP			0x3802
+#define	PIMA15740_FMT_I_FLASHPIX		0x3803
+#define	PIMA15740_FMT_I_BMP			0x3804
+#define	PIMA15740_FMT_I_CIFF			0x3805
+#define	PIMA15740_FMT_I_GIF			0x3807
+#define	PIMA15740_FMT_I_JFIF			0x3808
+#define	PIMA15740_FMT_I_PCD			0x3809
+#define	PIMA15740_FMT_I_PICT			0x380a
+#define	PIMA15740_FMT_I_PNG			0x380b
+#define	PIMA15740_FMT_I_TIFF			0x380d
+#define	PIMA15740_FMT_I_TIFF_IT			0x380e
+#define	PIMA15740_FMT_I_JP2			0x380f
+#define	PIMA15740_FMT_I_JPX			0x3810
+
+/* PIMA 15740 object protection status */
+#define	PIMA15740_OBJECT_NO_PROTECTION		0x0000
+#define	PIMA15740_OBJECT_READ_ONLY		0x0001
+
+/* PIMA 15740 object association type */
+#define	PIMA15740_AS_UNDEFINED			0x0000
+#define	PIMA15740_AS_GENERIC_FOLDER		0x0001
+
+
+static const char storage_desc[] = STORAGE_DESCRIPTION;
+static const char device_version[] = DRIVER_VERSION;
+
+
+/*-------------------------------------------------------------------------*/
+
+/* PIMA 15740 data structure */
+
+enum pima15740_container_type {
+	TYPE_UNDEFINED		= 0,
+	TYPE_COMMAND_BLOCK	= 1,
+	TYPE_DATA_BLOCK		= 2,
+	TYPE_RESPONSE_BLOCK	= 3,
+	TYPE_EVENT_BLOCK	= 4,
+};
+
+/* PIMA15740 generic container structure, little endian */
+struct pima15740_container {
+	__le32			container_len;
+	__le16			container_type;
+	__le16			code;
+	__le32			transaction_id;
+} __attribute__ ((packed));
+
+/* data stage of Get Extended Event Data */
+struct sti_ext_event {
+	u16			event_code;
+	u32			transaction_id;
+	u16			param_num;
+} __attribute__ ((packed));
+
+/* data stage of Get Device Status Data */
+struct sti_dev_status {
+	u16			wlength;
+	u16			code;
+} __attribute__ ((packed));
+
+
+/* DeviceInfo Dataset */
+struct pima15740_device_info {
+	u16	standard_version;
+	u32	vendor_extension_id;
+	u16	vendor_extension_version;
+	u8	vendor_extension_desc_len;
+	u8	vendor_extension_desc[0];
+	u16	functional_mode;
+	u32	operations_supported_count;
+	u16	operations_supported[14];
+	u32	events_supported_count;
+	u16	events_supported[0];
+	u32	device_properties_count;
+	u16	device_properties_supported[0];
+	u32	capture_formats_count;
+	u16	capture_formats[0];
+	u32	image_formats_count;
+	u16	image_formats[10];
+	u8	manufacturer_len;
+	u8	manufacturer[sizeof(manufacturer) * 2];
+	u8	model_len;
+	u8	model[sizeof(longname) * 2];
+	u8	device_version_len;
+	u8	device_version[sizeof(device_version) * 2];
+	u8	serial_number_len;
+	u8	serial_number[sizeof(serial) * 2];
+} __attribute__ ((packed));
+
+static struct pima15740_device_info sti_device_info = {
+	.standard_version		= 100,
+	.vendor_extension_id		= 0,
+	.vendor_extension_version	= 0,
+	.vendor_extension_desc_len	= 0,
+	.functional_mode		= PIMA15740_STANDARD_MODE,
+	.operations_supported_count	= 14,
+	.operations_supported		= {
+		cpu_to_le16(PIMA15740_OP_GET_DEVICE_INFO),
+		cpu_to_le16(PIMA15740_OP_OPEN_SESSION),
+		cpu_to_le16(PIMA15740_OP_CLOSE_SESSION),
+		cpu_to_le16(PIMA15740_OP_GET_STORAGE_IDS),
+		cpu_to_le16(PIMA15740_OP_GET_STORAGE_INFO),
+		cpu_to_le16(PIMA15740_OP_GET_NUM_OBJECTS),
+		cpu_to_le16(PIMA15740_OP_GET_OBJECT_HANDLES),
+		cpu_to_le16(PIMA15740_OP_GET_OBJECT_INFO),
+		cpu_to_le16(PIMA15740_OP_GET_OBJECT),
+		cpu_to_le16(PIMA15740_OP_DELETE_OBJECT),
+		cpu_to_le16(PIMA15740_OP_SEND_OBJECT_INFO),
+		cpu_to_le16(PIMA15740_OP_SEND_OBJECT),
+		cpu_to_le16(PIMA15740_OP_COPY_OBJECT),
+		cpu_to_le16(PIMA15740_OP_MOVE_OBJECT)
+	},
+	.events_supported_count		= 0,
+	.device_properties_count	= 0,
+	.capture_formats_count		= 0,
+	.image_formats_count		= 10,
+	.image_formats			= {
+		cpu_to_le16(PIMA15740_FMT_I_EXIF_JPEG),
+		cpu_to_le16(PIMA15740_FMT_I_JFIF),
+		cpu_to_le16(PIMA15740_FMT_I_PNG),
+		cpu_to_le16(PIMA15740_FMT_I_TIFF),
+		cpu_to_le16(PIMA15740_FMT_I_TIFF_EP),
+		cpu_to_le16(PIMA15740_FMT_I_TIFF_IT),
+		cpu_to_le16(PIMA15740_FMT_I_BMP),
+		cpu_to_le16(PIMA15740_FMT_I_GIF),
+		cpu_to_le16(PIMA15740_FMT_I_UNDEFINED),
+		cpu_to_le16(PIMA15740_FMT_A_UNDEFINED)
+	},
+	/* others will be filled in sti_bind() */
+};
+
+
+/* StorageInfo Dataset */
+struct pima15740_storage_info {
+	u16	storage_type;
+	u16	filesystem_type;
+	u16	access_capability;
+	u64	max_capacity;
+	u64	free_space_in_bytes;
+	u32	free_space_in_images;
+	u8	storage_desc_len;
+	u8	storage_desc[sizeof(storage_desc) * 2];
+	u8	volume_label_len;
+	u8	volume_label[0];
+} __attribute__ ((packed));
+
+static struct pima15740_storage_info sti_storage_info = {
+	.storage_type		= cpu_to_le16(PIMA15740_STOR_FIXED_RAM),
+	.filesystem_type	= cpu_to_le16(PIMA15740_FS_HIERARCHICAL),
+	.access_capability	= cpu_to_le16(PIMA15740_ACCESS_CAP_RW),
+	.storage_desc_len	= sizeof(storage_desc),
+	.volume_label_len	= 0,
+	/* others will be filled later */
+};
+
+
+/* ObjectInfo Dataset */
+struct pima15740_object_info {
+	u32	storage_id;
+	u16	object_format;
+	u16	protection_status;
+	u32	object_compressed_size;
+	u16	thumb_format;
+	u32	thumb_compressed_size;
+	u32	thumb_pix_width;
+	u32	thumb_pix_height;
+	u32	image_pix_width;
+	u32	image_pix_height;
+	u32	image_bit_depth;
+	u32	parent_object;
+	u16	association_type;
+	u32	association_desc;
+	u32	sequence_number;
+	/* filename, capture date, modification date, keywords */
+	u8	obj_strings[];	/* size will be fixed later */
+} __attribute__ ((packed));
+
+/* object list element with object info data */
+struct sti_object {
+	struct list_head		list;
+	u32				obj_handle;
+	u32				parent_object;
+	u32				storage_id;
+	int				is_dir;
+	int				send_valid;
+	size_t				obj_info_size;
+	char				filename[NAME_MAX];
+	char				full_path[PATH_MAX];
+	struct pima15740_object_info	obj_info;
+};
+
+
+/*-------------------------------------------------------------------------*/
+
+/* device data structure */
+
+enum sti_buffer_state {
+	BUF_STATE_EMPTY = 0,
+	BUF_STATE_FULL,
+	BUF_STATE_BUSY
+};
+
+struct sti_buffhd {
+	void			*buf;
+	enum sti_buffer_state	state;
+	struct sti_buffhd	*next;
+	unsigned int		bulk_out_intended_length;
+	struct usb_request	*inreq;
+	int			inreq_busy;
+	struct usb_request	*outreq;
+	int			outreq_busy;
+};
+
+enum sti_state {
+	STI_STATE_COMMAND_PHASE = -10,	/* this one isn't used anywhere */
+	STI_STATE_DATA_PHASE,
+	STI_STATE_STATUS_PHASE,
+
+	STI_STATE_IDLE = 0,
+	STI_STATE_ABORT_BULK_OUT,
+	STI_STATE_CANCEL,
+	STI_STATE_RESET,
+	STI_STATE_INTERFACE_CHANGE,
+	STI_STATE_CONFIG_CHANGE,
+	STI_STATE_DISCONNECT,
+	STI_STATE_EXIT,
+	STI_STATE_TERMINATED
+};
+
+enum data_direction {
+	DATA_DIR_UNKNOWN = 0,
+	DATA_DIR_FROM_HOST,
+	DATA_DIR_TO_HOST,
+	DATA_DIR_NONE
+};
+
+struct sti_dev {
+	/* lock protects: device, req, endpoints states */
+	spinlock_t		lock;
+
+	/* filesem protects: backing folder in use */
+	struct rw_semaphore	filesem;
+
+	struct usb_gadget	*gadget;
+
+	/* reference counting: wait until released */
+	struct kref		ref;
+
+	/* handy copy of gadget->ep0 */
+	struct usb_ep		*ep0;
+
+	/* for control responses */
+	struct usb_request	*ep0req;
+	unsigned int		ep0_req_tag;
+	const char		*ep0req_name;
+
+	/* for interrupt responses */
+	struct usb_request	*intreq;
+	int			intreq_busy;
+	struct sti_buffhd	*intr_buffhd;
+
+	/* for exception handling */
+	enum sti_state		state;
+	unsigned int		exception_req_tag;
+
+	unsigned int		bulk_out_maxpacket;
+	u8			config, new_config;
+
+	unsigned int		running:1;
+	unsigned int		bulk_in_enabled:1;
+	unsigned int		bulk_out_enabled:1;
+	unsigned int		intr_in_enabled:1;
+	unsigned int		registered:1;
+	unsigned int		session_open:1;
+
+	unsigned long		atomic_bitflags;
+#define	REGISTERED		0
+#define	CLEAR_BULK_HALTS	1
+#define	SUSPENDED		2
+
+	struct usb_ep		*bulk_in;
+	struct usb_ep		*bulk_out;
+	struct usb_ep		*intr_in;
+
+	struct sti_buffhd	*next_buffhd_to_fill;
+	struct sti_buffhd	*next_buffhd_to_drain;
+	struct sti_buffhd	buffhds[NUM_BUFFERS];
+
+	int			thread_wakeup_needed;
+	struct completion	thread_notifier;
+	struct task_struct	*thread_task;
+
+	__le32			container_len;
+	__le16			container_type;
+	__le16			code;
+	__le32			transaction_id;
+
+	__le16			response_code;
+
+	u32			ops_params[PARAM_NUM_MAX];
+	u32			session_id;
+	u32			storage_id;
+	u32			object_num;
+	u32			sub_object_num;
+
+	char			root_path[PATH_MAX];
+	struct file		*root_filp;
+	struct list_head	obj_list;
+	struct list_head	tmp_obj_list;
+
+	struct sti_ext_event	ext_event_data;
+	struct sti_dev_status	status_data;
+
+	struct device		dev;
+};
+
+
+/*-------------------------------------------------------------------------*/
+
+#define	backing_folder_is_open(sti)	((sti)->root_filp != NULL)
+
+
+typedef void (*sti_routine_t)(struct sti_dev *);
+
+static int exception_in_progress(struct sti_dev *sti)
+{
+	return (sti->state > STI_STATE_IDLE);
+}
+
+/* make bulk-out requests be divisible by the maxpacket size */
+static void set_bulk_out_req_length(struct sti_dev *sti,
+		struct sti_buffhd *bh, unsigned int length)
+{
+	unsigned int	rem;
+	VDBG(sti, "---> %s()\n", __func__);
+
+	bh->bulk_out_intended_length = length;
+	rem = length % sti->bulk_out_maxpacket;
+	if (rem > 0)
+		length += sti->bulk_out_maxpacket - rem;
+	bh->outreq->length = length;
+
+	VDBG(sti, "<--- %s()\n", __func__);
+}
+
+
+/* global variables */
+static struct sti_dev			*the_sti;
+static struct usb_gadget_driver		sti_driver;
+
+static void close_backing_folder(struct sti_dev *sti);
+
+
+/*-------------------------------------------------------------------------*/
+
+#ifdef VERBOSE_DEBUG
+
+static void dump_msg(struct sti_dev *sti, const char *label,
+		const u8 *buf, unsigned int length)
+{
+	if (length < 512) {
+		DBG(sti, "%s, length %u:\n", label, length);
+		print_hex_dump(KERN_DEBUG, "", DUMP_PREFIX_OFFSET,
+				16, 1, buf, length, 0);
+	}
+}
+
+static void dump_cb(struct sti_dev *sti)
+{
+	print_hex_dump(KERN_DEBUG, "PIMA15740 Command Block: ",
+			DUMP_PREFIX_NONE, 16, 1, &sti->container_len,
+			PIMA15740_CONTAINER_LEN, 0);
+}
+
+static void dump_device_info(struct sti_dev *sti)
+{
+	int	i;
+
+	VDBG(sti, "DeviceInfo Dataset:\n");
+	VDBG(sti, "\tstandard_version: %u\n",
+			sti_device_info.standard_version);
+	VDBG(sti, "\tvendor_extension_id: %u\n",
+			sti_device_info.vendor_extension_id);
+	VDBG(sti, "\tvendor_extension_version: %u\n",
+			sti_device_info.vendor_extension_version);
+	VDBG(sti, "\tvendor_extension_desc_len: %u\n",
+			sti_device_info.vendor_extension_desc_len);
+	VDBG(sti, "\tfunctional_mode: 0x%04x\n",
+			sti_device_info.functional_mode);
+	VDBG(sti, "\toperations_supported_count: %u\n",
+			sti_device_info.operations_supported_count);
+	VDBG(sti, "\toperations_supported:\n");
+	for (i = 0; i < sti_device_info.operations_supported_count; i++)
+		VDBG(sti, "\t\t0x%04x\n",
+				sti_device_info.operations_supported[i]);
+	VDBG(sti, "\tevents_supported_count: %u\n",
+			sti_device_info.events_supported_count);
+	VDBG(sti, "\tdevice_properties_count: %u\n",
+			sti_device_info.device_properties_count);
+	VDBG(sti, "\tcapture_formats_count: %u\n",
+			sti_device_info.capture_formats_count);
+	VDBG(sti, "\timage_formats_count: %u\n",
+			sti_device_info.image_formats_count);
+	VDBG(sti, "\tmanufacturer_len: %u\n",
+			sti_device_info.manufacturer_len);
+	VDBG(sti, "\tmanufacturer: %s\n", manufacturer);
+	VDBG(sti, "\tmodel_len: %u\n",
+			sti_device_info.model_len);
+	VDBG(sti, "\tmodel: %s\n", longname);
+	VDBG(sti, "\tdevice_version_len: %u\n",
+			sti_device_info.device_version_len);
+	VDBG(sti, "\tdevice_version: %s\n", device_version);
+	VDBG(sti, "\tserial_number_len: %u\n",
+			sti_device_info.serial_number_len);
+	VDBG(sti, "\tserial_number: %s\n", serial);
+}
+
+static void dump_storage_info(struct sti_dev *sti)
+{
+	VDBG(sti, "StorageInfo Dataset:\n");
+	VDBG(sti, "\tstorage_type: 0x%04x\n", sti_storage_info.storage_type);
+	VDBG(sti, "\tfilesystem_type: 0x%04x\n",
+			sti_storage_info.filesystem_type);
+	VDBG(sti, "\taccess_capability: 0x%04x\n",
+			sti_storage_info.access_capability);
+	VDBG(sti, "\tmax_capacity: %llu\n", sti_storage_info.max_capacity);
+	VDBG(sti, "\tfree_space_in_bytes: %llu\n",
+			sti_storage_info.free_space_in_bytes);
+	VDBG(sti, "\tfree_space_in_images: %u\n",
+			sti_storage_info.free_space_in_images);
+	VDBG(sti, "\tstorage_desc_len: %u\n",
+			sti_storage_info.storage_desc_len);
+	VDBG(sti, "\tstorage_desc: %s\n", storage_desc);
+	VDBG(sti, "\tvolume_label_len: %u\n",
+			sti_storage_info.volume_label_len);
+}
+
+static void dump_object_info(struct sti_dev *sti, struct sti_object *obj)
+{
+	u8	filename_len;
+
+	VDBG(sti, "ObjectInfo Dataset:\n");
+	VDBG(sti, "\tstorage_id: 0x%08x\n", obj->obj_info.storage_id);
+	VDBG(sti, "\tobject_format: 0x%04x\n", obj->obj_info.object_format);
+	VDBG(sti, "\tprotection_status: 0x%04x\n",
+			obj->obj_info.protection_status);
+	VDBG(sti, "\tobject_compressed_size: %u\n",
+			obj->obj_info.object_compressed_size);
+	VDBG(sti, "\tthumb_format: %u\n", obj->obj_info.thumb_format);
+	VDBG(sti, "\tthumb_compressed_size: %u\n",
+			obj->obj_info.thumb_compressed_size);
+	VDBG(sti, "\tthumb_pix_width: %u\n",
+			obj->obj_info.thumb_pix_width);
+	VDBG(sti, "\tthumb_pix_height: %u\n",
+			obj->obj_info.thumb_pix_height);
+	VDBG(sti, "\timage_pix_width: %u\n",
+			obj->obj_info.image_pix_width);
+	VDBG(sti, "\timage_pix_height: %u\n",
+			obj->obj_info.image_pix_height);
+	VDBG(sti, "\timage_bit_depth: %u\n",
+			obj->obj_info.image_bit_depth);
+	VDBG(sti, "\tparent_object: 0x%08x\n",
+			obj->obj_info.parent_object);
+	VDBG(sti, "\tassociation_type: 0x%04x\n",
+			obj->obj_info.association_type);
+	VDBG(sti, "\tassociation_desc: 0x%08x\n",
+			obj->obj_info.association_desc);
+	VDBG(sti, "\tsequence_number: 0x%08x\n",
+			obj->obj_info.sequence_number);
+	VDBG(sti, "\tfilename_len: %u\n", obj->obj_info.obj_strings[0]);
+	filename_len = obj->obj_info.obj_strings[0];
+	VDBG(sti, "\tfilename: %s\n", obj->filename);
+	VDBG(sti, "\tcapture_date_len: %u\n",
+			obj->obj_info.obj_strings[filename_len * 2 + 1]);
+	VDBG(sti, "\tmodification_date_len: %u\n",
+			obj->obj_info.obj_strings[filename_len * 2 + 2]);
+	VDBG(sti, "\tkeywords_len: %u\n",
+			obj->obj_info.obj_strings[filename_len * 2 + 3]);
+}
+
+#else
+
+static void dump_msg(struct sti_dev *sti, const char *label,
+		const u8 *buf, unsigned int length)
+{}
+
+static void dump_cb(struct sti_dev *sti)
+{}
+
+static void dump_device_info(struct sti_dev *sti)
+{}
+
+static void dump_storage_info(struct sti_dev *sti)
+{}
+
+static void dump_object_info(struct sti_dev *sti, struct sti_object *obj)
+{}
+
+#endif /* VERBOSE_DEBUG */
+
+
+/*-------------------------------------------------------------------------*/
+
+
+
+/*
+ * Config descriptors must agree with the code that sets configurations
+ * and with code managing interfaces and their altsettings. They must
+ * also handle different speeds and other-speed requests.
+ */
+static int populate_config_buf(struct usb_gadget *gadget,
+		u8 *buf, u8 type, unsigned index)
+{
+	enum usb_device_speed			speed = gadget->speed;
+	int					len;
+	const struct usb_descriptor_header	**function;
+
+	if (index > 0)
+		return -EINVAL;
+
+	if (gadget_is_dualspeed(gadget) && type == USB_DT_OTHER_SPEED_CONFIG)
+		speed = (USB_SPEED_FULL + USB_SPEED_HIGH) - speed;
+	if (gadget_is_dualspeed(gadget) && speed == USB_SPEED_HIGH)
+		function = hs_function;
+	else
+		function = fs_function;
+
+	/* for now, don't advertise srp-only devices */
+	if (!gadget_is_otg(gadget))
+		function++;
+
+	len = usb_gadget_config_buf(&config_desc, buf, EP0_BUFSIZE, function);
+	((struct usb_config_descriptor *) buf)->bDescriptorType = type;
+
+	return len;
+}
+
+
+/*-------------------------------------------------------------------------*/
+
+/* these routines may be called in process context or in_irq */
+
+/* caller must hold sti->lock */
+static void wakeup_thread(struct sti_dev *sti)
+{
+	VDBG(sti, "---> %s()\n", __func__);
+
+	/* tell the main thread that something has happened */
+	sti->thread_wakeup_needed = 1;
+	if (sti->thread_task)
+		wake_up_process(sti->thread_task);
+
+	VDBG(sti, "<--- %s()\n", __func__);
+}
+
+
+static void raise_exception(struct sti_dev *sti, enum sti_state new_state)
+{
+	unsigned long		flags;
+	VDBG(sti, "---> %s()\n", __func__);
+
+	/*
+	 * Do nothing if a higher-priority exception is already in progress.
+	 * If a lower-or-equal priority exception is in progress, preempt it
+	 * and notify the main thread by sending it a signal.
+	 */
+	spin_lock_irqsave(&sti->lock, flags);
+	if (sti->state <= new_state) {
+		sti->exception_req_tag = sti->ep0_req_tag;
+		sti->state = new_state;
+		if (sti->thread_task)
+			send_sig_info(SIGUSR1, SEND_SIG_FORCED,
+					sti->thread_task);
+	}
+	spin_unlock_irqrestore(&sti->lock, flags);
+
+	VDBG(sti, "<--- %s()\n", __func__);
+}
+
+
+/*-------------------------------------------------------------------------*/
+
+/*
+ * The disconnect callback and ep0 routines. These always run in_irq,
+ * except that ep0_queue() is called in the main thread to acknowledge
+ * completion of various requests: set config, set interface, and
+ * Bulk-only device reset.
+ */
+
+static void sti_disconnect(struct usb_gadget *gadget)
+{
+	struct sti_dev		*sti = get_gadget_data(gadget);
+	VDBG(sti, "---> %s()\n", __func__);
+
+	DBG(sti, "disconnect or port reset\n");
+	raise_exception(sti, STI_STATE_DISCONNECT);
+
+	VDBG(sti, "<--- %s()\n", __func__);
+}
+
+static int ep0_queue(struct sti_dev *sti)
+{
+	int	rc;
+	VDBG(sti, "---> %s()\n", __func__);
+
+	rc = usb_ep_queue(sti->ep0, sti->ep0req, GFP_ATOMIC);
+	if (rc != 0 && rc != -ESHUTDOWN) {
+		/* we can't do much more than wait for a reset */
+		WARNING(sti, "error in submission: %s --> %d\n",
+				sti->ep0->name, rc);
+	}
+
+	VDBG(sti, "<--- %s()\n", __func__);
+	return rc;
+}
+
+static void ep0_complete(struct usb_ep *ep, struct usb_request *req)
+{
+	struct sti_dev		*sti = ep->driver_data;
+	VDBG(sti, "---> %s()\n", __func__);
+
+	if (req->actual > 0)
+		dump_msg(sti, sti->ep0req_name, req->buf, req->actual);
+
+	if (req->status || req->actual != req->length)
+		VDBG(sti, "%s --> %d, %u/%u\n", __func__,
+				req->status, req->actual, req->length);
+
+	/* request was cancelled */
+	if (req->status == -ECONNRESET)
+		usb_ep_fifo_flush(ep);
+
+	if (req->status == 0 && req->context)
+		((sti_routine_t) (req->context))(sti);
+
+	VDBG(sti, "<--- %s()\n", __func__);
+}
+
+
+/*-------------------------------------------------------------------------*/
+
+/* endpoint completion handlers, always run in_irq */
+
+static void bulk_in_complete(struct usb_ep *ep, struct usb_request *req)
+{
+	struct sti_dev		*sti = ep->driver_data;
+	struct sti_buffhd	*bh = req->context;
+	VDBG(sti, "---> %s()\n", __func__);
+
+	if (req->status || req->actual != req->length)
+		VDBG(sti, "%s --> %d, %u/%u\n", __func__,
+				req->status, req->actual, req->length);
+	/* request was cancelled */
+	if (req->status == -ECONNRESET)
+		usb_ep_fifo_flush(ep);
+
+	/* hold the lock while we update the request and buffer states */
+	smp_wmb();
+	spin_lock(&sti->lock);
+	bh->inreq_busy = 0;
+	bh->state = BUF_STATE_EMPTY;
+	wakeup_thread(sti);
+	spin_unlock(&sti->lock);
+
+	VDBG(sti, "<--- %s()\n", __func__);
+}
+
+static void bulk_out_complete(struct usb_ep *ep, struct usb_request *req)
+{
+	struct sti_dev		*sti = ep->driver_data;
+	struct sti_buffhd	*bh = req->context;
+	VDBG(sti, "---> %s()\n", __func__);
+
+	dump_msg(sti, "bulk-out", req->buf, req->actual);
+	if (req->status || req->actual != bh->bulk_out_intended_length)
+		VDBG(sti, "%s --> %d, %u/%u\n", __func__,
+				req->status, req->actual,
+				bh->bulk_out_intended_length);
+
+	/* request was cancelled */
+	if (req->status == -ECONNRESET)
+		usb_ep_fifo_flush(ep);
+
+	/* hold the lock while we update the request and buffer states */
+	smp_wmb();
+	spin_lock(&sti->lock);
+	bh->outreq_busy = 0;
+	bh->state = BUF_STATE_FULL;
+	wakeup_thread(sti);
+	spin_unlock(&sti->lock);
+
+	VDBG(sti, "<--- %s()\n", __func__);
+}
+
+
+/*-------------------------------------------------------------------------*/
+
+static int sti_set_halt(struct sti_dev *sti, struct usb_ep *ep)
+{
+	const char	*name;
+	VDBG(sti, "---> %s()\n", __func__);
+
+	if (ep == sti->bulk_in)
+		name = "bulk-in";
+	else if (ep == sti->bulk_out)
+		name = "bulk-out";
+	else
+		name = ep->name;
+
+	DBG(sti, "%s set halt\n", name);
+	VDBG(sti, "<--- %s()\n", __func__);
+
+	return usb_ep_set_halt(ep);
+}
+
+
+static void received_cancel_request(struct sti_dev *sti)
+{
+	struct usb_request	*req = sti->ep0req;
+	u16			cancel_code;
+	u32			trans_id;
+	int			rc;
+	VDBG(sti, "---> %s()\n", __func__);
+
+	/* error in command transfer */
+	if (req->status || req->length != req->actual) {
+		/* wait for reset */
+		sti_set_halt(sti, sti->ep0);
+		return;
+	}
+
+	VDBG(sti, "receive cancel request\n");
+
+	if (!req->buf)
+		return;
+
+	cancel_code = get_unaligned_le16(req->buf);
+	if (cancel_code != cpu_to_le16(STI_CANCEL_REQUEST_CODE)) {
+		VDBG(sti, "invalid cancel_code: 0x%04x\n", cancel_code);
+		goto out;
+	}
+
+	trans_id = get_unaligned_le32(req->buf + 2);
+	if (trans_id != sti->transaction_id) {
+		VDBG(sti, "invalid trans_id:0x%04x\n", trans_id);
+		goto out;
+	}
+
+	/* stall bulk endpoints */
+	sti_set_halt(sti, sti->bulk_out);
+
+	rc = sti_set_halt(sti, sti->bulk_in);
+	if (rc == -EAGAIN)
+		VDBG(sti, "delayed bulk-in endpoint halt\n");
+
+	sti->response_code = PIMA15740_RES_DEVICE_BUSY;
+out:
+	raise_exception(sti, STI_STATE_CANCEL);
+
+	VDBG(sti, "<--- %s()\n", __func__);
+}
+
+
+/* ep0 class-specific request handlers, always run in_irq */
+static int class_setup_req(struct sti_dev *sti,
+		const struct usb_ctrlrequest *ctrl)
+{
+	struct usb_request	*req = sti->ep0req;
+	int			value = -EOPNOTSUPP;
+	u16			w_index = le16_to_cpu(ctrl->wIndex);
+	u16                     w_value = le16_to_cpu(ctrl->wValue);
+	u16			w_length = le16_to_cpu(ctrl->wLength);
+	VDBG(sti, "---> %s()\n", __func__);
+
+	if (!sti->config)
+		return value;
+
+	/* handle class-specific requests */
+	switch (ctrl->bRequest) {
+
+	case STI_CANCEL_REQUEST:
+		if (ctrl->bRequestType != (USB_DIR_OUT |
+				USB_TYPE_CLASS | USB_RECIP_INTERFACE))
+			break;
+		if (w_index != 0 || w_value != 0 || w_length != 6) {
+			value = -EDOM;
+			break;
+		}
+
+		DBG(sti, "cancel request\n");
+
+		value = w_length;
+		sti->ep0req->context = received_cancel_request;
+		break;
+
+	case STI_GET_EXTENDED_EVENT_DATA:
+		/* asynchronous events by interrupt endpoint */
+		if (ctrl->bRequestType != (USB_DIR_IN |
+				USB_TYPE_CLASS | USB_RECIP_INTERFACE))
+			break;
+		if (w_index != 0 || w_value != 0) {
+			value = -EDOM;
+			break;
+		}
+
+		DBG(sti, "get extended event data\n");
+
+		sti->ext_event_data.event_code = PIMA15740_RES_OK;
+		sti->ext_event_data.transaction_id = sti->transaction_id;
+		sti->ext_event_data.param_num = 0;
+
+		value = min_t(unsigned, w_length,
+				sizeof(struct sti_ext_event));
+		memcpy(req->buf, &sti->ext_event_data, value);
+		break;
+
+	case STI_DEVICE_RESET_REQUEST:
+		if (ctrl->bRequestType != (USB_DIR_OUT |
+				USB_TYPE_CLASS | USB_RECIP_INTERFACE))
+			break;
+		if (w_index != 0 || w_value != 0 || w_length != 0) {
+			value = -EDOM;
+			break;
+		}
+
+		/* Raise an exception to stop the current operation
+		 * and reinitialize our state. */
+		DBG(sti, "device reset request\n");
+
+		sti->response_code = PIMA15740_RES_OK;
+		sti->session_open = 1;
+
+		raise_exception(sti, STI_STATE_RESET);
+		value = DELAYED_STATUS;
+		break;
+
+	case STI_GET_DEVICE_STATUS:
+		if (ctrl->bRequestType != (USB_DIR_IN |
+				USB_TYPE_CLASS | USB_RECIP_INTERFACE))
+			break;
+		if (w_index != 0 || w_value != 0) {
+			value = -EDOM;
+			break;
+		}
+
+		DBG(sti, "get device status\n");
+		sti->status_data.wlength = 4;
+		sti->status_data.code = sti->response_code;
+
+		value = min_t(unsigned, w_length,
+				sizeof(struct sti_dev_status));
+		memcpy(req->buf, &sti->status_data, value);
+		break;
+
+	default:
+		DBG(sti, "unknown class-specific control req "
+			"%02x.%02x v%04x i%04x l%u\n",
+			ctrl->bRequestType, ctrl->bRequest,
+			le16_to_cpu(ctrl->wValue), w_index, w_length);
+		break;
+	}
+
+	VDBG(sti, "<--- %s()\n", __func__);
+	return value;
+}
+
+
+/*-------------------------------------------------------------------------*/
+
+/* ep0 standard request handlers, always run in_irq */
+
+static int standard_setup_req(struct sti_dev *sti,
+		const struct usb_ctrlrequest *ctrl)
+{
+	struct usb_request	*req = sti->ep0req;
+	int			value = -EOPNOTSUPP;
+	u16			w_index = le16_to_cpu(ctrl->wIndex);
+	u16			w_value = le16_to_cpu(ctrl->wValue);
+	VDBG(sti, "---> %s()\n", __func__);
+
+	/* usually this just stores reply data in the pre-allocated ep0 buffer,
+	 * but config change events will also reconfigure hardware */
+	switch (ctrl->bRequest) {
+
+	case USB_REQ_GET_DESCRIPTOR:
+		if (ctrl->bRequestType != (USB_DIR_IN | USB_TYPE_STANDARD |
+				USB_RECIP_DEVICE))
+			break;
+		switch (w_value >> 8) {
+
+		case USB_DT_DEVICE:
+			VDBG(sti, "get device descriptor\n");
+			value = sizeof device_desc;
+			memcpy(req->buf, &device_desc, value);
+			break;
+		case USB_DT_DEVICE_QUALIFIER:
+			VDBG(sti, "get device qualifier\n");
+			if (!gadget_is_dualspeed(sti->gadget))
+				break;
+			value = sizeof dev_qualifier;
+			memcpy(req->buf, &dev_qualifier, value);
+			break;
+
+		case USB_DT_OTHER_SPEED_CONFIG:
+			VDBG(sti, "get other-speed config descriptor\n");
+			if (!gadget_is_dualspeed(sti->gadget))
+				break;
+			goto get_config;
+		case USB_DT_CONFIG:
+			VDBG(sti, "get configuration descriptor\n");
+get_config:
+			value = populate_config_buf(sti->gadget,
+					req->buf,
+					w_value >> 8,
+					w_value & 0xff);
+			break;
+
+		case USB_DT_STRING:
+			VDBG(sti, "get string descriptor\n");
+
+			/* wIndex == language code */
+			value = usb_gadget_get_string(&stringtab,
+					w_value & 0xff, req->buf);
+			break;
+		}
+		break;
+
+	/* one config, two speeds */
+	case USB_REQ_SET_CONFIGURATION:
+		if (ctrl->bRequestType != (USB_DIR_OUT | USB_TYPE_STANDARD |
+				USB_RECIP_DEVICE))
+			break;
+		VDBG(sti, "set configuration\n");
+		if (w_value == CONFIG_VALUE || w_value == 0) {
+			sti->new_config = w_value;
+
+			/* Raise an exception to wipe out previous transaction
+			 * state (queued bufs, etc) and set the new config. */
+			raise_exception(sti, STI_STATE_CONFIG_CHANGE);
+			value = DELAYED_STATUS;
+		}
+		break;
+
+	case USB_REQ_GET_CONFIGURATION:
+		if (ctrl->bRequestType != (USB_DIR_IN | USB_TYPE_STANDARD |
+				USB_RECIP_DEVICE))
+			break;
+		VDBG(sti, "get configuration\n");
+		*(u8 *) req->buf = sti->config;
+		value = 1;
+		break;
+
+	case USB_REQ_SET_INTERFACE:
+		if (ctrl->bRequestType != (USB_DIR_OUT | USB_TYPE_STANDARD |
+				USB_RECIP_INTERFACE))
+			break;
+		if (sti->config && w_index == 0) {
+
+			/* Raise an exception to wipe out previous transaction
+			 * state (queued bufs, etc) and install the new
+			 * interface altsetting. */
+			raise_exception(sti, STI_STATE_INTERFACE_CHANGE);
+			value = DELAYED_STATUS;
+		}
+		break;
+
+	case USB_REQ_GET_INTERFACE:
+		if (ctrl->bRequestType != (USB_DIR_IN | USB_TYPE_STANDARD |
+				USB_RECIP_INTERFACE))
+			break;
+		if (!sti->config)
+			break;
+		if (w_index != 0) {
+			value = -EDOM;
+			break;
+		}
+		VDBG(sti, "get interface\n");
+		*(u8 *) req->buf = 0;
+		value = 1;
+		break;
+
+	default:
+		VDBG(sti, "unknown control req %02x.%02x v%04x i%04x l%u\n",
+			ctrl->bRequestType, ctrl->bRequest,
+			w_value, w_index, le16_to_cpu(ctrl->wLength));
+	}
+
+	VDBG(sti, "<--- %s()\n", __func__);
+	return value;
+}
+
+static int sti_setup(struct usb_gadget *gadget,
+		const struct usb_ctrlrequest *ctrl)
+{
+	struct sti_dev		*sti = get_gadget_data(gadget);
+	int			rc;
+	int			w_length = le16_to_cpu(ctrl->wLength);
+	VDBG(sti, "---> %s()\n", __func__);
+
+	/* record arrival of a new request */
+	++sti->ep0_req_tag;
+	sti->ep0req->context = NULL;
+	sti->ep0req->length = 0;
+	dump_msg(sti, "ep0-setup", (u8 *) ctrl, sizeof(*ctrl));
+
+	if ((ctrl->bRequestType & USB_TYPE_MASK) == USB_TYPE_CLASS)
+		rc = class_setup_req(sti, ctrl);
+	else
+		rc = standard_setup_req(sti, ctrl);
+
+	/* respond with data/status or defer until later */
+	if (rc >= 0 && rc != DELAYED_STATUS) {
+		rc = min(rc, w_length);
+		sti->ep0req->length = rc;
+		sti->ep0req->zero = rc < w_length;
+		sti->ep0req_name = (ctrl->bRequestType & USB_DIR_IN ?
+				"ep0-in" : "ep0-out");
+		rc = ep0_queue(sti);
+	}
+
+	VDBG(sti, "<--- %s()\n", __func__);
+	/* device either stalls (rc < 0) or reports success */
+	return rc;
+}
+
+
+/*-------------------------------------------------------------------------*/
+
+/* all the following routines run in process context */
+
+/* use this for bulk or interrupt transfers, not ep0 */
+static void start_transfer(struct sti_dev *sti, struct usb_ep *ep,
+		struct usb_request *req, int *pbusy,
+		enum sti_buffer_state *state)
+{
+	int	rc;
+	VDBG(sti, "---> %s()\n", __func__);
+
+	if (ep == sti->bulk_in)
+		dump_msg(sti, "bulk-in", req->buf, req->length);
+	else if (ep == sti->intr_in)
+		dump_msg(sti, "intr-in", req->buf, req->length);
+
+	spin_lock_irq(&sti->lock);
+	*pbusy = 1;
+	*state = BUF_STATE_BUSY;
+	spin_unlock_irq(&sti->lock);
+
+	rc = usb_ep_queue(ep, req, GFP_KERNEL);
+	VDBG(sti, "start_transfer, rc: %d\n", rc);
+	if (rc != 0) {
+		*pbusy = 0;
+		*state = BUF_STATE_EMPTY;
+		if (rc != -ESHUTDOWN && !(rc == -EOPNOTSUPP &&
+						req->length == 0))
+			WARNING(sti, "error in submission: %s --> %d\n",
+					ep->name, rc);
+	}
+
+	VDBG(sti, "<--- %s()\n", __func__);
+}
+
+
+static int sleep_thread(struct sti_dev *sti)
+{
+	int	rc = 0;
+	VDBG(sti, "---> %s()\n", __func__);
+
+	/* wait until a signal arrives or we are woken up */
+	for (;;) {
+		try_to_freeze();
+		set_current_state(TASK_INTERRUPTIBLE);
+		if (signal_pending(current)) {
+			rc = -EINTR;
+			break;
+		}
+		if (sti->thread_wakeup_needed)
+			break;
+
+		schedule();
+	}
+
+	__set_current_state(TASK_RUNNING);
+	sti->thread_wakeup_needed = 0;
+
+	VDBG(sti, "<--- %s()\n", __func__);
+	return rc;
+}
+
+
+/*-------------------------------------------------------------------------*/
+
+static int fill_data_container(struct sti_buffhd *bh,
+		struct sti_dev *sti, unsigned int size)
+{
+	struct pima15740_container	*rb;
+	VDBG(sti, "---> %s()\n", __func__);
+
+	rb = bh->buf;
+
+	rb->container_len = size;
+	rb->container_type = TYPE_DATA_BLOCK;
+	rb->code = sti->code;
+	rb->transaction_id = sti->transaction_id;
+
+	bh->inreq->zero = 0;
+
+	VDBG(sti, "<--- %s()\n", __func__);
+	return 0;
+}
+
+
+static int send_response(struct sti_dev *sti, unsigned int code)
+{
+	struct sti_buffhd		*bh;
+	struct pima15740_container	*rb;
+	int				rc = 0;
+	VDBG(sti, "---> %s()\n", __func__);
+
+	/* wait for the next buffer to become available */
+	bh = sti->next_buffhd_to_fill;
+	while (bh->state != BUF_STATE_EMPTY) {
+		rc = sleep_thread(sti);
+		if (rc)
+			return rc;
+	}
+
+	rb = bh->buf;
+
+	rb->container_len = PIMA15740_CONTAINER_LEN;
+	rb->container_type = TYPE_RESPONSE_BLOCK;
+	rb->code = code;
+	rb->transaction_id = sti->transaction_id;
+
+	bh->inreq->length = PIMA15740_CONTAINER_LEN;
+	bh->state = BUF_STATE_FULL;
+	bh->inreq->zero = 0;
+
+	start_transfer(sti, sti->bulk_in, bh->inreq,
+			&bh->inreq_busy, &bh->state);
+
+	sti->next_buffhd_to_fill = bh->next;
+
+	VDBG(sti, "<--- %s()\n", __func__);
+	return rc;
+}
+
+
+static int send_params_response(struct sti_dev *sti, unsigned int code,
+		u32 p1, u32 p2, u32 p3, unsigned p_num)
+{
+	struct sti_buffhd		*bh;
+	struct pima15740_container	*rb;
+	int				rc = 0;
+	VDBG(sti, "---> %s()\n", __func__);
+
+	/* wait for the next buffer to become available */
+	bh = sti->next_buffhd_to_fill;
+	while (bh->state != BUF_STATE_EMPTY) {
+		rc = sleep_thread(sti);
+		if (rc)
+			return rc;
+	}
+
+	rb = bh->buf;
+
+	rb->container_len = PIMA15740_CONTAINER_LEN + p_num * 4;
+	rb->container_type = TYPE_RESPONSE_BLOCK;
+	rb->code = code;
+	rb->transaction_id = sti->transaction_id;
+
+	switch (p_num) {
+	case 3:
+		memcpy((u8 *)rb + PIMA15740_CONTAINER_LEN, &p1, 4);
+		memcpy((u8 *)rb + PIMA15740_CONTAINER_LEN + 4, &p2, 4);
+		memcpy((u8 *)rb + PIMA15740_CONTAINER_LEN + 8, &p3, 4);
+		break;
+	case 2:
+		memcpy((u8 *)rb + PIMA15740_CONTAINER_LEN, &p1, 4);
+		memcpy((u8 *)rb + PIMA15740_CONTAINER_LEN + 4, &p2, 4);
+		break;
+	case 1:
+		memcpy((u8 *)rb + PIMA15740_CONTAINER_LEN, &p1, 4);
+		break;
+	default:
+		break;
+	}
+
+	bh->inreq->length = PIMA15740_CONTAINER_LEN + p_num * 4;
+	bh->state = BUF_STATE_FULL;
+	bh->inreq->zero = 0;
+
+	start_transfer(sti, sti->bulk_in, bh->inreq,
+			&bh->inreq_busy, &bh->state);
+
+	sti->next_buffhd_to_fill = bh->next;
+
+	VDBG(sti, "<--- %s()\n", __func__);
+	return rc;
+}
+
+/* ISO-8859-1 to UTF-16LE */
+static unsigned short str_to_uni16(const char *src, char *dest)
+{
+	unsigned int	i;
+
+	for (i = 0; i < strlen(src); i++) {
+		dest[i * 2] = src[i];
+		dest[i * 2 + 1] = '\0';
+	}
+
+	/* null-terminated string */
+	dest[i * 2] = dest[i * 2 + 1] = '\0';
+
+	return (i + 1) * 2;
+}
+
+/* UTF-16LE to ISO-8859-1 */
+static void uni16_to_str(const char *src, char *dest, unsigned short len)
+{
+	unsigned int	i;
+
+	for (i = 0; i < len; i++)
+		dest[i] = src[i * 2];
+}
+
+
+static int do_get_device_info(struct sti_dev *sti, struct sti_buffhd *bh)
+{
+	size_t		size;
+	int		rc = 0;
+	VDBG(sti, "---> %s()\n", __func__);
+
+	/* dump DeviceInfo Dataset */
+	dump_device_info(sti);
+
+	size = sizeof sti_device_info;
+	fill_data_container(bh, sti, PIMA15740_CONTAINER_LEN + size);
+
+	memcpy(bh->buf + PIMA15740_CONTAINER_LEN, &sti_device_info, size);
+
+	bh->inreq->length = PIMA15740_CONTAINER_LEN + size;
+	bh->state = BUF_STATE_FULL;
+	start_transfer(sti, sti->bulk_in, bh->inreq,
+			&bh->inreq_busy, &bh->state);
+	sti->next_buffhd_to_fill = bh->next;
+
+	/* send response */
+	rc = send_response(sti, PIMA15740_RES_OK);
+
+	VDBG(sti, "<--- %s()\n", __func__);
+	return rc;
+}
+
+
+static int filldir_all(void *__buf, const char *name, int len,
+		loff_t pos, u64 ino, unsigned int d_type)
+{
+	struct sti_dev		*sti = __buf;
+	struct sti_object	*obj;
+	char			*ext;
+	u8			filename_len;
+	char			filename_utf16le[NAME_MAX * 2];
+	size_t			obj_size;
+	u16			object_format = PIMA15740_FMT_A_UNDEFINED;
+	int			rc = 0;
+	VDBG(sti, "---> %s()\n", __func__);
+	VDBG(sti, "name: %s, len: %d, pos: %lu, ino: %llu, d_type: %u\n",
+			name, len, (unsigned long)pos, ino, d_type);
+
+	/* ignore "." and ".." directories */
+	if (!strcmp(name, ".") || !strcmp(name, ".."))
+		goto out;
+
+	if (d_type != DT_DIR && d_type != DT_REG)
+		goto out;
+
+	/* filename strings length */
+	filename_len = len + 1;
+	VDBG(sti, "filename_len: %u\n", filename_len);
+
+	/* sti_object size */
+	obj_size = sizeof(struct sti_object) + 2 * filename_len + 4;
+	VDBG(sti, "obj_size: %u\n", obj_size);
+	/* obj_size > sizeof(struct sti_object) */
+	obj = kzalloc(obj_size, GFP_KERNEL);
+	if (!obj) {
+		rc = -ENOMEM;
+		goto out;
+	}
+
+	/* fill part of sti_object info */
+	obj->storage_id = STORAGE_ID;
+	obj->send_valid = 0;
+
+	/* ObjectInfo Dataset size */
+	obj->obj_info_size = sizeof(struct pima15740_object_info)
+		+ 2 * filename_len + 4;
+	VDBG(sti, "obj_info_size: %u\n", obj->obj_info_size);
+
+	/* filename */
+	memset(obj->filename, 0, sizeof(obj->filename));
+	strncpy(obj->filename, name, len);
+
+	/* fill ObjectInfo Dataset */
+	obj->obj_info.storage_id = cpu_to_le32(STORAGE_ID);
+
+	if (d_type == DT_DIR) {		/* association */
+		object_format = PIMA15740_FMT_A_ASSOCIATION;
+		obj->obj_info.association_type =
+			cpu_to_le16(PIMA15740_AS_GENERIC_FOLDER);
+		obj->is_dir = 1;
+	} else if (d_type == DT_REG) {	/* regular file */
+		ext = strrchr(obj->filename, '.');
+		if (ext) {
+			/* image object */
+			if (!strcasecmp(ext, ".jpg") ||
+					!strcasecmp(ext, ".jpeg") ||
+					!strcasecmp(ext, ".jpe"))
+				object_format = PIMA15740_FMT_I_EXIF_JPEG;
+			else if (!strcasecmp(ext, ".jfif"))
+				object_format = PIMA15740_FMT_I_JFIF;
+			else if (!strcasecmp(ext, ".tif") ||
+					!strcasecmp(ext, ".tiff"))
+				object_format = PIMA15740_FMT_I_TIFF;
+			else if (!strcasecmp(ext, ".png"))
+				object_format = PIMA15740_FMT_I_PNG;
+			else if (!strcasecmp(ext, ".bmp"))
+				object_format = PIMA15740_FMT_I_BMP;
+			else if (!strcasecmp(ext, ".gif"))
+				object_format = PIMA15740_FMT_I_GIF;
+			else	/* undefined non-image object */
+				object_format = PIMA15740_FMT_A_UNDEFINED;
+		} else	/* file without extension */
+			object_format = PIMA15740_FMT_A_UNDEFINED;
+		obj->obj_info.association_type =
+			cpu_to_le16(PIMA15740_AS_UNDEFINED);
+		obj->is_dir = 0;
+	}
+	obj->obj_info.object_format = cpu_to_le16(object_format);
+
+	/* protection_status, object_compressed_size will be filled later */
+	obj->obj_info.thumb_format = cpu_to_le16(0);
+	obj->obj_info.thumb_compressed_size = cpu_to_le32(0);
+	obj->obj_info.thumb_pix_width = cpu_to_le32(0);
+	obj->obj_info.thumb_pix_height = cpu_to_le32(0);
+	obj->obj_info.image_pix_width = cpu_to_le32(0);
+	obj->obj_info.image_pix_height = cpu_to_le32(0);
+	obj->obj_info.image_bit_depth = cpu_to_le32(0);
+
+	obj->obj_info.association_desc = cpu_to_le32(0);
+	obj->obj_info.sequence_number = cpu_to_le32(0);
+
+	/* filename_utf16le: UTF-16LE unicode string */
+	obj->obj_info.obj_strings[0] = filename_len;
+	memset(filename_utf16le, 0, sizeof(filename_utf16le));
+	str_to_uni16(obj->filename, filename_utf16le);
+	memcpy(obj->obj_info.obj_strings + 1, filename_utf16le,
+			filename_len * 2);
+
+	/* capture date */
+	obj->obj_info.obj_strings[filename_len * 2 + 1] = 0;
+
+	/* modification date */
+	obj->obj_info.obj_strings[filename_len * 2 + 2] = 0;
+
+	/* keywords */
+	obj->obj_info.obj_strings[filename_len * 2 + 3] = 0;
+
+	/* increase object number */
+	sti->sub_object_num++;
+
+	/* add to temp object list */
+	list_add_tail(&obj->list, &sti->tmp_obj_list);
+out:
+	VDBG(sti, "<--- %s()\n", __func__);
+	return rc;
+}
+
+
+/* alphabetic sort function */
+static int alnumsort(const void *a, const void *b)
+{
+	const struct sti_object *oa = *(const struct sti_object **)a;
+	const struct sti_object *ob = *(const struct sti_object **)b;
+	return strcmp(oa->filename, ob->filename);
+}
+
+
+/* descend through the hierarchical folder recursively */
+static int list_objects(struct sti_dev *sti, const char *folder_name,
+		struct sti_object *folder_obj, bool recursive)
+{
+	struct file		*filp;
+	struct dentry		*dentry;
+	struct sti_object	*obj = NULL;
+	struct sti_object	*tmp_obj;
+	struct sti_object	**pobj, **temp_pobj = NULL;
+	struct kstat		stat;
+	u32			parent_object;
+	int			i, rc = 0;
+	VDBG(sti, "---> %s()\n", __func__);
+
+	/* root directory */
+	if (!strcmp(folder_name, sti->root_path)) {
+		filp = sti->root_filp;
+		parent_object = 0;
+		VDBG(sti, "root directory\n");
+	} else {	/* subdirectory */
+		filp = filp_open(folder_name, O_RDONLY | O_DIRECTORY, 0);
+		if (IS_ERR(filp)) {
+			ERROR(sti, "unable to open folder: %s\n",
+					folder_name);
+			return PTR_ERR(filp);
+		}
+		VDBG(sti, "folder_name: %s\n", folder_name);
+		parent_object = folder_obj->obj_handle;
+	}
+	dentry = filp->f_dentry;
+
+	sti->sub_object_num = 0;
+	filp->f_pos = 0;
+	rc = vfs_readdir(filp, filldir_all, sti);
+	if (rc)
+		ERROR(sti, "vfs_readdir %s error: %d\n",
+				folder_name, rc);
+	VDBG(sti, "%d objects in folder %s\n",
+			sti->sub_object_num, folder_name);
+
+	/* no file in the directory */
+	if  (!sti->sub_object_num)
+		goto out;
+
+	/* pre-allocated objects array */
+	pobj = kzalloc((sti->sub_object_num + 1) * sizeof(struct sti_object *),
+			GFP_KERNEL);
+	if (!pobj) {
+		rc = -ENOMEM;
+		goto out;
+	}
+
+	temp_pobj = pobj;
+
+	i = 0;
+	list_for_each_entry_safe(obj, tmp_obj, &sti->tmp_obj_list, list) {
+		pobj[i] = obj;
+		/* remove from temp object list */
+		list_del_init(&obj->list);
+		i++;
+	}
+	VDBG(sti, "i = %d\n", i);
+	pobj[i] = NULL;
+
+	/* sort the objects array */
+	sort(pobj, sti->sub_object_num, sizeof(struct sti_object *),
+			alnumsort, NULL);
+
+	while (*pobj) {
+		/* increase total object number */
+		sti->object_num++;
+
+		/* fill object handle */
+		(*pobj)->obj_handle = sti->object_num;
+
+		/* fill parent object */
+		(*pobj)->parent_object = cpu_to_le32(parent_object);
+		(*pobj)->obj_info.parent_object = cpu_to_le32(parent_object);
+
+		/* object full path */
+		memset((*pobj)->full_path, 0, sizeof((*pobj)->full_path));
+		snprintf((*pobj)->full_path, sizeof((*pobj)->full_path),
+				"%s/%s", folder_name, (*pobj)->filename);
+
+		VDBG(sti, "full_path: %s, obj_handle: 0x%08x, "
+				"parent_object: 0x%08x\n",
+				(*pobj)->full_path, (*pobj)->obj_handle,
+				parent_object);
+
+		/* get file statistics info */
+		rc = vfs_stat((char __user *)(*pobj)->full_path, &stat);
+		if (rc) {
+			ERROR(sti, "vfs_stat error: %d\n", rc);
+			goto out;
+		}
+
+		/* fill remained ObjectInfo Dataset */
+		if (stat.mode & S_IWUSR)
+			(*pobj)->obj_info.protection_status =
+				cpu_to_le16(PIMA15740_OBJECT_NO_PROTECTION);
+		else
+			(*pobj)->obj_info.protection_status =
+				cpu_to_le16(PIMA15740_OBJECT_READ_ONLY);
+
+		(*pobj)->obj_info.object_compressed_size =
+			cpu_to_le32((u32)stat.size);
+
+		/* add to object list */
+		list_add_tail(&(*pobj)->list, &sti->obj_list);
+
+		if ((*pobj)->is_dir && recursive)
+			list_objects(sti, (*pobj)->full_path, *pobj, true);
+
+		pobj++;
+	}
+
+out:
+	/* free pre-allocated objects array */
+	kfree(temp_pobj);
+
+	if (strcmp(folder_name, sti->root_path))
+		filp_close(filp, current->files);
+
+	VDBG(sti, "<--- %s()\n", __func__);
+	return rc;
+}
+
+
+static int do_open_session(struct sti_dev *sti)
+{
+	struct sti_object	*obj;
+	u8			filename_len;
+	int			rc = 0;
+	VDBG(sti, "---> %s()\n", __func__);
+
+	if (sti->session_open) {
+		sti->response_code = PIMA15740_RES_SESSION_ALREADY_OPEN;
+		goto out;
+	}
+
+	sti->session_id = sti->ops_params[0];
+	VDBG(sti, "session_id: 0x%08x\n", sti->session_id);
+	if (sti->session_id) {
+		sti->response_code = PIMA15740_RES_OK;
+		sti->session_open = 1;
+	} else {
+		sti->response_code = PIMA15740_RES_INVALID_PARAMETER;
+		sti->session_open = 0;
+		goto out;
+	}
+
+	/* reset total object number */
+	sti->object_num = 0;
+
+	/* root object init */
+	filename_len = strlen(sti->root_filp->f_dentry->d_name.name) + 1;
+	VDBG(sti, "root object: %s\n", sti->root_path);
+	VDBG(sti, "filename_len: %u\n", filename_len);
+	obj = kzalloc(sizeof(*obj), GFP_KERNEL);
+	if (!obj) {
+		sti->response_code = PIMA15740_RES_DEVICE_BUSY;
+		goto out;
+	}
+
+	spin_lock_irq(&sti->lock);
+
+	obj->obj_handle = 0;
+	obj->parent_object = 0;
+	obj->storage_id = STORAGE_ID;
+	obj->is_dir = 1;
+	obj->send_valid = 0;
+	obj->obj_info_size = sizeof(struct pima15740_object_info);
+
+	/* root object filename */
+	memset(obj->filename, 0, sizeof(obj->filename));
+	strncpy(obj->filename, sti->root_filp->f_dentry->d_name.name,
+			sizeof(obj->filename));
+	VDBG(sti, "root object filename: %s\n", obj->filename);
+
+	/* root object full path */
+	memset(obj->full_path, 0, sizeof(obj->full_path));
+	strncpy(obj->full_path, sti->root_path, sizeof(obj->full_path));
+	VDBG(sti, "root object full path: %s\n", obj->full_path);
+
+	/* add to object list */
+	list_add_tail(&obj->list, &sti->obj_list);
+
+	spin_unlock_irq(&sti->lock);
+out:
+	/* send response */
+	rc = send_response(sti, sti->response_code);
+
+	VDBG(sti, "<--- %s()\n", __func__);
+	return rc;
+}
+
+
+static int do_close_session(struct sti_dev *sti)
+{
+	struct sti_object	*obj, *tmp_obj;
+	int			rc = 0;
+	VDBG(sti, "---> %s()\n", __func__);
+
+	if (sti->session_open) {
+		sti->response_code = PIMA15740_RES_OK;
+		sti->session_open = 0;
+	} else {
+		sti->response_code = PIMA15740_RES_SESSION_NOT_OPEN;
+		goto out;
+	}
+
+	spin_lock_irq(&sti->lock);
+
+	/* release object list */
+	list_for_each_entry_safe(obj, tmp_obj, &sti->obj_list, list) {
+		list_del_init(&obj->list);
+		kfree(obj);
+	}
+
+	spin_unlock_irq(&sti->lock);
+
+	DBG(sti, "release object list\n");
+out:
+	/* send response */
+	rc = send_response(sti, sti->response_code);
+
+	VDBG(sti, "<--- %s()\n", __func__);
+	return rc;
+}
+
+
+static int do_get_storage_ids(struct sti_dev *sti, struct sti_buffhd *bh)
+{
+	size_t		size;
+	u32		i;
+	int		rc = 0;
+	VDBG(sti, "---> %s()\n", __func__);
+
+	if (!sti->session_open) {
+		sti->response_code = PIMA15740_RES_SESSION_NOT_OPEN;
+		goto out;
+	}
+
+	sti->storage_id = cpu_to_le32(STORAGE_ID);
+	DBG(sti, "storage_id: 0x%08x\n", sti->storage_id);
+
+	/* 4 bytes array number and 4 bytes storage id */
+	size = 8;
+	fill_data_container(bh, sti, PIMA15740_CONTAINER_LEN + size);
+
+	/* support one storage id */
+	i = 1;
+	memcpy(bh->buf + PIMA15740_CONTAINER_LEN, &i, 4);
+	memcpy(bh->buf + PIMA15740_CONTAINER_LEN + 4, &sti->storage_id, 4);
+
+	bh->inreq->length = PIMA15740_CONTAINER_LEN + size;
+	bh->state = BUF_STATE_FULL;
+	start_transfer(sti, sti->bulk_in, bh->inreq,
+			&bh->inreq_busy, &bh->state);
+	sti->next_buffhd_to_fill = bh->next;
+
+	sti->response_code = PIMA15740_RES_OK;
+out:
+	/* send response */
+	rc = send_response(sti, sti->response_code);
+
+	VDBG(sti, "<--- %s()\n", __func__);
+	return rc;
+}
+
+
+static int do_get_storage_info(struct sti_dev *sti, struct sti_buffhd *bh)
+{
+	size_t		size;
+	u32		storage_id;
+	u64		sbytes_max, sbytes_free;
+	struct kstatfs	sbuf;
+	int		rc = 0;
+	VDBG(sti, "---> %s()\n", __func__);
+
+	if (!sti->session_open) {
+		sti->response_code = PIMA15740_RES_SESSION_NOT_OPEN;
+		goto out;
+	}
+
+	/* storage id */
+	storage_id = sti->ops_params[0];
+	if (storage_id != sti->storage_id) {
+		WARNING(sti, "invalid storage id: 0x%08x\n", storage_id);
+		sti->response_code = PIMA15740_RES_INVALID_STORAGE_ID;
+		goto out;
+	}
+
+	/* get filesystem statistics info */
+	rc = vfs_statfs(sti->root_filp->f_dentry, &sbuf);
+	if (rc) {
+		sti->response_code = PIMA15740_RES_ACCESS_DENIED;
+		goto out;
+	}
+
+	/* fill remained items in StorageInfo Dataset */
+	sbytes_max = (u64) sbuf.f_bsize * sbuf.f_blocks;
+	sbytes_free = (u64) sbuf.f_bsize * sbuf.f_bfree;
+	sti_storage_info.max_capacity = cpu_to_le64(sbytes_max);
+	sti_storage_info.free_space_in_bytes = cpu_to_le64(sbytes_free);
+	sti_storage_info.free_space_in_images = cpu_to_le32((u32)~0);
+	str_to_uni16(storage_desc, sti_storage_info.storage_desc);
+
+	/* dump StorageInfo Dataset */
+	dump_storage_info(sti);
+
+	memcpy(bh->buf + PIMA15740_CONTAINER_LEN, &sti_storage_info,
+			sizeof(sti_storage_info));
+
+	size = PIMA15740_CONTAINER_LEN + sizeof(sti_storage_info);
+	fill_data_container(bh, sti, size);
+
+	bh->inreq->length = size;
+	bh->state = BUF_STATE_FULL;
+	start_transfer(sti, sti->bulk_in, bh->inreq,
+			&bh->inreq_busy, &bh->state);
+	sti->next_buffhd_to_fill = bh->next;
+
+	sti->response_code = PIMA15740_RES_OK;
+out:
+	/* send response */
+	rc = send_response(sti, sti->response_code);
+
+	VDBG(sti, "<--- %s()\n", __func__);
+	return rc;
+}
+
+
+static int do_get_num_objects(struct sti_dev *sti, struct sti_buffhd *bh)
+{
+	int		i;
+	int		rc = 0;
+	VDBG(sti, "---> %s()\n", __func__);
+
+	if (!sti->session_open) {
+		sti->response_code = PIMA15740_RES_SESSION_NOT_OPEN;
+		goto out;
+	}
+
+	for (i = 0; i < PARAM_NUM_MAX; i++)
+		VDBG(sti, "parameter[%u]: 0x%08x\n",
+				i + 1, sti->ops_params[i]);
+
+	if (!backing_folder_is_open(sti)) {
+		ERROR(sti, "backing folder is not open\n");
+		sti->response_code = PIMA15740_RES_STORE_NOT_AVAILABLE;
+		goto out;
+	}
+
+	DBG(sti, "total object number: %u\n", sti->object_num);
+
+	sti->response_code = PIMA15740_RES_OK;
+out:
+	/* send response */
+	rc = send_params_response(sti, sti->response_code,
+			sti->object_num, 0, 0,
+			1);
+
+	VDBG(sti, "<--- %s()\n", __func__);
+	return rc;
+}
+
+
+static int do_get_object_handles(struct sti_dev *sti, struct sti_buffhd *bh)
+{
+	size_t			size;
+	u32			storage_id, obj_handle;
+	u32			new_obj_num, old_obj_num, tmp_obj_num;
+	char			*cur_path = NULL;
+	struct sti_object	*obj;
+	int			i, rc = 0;
+
+	VDBG(sti, "---> %s()\n", __func__);
+
+	if (!sti->session_open) {
+		sti->response_code = PIMA15740_RES_SESSION_NOT_OPEN;
+		goto out;
+	}
+
+	for (i = 0; i < PARAM_NUM_MAX; i++)
+		VDBG(sti, "parameter[%u]: 0x%08x\n",
+				i + 1, sti->ops_params[i]);
+
+	if (!backing_folder_is_open(sti)) {
+		ERROR(sti, "backing folder is not open\n");
+		sti->response_code = PIMA15740_RES_STORE_NOT_AVAILABLE;
+		goto out;
+	}
+
+	storage_id = sti->ops_params[0];
+	obj_handle = sti->ops_params[2];
+	old_obj_num = sti->object_num;
+
+	if (storage_id == 0xffffffff) {
+		/* list all objects recursive */
+		rc = list_objects(sti, sti->root_path, NULL, true);
+		new_obj_num = sti->object_num;
+	} else {
+		/* list objects of current folder */
+		list_for_each_entry(obj, &sti->obj_list, list) {
+			if (obj->obj_handle == obj_handle)
+				break;
+		}
+
+		if (obj_handle == 0xffffffff)
+			cur_path = sti->root_path;
+		else
+			cur_path = obj->full_path;
+		VDBG(sti, "current path: %s\n", cur_path);
+
+		if (cur_path)
+			rc = list_objects(sti, cur_path, obj, false);
+		else {
+			sti->response_code = PIMA15740_RES_DEVICE_BUSY;
+			goto out;
+		}
+
+		new_obj_num = sti->sub_object_num;
+	}
+
+	if (rc) {
+		sti->response_code = PIMA15740_RES_DEVICE_BUSY;
+		goto out;
+	}
+
+	/* 4 bytes array number plus object handles size */
+	size = 4 + new_obj_num * 4;
+	VDBG(sti, "object number: %u, payload size: %u\n",
+			new_obj_num, size);
+	fill_data_container(bh, sti, PIMA15740_CONTAINER_LEN + size);
+
+	/* fill object handles array */
+	memcpy(bh->buf + PIMA15740_CONTAINER_LEN, &new_obj_num, 4);
+	for (i = 1; i <= new_obj_num; i++) {
+		tmp_obj_num = old_obj_num + i;
+		memcpy(bh->buf + PIMA15740_CONTAINER_LEN + i * 4,
+				&tmp_obj_num, 4);
+	}
+
+	bh->inreq->length = PIMA15740_CONTAINER_LEN + size;
+	bh->state = BUF_STATE_FULL;
+	start_transfer(sti, sti->bulk_in, bh->inreq,
+			&bh->inreq_busy, &bh->state);
+	sti->next_buffhd_to_fill = bh->next;
+
+	sti->response_code = PIMA15740_RES_OK;
+out:
+	/* send response */
+	rc = send_response(sti, sti->response_code);
+
+	VDBG(sti, "<--- %s()\n", __func__);
+	return rc;
+}
+
+
+static int do_get_object_info(struct sti_dev *sti, struct sti_buffhd *bh)
+{
+	size_t			size = 0;
+	u32			obj_handle;
+	struct sti_object	*obj;
+	int			rc = 0;
+	VDBG(sti, "---> %s()\n", __func__);
+
+	if (!sti->session_open) {
+		sti->response_code = PIMA15740_RES_SESSION_NOT_OPEN;
+		goto out;
+	}
+
+	obj_handle = sti->ops_params[0];
+	if (obj_handle == 0 || obj_handle > sti->object_num) {
+		WARNING(sti, "invalid object handle: 0x%08x\n", obj_handle);
+		sti->response_code = PIMA15740_RES_INVALID_OBJECT_HANDLE;
+		goto out;
+	}
+
+	spin_lock_irq(&sti->lock);
+
+	/* find the object */
+	list_for_each_entry(obj, &sti->obj_list, list) {
+		if (obj->obj_handle == obj_handle)
+			break;
+	}
+
+	memcpy(bh->buf + PIMA15740_CONTAINER_LEN, &obj->obj_info,
+			obj->obj_info_size);
+	size = PIMA15740_CONTAINER_LEN + obj->obj_info_size;
+	fill_data_container(bh, sti, size);
+
+	bh->inreq->length = size;
+	bh->state = BUF_STATE_FULL;
+
+	spin_unlock_irq(&sti->lock);
+
+	start_transfer(sti, sti->bulk_in, bh->inreq,
+			&bh->inreq_busy, &bh->state);
+	sti->next_buffhd_to_fill = bh->next;
+
+	DBG(sti, "get object info: %s\n", obj->full_path);
+	VDBG(sti, "obj_handle: 0x%08x\n", obj->obj_handle);
+
+	/* dump ObjectInfo Dataset */
+	dump_object_info(sti, obj);
+
+	sti->response_code = PIMA15740_RES_OK;
+out:
+	/* send response */
+	rc = send_response(sti, sti->response_code);
+
+	VDBG(sti, "<--- %s()\n", __func__);
+	return rc;
+}
+
+
+static int do_get_object(struct sti_dev *sti, struct sti_buffhd *bh)
+{
+	u32			obj_handle;
+	loff_t			file_size, file_offset, file_offset_tmp;
+	unsigned int		amount_left, amount;
+	ssize_t			nread;
+	struct sti_object	*obj;
+	struct file		*filp = NULL;
+	struct inode		*inode = NULL;
+	char __user		*buf;
+	int			rc = 0;
+	VDBG(sti, "---> %s()\n", __func__);
+
+	if (!sti->session_open) {
+		sti->response_code = PIMA15740_RES_SESSION_NOT_OPEN;
+		goto out1;
+	}
+
+	obj_handle = sti->ops_params[0];
+	if (obj_handle == 0 || obj_handle > sti->object_num) {
+		WARNING(sti, "invalid object handle: 0x%08x\n", obj_handle);
+		sti->response_code = PIMA15740_RES_INVALID_OBJECT_HANDLE;
+		goto out1;
+	}
+
+	spin_lock_irq(&sti->lock);
+
+	/* find the object */
+	list_for_each_entry(obj, &sti->obj_list, list) {
+		if (obj->obj_handle == obj_handle)
+			break;
+	}
+
+	spin_unlock_irq(&sti->lock);
+
+	/* open object file */
+	filp = filp_open(obj->full_path, O_RDONLY | O_LARGEFILE, 0);
+	if (IS_ERR(filp)) {
+		ERROR(sti, "unable to open file: %s. Err = %d\n",
+				obj->full_path, (int) PTR_ERR(filp));
+		sti->response_code = PIMA15740_RES_STORE_NOT_AVAILABLE;
+		goto out1;
+	}
+
+	/* figure out the size and read the remaining amount */
+	inode = filp->f_dentry->d_inode;
+	file_size = i_size_read(inode->i_mapping->host);
+	VDBG(sti, "object file size: %llu\n", (unsigned long long) file_size);
+	if (unlikely(file_size == 0)) {
+		sti->response_code = PIMA15740_RES_STORE_NOT_AVAILABLE;
+		goto out2;
+	}
+
+	DBG(sti, "get object: %s\n", obj->full_path);
+
+	file_offset = 0;
+	amount_left = file_size;
+
+	while (amount_left > 0) {
+		bh = sti->next_buffhd_to_fill;
+		while (bh->state != BUF_STATE_EMPTY) {
+			rc = sleep_thread(sti);
+			if (rc) {
+				filp_close(filp, current->files);
+				return rc;
+			}
+		}
+
+		/* don't read more than the buffer size  */
+		if (file_offset == 0) {
+			fill_data_container(bh, sti,
+					file_size + PIMA15740_CONTAINER_LEN);
+			buf = (char __user *) bh->buf +
+				PIMA15740_CONTAINER_LEN;
+			amount = min((unsigned int) amount_left,
+				mod_data.buflen - PIMA15740_CONTAINER_LEN);
+		} else {
+			buf = (char __user *) bh->buf;
+			amount = min((unsigned int) amount_left,
+					mod_data.buflen);
+		}
+
+		/* no more left to read */
+		if (amount == 0)
+			break;
+
+		/* perform the read */
+		file_offset_tmp = file_offset;
+		nread = vfs_read(filp, buf, amount, &file_offset_tmp);
+		VDBG(sti, "file read %u @ %llu -> %d\n", amount,
+				(unsigned long long) file_offset,
+				(int) nread);
+
+		if (signal_pending(current)) {
+			filp_close(filp, current->files);
+			return -EINTR;
+		}
+
+		if (nread < 0) {
+			WARNING(sti, "error in file read: %d\n",
+					(int) nread);
+			nread = 0;
+		} else if (nread < amount) {
+			WARNING(sti, "partial file read: %d/%u\n",
+					(int) nread, amount);
+			/* round down to a block */
+			nread -= (nread & 511);
+		}
+
+		/*
+		 * PIMA 15740 generic container head resides in
+		 * first data block payload
+		 */
+		if (file_offset == 0)
+			bh->inreq->length = nread + PIMA15740_CONTAINER_LEN;
+		else
+			bh->inreq->length = nread;
+		bh->state = BUF_STATE_FULL;
+		bh->inreq->zero = 0;
+
+		file_offset += nread;
+		amount_left -= nread;
+
+		/* send this buffer and go read some more */
+		start_transfer(sti, sti->bulk_in, bh->inreq,
+				&bh->inreq_busy, &bh->state);
+		sti->next_buffhd_to_fill = bh->next;
+	}
+
+	sti->response_code = PIMA15740_RES_OK;
+out2:
+	filp_close(filp, current->files);
+out1:
+	/* send response */
+	rc = send_response(sti, sti->response_code);
+
+	VDBG(sti, "<--- %s()\n", __func__);
+	return rc;
+}
+
+
+static int do_delete_object(struct sti_dev *sti, struct sti_buffhd *bh)
+{
+	u32			obj_handle;
+	struct sti_object	*obj, *tmp_obj;
+	struct nameidata	nd;
+	int			i;
+	int			rc = 0;
+	VDBG(sti, "---> %s()\n", __func__);
+
+	if (!sti->session_open) {
+		sti->response_code = PIMA15740_RES_SESSION_NOT_OPEN;
+		goto out;
+	}
+
+	for (i = 0; i < PARAM_NUM_MAX; i++)
+		VDBG(sti, "parameter[%u]: 0x%08x\n",
+				i + 1, sti->ops_params[i]);
+
+	obj_handle = sti->ops_params[0];
+	if (obj_handle == 0 || obj_handle > sti->object_num) {
+		WARNING(sti, "invalid object handle: 0x%08x\n", obj_handle);
+		sti->response_code = PIMA15740_RES_INVALID_OBJECT_HANDLE;
+		goto out;
+	}
+
+	spin_lock_irq(&sti->lock);
+
+	/* find the object */
+	list_for_each_entry_safe(obj, tmp_obj, &sti->obj_list, list) {
+		if (obj->obj_handle == obj_handle) {
+			list_del_init(&obj->list);
+			kfree(obj);
+			break;
+		}
+	}
+
+	spin_unlock_irq(&sti->lock);
+
+	/* lookup the object file */
+	rc = path_lookup(obj->full_path, 0, &nd);
+	if (rc) {
+		ERROR(sti, "invalid object file path: %s\n", obj->full_path);
+		sti->response_code = PIMA15740_RES_STORE_NOT_AVAILABLE;
+		goto out;
+	}
+
+	/* unlink the file */
+	rc = vfs_unlink(nd.path.dentry->d_parent->d_inode, nd.path.dentry);
+	if (rc) {
+		ERROR(sti, "can't delete object\n");
+		sti->response_code = PIMA15740_RES_DEVICE_BUSY;
+		goto out;
+	}
+
+	DBG(sti, "delete object: %s\n", obj->full_path);
+
+	sti->response_code = PIMA15740_RES_OK;
+out:
+	/* send response */
+	rc = send_response(sti, sti->response_code);
+
+	VDBG(sti, "<--- %s()\n", __func__);
+	return rc;
+}
+
+
+static int do_send_object_info(struct sti_dev *sti, struct sti_buffhd *bh)
+{
+	u8			filename_len;
+	u32			storage_id;
+	u32			parent_object = 0xffffffff;
+	unsigned int		offset;
+	struct sti_object	*obj, *parent_obj;
+	size_t			obj_size;
+	int			i;
+	int			rc = 0;
+	VDBG(sti, "---> %s()\n", __func__);
+
+	if (!sti->session_open) {
+		sti->response_code = PIMA15740_RES_SESSION_NOT_OPEN;
+		goto out2;
+	}
+
+	for (i = 0; i < PARAM_NUM_MAX; i++)
+		VDBG(sti, "parameter[%u]: 0x%08x\n",
+				i + 1, sti->ops_params[i]);
+
+	/* destination storage id */
+	storage_id = sti->ops_params[0];
+	if (storage_id != STORAGE_ID) {
+		WARNING(sti, "invalid storage id: 0x%08x\n", storage_id);
+		sti->response_code = PIMA15740_RES_INVALID_STORAGE_ID;
+		goto out2;
+	}
+
+	/* parent object handle where object should be placed */
+	parent_object = sti->ops_params[1];
+
+	/* if root directory, parent object is 0xffffffff  */
+	if (parent_object == 0 || (parent_object > sti->object_num
+				&& parent_object != 0xffffffff)) {
+		WARNING(sti, "invalid parent handle: 0x%08x\n",
+				parent_object);
+		sti->response_code = PIMA15740_RES_INVALID_PARENT_OBJECT;
+		goto out2;
+	}
+
+	/* queue a request to read ObjectInfo Dataset */
+	set_bulk_out_req_length(sti, bh, 512);
+	bh->outreq->short_not_ok = 1;
+	start_transfer(sti, sti->bulk_out, bh->outreq,
+			&bh->outreq_busy, &bh->state);
+
+	/* wait for the ObjectInfo Dataset to arrive */
+	while (bh->state != BUF_STATE_FULL) {
+		rc = sleep_thread(sti);
+		if (rc)
+			goto out1;
+	}
+
+	/* filename strings length */
+	offset = offsetof(struct pima15740_object_info, obj_strings[0]);
+	filename_len = *(u8 *)(bh->outreq->buf + PIMA15740_CONTAINER_LEN
+			+ offset);
+	VDBG(sti, "filename_len: %u\n", filename_len);
+
+	/* sti_object size */
+	obj_size = sizeof(*obj) + 2 * filename_len + 4;
+	VDBG(sti, "obj_size: %u\n", obj_size);
+	obj = kzalloc(obj_size, GFP_KERNEL);
+	if (!obj) {
+		sti->response_code = PIMA15740_RES_STORE_NOT_AVAILABLE;
+		goto out2;
+	}
+
+	spin_lock_irq(&sti->lock);
+
+	/* increase total object number */
+	sti->object_num++;
+
+	/* fill sti_object info */
+	obj->obj_handle = sti->object_num;
+	VDBG(sti, "obj_handle: 0x%08x\n", obj->obj_handle);
+
+	if (parent_object == 0xffffffff)
+		obj->parent_object = 0;
+	else
+		obj->parent_object = parent_object;
+	VDBG(sti, "parent_object: 0x%08x\n", obj->parent_object);
+
+	obj->storage_id = storage_id;
+
+	/* mark object ready to send */
+	obj->send_valid = 1;
+
+	/* ObjectInfo Dataset size */
+	obj->obj_info_size = sizeof(struct pima15740_object_info)
+		+ 2 * filename_len + 4;
+	VDBG(sti, "obj_info_size: %u\n", obj->obj_info_size);
+
+	/* filename */
+	offset = offsetof(struct pima15740_object_info, obj_strings[1]);
+	uni16_to_str(bh->outreq->buf + PIMA15740_CONTAINER_LEN + offset,
+			obj->filename, filename_len);
+
+	/* object full path */
+	memset(obj->full_path, 0, sizeof(obj->full_path));
+	if (parent_object == 0xffffffff) {
+		snprintf(obj->full_path, sizeof(obj->full_path),  "%s/%s",
+				sti->root_path, obj->filename);
+	} else {
+		/* find the parent object */
+		list_for_each_entry(parent_obj, &sti->obj_list, list) {
+			if (parent_obj->obj_handle == parent_object)
+				break;
+		}
+		snprintf(obj->full_path, sizeof(obj->full_path), "%s/%s",
+				parent_obj->full_path, obj->filename);
+	}
+	VDBG(sti, "full_path: %s\n", obj->full_path);
+
+	/* fetch ObjectInfo Dataset from buffer */
+	memcpy(&obj->obj_info, bh->outreq->buf + PIMA15740_CONTAINER_LEN,
+			obj->obj_info_size);
+
+	/* root directory, modify parent object */
+	if (parent_object == 0xffffffff)
+		obj->obj_info.parent_object = cpu_to_le32(0);
+	else
+		obj->obj_info.parent_object = parent_object;
+
+	obj->obj_info.storage_id = storage_id;
+
+	/* capture date */
+	obj->obj_info.obj_strings[filename_len * 2 + 1] = 0;
+
+	/* modification date */
+	obj->obj_info.obj_strings[filename_len * 2 + 2] = 0;
+
+	/* keywords */
+	obj->obj_info.obj_strings[filename_len * 2 + 3] = 0;
+
+	bh->state = BUF_STATE_EMPTY;
+
+	/* add to object list */
+	list_add_tail(&obj->list, &sti->obj_list);
+
+	spin_unlock_irq(&sti->lock);
+
+	DBG(sti, "send object info: %s\n", obj->filename);
+
+	/* dump ObjectInfo Dataset */
+	dump_object_info(sti, obj);
+out2:
+	/* send response */
+	rc = send_params_response(sti, PIMA15740_RES_OK,
+			sti->storage_id, parent_object, sti->object_num,
+			3);
+out1:
+	VDBG(sti, "<--- %s()\n", __func__);
+	return rc;
+}
+
+
+static int do_send_object(struct sti_dev *sti, struct sti_buffhd *bh)
+{
+	int			rc = -EINVAL;
+	int			get_some_more;
+	u32			amount_left_to_req, amount_left_to_write;
+	loff_t			file_size, file_offset, file_offset_tmp,
+				usb_offset;
+	unsigned int		amount;
+	ssize_t			nwritten;
+	struct sti_object	*obj;
+	struct file		*filp = NULL;
+	char __user		*buf;
+	VDBG(sti, "---> %s()\n", __func__);
+
+	spin_lock_irq(&sti->lock);
+
+	/* find the object */
+	list_for_each_entry(obj, &sti->obj_list, list) {
+		if (obj->send_valid)
+			break;
+	}
+
+	/* mark object already sent */
+	obj->send_valid = 0;
+
+	spin_unlock_irq(&sti->lock);
+
+	/* open object file */
+	filp = filp_open(obj->full_path, O_CREAT | O_RDWR | O_LARGEFILE, 0666);
+	if (IS_ERR(filp)) {
+		ERROR(sti, "unable to open file: %s. Err = %d\n",
+				obj->full_path, (int) PTR_ERR(filp));
+		sti->response_code = PIMA15740_RES_STORE_NOT_AVAILABLE;
+		goto out1;
+	}
+
+	file_size = obj->obj_info.object_compressed_size;
+	VDBG(sti, "object file size: %llu\n",
+			(unsigned long long) file_size);
+	if (unlikely(file_size == 0)) {
+		sti->response_code = PIMA15740_RES_STORE_NOT_AVAILABLE;
+		goto out2;
+	}
+
+	DBG(sti, "send object: %s\n", obj->full_path);
+
+	/* carry out the file writes */
+	get_some_more = 1;
+	file_offset = usb_offset = 0;
+
+	amount_left_to_req = file_size + PIMA15740_CONTAINER_LEN;
+	amount_left_to_write = file_size;
+	VDBG(sti, "in total: amount_left_to_req: %u\n",
+			amount_left_to_req);
+	VDBG(sti, "in total: amount_left_to_write: %u\n",
+			amount_left_to_write);
+
+	while (amount_left_to_write > 0) {
+		bh = sti->next_buffhd_to_fill;
+		if (bh->state == BUF_STATE_EMPTY && get_some_more) {
+			amount = min(amount_left_to_req, mod_data.buflen);
+			amount = min((loff_t) amount, file_size
+				+ PIMA15740_CONTAINER_LEN - usb_offset);
+			VDBG(sti, "usb amount: %u\n", amount);
+
+			/* no left data request to transfer */
+			if (amount == 0) {
+				get_some_more = 0;
+				continue;
+			}
+
+			/* get the next buffer */
+			usb_offset += amount;
+			amount_left_to_req -= amount;
+
+			if (amount_left_to_req == 0)
+				get_some_more = 0;
+
+			/* amount is always divisible by bulk-out
+			   maxpacket size */
+			bh->outreq->length = bh->bulk_out_intended_length =
+					amount;
+			bh->outreq->short_not_ok = 1;
+			start_transfer(sti, sti->bulk_out, bh->outreq,
+					&bh->outreq_busy, &bh->state);
+			sti->next_buffhd_to_fill = bh->next;
+			continue;
+		}
+
+		/* write the received data to the backing folder */
+		bh = sti->next_buffhd_to_drain;
+
+		/* host stopped early */
+		if (bh->state == BUF_STATE_EMPTY && !get_some_more) {
+			WARNING(sti, "host stops early, bh->state: %d\n",
+					bh->state);
+			sti->response_code = PIMA15740_RES_INCOMPLETE_TRANSFER;
+			goto out2;
+		}
+
+		if (bh->state == BUF_STATE_FULL) {
+			smp_rmb();
+			sti->next_buffhd_to_drain = bh->next;
+			bh->state = BUF_STATE_EMPTY;
+
+			/* something go wrong with the transfer */
+			if (bh->outreq->status != 0) {
+				sti->response_code =
+					PIMA15740_RES_INCOMPLETE_TRANSFER;
+				goto out2;
+			}
+
+			/*
+			 * PIMA 15740 generic container head resides in
+			 * first data block payload
+			 */
+			if (file_offset == 0) {
+				buf = (char __user *) bh->buf +
+					PIMA15740_CONTAINER_LEN;
+				amount = bh->outreq->actual -
+					PIMA15740_CONTAINER_LEN;
+			} else {
+				buf = (char __user *) bh->buf;
+				amount = bh->outreq->actual;
+			}
+			amount = min((loff_t) amount,
+					file_size - file_offset);
+
+			/* across page boundary, recalculate the length */
+			if (amount == 0) {
+				INFO(sti, "extra bulk out zlp packets\n");
+				usb_offset -= bh->outreq->length;
+				amount_left_to_req += bh->outreq->length;
+				continue;
+			}
+
+			/* perform the write */
+			file_offset_tmp = file_offset;
+			nwritten = vfs_write(filp, (char __user *) buf,
+					amount, &file_offset_tmp);
+			VDBG(sti, "file write %u @ %llu -> %d\n", amount,
+					(unsigned long long) file_offset,
+					(int) nwritten);
+
+			if (signal_pending(current)) {
+				filp_close(filp, current->files);
+				return -EINTR;
+			}
+
+			if (nwritten < 0) {
+				VDBG(sti, "error in file write: %d\n",
+						(int) nwritten);
+				nwritten = 0;
+			} else if (nwritten < amount) {
+				VDBG(sti, "partial file write: %d/%u\n",
+						(int) nwritten, amount);
+				/* round down to a block */
+				nwritten -= (nwritten & 511);
+			}
+
+			file_offset += nwritten;
+			amount_left_to_write -= nwritten;
+
+			VDBG(sti, "file_offset: %llu, "
+					"amount_left_to_write: %u\n",
+					(unsigned long long) file_offset,
+					amount_left_to_write);
+
+			/* error occurred */
+			if (nwritten < amount) {
+				sti->response_code =
+					PIMA15740_RES_INCOMPLETE_TRANSFER;
+				goto out2;
+			}
+			continue;
+		}
+
+		/* wait for something to happen */
+		rc = sleep_thread(sti);
+		if (rc) {
+			filp_close(filp, current->files);
+			return rc;
+		}
+	}
+
+	/* fsync object file */
+	vfs_fsync(filp, filp->f_path.dentry, 1);
+
+	sti->response_code = PIMA15740_RES_OK;
+out2:
+	filp_close(filp, current->files);
+out1:
+	/* send response */
+	rc = send_response(sti, sti->response_code);
+
+	VDBG(sti, "<--- %s()\n", __func__);
+	return rc;
+}
+
+
+static int do_copy_object(struct sti_dev *sti, struct sti_buffhd *bh)
+{
+	int			rc = 0, i;
+	size_t			size = 0;
+	unsigned int		old_obj_handle, new_obj_parent_handle;
+	unsigned int		new_storage_id, amount, amount_left;
+	struct sti_object	*old_obj = NULL,  *new_obj_parent = NULL;
+	struct sti_object	*new_obj, *tmp_obj;
+	char			*new_obj_fname;
+	struct file		*old_fp, *new_fp;
+	struct inode		*inode = NULL;
+	char __user		*buf;
+	loff_t			file_size, file_offset, file_offset_tmp;
+	ssize_t			nread, nwritten;
+	VDBG(sti, "---> %s()\n", __func__);
+
+	if (!sti->session_open) {
+		sti->response_code = PIMA15740_RES_SESSION_NOT_OPEN;
+		goto out1;
+	}
+
+	old_obj_handle = sti->ops_params[0];
+	new_storage_id = sti->ops_params[1];
+	new_obj_parent_handle = sti->ops_params[2];
+
+	if ((old_obj_handle == 0) || (old_obj_handle > sti->object_num)) {
+		WARNING(sti, "invalid object handle: %u\n", old_obj_handle);
+		sti->response_code = PIMA15740_RES_INVALID_OBJECT_HANDLE;
+		goto out1;
+	}
+
+	if (new_storage_id != sti->storage_id) {
+		WARNING(sti, "invalid storage id: %u\n", new_storage_id);
+		sti->response_code = PIMA15740_RES_INVALID_STORAGE_ID;
+		goto out1;
+	}
+
+	if (new_obj_parent_handle > sti->object_num
+			&& new_obj_parent_handle != 0xffffffff) {
+		WARNING(sti, "invalid parent object handle: %u\n",
+				new_obj_parent_handle);
+		sti->response_code = PIMA15740_RES_INVALID_PARENT_OBJECT;
+		goto out1;
+	}
+
+	spin_lock_irq(&sti->lock);
+
+	/* find the old object to be copied */
+	i = 0;
+	list_for_each_entry(tmp_obj, &sti->obj_list, list) {
+		if (tmp_obj->obj_handle == old_obj_handle) {
+			i++;
+			old_obj = tmp_obj;
+		}
+
+		if (tmp_obj->obj_handle == new_obj_parent_handle) {
+			i++;
+			new_obj_parent = tmp_obj;
+		}
+
+		if (i == 2)
+			break;
+	}
+
+	spin_unlock_irq(&sti->lock);
+
+	if (i != 2 || !old_obj || !new_obj_parent) {
+		WARNING(sti, "invalid objects %u or %u\n",
+			old_obj_handle, new_obj_parent_handle);
+		sti->response_code = PIMA15740_RES_INVALID_PARENT_OBJECT;
+		goto out1;
+	}
+
+	size = strlen(new_obj_parent->full_path) +
+		strlen(old_obj->filename) + 2;
+	new_obj_fname = kzalloc(size, GFP_KERNEL);
+	if (!new_obj_fname) {
+		sti->response_code = PIMA15740_RES_DEVICE_BUSY;
+		rc = -EINVAL;
+		goto out1;
+	}
+	strncpy(new_obj_fname, new_obj_parent->full_path, size);
+	strncat(new_obj_fname, "/", size);
+	strncat(new_obj_fname, old_obj->filename, size);
+
+	VDBG(sti, "copy object: from [%s] to [%s]\n",
+			old_obj->full_path, new_obj_fname);
+
+	old_fp = filp_open(old_obj->full_path, O_RDONLY | O_LARGEFILE, 0);
+	if (IS_ERR(old_fp)) {
+		ERROR(sti, "unable to open file: %s. Err = %d\n",
+			old_obj->full_path, (int) PTR_ERR(old_fp));
+		sti->response_code = PIMA15740_RES_DEVICE_BUSY;
+		rc = -EINVAL;
+		goto out2;
+	}
+
+	new_fp = filp_open(new_obj_fname, O_CREAT | O_RDWR | O_LARGEFILE, 0666);
+	if (IS_ERR(new_fp)) {
+		ERROR(sti, "unable to create file: %s. Err = %d\n",
+			new_obj_fname, (int) PTR_ERR(new_fp));
+		sti->response_code = PIMA15740_RES_DEVICE_BUSY;
+		rc = -EINVAL;
+		goto out3;
+	}
+
+	buf = kzalloc(PAGE_SIZE, GFP_KERNEL);
+	if (!buf) {
+		sti->response_code = PIMA15740_RES_OPERATION_NOT_SUPPORTED;
+		rc = -EINVAL;
+		goto out4;
+	}
+
+	inode = old_fp->f_dentry->d_inode;
+	file_size = i_size_read(inode->i_mapping->host);
+	VDBG(sti, "object file size: %llu\n", (unsigned long long) file_size);
+
+	if (unlikely(file_size == 0)) {
+		sti->response_code = PIMA15740_RES_STORE_NOT_AVAILABLE;
+		rc = -EIO;
+		goto out5;
+	}
+
+	file_offset = 0;
+	amount_left = file_size;
+
+	while (amount_left > 0) {
+		amount = min(amount_left, (unsigned int) PAGE_SIZE);
+		if (amount == 0)
+			break;
+
+		file_offset_tmp = file_offset;
+		nread = vfs_read(old_fp, buf, amount, &file_offset_tmp);
+
+		if (signal_pending(current)) {
+			rc = -EINTR;
+			goto out5;
+		}
+
+		if (nread < 0) {
+			DBG(sti, "error in file read: %d\n",
+					(int) nread);
+			nread = 0;
+		} else if (nread < amount) {
+			DBG(sti, "partial file read: %d/%u\n",
+					(int) nread, amount);
+			/* round down to a block */
+			nread -= (nread & 511);
+		}
+
+		amount = min(amount, (unsigned int) nread);
+		file_offset_tmp = file_offset;
+		nwritten = vfs_write(new_fp, buf, amount, &file_offset_tmp);
+
+		if (signal_pending(current)) {
+			rc = -EINTR;
+			goto out5;
+		}
+
+		if (nwritten < 0) {
+			VDBG(sti, "error in file write: %d\n",
+					(int) nwritten);
+			nwritten = 0;
+		} else if (nwritten < amount) {
+			VDBG(sti, "partial file write: %d/%u\n",
+					(int) nwritten, amount);
+			/* round down to a block */
+			nwritten -= (nwritten & 511);
+		}
+
+		amount = min(amount, (unsigned int) nwritten);
+		file_offset += amount;
+		amount_left -= amount;
+	}
+
+	size = sizeof(*old_obj);
+	new_obj = kzalloc(size, GFP_KERNEL);
+	if (!new_obj) {
+		rc = -ENOMEM;
+		goto out5;
+	}
+
+	spin_lock_irq(&sti->lock);
+
+	sti->object_num++;
+
+	/* change obj_handle */
+	new_obj->obj_handle = sti->object_num;
+
+	/* change parent object */
+	if (new_obj_parent_handle == 0xffffffff)
+		new_obj->parent_object = 0;
+	else
+		new_obj->parent_object = new_obj_parent_handle;
+
+	new_obj->storage_id = old_obj->storage_id;
+	new_obj->is_dir = old_obj->is_dir;
+	new_obj->send_valid = old_obj->send_valid;
+	new_obj->obj_info_size = old_obj->obj_info_size;
+	strncpy(new_obj->filename, old_obj->filename,
+			sizeof(new_obj->filename));
+
+	/* change full path name */
+	strncpy(new_obj->full_path, new_obj_fname, sizeof(new_obj->full_path));
+
+	/* copy object_info */
+	memcpy(&new_obj->obj_info, &old_obj->obj_info, old_obj->obj_info_size);
+
+	/* fill parent_object in object_info */
+	new_obj->obj_info.parent_object = new_obj->parent_object;
+
+	/* add to object list */
+	list_add_tail(&new_obj->list, &sti->obj_list);
+
+	spin_unlock_irq(&sti->lock);
+
+	sti->response_code = PIMA15740_RES_OK;
+out5:
+	kfree(buf);
+out4:
+	filp_close(new_fp, current->files);
+out3:
+	filp_close(old_fp, current->files);
+out2:
+	kfree(new_obj_fname);
+out1:
+	/* send response */
+	rc = send_params_response(sti, sti->response_code,
+			sti->object_num, 0, 0,
+			1);
+
+	VDBG(sti, "<--- %s()\n", __func__);
+	return rc;
+}
+
+
+static int do_move_object(struct sti_dev *sti, struct sti_buffhd *bh)
+{
+	int			i, rc = 0;
+	size_t			size = 0;
+	unsigned int		old_obj_handle, new_obj_parent_handle;
+	unsigned int		new_storage_id;
+	char			*new_obj_fname;
+	struct file		*old_fp, *new_fp;
+	struct inode		*old_dir, *new_dir;
+	struct dentry		*old_dentry, *new_dentry;
+	struct sti_object	*old_obj = NULL;
+	struct sti_object	*new_obj = NULL;
+	struct sti_object	*new_obj_parent = NULL;
+	struct sti_object	 *tmp_obj = NULL;
+	VDBG(sti, "---> %s()\n", __func__);
+
+	if (!sti->session_open) {
+		sti->response_code = PIMA15740_RES_SESSION_NOT_OPEN;
+		goto out1;
+	}
+
+	old_obj_handle = sti->ops_params[0];
+	new_storage_id = sti->ops_params[1];
+	new_obj_parent_handle = sti->ops_params[2];
+
+	if ((old_obj_handle == 0) || (old_obj_handle > sti->object_num)) {
+		WARNING(sti, "invalid object handle: %u\n", old_obj_handle);
+		sti->response_code = PIMA15740_RES_INVALID_OBJECT_HANDLE;
+		goto out1;
+	}
+
+	if (new_storage_id != sti->storage_id) {
+		WARNING(sti, "invalid storage id: %u\n", new_storage_id);
+		sti->response_code = PIMA15740_RES_INVALID_STORAGE_ID;
+		goto out1;
+	}
+
+	if (new_obj_parent_handle > sti->object_num
+			&& new_obj_parent_handle != 0xffffffff) {
+		WARNING(sti, "invalid parent object handle: %u\n",
+				new_obj_parent_handle);
+		sti->response_code = PIMA15740_RES_INVALID_PARENT_OBJECT;
+		goto out1;
+	}
+
+	spin_lock_irq(&sti->lock);
+
+	/* find the old object to be moved */
+	i = 0;
+	list_for_each_entry(tmp_obj, &sti->obj_list, list) {
+		if (tmp_obj->obj_handle == old_obj_handle) {
+			i++;
+			old_obj = tmp_obj;
+		}
+
+		if (tmp_obj->obj_handle == new_obj_parent_handle) {
+			i++;
+			new_obj_parent = tmp_obj;
+		}
+
+		if (i == 2)
+			break;
+	}
+
+	spin_unlock_irq(&sti->lock);
+
+	if (i != 2 || !old_obj || !new_obj_parent) {
+		WARNING(sti, "invalid objects %u or %u\n",
+			old_obj_handle, new_obj_parent_handle);
+		sti->response_code = PIMA15740_RES_INVALID_PARENT_OBJECT;
+		goto out1;
+	}
+
+	size = strlen(new_obj_parent->full_path) +
+		strlen(old_obj->filename) + 2;
+	new_obj_fname = kzalloc(size, GFP_KERNEL);
+	if (!new_obj_fname) {
+		sti->response_code = PIMA15740_RES_DEVICE_BUSY;
+		rc = -EINVAL;
+		goto out1;
+	}
+	strncpy(new_obj_fname, new_obj_parent->full_path, size);
+	strncat(new_obj_fname, "/", size);
+	strncat(new_obj_fname, old_obj->filename, size);
+
+	VDBG(sti, "move object: from [%s] to [%s]\n",
+			old_obj->full_path, new_obj_fname);
+
+	old_fp = filp_open(old_obj->full_path, O_RDONLY | O_LARGEFILE, 0);
+	if (IS_ERR(old_fp)) {
+		ERROR(sti, "unable to open file: %s. Err = %d\n",
+			old_obj->full_path, (int) PTR_ERR(old_fp));
+		sti->response_code = PIMA15740_RES_DEVICE_BUSY;
+		rc = -EINVAL;
+		goto out2;
+	}
+
+	new_fp = filp_open(new_obj_fname, O_CREAT | O_RDWR | O_LARGEFILE, 0666);
+	if (IS_ERR(new_fp)) {
+		ERROR(sti, "unable to create file: %s. Err = %d\n",
+			new_obj_fname, (int) PTR_ERR(new_fp));
+		sti->response_code = PIMA15740_RES_DEVICE_BUSY;
+		rc = -EINVAL;
+		goto out3;
+	}
+
+	old_dir = old_fp->f_dentry->d_parent->d_inode;
+	new_dir = new_fp->f_dentry->d_parent->d_inode;
+	old_dentry = old_fp->f_dentry;
+	new_dentry = new_fp->f_dentry;
+
+	rc = vfs_rename(old_dir, old_dentry, new_dir, new_dentry);
+
+	if (rc) {
+		sti->response_code = PIMA15740_RES_OPERATION_NOT_SUPPORTED;
+		goto out4;
+	} else
+		sti->response_code = PIMA15740_RES_OK;
+
+	size = sizeof(*old_obj);
+	new_obj = kzalloc(size, GFP_KERNEL);
+	if (!new_obj) {
+		rc = -ENOMEM;
+		goto out4;
+	}
+
+	spin_lock_irq(&sti->lock);
+
+	/* change parent object */
+	if (new_obj_parent_handle == 0xffffffff)
+		new_obj->parent_object = 0;
+	else
+		new_obj->parent_object = new_obj_parent_handle;
+
+	new_obj->obj_handle = old_obj->obj_handle;
+	new_obj->storage_id = old_obj->storage_id;
+	new_obj->is_dir = old_obj->is_dir;
+	new_obj->send_valid = old_obj->send_valid;
+	new_obj->obj_info_size = old_obj->obj_info_size;
+	strncpy(new_obj->filename, old_obj->filename,
+			sizeof(new_obj->filename));
+
+	/* change full path name */
+	strncpy(new_obj->full_path, new_obj_fname, sizeof(new_obj->full_path));
+
+	/* copy object_info */
+	memcpy(&new_obj->obj_info, &old_obj->obj_info, old_obj->obj_info_size);
+
+	/* fill parent_object in object_info */
+	new_obj->obj_info.parent_object = new_obj->parent_object;
+
+	/* add to object list */
+	list_add_tail(&new_obj->list, &sti->obj_list);
+
+	/* remove from object list */
+	list_del_init(&old_obj->list);
+
+	spin_unlock_irq(&sti->lock);
+
+	kfree(old_obj);
+out4:
+	filp_close(new_fp, current->files);
+out3:
+	filp_close(old_fp, current->files);
+out2:
+	kfree(new_obj_fname);
+out1:
+	/* send response */
+	rc = send_response(sti, sti->response_code);
+
+	VDBG(sti, "<--- %s()\n", __func__);
+	return rc;
+}
+
+
+/* TODO: PIMA 15740 Event handling via interrupt endpoint */
+static int send_status(struct sti_dev *sti)
+{
+	VDBG(sti, "---> %s()\n", __func__);
+	VDBG(sti, "<--- %s()\n", __func__);
+	return 0;
+}
+
+
+/*-------------------------------------------------------------------------*/
+
+/* handle supported PIMA 15740 operations */
+static int do_still_image_command(struct sti_dev *sti)
+{
+	struct sti_buffhd	*bh;
+	int			rc = -EINVAL;
+	int			reply = -EINVAL;
+	VDBG(sti, "---> %s()\n", __func__);
+
+	dump_cb(sti);
+
+	if (!backing_folder_is_open(sti)) {
+		ERROR(sti, "backing folder is not open\n");
+		return rc;
+	}
+
+	/* wait for the next buffer to become available for data or status */
+	bh = sti->next_buffhd_to_drain = sti->next_buffhd_to_fill;
+	while (bh->state != BUF_STATE_EMPTY) {
+		rc = sleep_thread(sti);
+		if (rc)
+			return rc;
+	}
+
+	down_read(&sti->filesem);
+	switch (sti->code) {
+
+	case PIMA15740_OP_GET_DEVICE_INFO:
+		DBG(sti, "PIMA15740 OPS: get device info\n");
+		reply = do_get_device_info(sti, bh);
+		break;
+
+	case PIMA15740_OP_OPEN_SESSION:
+		DBG(sti, "PIMA15740 OPS: open session\n");
+		reply = do_open_session(sti);
+		break;
+
+	case PIMA15740_OP_CLOSE_SESSION:
+		DBG(sti, "PIMA15740 OPS: close session\n");
+		reply = do_close_session(sti);
+		break;
+
+	case PIMA15740_OP_GET_STORAGE_IDS:
+		DBG(sti, "PIMA15740 OPS: get storage ids\n");
+		reply = do_get_storage_ids(sti, bh);
+		break;
+
+	case PIMA15740_OP_GET_STORAGE_INFO:
+		DBG(sti, "PIMA15740 OPS: get storage info\n");
+		reply = do_get_storage_info(sti, bh);
+		break;
+
+	case PIMA15740_OP_GET_NUM_OBJECTS:
+		DBG(sti, "PIMA15740 OPS: get num objects\n");
+		reply = do_get_num_objects(sti, bh);
+		break;
+
+	case PIMA15740_OP_GET_OBJECT_HANDLES:
+		DBG(sti, "PIMA15740 OPS: get object handles\n");
+		reply = do_get_object_handles(sti, bh);
+		break;
+
+	case PIMA15740_OP_GET_OBJECT_INFO:
+		DBG(sti, "PIMA15740 OPS: get object info\n");
+		reply = do_get_object_info(sti, bh);
+		break;
+
+	case PIMA15740_OP_GET_OBJECT:
+		DBG(sti, "PIMA15740 OPS: get object\n");
+		reply = do_get_object(sti, bh);
+		break;
+
+	case PIMA15740_OP_DELETE_OBJECT:
+		DBG(sti, "PIMA15740 OPS: delete object\n");
+		reply = do_delete_object(sti, bh);
+		break;
+
+	case PIMA15740_OP_SEND_OBJECT_INFO:
+		DBG(sti, "PIMA15740 OPS: send object info\n");
+		reply = do_send_object_info(sti, bh);
+		break;
+
+	case PIMA15740_OP_SEND_OBJECT:
+		DBG(sti, "PIMA15740 OPS: send object\n");
+		reply = do_send_object(sti, bh);
+		break;
+
+	case PIMA15740_OP_COPY_OBJECT:
+		DBG(sti, "PIMA15740 OPS: copy object\n");
+		reply = do_copy_object(sti, bh);
+		break;
+
+	case PIMA15740_OP_MOVE_OBJECT:
+		DBG(sti, "PIMA15740 OPS: move object\n");
+		reply = do_move_object(sti, bh);
+		break;
+
+	default:
+		WARNING(sti, "unknown PIMA15740 OPS 0x%04x\n", sti->code);
+		break;
+	}
+	up_read(&sti->filesem);
+
+	if (reply == -EINTR || signal_pending(current))
+		rc = -EINTR;
+
+	if (reply == -EINVAL)
+		rc = 0;
+
+	VDBG(sti, "<--- %s()\n", __func__);
+	return rc;
+}
+
+
+/*-------------------------------------------------------------------------*/
+
+/* received PIMA 15740 Command Blocks */
+static int received_cb(struct sti_dev *sti, struct sti_buffhd *bh)
+{
+	struct usb_request		*req = bh->outreq;
+	struct pima15740_container	*cb = req->buf;
+	unsigned short		n;
+	VDBG(sti, "---> %s()\n", __func__);
+
+	/* this is not a real packet */
+	if (req->status)
+		return -EINVAL;
+
+	/* save the command for later */
+	sti->container_len = cb->container_len;
+	sti->container_type = cb->container_type;
+	sti->code = cb->code;
+	sti->transaction_id = cb->transaction_id;
+
+	/* get Command Block Parameters 1..N */
+	n = sti->container_len - PIMA15740_CONTAINER_LEN;
+	if (n != 0)
+		memcpy(sti->ops_params, cb + 1, n);
+
+	VDBG(sti, "Command Block: len=%u, type=0x%04x, "
+			"code=0x%04x, trans_id=0x%08x\n",
+			sti->container_len, sti->container_type,
+			sti->code, sti->transaction_id);
+
+	VDBG(sti, "<--- %s()\n", __func__);
+	return 0;
+}
+
+
+static int get_next_command(struct sti_dev *sti)
+{
+	struct sti_buffhd	*bh;
+	int			rc = 0;
+	VDBG(sti, "---> %s()\n", __func__);
+
+	/* wait for the next buffer to become available */
+	bh = sti->next_buffhd_to_fill;
+	while (bh->state != BUF_STATE_EMPTY) {
+		rc = sleep_thread(sti);
+		if (rc)
+			return rc;
+	}
+
+	/* queue a request to read a Bulk-only Command Block */
+	set_bulk_out_req_length(sti, bh, 512);
+	bh->outreq->short_not_ok = 1;
+	start_transfer(sti, sti->bulk_out, bh->outreq,
+			&bh->outreq_busy, &bh->state);
+
+	/* we will drain the buffer in software, which means we
+	 * can reuse it for the next filling. No need to advance
+	 * next_buffhd_to_fill. */
+
+	/* wait for the Command Block to arrive */
+	while (bh->state != BUF_STATE_FULL) {
+		rc = sleep_thread(sti);
+		if (rc)
+			return rc;
+	}
+	smp_rmb();
+	rc = received_cb(sti, bh);
+	bh->state = BUF_STATE_EMPTY;
+
+	VDBG(sti, "<--- %s()\n", __func__);
+	return rc;
+}
+
+
+/*-------------------------------------------------------------------------*/
+
+static int enable_endpoint(struct sti_dev *sti, struct usb_ep *ep,
+		const struct usb_endpoint_descriptor *d)
+{
+	int	rc;
+	VDBG(sti, "---> %s()\n", __func__);
+
+	ep->driver_data = sti;
+	rc = usb_ep_enable(ep, d);
+	if (rc)
+		ERROR(sti, "can't enable %s, result %d\n", ep->name, rc);
+
+	VDBG(sti, "<--- %s()\n", __func__);
+	return rc;
+}
+
+static int alloc_request(struct sti_dev *sti, struct usb_ep *ep,
+		struct usb_request **preq)
+{
+	VDBG(sti, "---> %s()\n", __func__);
+
+	*preq = usb_ep_alloc_request(ep, GFP_ATOMIC);
+	if (*preq)
+		return 0;
+
+	ERROR(sti, "can't allocate request for %s\n", ep->name);
+
+	VDBG(sti, "<--- %s()\n", __func__);
+	return -ENOMEM;
+}
+
+/*
+ * Reset interface setting and re-init endpoint state (toggle etc).
+ * Call with altsetting < 0 to disable the interface.  The only other
+ * available altsetting is 0, which enables the interface.
+ */
+static int do_set_interface(struct sti_dev *sti, int altsetting)
+{
+	int	rc = 0;
+	int	i;
+	const struct usb_endpoint_descriptor	*d;
+	VDBG(sti, "---> %s()\n", __func__);
+
+	if (sti->running)
+		DBG(sti, "reset interface\n");
+
+reset:
+	/* deallocate the requests */
+	for (i = 0; i < NUM_BUFFERS; ++i) {
+		struct sti_buffhd *bh = &sti->buffhds[i];
+
+		if (bh->inreq) {
+			usb_ep_free_request(sti->bulk_in, bh->inreq);
+			bh->inreq = NULL;
+		}
+		if (bh->outreq) {
+			usb_ep_free_request(sti->bulk_out, bh->outreq);
+			bh->outreq = NULL;
+		}
+	}
+	if (sti->intreq) {
+		usb_ep_free_request(sti->intr_in, sti->intreq);
+		sti->intreq = NULL;
+	}
+
+	/* disable the endpoints */
+	if (sti->bulk_in_enabled) {
+		usb_ep_disable(sti->bulk_in);
+		sti->bulk_in_enabled = 0;
+	}
+	if (sti->bulk_out_enabled) {
+		usb_ep_disable(sti->bulk_out);
+		sti->bulk_out_enabled = 0;
+	}
+	if (sti->intr_in_enabled) {
+		usb_ep_disable(sti->intr_in);
+		sti->intr_in_enabled = 0;
+	}
+
+	sti->running = 0;
+	if (altsetting < 0 || rc != 0)
+		return rc;
+
+	DBG(sti, "set interface %d\n", altsetting);
+
+	/* enable the endpoints */
+	d = ep_desc(sti->gadget, &fs_bulk_in_desc, &hs_bulk_in_desc);
+	rc = enable_endpoint(sti, sti->bulk_in, d);
+	if (rc)
+		goto reset;
+	sti->bulk_in_enabled = 1;
+
+	d = ep_desc(sti->gadget, &fs_bulk_out_desc, &hs_bulk_out_desc);
+	rc = enable_endpoint(sti, sti->bulk_out, d);
+	if (rc)
+		goto reset;
+	sti->bulk_out_enabled = 1;
+	sti->bulk_out_maxpacket = le16_to_cpu(d->wMaxPacketSize);
+	clear_bit(CLEAR_BULK_HALTS, &sti->atomic_bitflags);
+
+	d = ep_desc(sti->gadget, &fs_intr_in_desc, &hs_intr_in_desc);
+	rc = enable_endpoint(sti, sti->intr_in, d);
+	if (rc)
+		goto reset;
+	sti->intr_in_enabled = 1;
+
+	/* allocate the requests */
+	for (i = 0; i < NUM_BUFFERS; ++i) {
+		struct sti_buffhd	*bh = &sti->buffhds[i];
+
+		rc = alloc_request(sti, sti->bulk_in, &bh->inreq);
+		if (rc)
+			goto reset;
+
+		rc = alloc_request(sti, sti->bulk_out, &bh->outreq);
+		if (rc)
+			goto reset;
+
+		bh->inreq->buf = bh->outreq->buf = bh->buf;
+		bh->inreq->context = bh->outreq->context = bh;
+		bh->inreq->complete = bulk_in_complete;
+		bh->outreq->complete = bulk_out_complete;
+	}
+
+	rc = alloc_request(sti, sti->intr_in, &sti->intreq);
+	if (rc)
+		goto reset;
+
+	sti->running = 1;
+
+	VDBG(sti, "<--- %s()\n", __func__);
+	return rc;
+}
+
+
+/*
+ * Change our operational configuration.  This code must agree with the code
+ * that returns config descriptors, and with interface altsetting code.
+ *
+ * It's also responsible for power management interactions.  Some
+ * configurations might not work with our current power sources.
+ * For now we just assume the gadget is always self-powered.
+ */
+static int do_set_config(struct sti_dev *sti, u8 new_config)
+{
+	int	rc = 0;
+	VDBG(sti, "---> %s()\n", __func__);
+
+	/* disable the single interface */
+	if (sti->config != 0) {
+		DBG(sti, "reset config\n");
+		sti->config = 0;
+		rc = do_set_interface(sti, -1);
+	}
+
+	/* enable the interface */
+	if (new_config != 0) {
+		sti->config = new_config;
+		rc = do_set_interface(sti, 0);
+		if (rc)
+			sti->config = 0;	/* reset on errors */
+		else {
+			char *speed;
+
+			switch (sti->gadget->speed) {
+			case USB_SPEED_LOW:
+				speed = "low";
+				break;
+			case USB_SPEED_FULL:
+				speed = "full";
+				break;
+			case USB_SPEED_HIGH:
+				speed = "high";
+				break;
+			default:
+				speed = "?";
+				break;
+			}
+			INFO(sti, "%s speed config #%d\n",
+					speed, sti->config);
+		}
+	}
+
+	VDBG(sti, "<--- %s()\n", __func__);
+	return rc;
+}
+
+
+/*-------------------------------------------------------------------------*/
+
+static void handle_exception(struct sti_dev *sti)
+{
+	siginfo_t		info;
+	int			sig;
+	int			i;
+	int			num_active;
+	struct sti_buffhd	*bh;
+	enum sti_state		old_state;
+	u8			new_config;
+	unsigned int		exception_req_tag;
+	int			rc;
+
+	VDBG(sti, "---> %s()\n", __func__);
+
+	/* Clear the existing signals. Anything but SIGUSR1 is converted
+	 * into a high-priority EXIT exception. */
+	for (;;) {
+		sig = dequeue_signal_lock(current, &current->blocked, &info);
+		if (!sig)
+			break;
+
+		if (sig != SIGUSR1) {
+			if (sti->state < STI_STATE_EXIT)
+				DBG(sti, "main thread exiting on signal\n");
+			raise_exception(sti, STI_STATE_EXIT);
+		}
+	}
+
+	/* cancel all the pending transfers */
+	if (sti->intreq_busy)
+		usb_ep_dequeue(sti->intr_in, sti->intreq);
+
+	for (i = 0; i < NUM_BUFFERS; ++i) {
+		bh = &sti->buffhds[i];
+		if (bh->inreq_busy)
+			usb_ep_dequeue(sti->bulk_in, bh->inreq);
+		if (bh->outreq_busy)
+			usb_ep_dequeue(sti->bulk_out, bh->outreq);
+	}
+
+	/* wait until everything is idle */
+	for (;;) {
+		num_active = sti->intreq_busy;
+		for (i = 0; i < NUM_BUFFERS; ++i) {
+			bh = &sti->buffhds[i];
+			num_active += bh->inreq_busy + bh->outreq_busy;
+		}
+
+		if (num_active == 0)
+			break;
+
+		if (sleep_thread(sti))
+			return;
+	}
+
+	/* clear out the controller's fifos */
+	if (sti->bulk_in_enabled)
+		usb_ep_fifo_flush(sti->bulk_in);
+	if (sti->bulk_out_enabled)
+		usb_ep_fifo_flush(sti->bulk_out);
+	if (sti->intr_in_enabled)
+		usb_ep_fifo_flush(sti->intr_in);
+
+	/*
+	 * Reset the I/O buffer states and pointers, the device
+	 * state, and the exception. Then invoke the handler.
+	 */
+	spin_lock_irq(&sti->lock);
+
+	for (i = 0; i < NUM_BUFFERS; ++i) {
+		bh = &sti->buffhds[i];
+		bh->state = BUF_STATE_EMPTY;
+	}
+	sti->next_buffhd_to_fill = sti->next_buffhd_to_drain =
+			&sti->buffhds[0];
+
+	exception_req_tag = sti->exception_req_tag;
+	new_config = sti->new_config;
+	old_state = sti->state;
+
+	if (old_state == STI_STATE_ABORT_BULK_OUT)
+		sti->state = STI_STATE_STATUS_PHASE;
+	else
+		sti->state = STI_STATE_IDLE;
+	spin_unlock_irq(&sti->lock);
+
+	/* carry out any extra actions required for the exception */
+	switch (old_state) {
+	default:
+		break;
+
+	case STI_STATE_CANCEL:
+		if (usb_ep_clear_halt(sti->bulk_out) ||
+				usb_ep_clear_halt(sti->bulk_in))
+			sti->response_code = PIMA15740_RES_DEVICE_BUSY;
+		else
+			sti->response_code = PIMA15740_RES_OK;
+		break;
+
+	case STI_STATE_ABORT_BULK_OUT:
+		send_status(sti);
+		spin_lock_irq(&sti->lock);
+		if (sti->state == STI_STATE_STATUS_PHASE)
+			sti->state = STI_STATE_IDLE;
+		spin_unlock_irq(&sti->lock);
+		break;
+
+	case STI_STATE_RESET:
+		/* in case we were forced against our will to halt a
+		 * bulk endpoint, clear the halt now */
+		if (test_and_clear_bit(CLEAR_BULK_HALTS,
+				&sti->atomic_bitflags)) {
+			usb_ep_clear_halt(sti->bulk_in);
+			usb_ep_clear_halt(sti->bulk_out);
+		}
+
+		if (sti->ep0_req_tag == exception_req_tag)
+			/* complete the status stage */
+			ep0_queue(sti);
+		break;
+
+	case STI_STATE_INTERFACE_CHANGE:
+		rc = do_set_interface(sti, 0);
+		if (sti->ep0_req_tag != exception_req_tag)
+			break;
+		if (rc != 0)		/* STALL on errors */
+			sti_set_halt(sti, sti->ep0);
+		else			/* complete the status stage */
+			ep0_queue(sti);
+		break;
+
+	case STI_STATE_CONFIG_CHANGE:
+		rc = do_set_config(sti, new_config);
+		if (sti->ep0_req_tag != exception_req_tag)
+			break;
+		if (rc != 0)		/* STALL on errors */
+			sti_set_halt(sti, sti->ep0);
+		else			/* complete the status stage */
+			ep0_queue(sti);
+		break;
+
+	case STI_STATE_DISCONNECT:
+		do_set_config(sti, 0);	/* unconfigured state */
+		break;
+
+	case STI_STATE_EXIT:
+	case STI_STATE_TERMINATED:
+		do_set_config(sti, 0);	/* free resources */
+		spin_lock_irq(&sti->lock);
+		sti->state = STI_STATE_TERMINATED;	/* stop the thread */
+		spin_unlock_irq(&sti->lock);
+		break;
+	}
+
+	VDBG(sti, "<--- %s()\n", __func__);
+}
+
+
+/*-------------------------------------------------------------------------*/
+
+static int sti_main_thread(void *sti_)
+{
+	struct sti_dev		*sti = sti_;
+	VDBG(sti, "---> %s()\n", __func__);
+
+	/*
+	 * allow the thread to be killed by a signal, but set the signal mask
+	 * to block everything but INT, TERM, KILL, and USR1
+	 */
+	allow_signal(SIGINT);
+	allow_signal(SIGTERM);
+	allow_signal(SIGKILL);
+	allow_signal(SIGUSR1);
+
+	/* allow the thread to be frozen */
+	set_freezable();
+
+	/*
+	 * arrange for userspace references to be interpreted as kernel
+	 * pointers. That way we can pass a kernel pointer to a routine
+	 * that expects a __user pointer and it will work okay.
+	 */
+	set_fs(get_ds());
+
+	/* the main loop */
+	while (sti->state != STI_STATE_TERMINATED) {
+		if (exception_in_progress(sti) || signal_pending(current)) {
+			handle_exception(sti);
+			continue;
+		}
+
+		if (!sti->running) {
+			sleep_thread(sti);
+			continue;
+		}
+
+		if (get_next_command(sti))
+			continue;
+
+		spin_lock_irq(&sti->lock);
+		if (!exception_in_progress(sti))
+			sti->state = STI_STATE_DATA_PHASE;
+		spin_unlock_irq(&sti->lock);
+
+		if (do_still_image_command(sti))
+			continue;
+
+		spin_lock_irq(&sti->lock);
+		if (!exception_in_progress(sti))
+			sti->state = STI_STATE_STATUS_PHASE;
+		spin_unlock_irq(&sti->lock);
+
+		if (send_status(sti))
+			continue;
+
+		spin_lock_irq(&sti->lock);
+		if (!exception_in_progress(sti))
+			sti->state = STI_STATE_IDLE;
+		spin_unlock_irq(&sti->lock);
+	}
+
+	spin_lock_irq(&sti->lock);
+	sti->thread_task = NULL;
+	spin_unlock_irq(&sti->lock);
+
+	/* in case we are exiting because of a signal, unregister the
+	 * gadget driver */
+	if (test_and_clear_bit(REGISTERED, &sti->atomic_bitflags))
+		usb_gadget_unregister_driver(&sti_driver);
+
+	/* let the unbind and cleanup routines know the thread has exited */
+	complete_and_exit(&sti->thread_notifier, 0);
+
+	VDBG(sti, "<--- %s()\n", __func__);
+}
+
+
+/*-------------------------------------------------------------------------*/
+
+static int open_backing_folder(struct sti_dev *sti, const char *folder_name)
+{
+	struct file		*filp = NULL;
+	int			rc = -EINVAL;
+	struct inode		*inode = NULL;
+	size_t			len;
+	VDBG(sti, "---> %s()\n", __func__);
+
+	/* remove the trailing path sign */
+	len = strlen(folder_name);
+	if (len > 1 && folder_name[len-1] == '/')
+		((char *) folder_name)[len-1] = 0;
+
+	memset(sti->root_path, 0, sizeof(sti->root_path));
+	strncpy(sti->root_path, folder_name, sizeof(sti->root_path));
+
+	filp = filp_open(sti->root_path, O_RDONLY | O_DIRECTORY, 0);
+	if (IS_ERR(filp)) {
+		ERROR(sti, "unable to open backing folder: %s\n",
+				sti->root_path);
+		return PTR_ERR(filp);
+	}
+
+	if (filp->f_path.dentry)
+		inode = filp->f_dentry->d_inode;
+
+	if (!inode || !S_ISDIR(inode->i_mode)) {
+		ERROR(sti, "%s is not a directory\n", sti->root_path);
+		goto out;
+	}
+
+	get_file(filp);
+
+	sti->root_filp = filp;
+
+	INFO(sti, "open backing folder: %s\n", folder_name);
+	rc = 0;
+out:
+	filp_close(filp, current->files);
+
+	VDBG(sti, "<--- %s()\n", __func__);
+	return rc;
+}
+
+static void close_backing_folder(struct sti_dev *sti)
+{
+	VDBG(sti, "---> %s()\n", __func__);
+
+	if (sti->root_filp) {
+		INFO(sti, "close backing folder\n");
+		fput(sti->root_filp);
+		sti->root_filp = NULL;
+	}
+
+	VDBG(sti, "<--- %s()\n", __func__);
+}
+
+
+/*-------------------------------------------------------------------------*/
+
+/* sysfs attribute files */
+static ssize_t show_folder(struct device *dev, struct device_attribute *attr,
+		char *buf)
+{
+	struct sti_dev	*sti = dev_get_drvdata(dev);
+	char		*p;
+	ssize_t		rc;
+
+	down_read(&sti->filesem);
+	if (backing_folder_is_open(sti)) {
+		/* get the complete pathname */
+		p = d_path(&sti->root_filp->f_path, buf, PAGE_SIZE - 1);
+		if (IS_ERR(p))
+			rc = PTR_ERR(p);
+		else {
+			rc = strlen(p);
+			memmove(buf, p, rc);
+
+			/* add a newline */
+			buf[rc] = '\n';
+			buf[++rc] = 0;
+		}
+	} else {	/* no file */
+		*buf = 0;
+		rc = 0;
+	}
+	up_read(&sti->filesem);
+
+	return rc;
+}
+
+
+static ssize_t store_folder(struct device *dev, struct device_attribute *attr,
+		const char *buf, size_t count)
+{
+	struct sti_dev	*sti = dev_get_drvdata(dev);
+	int		rc = 0;
+
+	/* remove a trailing newline */
+	if (count > 0 && buf[count-1] == '\n')
+		((char *) buf)[count-1] = 0;
+
+	/* eject current medium */
+	down_write(&sti->filesem);
+	if (backing_folder_is_open(sti))
+		close_backing_folder(sti);
+
+	/* load new medium */
+	if (count > 0 && buf[0])
+		rc = open_backing_folder(sti, buf);
+
+	up_write(&sti->filesem);
+
+	return (rc < 0 ? rc : count);
+}
+
+/* the write permissions and store_xxx pointers are set in sti_bind() */
+static DEVICE_ATTR(folder, 0444, show_folder, NULL);
+
+
+/*-------------------------------------------------------------------------*/
+
+static void sti_release(struct kref *ref)
+{
+	struct sti_dev	*sti = container_of(ref, struct sti_dev, ref);
+
+	while (!list_empty(&sti->obj_list)) {
+		struct sti_object *obj = NULL;
+		obj = list_entry(sti->obj_list.next, struct sti_object, list);
+		list_del_init(&obj->list);
+		kfree(obj);
+	}
+
+	while (!list_empty(&sti->tmp_obj_list)) {
+		struct sti_object *obj = NULL;
+		obj = list_entry(sti->tmp_obj_list.next, struct sti_object,
+				list);
+		list_del_init(&obj->list);
+		kfree(obj);
+	}
+
+	kfree(sti);
+}
+
+static void gadget_release(struct device *dev)
+{
+	struct sti_dev	*sti = dev_get_drvdata(dev);
+	VDBG(sti, "---> %s()\n", __func__);
+	VDBG(sti, "<--- %s()\n", __func__);
+
+	kref_put(&sti->ref, sti_release);
+}
+
+
+static void /* __init_or_exit */ sti_unbind(struct usb_gadget *gadget)
+{
+	struct sti_dev		*sti = get_gadget_data(gadget);
+	int			i;
+	struct usb_request	*req = sti->ep0req;
+	VDBG(sti, "---> %s()\n", __func__);
+
+	DBG(sti, "unbind\n");
+	clear_bit(REGISTERED, &sti->atomic_bitflags);
+
+	/* unregister the sysfs attribute files */
+	if (sti->registered) {
+		device_remove_file(&sti->dev, &dev_attr_folder);
+		close_backing_folder(sti);
+		device_unregister(&sti->dev);
+		sti->registered = 0;
+	}
+
+	/* if the thread isn't already dead, tell it to exit now */
+	if (sti->state != STI_STATE_TERMINATED) {
+		raise_exception(sti, STI_STATE_EXIT);
+		wait_for_completion(&sti->thread_notifier);
+
+		/* the cleanup routine waits for this completion also */
+		complete(&sti->thread_notifier);
+	}
+
+	/* free the data buffers */
+	for (i = 0; i < NUM_BUFFERS; ++i)
+		kfree(sti->buffhds[i].buf);
+
+	/* free the request and buffer for endpoint 0 */
+	if (req) {
+		kfree(req->buf);
+		usb_ep_free_request(sti->ep0, req);
+	}
+
+	set_gadget_data(gadget, NULL);
+
+	VDBG(sti, "<--- %s()\n", __func__);
+}
+
+
+static int __init check_parameters(struct sti_dev *sti)
+{
+	int	gcnum;
+	VDBG(sti, "---> %s()\n", __func__);
+
+	/* parameter wasn't set */
+	if (mod_data.release == 0xffff) {
+		gcnum = usb_gadget_controller_number(sti->gadget);
+		if (gcnum >= 0)
+			mod_data.release = 0x0300 + gcnum;
+		else {
+			WARNING(sti, "controller '%s' not recognized\n",
+				sti->gadget->name);
+			mod_data.release = 0x0399;
+		}
+	}
+
+	mod_data.buflen &= PAGE_CACHE_MASK;
+	if (mod_data.buflen <= 0) {
+		ERROR(sti, "invalid buflen\n");
+		return -ETOOSMALL;
+	}
+
+	VDBG(sti, "<--- %s()\n", __func__);
+	return 0;
+}
+
+
+static int __init sti_bind(struct usb_gadget *gadget)
+{
+	struct sti_dev		*sti = the_sti;
+	int			rc;
+	int			i;
+	struct usb_ep		*ep;
+	struct usb_request	*req;
+
+	sti->gadget = gadget;
+	set_gadget_data(gadget, sti);
+	sti->ep0 = gadget->ep0;
+	sti->ep0->driver_data = sti;
+
+	rc = check_parameters(sti);
+	if (rc)
+		goto out;
+
+	/* enable store_xxx attributes */
+	dev_attr_folder.attr.mode = 0644;
+	dev_attr_folder.store = store_folder;
+
+	sti->dev.release = gadget_release;
+	sti->dev.parent = &gadget->dev;
+	sti->dev.driver = &sti_driver.driver;
+	dev_set_drvdata(&sti->dev, sti);
+	dev_set_name(&sti->dev, "%s", sti_driver.driver.name);
+
+	rc = device_register(&sti->dev);
+	if (rc) {
+		INFO(sti, "failed to register sti: %d\n", rc);
+		goto out;
+	}
+
+	rc = device_create_file(&sti->dev, &dev_attr_folder);
+	if (rc) {
+		device_unregister(&sti->dev);
+		goto out;
+	}
+
+	sti->registered = 1;
+	kref_get(&sti->ref);
+
+	/* initialize object list */
+	INIT_LIST_HEAD(&sti->obj_list);
+	INIT_LIST_HEAD(&sti->tmp_obj_list);
+
+	if (mod_data.folder && *mod_data.folder)
+		rc = open_backing_folder(sti, mod_data.folder);
+		if (rc)
+			goto out;
+
+	/* find all the endpoints we will use */
+	usb_ep_autoconfig_reset(gadget);
+	ep = usb_ep_autoconfig(gadget, &fs_bulk_in_desc);
+	if (!ep)
+		goto autoconf_fail;
+
+	/* claim bulk-in endpoint */
+	ep->driver_data = sti;
+	sti->bulk_in = ep;
+
+	ep = usb_ep_autoconfig(gadget, &fs_bulk_out_desc);
+	if (!ep)
+		goto autoconf_fail;
+
+	/* claim bulk-out endpoint */
+	ep->driver_data = sti;
+	sti->bulk_out = ep;
+
+	ep = usb_ep_autoconfig(gadget, &fs_intr_in_desc);
+	if (!ep)
+		goto autoconf_fail;
+
+	/* claim intr-in endpoint */
+	ep->driver_data = sti;
+	sti->intr_in = ep;
+
+	/* fix up the descriptors */
+	device_desc.bMaxPacketSize0 = sti->ep0->maxpacket;
+	device_desc.idVendor = cpu_to_le16(mod_data.vendor);
+	device_desc.idProduct = cpu_to_le16(mod_data.product);
+	device_desc.bcdDevice = cpu_to_le16(mod_data.release);
+
+	fs_function[3 + FS_FUNCTION_PRE_EP_ENTRIES] = NULL;
+
+	if (gadget_is_dualspeed(gadget)) {
+		hs_function[3 + HS_FUNCTION_PRE_EP_ENTRIES] = NULL;
+
+		/* assume ep0 uses the same maxpacket value for both speeds */
+		dev_qualifier.bMaxPacketSize0 = sti->ep0->maxpacket;
+
+		/* assume endpoint addresses are the same for both speeds */
+		hs_bulk_in_desc.bEndpointAddress =
+				fs_bulk_in_desc.bEndpointAddress;
+		hs_bulk_out_desc.bEndpointAddress =
+				fs_bulk_out_desc.bEndpointAddress;
+		hs_intr_in_desc.bEndpointAddress =
+				fs_intr_in_desc.bEndpointAddress;
+	}
+
+	if (gadget_is_otg(gadget))
+		otg_desc.bmAttributes |= USB_OTG_HNP;
+
+	rc = -ENOMEM;
+
+	/* allocate the request and buffer for endpoint 0 */
+	sti->ep0req = req = usb_ep_alloc_request(sti->ep0, GFP_KERNEL);
+	if (!req)
+		goto autoconf_fail;
+
+	req->buf = kmalloc(EP0_BUFSIZE, GFP_KERNEL);
+	if (!req->buf)
+		goto autoconf_fail;
+
+	req->complete = ep0_complete;
+
+	/* allocate the data buffers */
+	for (i = 0; i < NUM_BUFFERS; ++i) {
+		struct sti_buffhd	*bh = &sti->buffhds[i];
+
+		/*
+		 * Allocate for the bulk-in endpoint. We assume that
+		 * the buffer will also work with the bulk-out (and
+		 * interrupt-in) endpoint.
+		 */
+		bh->buf = kmalloc(mod_data.buflen, GFP_KERNEL);
+		if (!bh->buf)
+			goto autoconf_fail;
+
+		bh->next = bh + 1;
+	}
+	sti->buffhds[NUM_BUFFERS - 1].next = &sti->buffhds[0];
+
+	/* this should reflect the actual gadget power source */
+	usb_gadget_set_selfpowered(gadget);
+
+	snprintf(manufacturer, sizeof manufacturer, "%s %s with %s",
+			init_utsname()->sysname, init_utsname()->release,
+			gadget->name);
+
+	DBG(sti, "manufacturer: %s\n", manufacturer);
+
+	/*
+	 * on a real device, serial[] would be loaded from permanent
+	 * storage. We just encode it from the driver version string.
+	 */
+	for (i = 0; i < sizeof(serial) - 2; i += 2) {
+		unsigned char		c = DRIVER_VERSION[i / 2];
+
+		if (!c)
+			break;
+
+		snprintf(&serial[i], sizeof(&serial[i]), "%02X", c);
+	}
+
+	/* fill remained device info */
+	sti_device_info.manufacturer_len = sizeof(manufacturer);
+	str_to_uni16(manufacturer, sti_device_info.manufacturer);
+
+	sti_device_info.model_len = sizeof(longname);
+	str_to_uni16(longname, sti_device_info.model);
+
+	sti_device_info.device_version_len = sizeof(device_version);
+	str_to_uni16(device_version, sti_device_info.device_version);
+
+	sti_device_info.serial_number_len = sizeof(serial);
+	str_to_uni16(serial, sti_device_info.serial_number);
+
+	/* create main kernel thread */
+	sti->thread_task = kthread_create(sti_main_thread, sti,
+			"still-image-gadget");
+
+	if (IS_ERR(sti->thread_task)) {
+		rc = PTR_ERR(sti->thread_task);
+		goto autoconf_fail;
+	}
+
+	INFO(sti, DRIVER_DESC ", version: " DRIVER_VERSION "\n");
+	INFO(sti, "VendorID=x%04x, ProductID=x%04x, Release=x%04x\n",
+			mod_data.vendor, mod_data.product, mod_data.release);
+	INFO(sti, "I/O thread pid: %d, buflen=%u\n",
+			task_pid_nr(sti->thread_task), mod_data.buflen);
+
+	set_bit(REGISTERED, &sti->atomic_bitflags);
+
+	/* tell the thread to start working */
+	wake_up_process(sti->thread_task);
+
+	DBG(sti, "bind\n");
+	return 0;
+
+autoconf_fail:
+	ERROR(sti, "unable to autoconfigure all endpoints\n");
+	rc = -ENOTSUPP;
+out:
+	/* the thread is dead */
+	sti->state = STI_STATE_TERMINATED;
+
+	sti_unbind(gadget);
+	complete(&sti->thread_notifier);
+
+	VDBG(sti, "<---> %s()\n", __func__);
+	return rc;
+}
+
+
+/*-------------------------------------------------------------------------*/
+
+static void sti_suspend(struct usb_gadget *gadget)
+{
+	struct sti_dev		*sti = get_gadget_data(gadget);
+
+	DBG(sti, "suspend\n");
+	set_bit(SUSPENDED, &sti->atomic_bitflags);
+}
+
+
+static void sti_resume(struct usb_gadget *gadget)
+{
+	struct sti_dev		*sti = get_gadget_data(gadget);
+
+	DBG(sti, "resume\n");
+	clear_bit(SUSPENDED, &sti->atomic_bitflags);
+}
+
+
+/*-------------------------------------------------------------------------*/
+
+static struct usb_gadget_driver		sti_driver = {
+#ifdef CONFIG_USB_GADGET_DUALSPEED
+	.speed		= USB_SPEED_HIGH,
+#else
+	.speed		= USB_SPEED_FULL,
+#endif
+	.function	= (char *) longname,
+	.bind		= sti_bind,
+	.unbind		= sti_unbind,
+	.disconnect	= sti_disconnect,
+	.setup		= sti_setup,
+	.suspend	= sti_suspend,
+	.resume		= sti_resume,
+
+	.driver		= {
+		.name		= (char *) shortname,
+		.owner		= THIS_MODULE,
+		/* .release = ... */
+		/* .suspend = ... */
+		/* .resume = ...  */
+	},
+};
+
+
+static int __init sti_alloc(void)
+{
+	struct sti_dev		*sti;
+
+	sti = kzalloc(sizeof *sti, GFP_KERNEL);
+	if (!sti)
+		return -ENOMEM;
+
+	spin_lock_init(&sti->lock);
+	init_rwsem(&sti->filesem);
+	kref_init(&sti->ref);
+	init_completion(&sti->thread_notifier);
+
+	the_sti = sti;
+
+	return 0;
+}
+
+
+static int __init sti_init(void)
+{
+	int		rc;
+	struct sti_dev	*sti;
+
+	rc = sti_alloc();
+	if (rc)
+		return rc;
+
+	sti = the_sti;
+
+	rc = usb_gadget_register_driver(&sti_driver);
+	if (rc)
+		kref_put(&sti->ref, sti_release);
+
+	return rc;
+}
+module_init(sti_init);
+
+
+static void __exit sti_cleanup(void)
+{
+	struct sti_dev	*sti = the_sti;
+
+	/* unregister the driver if the thread hasn't already done */
+	if (test_and_clear_bit(REGISTERED, &sti->atomic_bitflags))
+		usb_gadget_unregister_driver(&sti_driver);
+
+	/* wait for the thread to finish up */
+	wait_for_completion(&sti->thread_notifier);
+
+	kref_put(&sti->ref, sti_release);
+}
+module_exit(sti_cleanup);
diff --git a/drivers/usb/otg/Kconfig b/drivers/usb/otg/Kconfig
index 3d2d3e5..69ff37b 100644
--- a/drivers/usb/otg/Kconfig
+++ b/drivers/usb/otg/Kconfig
@@ -69,4 +69,18 @@ config NOP_USB_XCEIV
 	 built-in with usb ip or which are autonomous and doesn't require any
 	 phy programming such as ISP1x04 etc.
 
+config USB_LANGWELL_OTG
+	tristate "Intel Langwell USB OTG dual-role support"
+	depends on USB && X86_MRST
+	select USB_OTG
+	select USB_OTG_UTILS
+	help
+	  Say Y here if you want to build Intel Langwell USB OTG
+	  transciever driver in kernel. This driver implements role
+	  switch between EHCI host driver and Langwell USB OTG
+	  client driver.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called langwell_otg.
+
 endif # USB || OTG
diff --git a/drivers/usb/otg/Makefile b/drivers/usb/otg/Makefile
index aeb49a8..b6609db 100644
--- a/drivers/usb/otg/Makefile
+++ b/drivers/usb/otg/Makefile
@@ -9,6 +9,7 @@ obj-$(CONFIG_USB_OTG_UTILS)	+= otg.o
 obj-$(CONFIG_USB_GPIO_VBUS)	+= gpio_vbus.o
 obj-$(CONFIG_ISP1301_OMAP)	+= isp1301_omap.o
 obj-$(CONFIG_TWL4030_USB)	+= twl4030-usb.o
+obj-$(CONFIG_USB_LANGWELL_OTG)	+= langwell_otg.o
 obj-$(CONFIG_NOP_USB_XCEIV)	+= nop-usb-xceiv.o
 obj-$(CONFIG_USB_ULPI)		+= ulpi.o
 
diff --git a/drivers/usb/otg/langwell_otg.c b/drivers/usb/otg/langwell_otg.c
new file mode 100644
index 0000000..46ae881
--- /dev/null
+++ b/drivers/usb/otg/langwell_otg.c
@@ -0,0 +1,2260 @@
+/*
+ * Intel Langwell USB OTG transceiver driver
+ * Copyright (C) 2008 - 2009, Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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.,
+ * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+/* This driver helps to switch Langwell OTG controller function between host
+ * and peripheral. It works with EHCI driver and Langwell client controller
+ * driver together.
+ */
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/pci.h>
+#include <linux/errno.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <linux/moduleparam.h>
+#include <linux/usb/ch9.h>
+#include <linux/usb/gadget.h>
+#include <linux/usb.h>
+#include <linux/usb/otg.h>
+#include <linux/notifier.h>
+#include <asm/ipc_defs.h>
+#include <linux/delay.h>
+#include "../core/hcd.h"
+
+#include <linux/usb/langwell_otg.h>
+
+#define	DRIVER_DESC		"Intel Langwell USB OTG transceiver driver"
+#define	DRIVER_VERSION		"March 19, 2010"
+
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_AUTHOR("Henry Yuan <hang.yuan@intel.com>, Hao Wu <hao.wu@intel.com>");
+MODULE_VERSION(DRIVER_VERSION);
+MODULE_LICENSE("GPL");
+
+static const char driver_name[] = "langwell_otg";
+
+static int langwell_otg_probe(struct pci_dev *pdev,
+			const struct pci_device_id *id);
+static void langwell_otg_remove(struct pci_dev *pdev);
+static int langwell_otg_suspend(struct pci_dev *pdev, pm_message_t message);
+static int langwell_otg_resume(struct pci_dev *pdev);
+
+static int langwell_otg_set_host(struct otg_transceiver *otg,
+				struct usb_bus *host);
+static int langwell_otg_set_peripheral(struct otg_transceiver *otg,
+				struct usb_gadget *gadget);
+static int langwell_otg_start_srp(struct otg_transceiver *otg);
+
+static const struct pci_device_id pci_ids[] = {{
+	.class =        ((PCI_CLASS_SERIAL_USB << 8) | 0xfe),
+	.class_mask =   ~0,
+	.vendor =	0x8086,
+	.device =	0x0811,
+	.subvendor =	PCI_ANY_ID,
+	.subdevice =	PCI_ANY_ID,
+}, { /* end: all zeroes */ }
+};
+
+static struct pci_driver otg_pci_driver = {
+	.name =		(char *) driver_name,
+	.id_table =	pci_ids,
+
+	.probe =	langwell_otg_probe,
+	.remove =	langwell_otg_remove,
+
+	.suspend =	langwell_otg_suspend,
+	.resume =	langwell_otg_resume,
+};
+
+static const char *state_string(enum usb_otg_state state)
+{
+	switch (state) {
+	case OTG_STATE_A_IDLE:
+		return "a_idle";
+	case OTG_STATE_A_WAIT_VRISE:
+		return "a_wait_vrise";
+	case OTG_STATE_A_WAIT_BCON:
+		return "a_wait_bcon";
+	case OTG_STATE_A_HOST:
+		return "a_host";
+	case OTG_STATE_A_SUSPEND:
+		return "a_suspend";
+	case OTG_STATE_A_PERIPHERAL:
+		return "a_peripheral";
+	case OTG_STATE_A_WAIT_VFALL:
+		return "a_wait_vfall";
+	case OTG_STATE_A_VBUS_ERR:
+		return "a_vbus_err";
+	case OTG_STATE_B_IDLE:
+		return "b_idle";
+	case OTG_STATE_B_SRP_INIT:
+		return "b_srp_init";
+	case OTG_STATE_B_PERIPHERAL:
+		return "b_peripheral";
+	case OTG_STATE_B_WAIT_ACON:
+		return "b_wait_acon";
+	case OTG_STATE_B_HOST:
+		return "b_host";
+	default:
+		return "UNDEFINED";
+	}
+}
+
+/* HSM timers */
+static inline struct langwell_otg_timer *otg_timer_initializer
+(void (*function)(unsigned long), unsigned long expires, unsigned long data)
+{
+	struct langwell_otg_timer *timer;
+	timer = kmalloc(sizeof(struct langwell_otg_timer), GFP_KERNEL);
+	timer->function = function;
+	timer->expires = expires;
+	timer->data = data;
+	return timer;
+}
+
+static struct langwell_otg_timer *a_wait_vrise_tmr, *a_aidl_bdis_tmr,
+	*b_se0_srp_tmr, *b_srp_init_tmr;
+
+static struct list_head active_timers;
+
+static struct langwell_otg *the_transceiver;
+
+/* host/client notify transceiver when event affects HNP state */
+void langwell_update_transceiver()
+{
+	struct langwell_otg *langwell = the_transceiver;
+
+	otg_dbg("transceiver is updated\n");
+
+	if (!langwell->qwork)
+		return ;
+
+	queue_work(langwell->qwork, &langwell->work);
+}
+EXPORT_SYMBOL(langwell_update_transceiver);
+
+static int langwell_otg_set_host(struct otg_transceiver *otg,
+					struct usb_bus *host)
+{
+	otg->host = host;
+
+	return 0;
+}
+
+static int langwell_otg_set_peripheral(struct otg_transceiver *otg,
+					struct usb_gadget *gadget)
+{
+	otg->gadget = gadget;
+
+	return 0;
+}
+
+static int langwell_otg_set_power(struct otg_transceiver *otg,
+				unsigned mA)
+{
+	return 0;
+}
+
+/* A-device drives vbus, controlled through PMIC CHRGCNTL register*/
+static void langwell_otg_drv_vbus(int on)
+{
+	struct ipc_pmic_reg_data	pmic_data = {0};
+	struct ipc_pmic_reg_data	data = {0};
+
+	data.pmic_reg_data[0].register_address = 0xd2;
+	data.ioc = 0;
+	data.num_entries = 1;
+
+	if (ipc_pmic_register_read(&data)) {
+		otg_dbg("Failed to read PMIC register 0x00.\n");
+		return;
+	}
+
+	if (data.pmic_reg_data[0].value & 0x20)
+		otg_dbg("battery attached(%x)\n", data.pmic_reg_data[0].value);
+	else {
+		otg_dbg("no battery detected\n");
+		return;
+	}
+
+	pmic_data.ioc = 0;
+	pmic_data.pmic_reg_data[0].register_address = 0xd4;
+	pmic_data.num_entries = 1;
+	if (on)
+		pmic_data.pmic_reg_data[0].value = 0x20;
+	else
+		pmic_data.pmic_reg_data[0].value = 0xc0;
+
+	if (ipc_pmic_register_write(&pmic_data, TRUE))
+		otg_dbg("Failed to write PMIC.\n");
+}
+
+/* charge vbus or discharge vbus through a resistor to ground */
+static void langwell_otg_chrg_vbus(int on)
+{
+
+	u32	val;
+
+	val = readl(the_transceiver->regs + CI_OTGSC);
+
+	if (on)
+		writel((val & ~OTGSC_INTSTS_MASK) | OTGSC_VC,
+				the_transceiver->regs + CI_OTGSC);
+	else
+		writel((val & ~OTGSC_INTSTS_MASK) | OTGSC_VD,
+				the_transceiver->regs + CI_OTGSC);
+
+}
+
+/* Start SRP */
+static int langwell_otg_start_srp(struct otg_transceiver *otg)
+{
+	u32	val;
+
+	otg_dbg("Start SRP ->\n");
+
+	val = readl(the_transceiver->regs + CI_OTGSC);
+
+	writel((val & ~OTGSC_INTSTS_MASK) | OTGSC_HADP,
+		the_transceiver->regs + CI_OTGSC);
+
+	/* Check if the data plus is finished or not */
+	msleep(8);
+	val = readl(the_transceiver->regs + CI_OTGSC);
+	if (val & (OTGSC_HADP | OTGSC_DP))
+		otg_dbg("DataLine SRP Error\n");
+
+	/* Disable interrupt - b_sess_vld */
+	val = readl(the_transceiver->regs + CI_OTGSC);
+	val &= (~(OTGSC_BSVIE | OTGSC_BSEIE));
+	writel(val, the_transceiver->regs + CI_OTGSC);
+
+	/* Start VBus SRP */
+	langwell_otg_drv_vbus(1);
+	msleep(15);
+	langwell_otg_drv_vbus(0);
+
+	/* Enable interrupt - b_sess_vld*/
+	val = readl(the_transceiver->regs + CI_OTGSC);
+	val |= (OTGSC_BSVIE | OTGSC_BSEIE);
+	writel(val, the_transceiver->regs + CI_OTGSC);
+
+	otg_dbg("Start SRP <-\n");
+	return 0;
+}
+
+/* stop SOF via bus_suspend */
+static void langwell_otg_loc_sof(int on)
+{
+	struct usb_hcd	*hcd;
+	int		err;
+
+	otg_dbg("loc_sof -> %d\n", on);
+
+	hcd = bus_to_hcd(the_transceiver->otg.host);
+	if (on)
+		err = hcd->driver->bus_resume(hcd);
+	else
+		err = hcd->driver->bus_suspend(hcd);
+
+	if (err)
+		otg_dbg("Failed to resume/suspend bus - %d\n", err);
+}
+
+static int langwell_otg_check_otgsc(void)
+{
+	struct langwell_otg 	*langwell;
+	u32 			val_otgsc, val_usbcfg;
+
+	langwell = the_transceiver;
+
+	val_otgsc = readl(langwell->regs + CI_OTGSC);
+	val_usbcfg = readl(langwell->usbcfg);
+
+	otg_dbg("check sync OTGSC and USBCFG\n");
+	otg_dbg("OTGSC = %08x, USBCFG = %08x\n", val_otgsc, val_usbcfg);
+	otg_dbg("OTGSC_AVV = %d\n", !!(val_otgsc & OTGSC_AVV));
+	otg_dbg("USBCFG.VBUSVAL = %d\n", !!(val_usbcfg & USBCFG_VBUSVAL));
+	otg_dbg("OTGSC_ASV = %d\n", !!(val_otgsc & OTGSC_ASV));
+	otg_dbg("USBCFG.AVALID = %d\n", !!(val_usbcfg & USBCFG_AVALID));
+	otg_dbg("OTGSC_BSV = %d\n", !!(val_otgsc & OTGSC_BSV));
+	otg_dbg("USBCFG.BVALID = %d\n", !!(val_usbcfg & USBCFG_BVALID));
+	otg_dbg("OTGSC_BSE = %d\n", !!(val_otgsc & OTGSC_BSE));
+	otg_dbg("USBCFG.SESEND = %d\n", !!(val_usbcfg & USBCFG_SESEND));
+
+	/* Check USBCFG VBusValid/AValid/BValid/SessEnd */
+	if (!!(val_otgsc & OTGSC_AVV) ^ !!(val_usbcfg & USBCFG_VBUSVAL)) {
+		otg_dbg("OTGSC AVV and USBCFG VBUSVAL are not sync.\n");
+		return -1;
+	} else if (!!(val_otgsc & OTGSC_ASV) ^ !!(val_usbcfg & USBCFG_AVALID)) {
+		otg_dbg("OTGSC ASV and USBCFG AVALID are not sync.\n");
+		return -1;
+	} else if (!!(val_otgsc & OTGSC_BSV) ^ !!(val_usbcfg & USBCFG_BVALID)) {
+		otg_dbg("OTGSC BSV and USBCFG BVALID are not sync.\n");
+		return -1;
+	} else if (!!(val_otgsc & OTGSC_BSE) ^ !!(val_usbcfg & USBCFG_SESEND)) {
+		otg_dbg("OTGSC BSE and USBCFG SESSEN are not sync.\n");
+		return -1;
+	}
+
+	otg_dbg("OTGSC and USBCFG are synced\n");
+
+	return 0;
+}
+
+static void langwell_otg_phy_low_power(int on)
+{
+	u8	val, phcd;
+	int	retval;
+
+	otg_dbg("phy low power mode-> %d start\n", on);
+
+	phcd = 0x40;
+
+	val = readb(the_transceiver->regs + CI_HOSTPC1 + 2);
+
+	if (on) {
+		/* Due to hardware issue, after set PHCD, sync will failed
+		 * between USBCFG and OTGSC, so before set PHCD, check if
+		 * sync is in process now. If the answer is "yes", then do
+		 * not touch PHCD bit */
+		retval = langwell_otg_check_otgsc();
+		if (retval) {
+			otg_dbg("Skip PHCD programming..\n");
+			return ;
+		}
+
+		writeb(val | phcd, the_transceiver->regs + CI_HOSTPC1 + 2);
+	} else
+		writeb(val & ~phcd, the_transceiver->regs + CI_HOSTPC1 + 2);
+
+	otg_dbg("phy low power mode<- %d done\n", on);
+}
+
+/* After drv vbus, add 2 ms delay to set PHCD */
+static void langwell_otg_phy_low_power_wait(int on)
+{
+	otg_dbg("2 ms delay before set PHY low power mode\n");
+
+	mdelay(2);
+	langwell_otg_phy_low_power(on);
+}
+
+/* Enable/Disable OTG interrupts */
+static void langwell_otg_intr(int on)
+{
+	u32 val;
+
+	otg_dbg("interrupt -> %d\n", on);
+
+	val = readl(the_transceiver->regs + CI_OTGSC);
+
+	/* OTGSC_INT_MASK doesn't contains 1msInt */
+	if (on) {
+		val = val | (OTGSC_INT_MASK);
+		writel(val, the_transceiver->regs + CI_OTGSC);
+	} else {
+		val = val & ~(OTGSC_INT_MASK);
+		writel(val, the_transceiver->regs + CI_OTGSC);
+	}
+}
+
+/* set HAAR: Hardware Assist Auto-Reset */
+static void langwell_otg_HAAR(int on)
+{
+	u32	val;
+
+	otg_dbg("HAAR -> %d\n", on);
+
+	val = readl(the_transceiver->regs + CI_OTGSC);
+	if (on)
+		writel((val & ~OTGSC_INTSTS_MASK) | OTGSC_HAAR,
+				the_transceiver->regs + CI_OTGSC);
+	else
+		writel((val & ~OTGSC_INTSTS_MASK) & ~OTGSC_HAAR,
+				the_transceiver->regs + CI_OTGSC);
+}
+
+/* set HABA: Hardware Assist B-Disconnect to A-Connect */
+static void langwell_otg_HABA(int on)
+{
+	u32	val;
+
+	otg_dbg("HABA -> %d\n", on);
+
+	val = readl(the_transceiver->regs + CI_OTGSC);
+	if (on)
+		writel((val & ~OTGSC_INTSTS_MASK) | OTGSC_HABA,
+				the_transceiver->regs + CI_OTGSC);
+	else
+		writel((val & ~OTGSC_INTSTS_MASK) & ~OTGSC_HABA,
+				the_transceiver->regs + CI_OTGSC);
+}
+
+static int langwell_otg_check_se0_srp(int on)
+{
+	u32 val;
+
+	int delay_time = TB_SE0_SRP * 10; /* step is 100us */
+
+	otg_dbg("check_se0_srp -> \n");
+
+	do {
+		udelay(100);
+		if (!delay_time--)
+			break;
+		val = readl(the_transceiver->regs + CI_PORTSC1);
+		val &= PORTSC_LS;
+	} while (!val);
+
+	otg_dbg("check_se0_srp <- \n");
+	return val;
+}
+
+/* The timeout callback function to set time out bit */
+static void set_tmout(unsigned long indicator)
+{
+	*(int *)indicator = 1;
+}
+
+void langwell_otg_nsf_msg(unsigned long indicator)
+{
+	switch (indicator) {
+	case 2:
+	case 4:
+	case 6:
+	case 7:
+		printk(KERN_ERR "OTG:NSF-%lu - deivce not responding\n",
+				indicator);
+		break;
+	case 3:
+		printk(KERN_ERR "OTG:NSF-%lu - deivce not supported\n",
+				indicator);
+		break;
+	default:
+		printk(KERN_ERR "Do not have this kind of NSF\n");
+		break;
+	}
+}
+
+/* Initialize timers */
+static void langwell_otg_init_timers(struct otg_hsm *hsm)
+{
+	/* HSM used timers */
+	a_wait_vrise_tmr = otg_timer_initializer(&set_tmout, TA_WAIT_VRISE,
+				(unsigned long)&hsm->a_wait_vrise_tmout);
+	a_aidl_bdis_tmr = otg_timer_initializer(&set_tmout, TA_AIDL_BDIS,
+				(unsigned long)&hsm->a_aidl_bdis_tmout);
+	b_se0_srp_tmr = otg_timer_initializer(&set_tmout, TB_SE0_SRP,
+				(unsigned long)&hsm->b_se0_srp);
+	b_srp_init_tmr = otg_timer_initializer(&set_tmout, TB_SRP_INIT,
+				(unsigned long)&hsm->b_srp_init_tmout);
+}
+
+/* Free timers */
+static void langwell_otg_free_timers(void)
+{
+	kfree(a_wait_vrise_tmr);
+	kfree(a_aidl_bdis_tmr);
+	kfree(b_se0_srp_tmr);
+	kfree(b_srp_init_tmr);
+}
+
+/* The timeout callback function to set time out bit */
+static void langwell_otg_timer_fn(unsigned long indicator)
+{
+	struct langwell_otg *langwell;
+
+	langwell = the_transceiver;
+
+	*(int *)indicator = 1;
+
+	otg_dbg("kernel timer - timeout\n");
+
+	queue_work(langwell->qwork, &langwell->work);
+}
+
+/* kernel timer used instead of HW based interrupt */
+static void langwell_otg_add_ktimer(enum langwell_otg_timer_type timers)
+{
+	struct langwell_otg *langwell;
+	unsigned long j = jiffies;
+	unsigned long data, time;
+
+	langwell = the_transceiver;
+
+	switch (timers) {
+	case TA_WAIT_VRISE_TMR:
+		langwell->hsm.a_wait_vrise_tmout = 0;
+		data = (unsigned long)&langwell->hsm.a_wait_vrise_tmout;
+		time = TA_WAIT_VRISE;
+		break;
+	case TA_WAIT_BCON_TMR:
+		langwell->hsm.a_wait_bcon_tmout = 0;
+		data = (unsigned long)&langwell->hsm.a_wait_bcon_tmout;
+		time = TA_WAIT_BCON;
+		break;
+	case TA_AIDL_BDIS_TMR:
+		langwell->hsm.a_aidl_bdis_tmout = 0;
+		data = (unsigned long)&langwell->hsm.a_aidl_bdis_tmout;
+		time = TA_AIDL_BDIS;
+		break;
+	case TB_ASE0_BRST_TMR:
+		langwell->hsm.b_ase0_brst_tmout = 0;
+		data = (unsigned long)&langwell->hsm.b_ase0_brst_tmout;
+		time = TB_ASE0_BRST;
+		break;
+	case TB_SRP_INIT_TMR:
+		langwell->hsm.b_srp_init_tmout = 0;
+		data = (unsigned long)&langwell->hsm.b_srp_init_tmout;
+		time = TB_SRP_INIT;
+		break;
+	case TB_SRP_FAIL_TMR:
+		langwell->hsm.b_srp_fail_tmout = 0;
+		data = (unsigned long)&langwell->hsm.b_srp_fail_tmout;
+		time = TB_SRP_FAIL;
+		break;
+	case TB_BUS_SUSPEND_TMR:
+		langwell->hsm.b_bus_suspend_tmout = 0;
+		data = (unsigned long)&langwell->hsm.b_bus_suspend_tmout;
+		time = TB_BUS_SUSPEND;
+		break;
+	default:
+		otg_dbg("OTG: unkown timer, can not enable such timer\n");
+		return;
+	}
+
+	langwell->hsm_timer.data = data;
+	langwell->hsm_timer.function = langwell_otg_timer_fn;
+	langwell->hsm_timer.expires = j + time * HZ / 1000; /* milliseconds */
+
+	add_timer(&langwell->hsm_timer);
+
+	otg_dbg("OTG: add timer successfully\n");
+}
+
+/* Add timer to timer list */
+static void langwell_otg_add_timer(void *gtimer)
+{
+	struct langwell_otg_timer *timer = (struct langwell_otg_timer *)gtimer;
+	struct langwell_otg_timer *tmp_timer;
+	u32	val32;
+
+	/* Check if the timer is already in the active list,
+	 * if so update timer count
+	 */
+	list_for_each_entry(tmp_timer, &active_timers, list)
+		if (tmp_timer == timer) {
+			timer->count = timer->expires;
+			return;
+		}
+	timer->count = timer->expires;
+
+	if (list_empty(&active_timers)) {
+		val32 = readl(the_transceiver->regs + CI_OTGSC);
+		writel(val32 | OTGSC_1MSE, the_transceiver->regs + CI_OTGSC);
+	}
+
+	list_add_tail(&timer->list, &active_timers);
+}
+
+/* Remove timer from the timer list; clear timeout status */
+static void langwell_otg_del_timer(void *gtimer)
+{
+	struct langwell_otg_timer *timer = (struct langwell_otg_timer *)gtimer;
+	struct langwell_otg_timer *tmp_timer, *del_tmp;
+	u32 val32;
+
+	list_for_each_entry_safe(tmp_timer, del_tmp, &active_timers, list)
+		if (tmp_timer == timer)
+			list_del(&timer->list);
+
+	if (list_empty(&active_timers)) {
+		val32 = readl(the_transceiver->regs + CI_OTGSC);
+		writel(val32 & ~OTGSC_1MSE, the_transceiver->regs + CI_OTGSC);
+	}
+}
+
+/* Reduce timer count by 1, and find timeout conditions.*/
+static int langwell_otg_tick_timer(u32 *int_sts)
+{
+	struct langwell_otg_timer *tmp_timer, *del_tmp;
+	int expired = 0;
+
+	list_for_each_entry_safe(tmp_timer, del_tmp, &active_timers, list) {
+		tmp_timer->count--;
+		/* check if timer expires */
+		if (!tmp_timer->count) {
+			list_del(&tmp_timer->list);
+			tmp_timer->function(tmp_timer->data);
+			expired = 1;
+		}
+	}
+
+	if (list_empty(&active_timers)) {
+		otg_dbg("tick timer: disable 1ms int\n");
+		*int_sts = *int_sts & ~OTGSC_1MSE;
+	}
+	return expired;
+}
+
+static void reset_otg(void)
+{
+	u32	val;
+	int	delay_time = 1000;
+
+	otg_dbg("reseting OTG controller ...\n");
+	val = readl(the_transceiver->regs + CI_USBCMD);
+	writel(val | USBCMD_RST, the_transceiver->regs + CI_USBCMD);
+	do {
+		udelay(100);
+		if (!delay_time--)
+			otg_dbg("reset timeout\n");
+		val = readl(the_transceiver->regs + CI_USBCMD);
+		val &= USBCMD_RST;
+	} while (val != 0);
+	otg_dbg("reset done.\n");
+}
+
+static void set_host_mode(void)
+{
+	u32 	val;
+
+	reset_otg();
+	val = readl(the_transceiver->regs + CI_USBMODE);
+	val = (val & (~USBMODE_CM)) | USBMODE_HOST;
+	writel(val, the_transceiver->regs + CI_USBMODE);
+}
+
+static void set_client_mode(void)
+{
+	u32 	val;
+
+	reset_otg();
+	val = readl(the_transceiver->regs + CI_USBMODE);
+	val = (val & (~USBMODE_CM)) | USBMODE_DEVICE;
+	writel(val, the_transceiver->regs + CI_USBMODE);
+}
+
+static void init_hsm(void)
+{
+	struct langwell_otg	*langwell = the_transceiver;
+	u32			val32;
+
+	/* read OTGSC after reset */
+	val32 = readl(langwell->regs + CI_OTGSC);
+	otg_dbg("%s: OTGSC init value = 0x%x\n", __func__, val32);
+
+	/* set init state */
+	if (val32 & OTGSC_ID) {
+		langwell->hsm.id = 1;
+		langwell->otg.default_a = 0;
+		set_client_mode();
+		langwell->otg.state = OTG_STATE_B_IDLE;
+		langwell_otg_drv_vbus(0);
+	} else {
+		langwell->hsm.id = 0;
+		langwell->otg.default_a = 1;
+		set_host_mode();
+		langwell->otg.state = OTG_STATE_A_IDLE;
+	}
+
+	/* set session indicator */
+	if (val32 & OTGSC_BSE)
+		langwell->hsm.b_sess_end = 1;
+	if (val32 & OTGSC_BSV)
+		langwell->hsm.b_sess_vld = 1;
+	if (val32 & OTGSC_ASV)
+		langwell->hsm.a_sess_vld = 1;
+	if (val32 & OTGSC_AVV)
+		langwell->hsm.a_vbus_vld = 1;
+
+	/* defautly power the bus */
+	langwell->hsm.a_bus_req = 1;
+	langwell->hsm.a_bus_drop = 0;
+	/* defautly don't request bus as B device */
+	langwell->hsm.b_bus_req = 0;
+	/* no system error */
+	langwell->hsm.a_clr_err = 0;
+
+	langwell_otg_phy_low_power_wait(1);
+}
+
+static void update_hsm(void)
+{
+	struct langwell_otg	*langwell = the_transceiver;
+	u32			val32;
+
+	/* read OTGSC */
+	val32 = readl(langwell->regs + CI_OTGSC);
+	otg_dbg("%s: OTGSC current value = 0x%x\n", __func__, val32);
+
+	langwell->hsm.id = !!(val32 & OTGSC_ID);
+	langwell->hsm.b_sess_end = !!(val32 & OTGSC_BSE);
+	langwell->hsm.b_sess_vld = !!(val32 & OTGSC_BSV);
+	langwell->hsm.a_sess_vld = !!(val32 & OTGSC_ASV);
+	langwell->hsm.a_vbus_vld = !!(val32 & OTGSC_AVV);
+}
+
+static irqreturn_t otg_dummy_irq(int irq, void *_dev)
+{
+	void __iomem	*reg_base = _dev;
+	u32	val;
+	u32	int_mask = 0;
+
+	val = readl(reg_base + CI_USBMODE);
+	if ((val & USBMODE_CM) != USBMODE_DEVICE)
+		return IRQ_NONE;
+
+	val = readl(reg_base + CI_USBSTS);
+	int_mask = val & INTR_DUMMY_MASK;
+
+	if (int_mask == 0)
+		return IRQ_NONE;
+
+	/* clear hsm.b_conn here since host driver can't detect it
+	*  otg_dummy_irq called means B-disconnect happened.
+	*/
+	if (the_transceiver->hsm.b_conn) {
+		the_transceiver->hsm.b_conn = 0;
+		if (spin_trylock(&the_transceiver->wq_lock)) {
+			queue_work(the_transceiver->qwork,
+				&the_transceiver->work);
+			spin_unlock(&the_transceiver->wq_lock);
+		}
+	}
+	/* Clear interrupts */
+	writel(int_mask, reg_base + CI_USBSTS);
+	return IRQ_HANDLED;
+}
+
+static irqreturn_t otg_irq(int irq, void *_dev)
+{
+	struct	langwell_otg *langwell = _dev;
+	u32	int_sts, int_en;
+	u32	int_mask = 0;
+	int	flag = 0;
+
+	int_sts = readl(langwell->regs + CI_OTGSC);
+	int_en = (int_sts & OTGSC_INTEN_MASK) >> 8;
+	int_mask = int_sts & int_en;
+	if (int_mask == 0)
+		return IRQ_NONE;
+
+	if (int_mask & OTGSC_IDIS) {
+		otg_dbg("%s: id change int\n", __func__);
+		langwell->hsm.id = (int_sts & OTGSC_ID) ? 1 : 0;
+		flag = 1;
+	}
+	if (int_mask & OTGSC_DPIS) {
+		otg_dbg("%s: data pulse int\n", __func__);
+		langwell->hsm.a_srp_det = (int_sts & OTGSC_DPS) ? 1 : 0;
+		flag = 1;
+	}
+	if (int_mask & OTGSC_BSEIS) {
+		otg_dbg("%s: b session end int\n", __func__);
+		langwell->hsm.b_sess_end = (int_sts & OTGSC_BSE) ? 1 : 0;
+		flag = 1;
+	}
+	if (int_mask & OTGSC_BSVIS) {
+		otg_dbg("%s: b session valid int\n", __func__);
+		langwell->hsm.b_sess_vld = (int_sts & OTGSC_BSV) ? 1 : 0;
+		flag = 1;
+	}
+	if (int_mask & OTGSC_ASVIS) {
+		otg_dbg("%s: a session valid int\n", __func__);
+		langwell->hsm.a_sess_vld = (int_sts & OTGSC_ASV) ? 1 : 0;
+		flag = 1;
+	}
+	if (int_mask & OTGSC_AVVIS) {
+		otg_dbg("%s: a vbus valid int\n", __func__);
+		langwell->hsm.a_vbus_vld = (int_sts & OTGSC_AVV) ? 1 : 0;
+		flag = 1;
+	}
+
+	if (int_mask & OTGSC_1MSS) {
+		/* need to schedule otg_work if any timer is expired */
+		if (langwell_otg_tick_timer(&int_sts))
+			flag = 1;
+	}
+
+	writel((int_sts & ~OTGSC_INTSTS_MASK) | int_mask,
+			langwell->regs + CI_OTGSC);
+	if (flag)
+		queue_work(langwell->qwork, &langwell->work);
+
+	return IRQ_HANDLED;
+}
+
+static void langwell_otg_work(struct work_struct *work)
+{
+	struct langwell_otg *langwell = container_of(work,
+					struct langwell_otg, work);
+	int	retval;
+
+	otg_dbg("%s: old state = %s\n", __func__,
+			state_string(langwell->otg.state));
+
+	switch (langwell->otg.state) {
+	case OTG_STATE_UNDEFINED:
+	case OTG_STATE_B_IDLE:
+		if (!langwell->hsm.id) {
+			langwell_otg_del_timer(b_srp_init_tmr);
+			del_timer_sync(&langwell->hsm_timer);
+			langwell->otg.default_a = 1;
+			langwell->hsm.a_srp_det = 0;
+			langwell_otg_chrg_vbus(0);
+			set_host_mode();
+			langwell_otg_phy_low_power(1);
+			langwell->otg.state = OTG_STATE_A_IDLE;
+			queue_work(langwell->qwork, &langwell->work);
+		} else if (langwell->hsm.b_srp_init_tmout) {
+			langwell->hsm.b_srp_init_tmout = 0;
+			printk(KERN_WARNING "USB OTG: SRP init timeout\n");
+		} else if (langwell->hsm.b_srp_fail_tmout) {
+			langwell->hsm.b_srp_fail_tmout = 0;
+			langwell->hsm.b_bus_req = 0;
+			langwell_otg_nsf_msg(6);
+		} else if (langwell->hsm.b_sess_vld) {
+			langwell_otg_del_timer(b_srp_init_tmr);
+			del_timer_sync(&langwell->hsm_timer);
+			langwell->hsm.b_sess_end = 0;
+			langwell->hsm.a_bus_suspend = 0;
+			langwell_otg_chrg_vbus(0);
+			if (langwell->client_ops) {
+				langwell->client_ops->resume(langwell->pdev);
+				langwell->otg.state = OTG_STATE_B_PERIPHERAL;
+			} else
+				otg_dbg("client driver not loaded.\n");
+
+		} else if (langwell->hsm.b_bus_req &&
+				(langwell->hsm.b_sess_end)) {
+			del_timer_sync(&langwell->hsm_timer);
+			/* workaround for b_se0_srp detection */
+			retval = langwell_otg_check_se0_srp(0);
+			if (retval) {
+				langwell->hsm.b_bus_req = 0;
+				otg_dbg("LS is not SE0, try again later\n");
+			} else {
+				/* clear the PHCD before start srp */
+				langwell_otg_phy_low_power(0);
+
+				/* Start SRP */
+				langwell_otg_add_timer(b_srp_init_tmr);
+				langwell_otg_start_srp(&langwell->otg);
+				langwell_otg_del_timer(b_srp_init_tmr);
+				langwell_otg_add_ktimer(TB_SRP_FAIL_TMR);
+
+				/* reset PHY low power mode here */
+				langwell_otg_phy_low_power_wait(1);
+			}
+		}
+		break;
+	case OTG_STATE_B_SRP_INIT:
+		if (!langwell->hsm.id) {
+			langwell->otg.default_a = 1;
+			langwell->hsm.a_srp_det = 0;
+			langwell_otg_drv_vbus(0);
+			langwell_otg_chrg_vbus(0);
+			set_host_mode();
+			langwell_otg_phy_low_power(1);
+			langwell->otg.state = OTG_STATE_A_IDLE;
+			queue_work(langwell->qwork, &langwell->work);
+		} else if (langwell->hsm.b_sess_vld) {
+			langwell_otg_chrg_vbus(0);
+			if (langwell->client_ops) {
+				langwell->client_ops->resume(langwell->pdev);
+				langwell->otg.state = OTG_STATE_B_PERIPHERAL;
+			} else
+				otg_dbg("client driver not loaded.\n");
+		}
+		break;
+	case OTG_STATE_B_PERIPHERAL:
+		if (!langwell->hsm.id) {
+			langwell->otg.default_a = 1;
+			langwell->hsm.a_srp_det = 0;
+
+			langwell_otg_chrg_vbus(0);
+
+			if (langwell->client_ops) {
+				langwell->client_ops->suspend(langwell->pdev,
+					PMSG_FREEZE);
+			} else
+				otg_dbg("client driver has been removed.\n");
+
+			set_host_mode();
+			langwell_otg_phy_low_power(1);
+			langwell->otg.state = OTG_STATE_A_IDLE;
+			queue_work(langwell->qwork, &langwell->work);
+		} else if (!langwell->hsm.b_sess_vld) {
+			langwell->hsm.b_hnp_enable = 0;
+
+			if (langwell->client_ops) {
+				langwell->client_ops->suspend(langwell->pdev,
+					PMSG_FREEZE);
+			} else
+				otg_dbg("client driver has been removed.\n");
+
+			langwell->otg.state = OTG_STATE_B_IDLE;
+		} else if (langwell->hsm.b_bus_req && langwell->hsm.b_hnp_enable
+			&& langwell->hsm.a_bus_suspend) {
+
+			if (langwell->client_ops) {
+				langwell->client_ops->suspend(langwell->pdev,
+					PMSG_FREEZE);
+			} else
+				otg_dbg("client driver has been removed.\n");
+
+			langwell_otg_HAAR(1);
+			langwell->hsm.a_conn = 0;
+
+			if (langwell->host_ops) {
+				langwell->host_ops->probe(langwell->pdev,
+					langwell->host_ops->id_table);
+				langwell->otg.state = OTG_STATE_B_WAIT_ACON;
+			} else
+				otg_dbg("host driver not loaded.\n");
+
+			langwell->hsm.a_bus_resume = 0;
+			langwell_otg_add_ktimer(TB_ASE0_BRST_TMR);
+		}
+		break;
+
+	case OTG_STATE_B_WAIT_ACON:
+		if (!langwell->hsm.id) {
+			/* delete hsm timer for a_wait_bcon_tmr */
+			del_timer_sync(&langwell->hsm_timer);
+
+			langwell->otg.default_a = 1;
+			langwell->hsm.a_srp_det = 0;
+
+			langwell_otg_chrg_vbus(0);
+
+			langwell_otg_HAAR(0);
+			if (langwell->host_ops)
+				langwell->host_ops->remove(langwell->pdev);
+			else
+				otg_dbg("host driver has been removed.\n");
+
+			set_host_mode();
+			langwell_otg_phy_low_power(1);
+			langwell->otg.state = OTG_STATE_A_IDLE;
+			queue_work(langwell->qwork, &langwell->work);
+		} else if (!langwell->hsm.b_sess_vld) {
+			/* delete hsm timer for a_wait_bcon_tmr */
+			del_timer_sync(&langwell->hsm_timer);
+
+			langwell->hsm.b_hnp_enable = 0;
+			langwell->hsm.b_bus_req = 0;
+			langwell_otg_chrg_vbus(0);
+			langwell_otg_HAAR(0);
+
+			if (langwell->host_ops)
+				langwell->host_ops->remove(langwell->pdev);
+			else
+				otg_dbg("host driver has been removed.\n");
+
+			set_client_mode();
+			langwell_otg_phy_low_power(1);
+			langwell->otg.state = OTG_STATE_B_IDLE;
+		} else if (langwell->hsm.a_conn) {
+			/* delete hsm timer for a_wait_bcon_tmr */
+			del_timer_sync(&langwell->hsm_timer);
+
+			langwell_otg_HAAR(0);
+			langwell->otg.state = OTG_STATE_B_HOST;
+			queue_work(langwell->qwork, &langwell->work);
+		} else if (langwell->hsm.a_bus_resume ||
+				langwell->hsm.b_ase0_brst_tmout) {
+			/* delete hsm timer for a_wait_bcon_tmr */
+			del_timer_sync(&langwell->hsm_timer);
+
+			langwell_otg_HAAR(0);
+			langwell_otg_nsf_msg(7);
+
+			if (langwell->host_ops)
+				langwell->host_ops->remove(langwell->pdev);
+			else
+				otg_dbg("host driver has been removed.\n");
+
+			langwell->hsm.a_bus_suspend = 0;
+			langwell->hsm.b_bus_req = 0;
+
+			if (langwell->client_ops)
+				langwell->client_ops->resume(langwell->pdev);
+			else
+				otg_dbg("client driver not loaded.\n");
+
+			langwell->otg.state = OTG_STATE_B_PERIPHERAL;
+		}
+		break;
+
+	case OTG_STATE_B_HOST:
+		if (!langwell->hsm.id) {
+			langwell->otg.default_a = 1;
+			langwell->hsm.a_srp_det = 0;
+
+			langwell_otg_chrg_vbus(0);
+			if (langwell->host_ops)
+				langwell->host_ops->remove(langwell->pdev);
+			else
+				otg_dbg("host driver has been removed.\n");
+
+			set_host_mode();
+			langwell_otg_phy_low_power(1);
+			langwell->otg.state = OTG_STATE_A_IDLE;
+			queue_work(langwell->qwork, &langwell->work);
+		} else if (!langwell->hsm.b_sess_vld) {
+			langwell->hsm.b_hnp_enable = 0;
+			langwell->hsm.b_bus_req = 0;
+			langwell_otg_chrg_vbus(0);
+			if (langwell->host_ops)
+				langwell->host_ops->remove(langwell->pdev);
+			else
+				otg_dbg("host driver has been removed.\n");
+
+			set_client_mode();
+			langwell_otg_phy_low_power(1);
+			langwell->otg.state = OTG_STATE_B_IDLE;
+		} else if ((!langwell->hsm.b_bus_req) ||
+				(!langwell->hsm.a_conn)) {
+			langwell->hsm.b_bus_req = 0;
+			langwell_otg_loc_sof(0);
+			if (langwell->host_ops)
+				langwell->host_ops->remove(langwell->pdev);
+			else
+				otg_dbg("host driver has been removed.\n");
+
+			langwell->hsm.a_bus_suspend = 0;
+
+			if (langwell->client_ops)
+				langwell->client_ops->resume(langwell->pdev);
+			else
+				otg_dbg("client driver not loaded.\n");
+
+			langwell->otg.state = OTG_STATE_B_PERIPHERAL;
+		}
+		break;
+
+	case OTG_STATE_A_IDLE:
+		langwell->otg.default_a = 1;
+		if (langwell->hsm.id) {
+			langwell->otg.default_a = 0;
+			langwell->hsm.b_bus_req = 0;
+			langwell->hsm.vbus_srp_up = 0;
+			langwell_otg_chrg_vbus(0);
+			set_client_mode();
+			langwell_otg_phy_low_power(1);
+			langwell->otg.state = OTG_STATE_B_IDLE;
+			queue_work(langwell->qwork, &langwell->work);
+		} else if (!langwell->hsm.a_bus_drop &&
+			(langwell->hsm.a_srp_det || langwell->hsm.a_bus_req)) {
+			langwell_otg_phy_low_power(0);
+			langwell_otg_drv_vbus(1);
+			langwell->hsm.a_srp_det = 1;
+			langwell->hsm.vbus_srp_up = 0;
+			langwell->hsm.a_wait_vrise_tmout = 0;
+			langwell_otg_add_timer(a_wait_vrise_tmr);
+			langwell->otg.state = OTG_STATE_A_WAIT_VRISE;
+			queue_work(langwell->qwork, &langwell->work);
+		} else if (!langwell->hsm.a_bus_drop &&
+				langwell->hsm.a_sess_vld) {
+			langwell->hsm.vbus_srp_up = 1;
+		} else if (!langwell->hsm.a_sess_vld &&
+				langwell->hsm.vbus_srp_up) {
+			msleep(10);
+			langwell_otg_phy_low_power(0);
+			langwell_otg_drv_vbus(1);
+			langwell->hsm.a_srp_det = 1;
+			langwell->hsm.vbus_srp_up = 0;
+			langwell->hsm.a_wait_vrise_tmout = 0;
+			langwell_otg_add_timer(a_wait_vrise_tmr);
+			langwell->otg.state = OTG_STATE_A_WAIT_VRISE;
+			queue_work(langwell->qwork, &langwell->work);
+		} else if (!langwell->hsm.a_sess_vld &&
+				!langwell->hsm.vbus_srp_up) {
+			langwell_otg_phy_low_power(1);
+		}
+		break;
+	case OTG_STATE_A_WAIT_VRISE:
+		if (langwell->hsm.id) {
+			langwell_otg_del_timer(a_wait_vrise_tmr);
+			langwell->hsm.b_bus_req = 0;
+			langwell->otg.default_a = 0;
+			langwell_otg_drv_vbus(0);
+			set_client_mode();
+			langwell_otg_phy_low_power_wait(1);
+			langwell->otg.state = OTG_STATE_B_IDLE;
+		} else if (langwell->hsm.a_vbus_vld) {
+			langwell_otg_del_timer(a_wait_vrise_tmr);
+			if (langwell->host_ops)
+				langwell->host_ops->probe(langwell->pdev,
+						langwell->host_ops->id_table);
+			else {
+				otg_dbg("host driver not loaded.\n");
+				break;
+			}
+			langwell->hsm.b_conn = 0;
+			/* Replace HW timer with kernel timer */
+			langwell_otg_add_ktimer(TA_WAIT_BCON_TMR);
+			langwell->otg.state = OTG_STATE_A_WAIT_BCON;
+		} else if (langwell->hsm.a_wait_vrise_tmout) {
+			if (langwell->hsm.a_vbus_vld) {
+				if (langwell->host_ops)
+					langwell->host_ops->probe(
+						langwell->pdev,
+						langwell->host_ops->id_table);
+				else {
+					otg_dbg("host driver not loaded.\n");
+					break;
+				}
+				langwell->hsm.b_conn = 0;
+				/* change to kernel timer */
+				langwell_otg_add_ktimer(TA_WAIT_BCON_TMR);
+				langwell->otg.state = OTG_STATE_A_WAIT_BCON;
+			} else {
+				langwell_otg_drv_vbus(0);
+				langwell_otg_phy_low_power_wait(1);
+				langwell->otg.state = OTG_STATE_A_VBUS_ERR;
+			}
+		}
+		break;
+	case OTG_STATE_A_WAIT_BCON:
+		if (langwell->hsm.id) {
+			/* delete hsm timer for a_wait_bcon_tmr */
+			del_timer_sync(&langwell->hsm_timer);
+
+			langwell->otg.default_a = 0;
+			langwell->hsm.b_bus_req = 0;
+			if (langwell->host_ops)
+				langwell->host_ops->remove(langwell->pdev);
+			else
+				otg_dbg("host driver has been removed.\n");
+			langwell_otg_drv_vbus(0);
+			set_client_mode();
+			langwell_otg_phy_low_power_wait(1);
+			langwell->otg.state = OTG_STATE_B_IDLE;
+			queue_work(langwell->qwork, &langwell->work);
+		} else if (!langwell->hsm.a_vbus_vld) {
+			/* delete hsm timer for a_wait_bcon_tmr */
+			del_timer_sync(&langwell->hsm_timer);
+
+			if (langwell->host_ops)
+				langwell->host_ops->remove(langwell->pdev);
+			else
+				otg_dbg("host driver has been removed.\n");
+			langwell_otg_drv_vbus(0);
+			langwell_otg_phy_low_power_wait(1);
+			langwell->otg.state = OTG_STATE_A_VBUS_ERR;
+		} else if (langwell->hsm.a_bus_drop ||
+				(langwell->hsm.a_wait_bcon_tmout &&
+				!langwell->hsm.a_bus_req)) {
+			/* delete hsm timer for a_wait_bcon_tmr */
+			del_timer_sync(&langwell->hsm_timer);
+
+			if (langwell->host_ops)
+				langwell->host_ops->remove(langwell->pdev);
+			else
+				otg_dbg("host driver has been removed.\n");
+			langwell_otg_drv_vbus(0);
+			langwell->otg.state = OTG_STATE_A_WAIT_VFALL;
+		} else if (langwell->hsm.b_conn) {
+			/* delete hsm timer for a_wait_bcon_tmr */
+			del_timer_sync(&langwell->hsm_timer);
+
+			langwell->hsm.a_suspend_req = 0;
+			langwell->otg.state = OTG_STATE_A_HOST;
+			if (langwell->hsm.a_srp_det &&
+					!langwell->otg.host->b_hnp_enable) {
+				/* SRP capable peripheral-only device */
+				langwell->hsm.a_bus_req = 1;
+				langwell->hsm.a_srp_det = 0;
+			} else if (!langwell->hsm.a_bus_req &&
+					langwell->otg.host->b_hnp_enable) {
+				/* It is not safe enough to do a fast
+				 * transistion from A_WAIT_BCON to
+				 * A_SUSPEND */
+				msleep(10000);
+				if (langwell->hsm.a_bus_req)
+					break;
+
+				if (request_irq(langwell->pdev->irq,
+					otg_dummy_irq, IRQF_SHARED,
+					driver_name, langwell->regs) != 0) {
+					otg_dbg("request interrupt %d fail\n",
+					langwell->pdev->irq);
+				}
+
+				langwell_otg_HABA(1);
+				langwell->hsm.b_bus_resume = 0;
+				langwell->hsm.a_aidl_bdis_tmout = 0;
+				langwell_otg_add_timer(a_aidl_bdis_tmr);
+
+				langwell_otg_loc_sof(0);
+				/* clear PHCD to enable HW timer */
+				langwell_otg_phy_low_power(0);
+				langwell->otg.state = OTG_STATE_A_SUSPEND;
+			} else if (!langwell->hsm.a_bus_req &&
+				!langwell->otg.host->b_hnp_enable) {
+				struct pci_dev *pdev = langwell->pdev;
+				if (langwell->host_ops)
+					langwell->host_ops->remove(pdev);
+				else
+					otg_dbg("host driver removed.\n");
+				langwell_otg_drv_vbus(0);
+				langwell->otg.state = OTG_STATE_A_WAIT_VFALL;
+			}
+		}
+		break;
+	case OTG_STATE_A_HOST:
+		if (langwell->hsm.id) {
+			langwell->otg.default_a = 0;
+			langwell->hsm.b_bus_req = 0;
+			if (langwell->host_ops)
+				langwell->host_ops->remove(langwell->pdev);
+			else
+				otg_dbg("host driver has been removed.\n");
+			langwell_otg_drv_vbus(0);
+			set_client_mode();
+			langwell_otg_phy_low_power_wait(1);
+			langwell->otg.state = OTG_STATE_B_IDLE;
+			queue_work(langwell->qwork, &langwell->work);
+		} else if (langwell->hsm.a_bus_drop ||
+				(!langwell->otg.host->b_hnp_enable &&
+					!langwell->hsm.a_bus_req)) {
+			if (langwell->host_ops)
+				langwell->host_ops->remove(langwell->pdev);
+			else
+				otg_dbg("host driver has been removed.\n");
+			langwell_otg_drv_vbus(0);
+			langwell->otg.state = OTG_STATE_A_WAIT_VFALL;
+		} else if (!langwell->hsm.a_vbus_vld) {
+			if (langwell->host_ops)
+				langwell->host_ops->remove(langwell->pdev);
+			else
+				otg_dbg("host driver has been removed.\n");
+			langwell_otg_drv_vbus(0);
+			langwell_otg_phy_low_power_wait(1);
+			langwell->otg.state = OTG_STATE_A_VBUS_ERR;
+		} else if (langwell->otg.host->b_hnp_enable
+				&& !langwell->hsm.a_bus_req) {
+			/* Set HABA to enable hardware assistance to signal
+			 *  A-connect after receiver B-disconnect. Hardware
+			 *  will then set client mode and enable URE, SLE and
+			 *  PCE after the assistance. otg_dummy_irq is used to
+			 *  clean these ints when client driver is not resumed.
+			 */
+			if (request_irq(langwell->pdev->irq,
+				otg_dummy_irq, IRQF_SHARED, driver_name,
+				langwell->regs) != 0) {
+				otg_dbg("request interrupt %d failed\n",
+						langwell->pdev->irq);
+			}
+
+			/* set HABA */
+			langwell_otg_HABA(1);
+			langwell->hsm.b_bus_resume = 0;
+			langwell->hsm.a_aidl_bdis_tmout = 0;
+			langwell_otg_add_timer(a_aidl_bdis_tmr);
+			langwell_otg_loc_sof(0);
+			/* clear PHCD to enable HW timer */
+			langwell_otg_phy_low_power(0);
+			langwell->otg.state = OTG_STATE_A_SUSPEND;
+		} else if (!langwell->hsm.b_conn || !langwell->hsm.a_bus_req) {
+			langwell->hsm.a_wait_bcon_tmout = 0;
+			/* add kernel timer */
+			langwell_otg_add_ktimer(TA_WAIT_BCON_TMR);
+			langwell->otg.state = OTG_STATE_A_WAIT_BCON;
+		}
+		break;
+	case OTG_STATE_A_SUSPEND:
+		if (langwell->hsm.id) {
+			langwell_otg_del_timer(a_aidl_bdis_tmr);
+			langwell_otg_HABA(0);
+			free_irq(langwell->pdev->irq, langwell->regs);
+			langwell->otg.default_a = 0;
+			langwell->hsm.b_bus_req = 0;
+			if (langwell->host_ops)
+				langwell->host_ops->remove(langwell->pdev);
+			else
+				otg_dbg("host driver has been removed.\n");
+			langwell_otg_drv_vbus(0);
+			set_client_mode();
+			langwell_otg_phy_low_power(1);
+			langwell->otg.state = OTG_STATE_B_IDLE;
+			queue_work(langwell->qwork, &langwell->work);
+		} else if (langwell->hsm.a_bus_req ||
+				langwell->hsm.b_bus_resume) {
+			langwell_otg_del_timer(a_aidl_bdis_tmr);
+			langwell_otg_HABA(0);
+			free_irq(langwell->pdev->irq, langwell->regs);
+			langwell->hsm.a_suspend_req = 0;
+			langwell_otg_loc_sof(1);
+			langwell->otg.state = OTG_STATE_A_HOST;
+		} else if (langwell->hsm.a_aidl_bdis_tmout ||
+				langwell->hsm.a_bus_drop) {
+			langwell_otg_del_timer(a_aidl_bdis_tmr);
+			langwell_otg_HABA(0);
+			free_irq(langwell->pdev->irq, langwell->regs);
+			if (langwell->host_ops)
+				langwell->host_ops->remove(langwell->pdev);
+			else
+				otg_dbg("host driver has been removed.\n");
+			langwell_otg_drv_vbus(0);
+			langwell->otg.state = OTG_STATE_A_WAIT_VFALL;
+		} else if (!langwell->hsm.b_conn &&
+				langwell->otg.host->b_hnp_enable) {
+			langwell_otg_del_timer(a_aidl_bdis_tmr);
+			langwell_otg_HABA(0);
+			free_irq(langwell->pdev->irq, langwell->regs);
+
+			if (langwell->host_ops)
+				langwell->host_ops->remove(langwell->pdev);
+			else
+				otg_dbg("host driver has been removed.\n");
+
+			langwell->hsm.b_bus_suspend = 0;
+			langwell->hsm.b_bus_suspend_vld = 0;
+
+			/* msleep(200); */
+			if (langwell->client_ops)
+				langwell->client_ops->resume(langwell->pdev);
+			else
+				otg_dbg("client driver not loaded.\n");
+
+			langwell_otg_add_ktimer(TB_BUS_SUSPEND_TMR);
+			langwell->otg.state = OTG_STATE_A_PERIPHERAL;
+			break;
+		} else if (!langwell->hsm.a_vbus_vld) {
+			langwell_otg_del_timer(a_aidl_bdis_tmr);
+			langwell_otg_HABA(0);
+			free_irq(langwell->pdev->irq, langwell->regs);
+			if (langwell->host_ops)
+				langwell->host_ops->remove(langwell->pdev);
+			else
+				otg_dbg("host driver has been removed.\n");
+			langwell_otg_drv_vbus(0);
+			langwell_otg_phy_low_power_wait(1);
+			langwell->otg.state = OTG_STATE_A_VBUS_ERR;
+		}
+		break;
+	case OTG_STATE_A_PERIPHERAL:
+		if (langwell->hsm.id) {
+			/* delete hsm timer for b_bus_suspend_tmr */
+			del_timer_sync(&langwell->hsm_timer);
+			langwell->otg.default_a = 0;
+			langwell->hsm.b_bus_req = 0;
+			if (langwell->client_ops)
+				langwell->client_ops->suspend(langwell->pdev,
+					PMSG_FREEZE);
+			else
+				otg_dbg("client driver has been removed.\n");
+			langwell_otg_drv_vbus(0);
+			set_client_mode();
+			langwell_otg_phy_low_power_wait(1);
+			langwell->otg.state = OTG_STATE_B_IDLE;
+			queue_work(langwell->qwork, &langwell->work);
+		} else if (!langwell->hsm.a_vbus_vld) {
+			/* delete hsm timer for b_bus_suspend_tmr */
+			del_timer_sync(&langwell->hsm_timer);
+			if (langwell->client_ops)
+				langwell->client_ops->suspend(langwell->pdev,
+					PMSG_FREEZE);
+			else
+				otg_dbg("client driver has been removed.\n");
+			langwell_otg_drv_vbus(0);
+			langwell_otg_phy_low_power_wait(1);
+			langwell->otg.state = OTG_STATE_A_VBUS_ERR;
+		} else if (langwell->hsm.a_bus_drop) {
+			/* delete hsm timer for b_bus_suspend_tmr */
+			del_timer_sync(&langwell->hsm_timer);
+			if (langwell->client_ops)
+				langwell->client_ops->suspend(langwell->pdev,
+					PMSG_FREEZE);
+			else
+				otg_dbg("client driver has been removed.\n");
+			langwell_otg_drv_vbus(0);
+			langwell->otg.state = OTG_STATE_A_WAIT_VFALL;
+		} else if (langwell->hsm.b_bus_suspend) {
+			/* delete hsm timer for b_bus_suspend_tmr */
+			del_timer_sync(&langwell->hsm_timer);
+			if (langwell->client_ops)
+				langwell->client_ops->suspend(langwell->pdev,
+					PMSG_FREEZE);
+			else
+				otg_dbg("client driver has been removed.\n");
+			if (langwell->host_ops)
+				langwell->host_ops->probe(langwell->pdev,
+						langwell->host_ops->id_table);
+			else
+				otg_dbg("host driver not loaded.\n");
+			langwell_otg_add_ktimer(TA_WAIT_BCON_TMR);
+			langwell->otg.state = OTG_STATE_A_WAIT_BCON;
+		} else if (langwell->hsm.b_bus_suspend_tmout) {
+			u32	val;
+			val = readl(langwell->regs + CI_PORTSC1);
+			if (!(val & PORTSC_SUSP))
+				break;
+			if (langwell->client_ops)
+				langwell->client_ops->suspend(langwell->pdev,
+						PMSG_FREEZE);
+			else
+				otg_dbg("client driver has been removed.\n");
+			if (langwell->host_ops)
+				langwell->host_ops->probe(langwell->pdev,
+						langwell->host_ops->id_table);
+			else
+				otg_dbg("host driver not loaded.\n");
+			/* replaced with kernel timer */
+			langwell_otg_add_ktimer(TA_WAIT_BCON_TMR);
+			langwell->otg.state = OTG_STATE_A_WAIT_BCON;
+		}
+		break;
+	case OTG_STATE_A_VBUS_ERR:
+		if (langwell->hsm.id) {
+			langwell->otg.default_a = 0;
+			langwell->hsm.a_clr_err = 0;
+			langwell->hsm.a_srp_det = 0;
+			set_client_mode();
+			langwell_otg_phy_low_power(1);
+			langwell->otg.state = OTG_STATE_B_IDLE;
+			queue_work(langwell->qwork, &langwell->work);
+		} else if (langwell->hsm.a_clr_err) {
+			langwell->hsm.a_clr_err = 0;
+			langwell->hsm.a_srp_det = 0;
+			reset_otg();
+			init_hsm();
+			if (langwell->otg.state == OTG_STATE_A_IDLE)
+				queue_work(langwell->qwork, &langwell->work);
+		} else {
+			/* FIXME: Because FW will clear PHCD bit when any VBus
+			 *  event detected. Reset PHCD to 1 again */
+			langwell_otg_phy_low_power(1);
+		}
+		break;
+	case OTG_STATE_A_WAIT_VFALL:
+		if (langwell->hsm.id) {
+			langwell->otg.default_a = 0;
+			set_client_mode();
+			langwell_otg_phy_low_power(1);
+			langwell->otg.state = OTG_STATE_B_IDLE;
+			queue_work(langwell->qwork, &langwell->work);
+		} else if (langwell->hsm.a_bus_req) {
+			langwell_otg_drv_vbus(1);
+			langwell->hsm.a_wait_vrise_tmout = 0;
+			langwell_otg_add_timer(a_wait_vrise_tmr);
+			langwell->otg.state = OTG_STATE_A_WAIT_VRISE;
+		} else if (!langwell->hsm.a_sess_vld) {
+			langwell->hsm.a_srp_det = 0;
+			set_host_mode();
+			langwell_otg_phy_low_power(1);
+			langwell->otg.state = OTG_STATE_A_IDLE;
+		}
+		break;
+	default:
+		;
+	}
+
+	otg_dbg("%s: new state = %s\n", __func__,
+			state_string(langwell->otg.state));
+}
+
+	static ssize_t
+show_registers(struct device *_dev, struct device_attribute *attr, char *buf)
+{
+	struct langwell_otg *langwell;
+	char *next;
+	unsigned size;
+	unsigned t;
+
+	langwell = the_transceiver;
+	next = buf;
+	size = PAGE_SIZE;
+
+	t = scnprintf(next, size,
+		"\n"
+		"USBCMD = 0x%08x \n"
+		"USBSTS = 0x%08x \n"
+		"USBINTR = 0x%08x \n"
+		"ASYNCLISTADDR = 0x%08x \n"
+		"PORTSC1 = 0x%08x \n"
+		"HOSTPC1 = 0x%08x \n"
+		"OTGSC = 0x%08x \n"
+		"USBMODE = 0x%08x \n",
+		readl(langwell->regs + 0x30),
+		readl(langwell->regs + 0x34),
+		readl(langwell->regs + 0x38),
+		readl(langwell->regs + 0x48),
+		readl(langwell->regs + 0x74),
+		readl(langwell->regs + 0xb4),
+		readl(langwell->regs + 0xf4),
+		readl(langwell->regs + 0xf8)
+		);
+	size -= t;
+	next += t;
+
+	return PAGE_SIZE - size;
+}
+static DEVICE_ATTR(registers, S_IRUGO, show_registers, NULL);
+
+static ssize_t
+show_hsm(struct device *_dev, struct device_attribute *attr, char *buf)
+{
+	struct langwell_otg *langwell;
+	char *next;
+	unsigned size;
+	unsigned t;
+	enum usb_otg_state state;
+
+	langwell = the_transceiver;
+	next = buf;
+	size = PAGE_SIZE;
+	state = langwell->otg.state;
+
+	/* Add a_set_b_hnp_en */
+	if (state == OTG_STATE_A_HOST || state == OTG_STATE_A_SUSPEND)
+		langwell->hsm.a_set_b_hnp_en = langwell->otg.host->b_hnp_enable;
+	else
+		langwell->hsm.a_set_b_hnp_en = 0;
+
+	t = scnprintf(next, size,
+		"\n"
+		"current state = %s\n"
+		"a_bus_resume = \t%d\n"
+		"a_bus_suspend = \t%d\n"
+		"a_conn = \t%d\n"
+		"a_sess_vld = \t%d\n"
+		"a_srp_det = \t%d\n"
+		"a_vbus_vld = \t%d\n"
+		"b_bus_resume = \t%d\n"
+		"b_bus_suspend = \t%d\n"
+		"b_conn = \t%d\n"
+		"b_se0_srp = \t%d\n"
+		"b_sess_end = \t%d\n"
+		"b_sess_vld = \t%d\n"
+		"id = \t%d\n"
+		"a_set_b_hnp_en = \t%d\n"
+		"b_srp_done = \t%d\n"
+		"b_hnp_enable = \t%d\n"
+		"a_wait_vrise_tmout = \t%d\n"
+		"a_wait_bcon_tmout = \t%d\n"
+		"a_aidl_bdis_tmout = \t%d\n"
+		"b_ase0_brst_tmout = \t%d\n"
+		"a_bus_drop = \t%d\n"
+		"a_bus_req = \t%d\n"
+		"a_clr_err = \t%d\n"
+		"a_suspend_req = \t%d\n"
+		"b_bus_req = \t%d\n"
+		"b_bus_suspend_tmout = \t%d\n"
+		"b_bus_suspend_vld = \t%d\n",
+		state_string(langwell->otg.state),
+		langwell->hsm.a_bus_resume,
+		langwell->hsm.a_bus_suspend,
+		langwell->hsm.a_conn,
+		langwell->hsm.a_sess_vld,
+		langwell->hsm.a_srp_det,
+		langwell->hsm.a_vbus_vld,
+		langwell->hsm.b_bus_resume,
+		langwell->hsm.b_bus_suspend,
+		langwell->hsm.b_conn,
+		langwell->hsm.b_se0_srp,
+		langwell->hsm.b_sess_end,
+		langwell->hsm.b_sess_vld,
+		langwell->hsm.id,
+		langwell->hsm.a_set_b_hnp_en,
+		langwell->hsm.b_srp_done,
+		langwell->hsm.b_hnp_enable,
+		langwell->hsm.a_wait_vrise_tmout,
+		langwell->hsm.a_wait_bcon_tmout,
+		langwell->hsm.a_aidl_bdis_tmout,
+		langwell->hsm.b_ase0_brst_tmout,
+		langwell->hsm.a_bus_drop,
+		langwell->hsm.a_bus_req,
+		langwell->hsm.a_clr_err,
+		langwell->hsm.a_suspend_req,
+		langwell->hsm.b_bus_req,
+		langwell->hsm.b_bus_suspend_tmout,
+		langwell->hsm.b_bus_suspend_vld
+		);
+	size -= t;
+	next += t;
+
+	return PAGE_SIZE - size;
+}
+static DEVICE_ATTR(hsm, S_IRUGO, show_hsm, NULL);
+
+static ssize_t
+get_a_bus_req(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct langwell_otg *langwell;
+	char *next;
+	unsigned size;
+	unsigned t;
+
+	langwell =  the_transceiver;
+	next = buf;
+	size = PAGE_SIZE;
+
+	t = scnprintf(next, size, "%d", langwell->hsm.a_bus_req);
+	size -= t;
+	next += t;
+
+	return PAGE_SIZE - size;
+}
+
+static ssize_t
+set_a_bus_req(struct device *dev, struct device_attribute *attr,
+		const char *buf, size_t count)
+{
+	struct langwell_otg *langwell;
+	langwell = the_transceiver;
+	if (!langwell->otg.default_a)
+		return -1;
+	if (count > 2)
+		return -1;
+
+	if (buf[0] == '0') {
+		langwell->hsm.a_bus_req = 0;
+		otg_dbg("a_bus_req = 0\n");
+	} else if (buf[0] == '1') {
+		/* If a_bus_drop is TRUE, a_bus_req can't be set */
+		if (langwell->hsm.a_bus_drop)
+			return -1;
+		langwell->hsm.a_bus_req = 1;
+		otg_dbg("a_bus_req = 1\n");
+	}
+
+	langwell_update_transceiver();
+
+	return count;
+}
+static DEVICE_ATTR(a_bus_req, S_IRUGO | S_IWUGO, get_a_bus_req, set_a_bus_req);
+
+static ssize_t
+get_a_bus_drop(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct langwell_otg *langwell;
+	char *next;
+	unsigned size;
+	unsigned t;
+
+	langwell =  the_transceiver;
+	next = buf;
+	size = PAGE_SIZE;
+
+	t = scnprintf(next, size, "%d", langwell->hsm.a_bus_drop);
+	size -= t;
+	next += t;
+
+	return PAGE_SIZE - size;
+}
+
+static ssize_t
+set_a_bus_drop(struct device *dev, struct device_attribute *attr,
+		const char *buf, size_t count)
+{
+	struct langwell_otg *langwell;
+	langwell = the_transceiver;
+	if (!langwell->otg.default_a)
+		return -1;
+	if (count > 2)
+		return -1;
+
+	if (buf[0] == '0') {
+		langwell->hsm.a_bus_drop = 0;
+		otg_dbg("a_bus_drop = 0\n");
+	} else if (buf[0] == '1') {
+		langwell->hsm.a_bus_drop = 1;
+		langwell->hsm.a_bus_req = 0;
+		otg_dbg("a_bus_drop = 1, then a_bus_req = 0\n");
+	}
+
+	langwell_update_transceiver();
+
+	return count;
+}
+static DEVICE_ATTR(a_bus_drop, S_IRUGO | S_IWUGO,
+	get_a_bus_drop, set_a_bus_drop);
+
+static ssize_t
+get_b_bus_req(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct langwell_otg *langwell;
+	char *next;
+	unsigned size;
+	unsigned t;
+
+	langwell =  the_transceiver;
+	next = buf;
+	size = PAGE_SIZE;
+
+	t = scnprintf(next, size, "%d", langwell->hsm.b_bus_req);
+	size -= t;
+	next += t;
+
+	return PAGE_SIZE - size;
+}
+
+static ssize_t
+set_b_bus_req(struct device *dev, struct device_attribute *attr,
+		const char *buf, size_t count)
+{
+	struct langwell_otg *langwell;
+	langwell = the_transceiver;
+
+	if (langwell->otg.default_a)
+		return -1;
+
+	if (count > 2)
+		return -1;
+
+	if (buf[0] == '0') {
+		langwell->hsm.b_bus_req = 0;
+		otg_dbg("b_bus_req = 0\n");
+	} else if (buf[0] == '1') {
+		langwell->hsm.b_bus_req = 1;
+		otg_dbg("b_bus_req = 1\n");
+	}
+
+	langwell_update_transceiver();
+
+	return count;
+}
+static DEVICE_ATTR(b_bus_req, S_IRUGO | S_IWUGO, get_b_bus_req, set_b_bus_req);
+
+static ssize_t
+set_a_clr_err(struct device *dev, struct device_attribute *attr,
+		const char *buf, size_t count)
+{
+	struct langwell_otg *langwell;
+	langwell = the_transceiver;
+
+	if (!langwell->otg.default_a)
+		return -1;
+	if (count > 2)
+		return -1;
+
+	if (buf[0] == '1') {
+		langwell->hsm.a_clr_err = 1;
+		otg_dbg("a_clr_err = 1\n");
+	}
+
+	langwell_update_transceiver();
+
+	return count;
+}
+static DEVICE_ATTR(a_clr_err, S_IWUGO, NULL, set_a_clr_err);
+
+static struct attribute *inputs_attrs[] = {
+	&dev_attr_a_bus_req.attr,
+	&dev_attr_a_bus_drop.attr,
+	&dev_attr_b_bus_req.attr,
+	&dev_attr_a_clr_err.attr,
+	NULL,
+};
+
+static struct attribute_group debug_dev_attr_group = {
+	.name = "inputs",
+	.attrs = inputs_attrs,
+};
+
+int langwell_register_host(struct pci_driver *host_driver)
+{
+	int	ret = 0;
+
+	the_transceiver->host_ops = host_driver;
+	queue_work(the_transceiver->qwork, &the_transceiver->work);
+	otg_dbg("host controller driver is registered\n");
+
+	return ret;
+}
+EXPORT_SYMBOL(langwell_register_host);
+
+void langwell_unregister_host(struct pci_driver *host_driver)
+{
+	if (the_transceiver->host_ops)
+		the_transceiver->host_ops->remove(the_transceiver->pdev);
+	the_transceiver->host_ops = NULL;
+	the_transceiver->hsm.a_bus_drop = 1;
+	queue_work(the_transceiver->qwork, &the_transceiver->work);
+	otg_dbg("host controller driver is unregistered\n");
+}
+EXPORT_SYMBOL(langwell_unregister_host);
+
+int langwell_register_peripheral(struct pci_driver *client_driver)
+{
+	int	ret = 0;
+
+	if (client_driver)
+		ret = client_driver->probe(the_transceiver->pdev,
+				client_driver->id_table);
+	if (!ret) {
+		the_transceiver->client_ops = client_driver;
+		queue_work(the_transceiver->qwork, &the_transceiver->work);
+		otg_dbg("client controller driver is registered\n");
+	}
+
+	return ret;
+}
+EXPORT_SYMBOL(langwell_register_peripheral);
+
+void langwell_unregister_peripheral(struct pci_driver *client_driver)
+{
+	if (the_transceiver->client_ops)
+		the_transceiver->client_ops->remove(the_transceiver->pdev);
+	the_transceiver->client_ops = NULL;
+	the_transceiver->hsm.b_bus_req = 0;
+	queue_work(the_transceiver->qwork, &the_transceiver->work);
+	otg_dbg("client controller driver is unregistered\n");
+}
+EXPORT_SYMBOL(langwell_unregister_peripheral);
+
+static int langwell_otg_probe(struct pci_dev *pdev,
+		const struct pci_device_id *id)
+{
+	unsigned long		resource, len;
+	void __iomem 		*base = NULL;
+	int			retval;
+	u32			val32;
+	struct langwell_otg	*langwell;
+	char			qname[] = "langwell_otg_queue";
+
+	retval = 0;
+	otg_dbg("\notg controller is detected.\n");
+	if (pci_enable_device(pdev) < 0) {
+		retval = -ENODEV;
+		goto done;
+	}
+
+	langwell = kzalloc(sizeof *langwell, GFP_KERNEL);
+	if (langwell == NULL) {
+		retval = -ENOMEM;
+		goto done;
+	}
+	the_transceiver = langwell;
+
+	/* control register: BAR 0 */
+	resource = pci_resource_start(pdev, 0);
+	len = pci_resource_len(pdev, 0);
+	if (!request_mem_region(resource, len, driver_name)) {
+		retval = -EBUSY;
+		goto err;
+	}
+	langwell->region = 1;
+
+	base = ioremap_nocache(resource, len);
+	if (base == NULL) {
+		retval = -EFAULT;
+		goto err;
+	}
+	langwell->regs = base;
+
+	if (!request_mem_region(USBCFG_ADDR, USBCFG_LEN, driver_name)) {
+		retval = -EBUSY;
+		goto err;
+	}
+	langwell->cfg_region = 1;
+
+	/* For the SCCB.USBCFG register */
+	base = ioremap_nocache(USBCFG_ADDR, USBCFG_LEN);
+	if (base == NULL) {
+		retval = -EFAULT;
+		goto err;
+	}
+	langwell->usbcfg = base;
+
+	if (!pdev->irq) {
+		otg_dbg("No IRQ.\n");
+		retval = -ENODEV;
+		goto err;
+	}
+
+	langwell->qwork = create_singlethread_workqueue(qname);
+	if (!langwell->qwork) {
+		otg_dbg("cannot create workqueue %s\n", qname);
+		retval = -ENOMEM;
+		goto err;
+	}
+	INIT_WORK(&langwell->work, langwell_otg_work);
+
+	/* OTG common part */
+	langwell->pdev = pdev;
+	langwell->otg.dev = &pdev->dev;
+	langwell->otg.label = driver_name;
+	langwell->otg.set_host = langwell_otg_set_host;
+	langwell->otg.set_peripheral = langwell_otg_set_peripheral;
+	langwell->otg.set_power = langwell_otg_set_power;
+	langwell->otg.start_srp = langwell_otg_start_srp;
+	langwell->otg.state = OTG_STATE_UNDEFINED;
+	if (otg_set_transceiver(&langwell->otg)) {
+		otg_dbg("can't set transceiver\n");
+		retval = -EBUSY;
+		goto err;
+	}
+
+	reset_otg();
+	init_hsm();
+
+	spin_lock_init(&langwell->lock);
+	spin_lock_init(&langwell->wq_lock);
+	INIT_LIST_HEAD(&active_timers);
+	langwell_otg_init_timers(&langwell->hsm);
+	init_timer(&langwell->hsm_timer);
+
+	if (request_irq(pdev->irq, otg_irq, IRQF_SHARED,
+				driver_name, langwell) != 0) {
+		otg_dbg("request interrupt %d failed\n", pdev->irq);
+		retval = -EBUSY;
+		goto err;
+	}
+
+	/* enable OTGSC int */
+	val32 = OTGSC_DPIE | OTGSC_BSEIE | OTGSC_BSVIE |
+		OTGSC_ASVIE | OTGSC_AVVIE | OTGSC_IDIE | OTGSC_IDPU;
+	writel(val32, langwell->regs + CI_OTGSC);
+
+	retval = device_create_file(&pdev->dev, &dev_attr_registers);
+	if (retval < 0) {
+		otg_dbg("Can't register sysfs attribute: %d\n", retval);
+		goto err;
+	}
+
+	retval = device_create_file(&pdev->dev, &dev_attr_hsm);
+	if (retval < 0) {
+		otg_dbg("Can't hsm sysfs attribute: %d\n", retval);
+		goto err;
+	}
+
+	retval = sysfs_create_group(&pdev->dev.kobj, &debug_dev_attr_group);
+	if (retval < 0) {
+		otg_dbg("Can't register sysfs attr group: %d\n", retval);
+		goto err;
+	}
+
+	if (langwell->otg.state == OTG_STATE_A_IDLE)
+		queue_work(langwell->qwork, &langwell->work);
+
+	return 0;
+
+err:
+	if (the_transceiver)
+		langwell_otg_remove(pdev);
+done:
+	return retval;
+}
+
+static void langwell_otg_remove(struct pci_dev *pdev)
+{
+	struct langwell_otg *langwell;
+
+	langwell = the_transceiver;
+
+	if (langwell->qwork) {
+		flush_workqueue(langwell->qwork);
+		destroy_workqueue(langwell->qwork);
+	}
+	langwell_otg_free_timers();
+
+	/* disable OTGSC interrupt as OTGSC doesn't change in reset */
+	writel(0, langwell->regs + CI_OTGSC);
+
+	if (pdev->irq)
+		free_irq(pdev->irq, langwell);
+	if (langwell->usbcfg)
+		iounmap(langwell->usbcfg);
+	if (langwell->cfg_region)
+		release_mem_region(USBCFG_ADDR, USBCFG_LEN);
+	if (langwell->regs)
+		iounmap(langwell->regs);
+	if (langwell->region)
+		release_mem_region(pci_resource_start(pdev, 0),
+				pci_resource_len(pdev, 0));
+
+	otg_set_transceiver(NULL);
+	pci_disable_device(pdev);
+	sysfs_remove_group(&pdev->dev.kobj, &debug_dev_attr_group);
+	device_remove_file(&pdev->dev, &dev_attr_hsm);
+	device_remove_file(&pdev->dev, &dev_attr_registers);
+	kfree(langwell);
+	langwell = NULL;
+}
+
+static void transceiver_suspend(struct pci_dev *pdev)
+{
+	pci_save_state(pdev);
+	pci_set_power_state(pdev, PCI_D3hot);
+	langwell_otg_phy_low_power(1);
+}
+
+static int langwell_otg_suspend(struct pci_dev *pdev, pm_message_t message)
+{
+	struct langwell_otg 	*langwell;
+	struct pci_driver	*ops;
+	int			ret = 0;
+
+	langwell = the_transceiver;
+
+	/* Disbale OTG interrupts */
+	langwell_otg_intr(0);
+
+	if (pdev->irq)
+		free_irq(pdev->irq, langwell);
+
+	/* Prevent more otg_work */
+	flush_workqueue(langwell->qwork);
+	destroy_workqueue(langwell->qwork);
+	langwell->qwork = NULL;
+
+	/* start actions */
+	switch (langwell->otg.state) {
+	case OTG_STATE_A_WAIT_VFALL:
+		langwell->otg.state = OTG_STATE_A_IDLE;
+	case OTG_STATE_A_IDLE:
+	case OTG_STATE_A_VBUS_ERR:
+	case OTG_STATE_B_IDLE:
+		transceiver_suspend(pdev);
+		break;
+	case OTG_STATE_A_WAIT_VRISE:
+		langwell_otg_del_timer(a_wait_vrise_tmr);
+		langwell->hsm.a_srp_det = 0;
+		langwell_otg_drv_vbus(0);
+		langwell->otg.state = OTG_STATE_A_IDLE;
+		transceiver_suspend(pdev);
+		break;
+	case OTG_STATE_A_WAIT_BCON:
+		del_timer_sync(&langwell->hsm_timer);
+		ops = langwell->host_ops;
+
+		switch (message.event) {
+		case PM_EVENT_SUSPEND:
+			if (ops && ops->driver.pm && ops->driver.pm->suspend)
+				ret = ops->driver.pm->suspend(&pdev->dev);
+			break;
+		case PM_EVENT_FREEZE:
+			if (ops && ops->driver.pm && ops->driver.pm->freeze)
+				ret = ops->driver.pm->freeze(&pdev->dev);
+			break;
+		case PM_EVENT_HIBERNATE:
+			if (ops && ops->driver.pm && ops->driver.pm->poweroff)
+				ret = ops->driver.pm->poweroff(&pdev->dev);
+			break;
+		default:
+			otg_dbg("not suspend/freeze/hibernate pm event\n");
+			ret = -EINVAL;
+			break;
+		}
+
+		if (ret) {
+			otg_dbg("pm suspend function error = %d\n", ret);
+			/* restart timer */
+			langwell_otg_add_ktimer(TA_WAIT_BCON_TMR);
+			goto error;
+		}
+
+		if (ops && ops->remove)
+			ops->remove(pdev);
+		else
+			otg_dbg("host driver has been removed.\n");
+
+		langwell->hsm.a_srp_det = 0;
+		langwell_otg_drv_vbus(0);
+		langwell->otg.state = OTG_STATE_A_IDLE;
+		transceiver_suspend(pdev);
+		break;
+	case OTG_STATE_A_HOST:
+		ops = langwell->host_ops;
+
+		switch (message.event) {
+		case PM_EVENT_SUSPEND:
+			if (ops && ops->driver.pm && ops->driver.pm->suspend)
+				ret = ops->driver.pm->suspend(&pdev->dev);
+			break;
+		case PM_EVENT_FREEZE:
+			if (ops && ops->driver.pm && ops->driver.pm->freeze)
+				ret = ops->driver.pm->freeze(&pdev->dev);
+			break;
+		case PM_EVENT_HIBERNATE:
+			if (ops && ops->driver.pm && ops->driver.pm->poweroff)
+				ret = ops->driver.pm->poweroff(&pdev->dev);
+			break;
+		default:
+			otg_dbg("not suspend/freeze/hibernate pm event\n");
+			ret = -EINVAL;
+			break;
+		}
+
+		if (ret) {
+			otg_dbg("pm suspend function error = %d\n", ret);
+			goto error;
+		}
+
+		if (ops && ops->remove)
+			ops->remove(pdev);
+		else
+			otg_dbg("host driver has been removed.\n");
+
+		langwell->hsm.a_srp_det = 0;
+		langwell_otg_drv_vbus(0);
+		langwell->otg.state = OTG_STATE_A_IDLE;
+		transceiver_suspend(pdev);
+		break;
+	case OTG_STATE_A_SUSPEND:
+		langwell_otg_del_timer(a_aidl_bdis_tmr);
+		langwell_otg_HABA(0);
+		if (langwell->host_ops && langwell->host_ops->remove)
+			langwell->host_ops->remove(pdev);
+		else
+			otg_dbg("host driver has been removed.\n");
+		langwell->hsm.a_srp_det = 0;
+		langwell_otg_drv_vbus(0);
+		langwell->otg.state = OTG_STATE_A_IDLE;
+		transceiver_suspend(pdev);
+		break;
+	case OTG_STATE_A_PERIPHERAL:
+		del_timer_sync(&langwell->hsm_timer);
+		if (langwell->client_ops && langwell->client_ops->suspend)
+			ret = langwell->client_ops->suspend(pdev, message);
+		else
+			otg_dbg("client driver has been removed.\n");
+
+		if (ret) {
+			otg_dbg("pm suspend function error = %d\n", ret);
+			goto error;
+		}
+
+		langwell_otg_drv_vbus(0);
+		langwell->hsm.a_srp_det = 0;
+		langwell->otg.state = OTG_STATE_A_IDLE;
+		transceiver_suspend(pdev);
+		break;
+	case OTG_STATE_B_HOST:
+		if (langwell->host_ops && langwell->host_ops->remove)
+			langwell->host_ops->remove(pdev);
+		else
+			otg_dbg("host driver has been removed.\n");
+		langwell->hsm.b_bus_req = 0;
+		langwell->otg.state = OTG_STATE_B_IDLE;
+		transceiver_suspend(pdev);
+		break;
+	case OTG_STATE_B_PERIPHERAL:
+		if (langwell->client_ops && langwell->client_ops->suspend)
+			ret = langwell->client_ops->suspend(pdev, message);
+		else
+			otg_dbg("client driver has been removed.\n");
+
+		if (ret) {
+			otg_dbg("pm suspend function error = %d\n", ret);
+			goto error;
+		}
+
+		langwell->otg.state = OTG_STATE_B_IDLE;
+		transceiver_suspend(pdev);
+		break;
+	case OTG_STATE_B_WAIT_ACON:
+		/* delete hsm timer for b_ase0_brst_tmr */
+		del_timer_sync(&langwell->hsm_timer);
+
+		langwell_otg_HAAR(0);
+		if (langwell->host_ops && langwell->host_ops->remove)
+			langwell->host_ops->remove(pdev);
+		else
+			otg_dbg("host driver has been removed.\n");
+		langwell->hsm.b_bus_req = 0;
+		langwell->otg.state = OTG_STATE_B_IDLE;
+		transceiver_suspend(pdev);
+		break;
+	default:
+		otg_dbg("error state before suspend\n ");
+		break;
+	}
+
+	return ret;
+error:
+	langwell->qwork = create_singlethread_workqueue("langwell_otg_queue");
+	if (!langwell->qwork) {
+		otg_dbg("cannot create workqueue langwell_otg_queue\n");
+		return -ENOMEM;
+	}
+
+	if (request_irq(pdev->irq, otg_irq, IRQF_SHARED,
+				driver_name, the_transceiver) != 0) {
+		otg_dbg("request interrupt %d failed\n", pdev->irq);
+		return -EBUSY;
+	}
+
+	/* enable OTG interrupts */
+	langwell_otg_intr(1);
+
+	return ret;
+}
+
+static void transceiver_resume(struct pci_dev *pdev)
+{
+	pci_restore_state(pdev);
+	pci_set_power_state(pdev, PCI_D0);
+}
+
+static int langwell_otg_resume(struct pci_dev *pdev)
+{
+	struct langwell_otg 	*langwell;
+	int 			ret = 0;
+
+	langwell = the_transceiver;
+
+	transceiver_resume(pdev);
+
+	langwell->qwork = create_singlethread_workqueue("langwell_otg_queue");
+	if (!langwell->qwork) {
+		otg_dbg("cannot create workqueue langwell_otg_queue\n");
+		ret = -ENOMEM;
+		goto error;
+	}
+
+	if (request_irq(pdev->irq, otg_irq, IRQF_SHARED,
+				driver_name, the_transceiver) != 0) {
+		otg_dbg("request interrupt %d failed\n", pdev->irq);
+		ret = -EBUSY;
+		goto error;
+	}
+
+	/* enable OTG interrupts */
+	langwell_otg_intr(1);
+
+	update_hsm();
+
+	langwell_update_transceiver();
+
+	return ret;
+error:
+	langwell_otg_intr(0);
+	transceiver_suspend(pdev);
+	return ret;
+}
+
+static int __init langwell_otg_init(void)
+{
+	return pci_register_driver(&otg_pci_driver);
+}
+module_init(langwell_otg_init);
+
+static void __exit langwell_otg_cleanup(void)
+{
+	pci_unregister_driver(&otg_pci_driver);
+}
+module_exit(langwell_otg_cleanup);
diff --git a/include/linux/usb/langwell_otg.h b/include/linux/usb/langwell_otg.h
new file mode 100644
index 0000000..cbb204b
--- /dev/null
+++ b/include/linux/usb/langwell_otg.h
@@ -0,0 +1,201 @@
+/*
+ * Intel Langwell USB OTG transceiver driver
+ * Copyright (C) 2008, Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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.,
+ * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#ifndef __LANGWELL_OTG_H__
+#define __LANGWELL_OTG_H__
+
+/* notify transceiver driver about OTG events */
+extern void langwell_update_transceiver(void);
+/* HCD register bus driver */
+extern int langwell_register_host(struct pci_driver *host_driver);
+/* HCD unregister bus driver */
+extern void langwell_unregister_host(struct pci_driver *host_driver);
+/* DCD register bus driver */
+extern int langwell_register_peripheral(struct pci_driver *client_driver);
+/* DCD unregister bus driver */
+extern void langwell_unregister_peripheral(struct pci_driver *client_driver);
+/* No silent failure, output warning message */
+extern void langwell_otg_nsf_msg(unsigned long message);
+
+#define CI_USBCMD		0x30
+#	define USBCMD_RST		BIT(1)
+#	define USBCMD_RS		BIT(0)
+#define CI_USBSTS		0x34
+#	define USBSTS_SLI		BIT(8)
+#	define USBSTS_URI		BIT(6)
+#	define USBSTS_PCI		BIT(2)
+#define CI_PORTSC1		0x74
+#	define PORTSC_PP		BIT(12)
+#	define PORTSC_LS		(BIT(11) | BIT(10))
+#	define PORTSC_SUSP		BIT(7)
+#	define PORTSC_CCS		BIT(0)
+#define CI_HOSTPC1		0xb4
+#	define HOSTPC1_PHCD		BIT(22)
+#define CI_OTGSC		0xf4
+#	define OTGSC_DPIE		BIT(30)
+#	define OTGSC_1MSE		BIT(29)
+#	define OTGSC_BSEIE		BIT(28)
+#	define OTGSC_BSVIE		BIT(27)
+#	define OTGSC_ASVIE		BIT(26)
+#	define OTGSC_AVVIE		BIT(25)
+#	define OTGSC_IDIE		BIT(24)
+#	define OTGSC_DPIS		BIT(22)
+#	define OTGSC_1MSS		BIT(21)
+#	define OTGSC_BSEIS		BIT(20)
+#	define OTGSC_BSVIS		BIT(19)
+#	define OTGSC_ASVIS		BIT(18)
+#	define OTGSC_AVVIS		BIT(17)
+#	define OTGSC_IDIS		BIT(16)
+#	define OTGSC_DPS		BIT(14)
+#	define OTGSC_1MST		BIT(13)
+#	define OTGSC_BSE		BIT(12)
+#	define OTGSC_BSV		BIT(11)
+#	define OTGSC_ASV		BIT(10)
+#	define OTGSC_AVV		BIT(9)
+#	define OTGSC_ID			BIT(8)
+#	define OTGSC_HABA		BIT(7)
+#	define OTGSC_HADP		BIT(6)
+#	define OTGSC_IDPU		BIT(5)
+#	define OTGSC_DP			BIT(4)
+#	define OTGSC_OT			BIT(3)
+#	define OTGSC_HAAR		BIT(2)
+#	define OTGSC_VC			BIT(1)
+#	define OTGSC_VD			BIT(0)
+#	define OTGSC_INTEN_MASK		(0x7f << 24)
+#	define OTGSC_INT_MASK		(0x5f << 24)
+#	define OTGSC_INTSTS_MASK	(0x7f << 16)
+#define CI_USBMODE		0xf8
+#	define USBMODE_CM		(BIT(1) | BIT(0))
+#	define USBMODE_IDLE		0
+#	define USBMODE_DEVICE		0x2
+#	define USBMODE_HOST		0x3
+#define USBCFG_ADDR			0xff10801c
+#define USBCFG_LEN			4
+#	define USBCFG_VBUSVAL		BIT(14)
+#	define USBCFG_AVALID		BIT(13)
+#	define USBCFG_BVALID		BIT(12)
+#	define USBCFG_SESEND		BIT(11)
+
+#define INTR_DUMMY_MASK (USBSTS_SLI | USBSTS_URI | USBSTS_PCI)
+
+struct otg_hsm {
+	/* Input */
+	int a_bus_resume;
+	int a_bus_suspend;
+	int a_conn;
+	int a_sess_vld;
+	int a_srp_det;
+	int a_vbus_vld;
+	int b_bus_resume;
+	int b_bus_suspend;
+	int b_conn;
+	int b_se0_srp;
+	int b_sess_end;
+	int b_sess_vld;
+	int id;
+
+	/* Internal variables */
+	int a_set_b_hnp_en;
+	int b_srp_done;
+	int b_hnp_enable;
+
+	/* Timeout indicator for timers */
+	int a_wait_vrise_tmout;
+	int a_wait_bcon_tmout;
+	int a_aidl_bdis_tmout;
+	int b_ase0_brst_tmout;
+	int b_bus_suspend_tmout;
+	int b_srp_init_tmout;
+	int b_srp_fail_tmout;
+
+	/* Informative variables */
+	int a_bus_drop;
+	int a_bus_req;
+	int a_clr_err;
+	int a_suspend_req;
+	int b_bus_req;
+
+	/* Output */
+	int drv_vbus;
+	int loc_conn;
+	int loc_sof;
+
+	/* Others */
+	int b_bus_suspend_vld;
+	int vbus_srp_up;
+};
+
+enum langwell_otg_timer_type {
+	TA_WAIT_VRISE_TMR,
+	TA_WAIT_BCON_TMR,
+	TA_AIDL_BDIS_TMR,
+	TB_ASE0_BRST_TMR,
+	TB_SE0_SRP_TMR,
+	TB_SRP_INIT_TMR,
+	TB_SRP_FAIL_TMR,
+	TB_BUS_SUSPEND_TMR
+};
+
+#define TA_WAIT_VRISE	100
+#define TA_WAIT_BCON	30000
+#define TA_AIDL_BDIS	15000
+#define TB_ASE0_BRST	5000
+#define TB_SE0_SRP	2
+#define TB_SRP_INIT	100
+#define TB_SRP_FAIL	5500
+#define TB_BUS_SUSPEND	500
+
+struct langwell_otg_timer {
+	unsigned long expires;	/* Number of count increase to timeout */
+	unsigned long count;	/* Tick counter */
+	void (*function)(unsigned long);	/* Timeout function */
+	unsigned long data;	/* Data passed to function */
+	struct list_head list;
+};
+
+struct langwell_otg {
+	struct otg_transceiver 	otg;
+	struct otg_hsm 		hsm;
+	void __iomem 		*regs;
+	void __iomem		*usbcfg;	/* SCCB USB config Reg */
+	unsigned 		region;
+	unsigned 		cfg_region;
+	struct pci_driver	*host_ops;
+	struct pci_driver	*client_ops;
+	struct pci_dev		*pdev;
+	struct work_struct 	work;
+	struct workqueue_struct	*qwork;
+	struct timer_list	hsm_timer;
+	spinlock_t 		lock;
+	spinlock_t 		wq_lock;
+};
+
+static inline struct langwell_otg *otg_to_langwell(struct otg_transceiver *otg)
+{
+	return container_of(otg, struct langwell_otg, otg);
+}
+
+#ifdef DEBUG
+#define otg_dbg(fmt, args...) \
+	printk(KERN_DEBUG fmt , ## args)
+#else
+#define otg_dbg(fmt, args...) \
+	do { } while (0)
+#endif /* DEBUG */
+#endif /* __LANGWELL_OTG_H__ */
diff --git a/include/linux/usb/langwell_udc.h b/include/linux/usb/langwell_udc.h
index c949178..fe2c698 100644
--- a/include/linux/usb/langwell_udc.h
+++ b/include/linux/usb/langwell_udc.h
@@ -306,5 +306,18 @@ struct langwell_op_regs {
 #define	EPCTRL_RXS	BIT(0)	/* RX endpoint STALL */
 } __attribute__ ((packed));
 
+
+/* export function declaration */
+
+/* gets the maximum power consumption */
+extern int langwell_udc_maxpower(int *mA);
+
+/* return errors of langwell_udc_maxpower() */
+#define	EOTGFAIL	1
+#define	EOTGNODEVICE	2
+#define	EOTGCHARGER	3
+#define	EOTGDISCONN	4
+#define	EOTGINVAL	5
+
 #endif /* __LANGWELL_UDC_H */
 
-- 
1.5.4.5

