generic-poky/meta-moblin/packages/linux/linux-moblin-2.6.33.2/linux-2.6.34-moorestown-usb...

8396 lines
228 KiB
Diff

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