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