From 239a368d5715d8f5b7733f9400339c2350c49369 Mon Sep 17 00:00:00 2001 From: Saul Wold Date: Fri, 24 Sep 2010 15:36:24 -0700 Subject: netbook: Correct netbook build by moving netbook configuration from moblin to meta Signed-off-by: Saul Wold --- ...moorestown-usb-otg-and-still-image-driver.patch | 8395 ++++++++++++++++++++ 1 file changed, 8395 insertions(+) create mode 100644 meta/recipes-kernel/linux/linux-netbook-2.6.33.2/linux-2.6.34-moorestown-usb-otg-and-still-image-driver.patch (limited to 'meta/recipes-kernel/linux/linux-netbook-2.6.33.2/linux-2.6.34-moorestown-usb-otg-and-still-image-driver.patch') diff --git a/meta/recipes-kernel/linux/linux-netbook-2.6.33.2/linux-2.6.34-moorestown-usb-otg-and-still-image-driver.patch b/meta/recipes-kernel/linux/linux-netbook-2.6.33.2/linux-2.6.34-moorestown-usb-otg-and-still-image-driver.patch new file mode 100644 index 000000000..40eecf646 --- /dev/null +++ b/meta/recipes-kernel/linux/linux-netbook-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 +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 +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 ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++ ++#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 ; " ++ "Hang Yuan "); ++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, ¤t->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 ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include "../core/hcd.h" ++ ++#include ++ ++#define DRIVER_DESC "Intel Langwell USB OTG transceiver driver" ++#define DRIVER_VERSION "March 19, 2010" ++ ++MODULE_DESCRIPTION(DRIVER_DESC); ++MODULE_AUTHOR("Henry Yuan , Hao Wu "); ++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 + -- cgit v1.2.3