aboutsummaryrefslogtreecommitdiffstats
path: root/meta-moblin/packages/linux/linux-moblin-2.6.33.2/linux-2.6.34-moorestown-usb-otg-and-still-image-driver.patch
diff options
context:
space:
mode:
authorJoshua Lock <josh@linux.intel.com>2010-05-18 14:51:13 +0100
committerJoshua Lock <josh@linux.intel.com>2010-05-19 12:20:16 +0100
commit5e8c7c54a9b297dae0081dd19a7bb94e23040a3d (patch)
tree948e3642c1bf426870b83c72c68c997dce66766c /meta-moblin/packages/linux/linux-moblin-2.6.33.2/linux-2.6.34-moorestown-usb-otg-and-still-image-driver.patch
parent5e07bc91281969d54896dd0a13e3d6134e432027 (diff)
downloadopenembedded-core-5e8c7c54a9b297dae0081dd19a7bb94e23040a3d.tar.gz
linux-moblin: add 2.6.33.2 kernel from MeeGo 1.0
Signed-off-by: Joshua Lock <josh@linux.intel.com>
Diffstat (limited to 'meta-moblin/packages/linux/linux-moblin-2.6.33.2/linux-2.6.34-moorestown-usb-otg-and-still-image-driver.patch')
-rw-r--r--meta-moblin/packages/linux/linux-moblin-2.6.33.2/linux-2.6.34-moorestown-usb-otg-and-still-image-driver.patch8395
1 files changed, 8395 insertions, 0 deletions
diff --git a/meta-moblin/packages/linux/linux-moblin-2.6.33.2/linux-2.6.34-moorestown-usb-otg-and-still-image-driver.patch b/meta-moblin/packages/linux/linux-moblin-2.6.33.2/linux-2.6.34-moorestown-usb-otg-and-still-image-driver.patch
new file mode 100644
index 0000000000..40eecf646b
--- /dev/null
+++ b/meta-moblin/packages/linux/linux-moblin-2.6.33.2/linux-2.6.34-moorestown-usb-otg-and-still-image-driver.patch
@@ -0,0 +1,8395 @@
+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
+