8396 lines
228 KiB
Diff
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, ¤t->blocked, &info);
|
|
+ if (!sig)
|
|
+ break;
|
|
+
|
|
+ if (sig != SIGUSR1) {
|
|
+ if (sti->state < STI_STATE_EXIT)
|
|
+ DBG(sti, "main thread exiting on signal\n");
|
|
+ raise_exception(sti, STI_STATE_EXIT);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /* cancel all the pending transfers */
|
|
+ if (sti->intreq_busy)
|
|
+ usb_ep_dequeue(sti->intr_in, sti->intreq);
|
|
+
|
|
+ for (i = 0; i < NUM_BUFFERS; ++i) {
|
|
+ bh = &sti->buffhds[i];
|
|
+ if (bh->inreq_busy)
|
|
+ usb_ep_dequeue(sti->bulk_in, bh->inreq);
|
|
+ if (bh->outreq_busy)
|
|
+ usb_ep_dequeue(sti->bulk_out, bh->outreq);
|
|
+ }
|
|
+
|
|
+ /* wait until everything is idle */
|
|
+ for (;;) {
|
|
+ num_active = sti->intreq_busy;
|
|
+ for (i = 0; i < NUM_BUFFERS; ++i) {
|
|
+ bh = &sti->buffhds[i];
|
|
+ num_active += bh->inreq_busy + bh->outreq_busy;
|
|
+ }
|
|
+
|
|
+ if (num_active == 0)
|
|
+ break;
|
|
+
|
|
+ if (sleep_thread(sti))
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ /* clear out the controller's fifos */
|
|
+ if (sti->bulk_in_enabled)
|
|
+ usb_ep_fifo_flush(sti->bulk_in);
|
|
+ if (sti->bulk_out_enabled)
|
|
+ usb_ep_fifo_flush(sti->bulk_out);
|
|
+ if (sti->intr_in_enabled)
|
|
+ usb_ep_fifo_flush(sti->intr_in);
|
|
+
|
|
+ /*
|
|
+ * Reset the I/O buffer states and pointers, the device
|
|
+ * state, and the exception. Then invoke the handler.
|
|
+ */
|
|
+ spin_lock_irq(&sti->lock);
|
|
+
|
|
+ for (i = 0; i < NUM_BUFFERS; ++i) {
|
|
+ bh = &sti->buffhds[i];
|
|
+ bh->state = BUF_STATE_EMPTY;
|
|
+ }
|
|
+ sti->next_buffhd_to_fill = sti->next_buffhd_to_drain =
|
|
+ &sti->buffhds[0];
|
|
+
|
|
+ exception_req_tag = sti->exception_req_tag;
|
|
+ new_config = sti->new_config;
|
|
+ old_state = sti->state;
|
|
+
|
|
+ if (old_state == STI_STATE_ABORT_BULK_OUT)
|
|
+ sti->state = STI_STATE_STATUS_PHASE;
|
|
+ else
|
|
+ sti->state = STI_STATE_IDLE;
|
|
+ spin_unlock_irq(&sti->lock);
|
|
+
|
|
+ /* carry out any extra actions required for the exception */
|
|
+ switch (old_state) {
|
|
+ default:
|
|
+ break;
|
|
+
|
|
+ case STI_STATE_CANCEL:
|
|
+ if (usb_ep_clear_halt(sti->bulk_out) ||
|
|
+ usb_ep_clear_halt(sti->bulk_in))
|
|
+ sti->response_code = PIMA15740_RES_DEVICE_BUSY;
|
|
+ else
|
|
+ sti->response_code = PIMA15740_RES_OK;
|
|
+ break;
|
|
+
|
|
+ case STI_STATE_ABORT_BULK_OUT:
|
|
+ send_status(sti);
|
|
+ spin_lock_irq(&sti->lock);
|
|
+ if (sti->state == STI_STATE_STATUS_PHASE)
|
|
+ sti->state = STI_STATE_IDLE;
|
|
+ spin_unlock_irq(&sti->lock);
|
|
+ break;
|
|
+
|
|
+ case STI_STATE_RESET:
|
|
+ /* in case we were forced against our will to halt a
|
|
+ * bulk endpoint, clear the halt now */
|
|
+ if (test_and_clear_bit(CLEAR_BULK_HALTS,
|
|
+ &sti->atomic_bitflags)) {
|
|
+ usb_ep_clear_halt(sti->bulk_in);
|
|
+ usb_ep_clear_halt(sti->bulk_out);
|
|
+ }
|
|
+
|
|
+ if (sti->ep0_req_tag == exception_req_tag)
|
|
+ /* complete the status stage */
|
|
+ ep0_queue(sti);
|
|
+ break;
|
|
+
|
|
+ case STI_STATE_INTERFACE_CHANGE:
|
|
+ rc = do_set_interface(sti, 0);
|
|
+ if (sti->ep0_req_tag != exception_req_tag)
|
|
+ break;
|
|
+ if (rc != 0) /* STALL on errors */
|
|
+ sti_set_halt(sti, sti->ep0);
|
|
+ else /* complete the status stage */
|
|
+ ep0_queue(sti);
|
|
+ break;
|
|
+
|
|
+ case STI_STATE_CONFIG_CHANGE:
|
|
+ rc = do_set_config(sti, new_config);
|
|
+ if (sti->ep0_req_tag != exception_req_tag)
|
|
+ break;
|
|
+ if (rc != 0) /* STALL on errors */
|
|
+ sti_set_halt(sti, sti->ep0);
|
|
+ else /* complete the status stage */
|
|
+ ep0_queue(sti);
|
|
+ break;
|
|
+
|
|
+ case STI_STATE_DISCONNECT:
|
|
+ do_set_config(sti, 0); /* unconfigured state */
|
|
+ break;
|
|
+
|
|
+ case STI_STATE_EXIT:
|
|
+ case STI_STATE_TERMINATED:
|
|
+ do_set_config(sti, 0); /* free resources */
|
|
+ spin_lock_irq(&sti->lock);
|
|
+ sti->state = STI_STATE_TERMINATED; /* stop the thread */
|
|
+ spin_unlock_irq(&sti->lock);
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ VDBG(sti, "<--- %s()\n", __func__);
|
|
+}
|
|
+
|
|
+
|
|
+/*-------------------------------------------------------------------------*/
|
|
+
|
|
+static int sti_main_thread(void *sti_)
|
|
+{
|
|
+ struct sti_dev *sti = sti_;
|
|
+ VDBG(sti, "---> %s()\n", __func__);
|
|
+
|
|
+ /*
|
|
+ * allow the thread to be killed by a signal, but set the signal mask
|
|
+ * to block everything but INT, TERM, KILL, and USR1
|
|
+ */
|
|
+ allow_signal(SIGINT);
|
|
+ allow_signal(SIGTERM);
|
|
+ allow_signal(SIGKILL);
|
|
+ allow_signal(SIGUSR1);
|
|
+
|
|
+ /* allow the thread to be frozen */
|
|
+ set_freezable();
|
|
+
|
|
+ /*
|
|
+ * arrange for userspace references to be interpreted as kernel
|
|
+ * pointers. That way we can pass a kernel pointer to a routine
|
|
+ * that expects a __user pointer and it will work okay.
|
|
+ */
|
|
+ set_fs(get_ds());
|
|
+
|
|
+ /* the main loop */
|
|
+ while (sti->state != STI_STATE_TERMINATED) {
|
|
+ if (exception_in_progress(sti) || signal_pending(current)) {
|
|
+ handle_exception(sti);
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ if (!sti->running) {
|
|
+ sleep_thread(sti);
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ if (get_next_command(sti))
|
|
+ continue;
|
|
+
|
|
+ spin_lock_irq(&sti->lock);
|
|
+ if (!exception_in_progress(sti))
|
|
+ sti->state = STI_STATE_DATA_PHASE;
|
|
+ spin_unlock_irq(&sti->lock);
|
|
+
|
|
+ if (do_still_image_command(sti))
|
|
+ continue;
|
|
+
|
|
+ spin_lock_irq(&sti->lock);
|
|
+ if (!exception_in_progress(sti))
|
|
+ sti->state = STI_STATE_STATUS_PHASE;
|
|
+ spin_unlock_irq(&sti->lock);
|
|
+
|
|
+ if (send_status(sti))
|
|
+ continue;
|
|
+
|
|
+ spin_lock_irq(&sti->lock);
|
|
+ if (!exception_in_progress(sti))
|
|
+ sti->state = STI_STATE_IDLE;
|
|
+ spin_unlock_irq(&sti->lock);
|
|
+ }
|
|
+
|
|
+ spin_lock_irq(&sti->lock);
|
|
+ sti->thread_task = NULL;
|
|
+ spin_unlock_irq(&sti->lock);
|
|
+
|
|
+ /* in case we are exiting because of a signal, unregister the
|
|
+ * gadget driver */
|
|
+ if (test_and_clear_bit(REGISTERED, &sti->atomic_bitflags))
|
|
+ usb_gadget_unregister_driver(&sti_driver);
|
|
+
|
|
+ /* let the unbind and cleanup routines know the thread has exited */
|
|
+ complete_and_exit(&sti->thread_notifier, 0);
|
|
+
|
|
+ VDBG(sti, "<--- %s()\n", __func__);
|
|
+}
|
|
+
|
|
+
|
|
+/*-------------------------------------------------------------------------*/
|
|
+
|
|
+static int open_backing_folder(struct sti_dev *sti, const char *folder_name)
|
|
+{
|
|
+ struct file *filp = NULL;
|
|
+ int rc = -EINVAL;
|
|
+ struct inode *inode = NULL;
|
|
+ size_t len;
|
|
+ VDBG(sti, "---> %s()\n", __func__);
|
|
+
|
|
+ /* remove the trailing path sign */
|
|
+ len = strlen(folder_name);
|
|
+ if (len > 1 && folder_name[len-1] == '/')
|
|
+ ((char *) folder_name)[len-1] = 0;
|
|
+
|
|
+ memset(sti->root_path, 0, sizeof(sti->root_path));
|
|
+ strncpy(sti->root_path, folder_name, sizeof(sti->root_path));
|
|
+
|
|
+ filp = filp_open(sti->root_path, O_RDONLY | O_DIRECTORY, 0);
|
|
+ if (IS_ERR(filp)) {
|
|
+ ERROR(sti, "unable to open backing folder: %s\n",
|
|
+ sti->root_path);
|
|
+ return PTR_ERR(filp);
|
|
+ }
|
|
+
|
|
+ if (filp->f_path.dentry)
|
|
+ inode = filp->f_dentry->d_inode;
|
|
+
|
|
+ if (!inode || !S_ISDIR(inode->i_mode)) {
|
|
+ ERROR(sti, "%s is not a directory\n", sti->root_path);
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ get_file(filp);
|
|
+
|
|
+ sti->root_filp = filp;
|
|
+
|
|
+ INFO(sti, "open backing folder: %s\n", folder_name);
|
|
+ rc = 0;
|
|
+out:
|
|
+ filp_close(filp, current->files);
|
|
+
|
|
+ VDBG(sti, "<--- %s()\n", __func__);
|
|
+ return rc;
|
|
+}
|
|
+
|
|
+static void close_backing_folder(struct sti_dev *sti)
|
|
+{
|
|
+ VDBG(sti, "---> %s()\n", __func__);
|
|
+
|
|
+ if (sti->root_filp) {
|
|
+ INFO(sti, "close backing folder\n");
|
|
+ fput(sti->root_filp);
|
|
+ sti->root_filp = NULL;
|
|
+ }
|
|
+
|
|
+ VDBG(sti, "<--- %s()\n", __func__);
|
|
+}
|
|
+
|
|
+
|
|
+/*-------------------------------------------------------------------------*/
|
|
+
|
|
+/* sysfs attribute files */
|
|
+static ssize_t show_folder(struct device *dev, struct device_attribute *attr,
|
|
+ char *buf)
|
|
+{
|
|
+ struct sti_dev *sti = dev_get_drvdata(dev);
|
|
+ char *p;
|
|
+ ssize_t rc;
|
|
+
|
|
+ down_read(&sti->filesem);
|
|
+ if (backing_folder_is_open(sti)) {
|
|
+ /* get the complete pathname */
|
|
+ p = d_path(&sti->root_filp->f_path, buf, PAGE_SIZE - 1);
|
|
+ if (IS_ERR(p))
|
|
+ rc = PTR_ERR(p);
|
|
+ else {
|
|
+ rc = strlen(p);
|
|
+ memmove(buf, p, rc);
|
|
+
|
|
+ /* add a newline */
|
|
+ buf[rc] = '\n';
|
|
+ buf[++rc] = 0;
|
|
+ }
|
|
+ } else { /* no file */
|
|
+ *buf = 0;
|
|
+ rc = 0;
|
|
+ }
|
|
+ up_read(&sti->filesem);
|
|
+
|
|
+ return rc;
|
|
+}
|
|
+
|
|
+
|
|
+static ssize_t store_folder(struct device *dev, struct device_attribute *attr,
|
|
+ const char *buf, size_t count)
|
|
+{
|
|
+ struct sti_dev *sti = dev_get_drvdata(dev);
|
|
+ int rc = 0;
|
|
+
|
|
+ /* remove a trailing newline */
|
|
+ if (count > 0 && buf[count-1] == '\n')
|
|
+ ((char *) buf)[count-1] = 0;
|
|
+
|
|
+ /* eject current medium */
|
|
+ down_write(&sti->filesem);
|
|
+ if (backing_folder_is_open(sti))
|
|
+ close_backing_folder(sti);
|
|
+
|
|
+ /* load new medium */
|
|
+ if (count > 0 && buf[0])
|
|
+ rc = open_backing_folder(sti, buf);
|
|
+
|
|
+ up_write(&sti->filesem);
|
|
+
|
|
+ return (rc < 0 ? rc : count);
|
|
+}
|
|
+
|
|
+/* the write permissions and store_xxx pointers are set in sti_bind() */
|
|
+static DEVICE_ATTR(folder, 0444, show_folder, NULL);
|
|
+
|
|
+
|
|
+/*-------------------------------------------------------------------------*/
|
|
+
|
|
+static void sti_release(struct kref *ref)
|
|
+{
|
|
+ struct sti_dev *sti = container_of(ref, struct sti_dev, ref);
|
|
+
|
|
+ while (!list_empty(&sti->obj_list)) {
|
|
+ struct sti_object *obj = NULL;
|
|
+ obj = list_entry(sti->obj_list.next, struct sti_object, list);
|
|
+ list_del_init(&obj->list);
|
|
+ kfree(obj);
|
|
+ }
|
|
+
|
|
+ while (!list_empty(&sti->tmp_obj_list)) {
|
|
+ struct sti_object *obj = NULL;
|
|
+ obj = list_entry(sti->tmp_obj_list.next, struct sti_object,
|
|
+ list);
|
|
+ list_del_init(&obj->list);
|
|
+ kfree(obj);
|
|
+ }
|
|
+
|
|
+ kfree(sti);
|
|
+}
|
|
+
|
|
+static void gadget_release(struct device *dev)
|
|
+{
|
|
+ struct sti_dev *sti = dev_get_drvdata(dev);
|
|
+ VDBG(sti, "---> %s()\n", __func__);
|
|
+ VDBG(sti, "<--- %s()\n", __func__);
|
|
+
|
|
+ kref_put(&sti->ref, sti_release);
|
|
+}
|
|
+
|
|
+
|
|
+static void /* __init_or_exit */ sti_unbind(struct usb_gadget *gadget)
|
|
+{
|
|
+ struct sti_dev *sti = get_gadget_data(gadget);
|
|
+ int i;
|
|
+ struct usb_request *req = sti->ep0req;
|
|
+ VDBG(sti, "---> %s()\n", __func__);
|
|
+
|
|
+ DBG(sti, "unbind\n");
|
|
+ clear_bit(REGISTERED, &sti->atomic_bitflags);
|
|
+
|
|
+ /* unregister the sysfs attribute files */
|
|
+ if (sti->registered) {
|
|
+ device_remove_file(&sti->dev, &dev_attr_folder);
|
|
+ close_backing_folder(sti);
|
|
+ device_unregister(&sti->dev);
|
|
+ sti->registered = 0;
|
|
+ }
|
|
+
|
|
+ /* if the thread isn't already dead, tell it to exit now */
|
|
+ if (sti->state != STI_STATE_TERMINATED) {
|
|
+ raise_exception(sti, STI_STATE_EXIT);
|
|
+ wait_for_completion(&sti->thread_notifier);
|
|
+
|
|
+ /* the cleanup routine waits for this completion also */
|
|
+ complete(&sti->thread_notifier);
|
|
+ }
|
|
+
|
|
+ /* free the data buffers */
|
|
+ for (i = 0; i < NUM_BUFFERS; ++i)
|
|
+ kfree(sti->buffhds[i].buf);
|
|
+
|
|
+ /* free the request and buffer for endpoint 0 */
|
|
+ if (req) {
|
|
+ kfree(req->buf);
|
|
+ usb_ep_free_request(sti->ep0, req);
|
|
+ }
|
|
+
|
|
+ set_gadget_data(gadget, NULL);
|
|
+
|
|
+ VDBG(sti, "<--- %s()\n", __func__);
|
|
+}
|
|
+
|
|
+
|
|
+static int __init check_parameters(struct sti_dev *sti)
|
|
+{
|
|
+ int gcnum;
|
|
+ VDBG(sti, "---> %s()\n", __func__);
|
|
+
|
|
+ /* parameter wasn't set */
|
|
+ if (mod_data.release == 0xffff) {
|
|
+ gcnum = usb_gadget_controller_number(sti->gadget);
|
|
+ if (gcnum >= 0)
|
|
+ mod_data.release = 0x0300 + gcnum;
|
|
+ else {
|
|
+ WARNING(sti, "controller '%s' not recognized\n",
|
|
+ sti->gadget->name);
|
|
+ mod_data.release = 0x0399;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ mod_data.buflen &= PAGE_CACHE_MASK;
|
|
+ if (mod_data.buflen <= 0) {
|
|
+ ERROR(sti, "invalid buflen\n");
|
|
+ return -ETOOSMALL;
|
|
+ }
|
|
+
|
|
+ VDBG(sti, "<--- %s()\n", __func__);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+
|
|
+static int __init sti_bind(struct usb_gadget *gadget)
|
|
+{
|
|
+ struct sti_dev *sti = the_sti;
|
|
+ int rc;
|
|
+ int i;
|
|
+ struct usb_ep *ep;
|
|
+ struct usb_request *req;
|
|
+
|
|
+ sti->gadget = gadget;
|
|
+ set_gadget_data(gadget, sti);
|
|
+ sti->ep0 = gadget->ep0;
|
|
+ sti->ep0->driver_data = sti;
|
|
+
|
|
+ rc = check_parameters(sti);
|
|
+ if (rc)
|
|
+ goto out;
|
|
+
|
|
+ /* enable store_xxx attributes */
|
|
+ dev_attr_folder.attr.mode = 0644;
|
|
+ dev_attr_folder.store = store_folder;
|
|
+
|
|
+ sti->dev.release = gadget_release;
|
|
+ sti->dev.parent = &gadget->dev;
|
|
+ sti->dev.driver = &sti_driver.driver;
|
|
+ dev_set_drvdata(&sti->dev, sti);
|
|
+ dev_set_name(&sti->dev, "%s", sti_driver.driver.name);
|
|
+
|
|
+ rc = device_register(&sti->dev);
|
|
+ if (rc) {
|
|
+ INFO(sti, "failed to register sti: %d\n", rc);
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ rc = device_create_file(&sti->dev, &dev_attr_folder);
|
|
+ if (rc) {
|
|
+ device_unregister(&sti->dev);
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ sti->registered = 1;
|
|
+ kref_get(&sti->ref);
|
|
+
|
|
+ /* initialize object list */
|
|
+ INIT_LIST_HEAD(&sti->obj_list);
|
|
+ INIT_LIST_HEAD(&sti->tmp_obj_list);
|
|
+
|
|
+ if (mod_data.folder && *mod_data.folder)
|
|
+ rc = open_backing_folder(sti, mod_data.folder);
|
|
+ if (rc)
|
|
+ goto out;
|
|
+
|
|
+ /* find all the endpoints we will use */
|
|
+ usb_ep_autoconfig_reset(gadget);
|
|
+ ep = usb_ep_autoconfig(gadget, &fs_bulk_in_desc);
|
|
+ if (!ep)
|
|
+ goto autoconf_fail;
|
|
+
|
|
+ /* claim bulk-in endpoint */
|
|
+ ep->driver_data = sti;
|
|
+ sti->bulk_in = ep;
|
|
+
|
|
+ ep = usb_ep_autoconfig(gadget, &fs_bulk_out_desc);
|
|
+ if (!ep)
|
|
+ goto autoconf_fail;
|
|
+
|
|
+ /* claim bulk-out endpoint */
|
|
+ ep->driver_data = sti;
|
|
+ sti->bulk_out = ep;
|
|
+
|
|
+ ep = usb_ep_autoconfig(gadget, &fs_intr_in_desc);
|
|
+ if (!ep)
|
|
+ goto autoconf_fail;
|
|
+
|
|
+ /* claim intr-in endpoint */
|
|
+ ep->driver_data = sti;
|
|
+ sti->intr_in = ep;
|
|
+
|
|
+ /* fix up the descriptors */
|
|
+ device_desc.bMaxPacketSize0 = sti->ep0->maxpacket;
|
|
+ device_desc.idVendor = cpu_to_le16(mod_data.vendor);
|
|
+ device_desc.idProduct = cpu_to_le16(mod_data.product);
|
|
+ device_desc.bcdDevice = cpu_to_le16(mod_data.release);
|
|
+
|
|
+ fs_function[3 + FS_FUNCTION_PRE_EP_ENTRIES] = NULL;
|
|
+
|
|
+ if (gadget_is_dualspeed(gadget)) {
|
|
+ hs_function[3 + HS_FUNCTION_PRE_EP_ENTRIES] = NULL;
|
|
+
|
|
+ /* assume ep0 uses the same maxpacket value for both speeds */
|
|
+ dev_qualifier.bMaxPacketSize0 = sti->ep0->maxpacket;
|
|
+
|
|
+ /* assume endpoint addresses are the same for both speeds */
|
|
+ hs_bulk_in_desc.bEndpointAddress =
|
|
+ fs_bulk_in_desc.bEndpointAddress;
|
|
+ hs_bulk_out_desc.bEndpointAddress =
|
|
+ fs_bulk_out_desc.bEndpointAddress;
|
|
+ hs_intr_in_desc.bEndpointAddress =
|
|
+ fs_intr_in_desc.bEndpointAddress;
|
|
+ }
|
|
+
|
|
+ if (gadget_is_otg(gadget))
|
|
+ otg_desc.bmAttributes |= USB_OTG_HNP;
|
|
+
|
|
+ rc = -ENOMEM;
|
|
+
|
|
+ /* allocate the request and buffer for endpoint 0 */
|
|
+ sti->ep0req = req = usb_ep_alloc_request(sti->ep0, GFP_KERNEL);
|
|
+ if (!req)
|
|
+ goto autoconf_fail;
|
|
+
|
|
+ req->buf = kmalloc(EP0_BUFSIZE, GFP_KERNEL);
|
|
+ if (!req->buf)
|
|
+ goto autoconf_fail;
|
|
+
|
|
+ req->complete = ep0_complete;
|
|
+
|
|
+ /* allocate the data buffers */
|
|
+ for (i = 0; i < NUM_BUFFERS; ++i) {
|
|
+ struct sti_buffhd *bh = &sti->buffhds[i];
|
|
+
|
|
+ /*
|
|
+ * Allocate for the bulk-in endpoint. We assume that
|
|
+ * the buffer will also work with the bulk-out (and
|
|
+ * interrupt-in) endpoint.
|
|
+ */
|
|
+ bh->buf = kmalloc(mod_data.buflen, GFP_KERNEL);
|
|
+ if (!bh->buf)
|
|
+ goto autoconf_fail;
|
|
+
|
|
+ bh->next = bh + 1;
|
|
+ }
|
|
+ sti->buffhds[NUM_BUFFERS - 1].next = &sti->buffhds[0];
|
|
+
|
|
+ /* this should reflect the actual gadget power source */
|
|
+ usb_gadget_set_selfpowered(gadget);
|
|
+
|
|
+ snprintf(manufacturer, sizeof manufacturer, "%s %s with %s",
|
|
+ init_utsname()->sysname, init_utsname()->release,
|
|
+ gadget->name);
|
|
+
|
|
+ DBG(sti, "manufacturer: %s\n", manufacturer);
|
|
+
|
|
+ /*
|
|
+ * on a real device, serial[] would be loaded from permanent
|
|
+ * storage. We just encode it from the driver version string.
|
|
+ */
|
|
+ for (i = 0; i < sizeof(serial) - 2; i += 2) {
|
|
+ unsigned char c = DRIVER_VERSION[i / 2];
|
|
+
|
|
+ if (!c)
|
|
+ break;
|
|
+
|
|
+ snprintf(&serial[i], sizeof(&serial[i]), "%02X", c);
|
|
+ }
|
|
+
|
|
+ /* fill remained device info */
|
|
+ sti_device_info.manufacturer_len = sizeof(manufacturer);
|
|
+ str_to_uni16(manufacturer, sti_device_info.manufacturer);
|
|
+
|
|
+ sti_device_info.model_len = sizeof(longname);
|
|
+ str_to_uni16(longname, sti_device_info.model);
|
|
+
|
|
+ sti_device_info.device_version_len = sizeof(device_version);
|
|
+ str_to_uni16(device_version, sti_device_info.device_version);
|
|
+
|
|
+ sti_device_info.serial_number_len = sizeof(serial);
|
|
+ str_to_uni16(serial, sti_device_info.serial_number);
|
|
+
|
|
+ /* create main kernel thread */
|
|
+ sti->thread_task = kthread_create(sti_main_thread, sti,
|
|
+ "still-image-gadget");
|
|
+
|
|
+ if (IS_ERR(sti->thread_task)) {
|
|
+ rc = PTR_ERR(sti->thread_task);
|
|
+ goto autoconf_fail;
|
|
+ }
|
|
+
|
|
+ INFO(sti, DRIVER_DESC ", version: " DRIVER_VERSION "\n");
|
|
+ INFO(sti, "VendorID=x%04x, ProductID=x%04x, Release=x%04x\n",
|
|
+ mod_data.vendor, mod_data.product, mod_data.release);
|
|
+ INFO(sti, "I/O thread pid: %d, buflen=%u\n",
|
|
+ task_pid_nr(sti->thread_task), mod_data.buflen);
|
|
+
|
|
+ set_bit(REGISTERED, &sti->atomic_bitflags);
|
|
+
|
|
+ /* tell the thread to start working */
|
|
+ wake_up_process(sti->thread_task);
|
|
+
|
|
+ DBG(sti, "bind\n");
|
|
+ return 0;
|
|
+
|
|
+autoconf_fail:
|
|
+ ERROR(sti, "unable to autoconfigure all endpoints\n");
|
|
+ rc = -ENOTSUPP;
|
|
+out:
|
|
+ /* the thread is dead */
|
|
+ sti->state = STI_STATE_TERMINATED;
|
|
+
|
|
+ sti_unbind(gadget);
|
|
+ complete(&sti->thread_notifier);
|
|
+
|
|
+ VDBG(sti, "<---> %s()\n", __func__);
|
|
+ return rc;
|
|
+}
|
|
+
|
|
+
|
|
+/*-------------------------------------------------------------------------*/
|
|
+
|
|
+static void sti_suspend(struct usb_gadget *gadget)
|
|
+{
|
|
+ struct sti_dev *sti = get_gadget_data(gadget);
|
|
+
|
|
+ DBG(sti, "suspend\n");
|
|
+ set_bit(SUSPENDED, &sti->atomic_bitflags);
|
|
+}
|
|
+
|
|
+
|
|
+static void sti_resume(struct usb_gadget *gadget)
|
|
+{
|
|
+ struct sti_dev *sti = get_gadget_data(gadget);
|
|
+
|
|
+ DBG(sti, "resume\n");
|
|
+ clear_bit(SUSPENDED, &sti->atomic_bitflags);
|
|
+}
|
|
+
|
|
+
|
|
+/*-------------------------------------------------------------------------*/
|
|
+
|
|
+static struct usb_gadget_driver sti_driver = {
|
|
+#ifdef CONFIG_USB_GADGET_DUALSPEED
|
|
+ .speed = USB_SPEED_HIGH,
|
|
+#else
|
|
+ .speed = USB_SPEED_FULL,
|
|
+#endif
|
|
+ .function = (char *) longname,
|
|
+ .bind = sti_bind,
|
|
+ .unbind = sti_unbind,
|
|
+ .disconnect = sti_disconnect,
|
|
+ .setup = sti_setup,
|
|
+ .suspend = sti_suspend,
|
|
+ .resume = sti_resume,
|
|
+
|
|
+ .driver = {
|
|
+ .name = (char *) shortname,
|
|
+ .owner = THIS_MODULE,
|
|
+ /* .release = ... */
|
|
+ /* .suspend = ... */
|
|
+ /* .resume = ... */
|
|
+ },
|
|
+};
|
|
+
|
|
+
|
|
+static int __init sti_alloc(void)
|
|
+{
|
|
+ struct sti_dev *sti;
|
|
+
|
|
+ sti = kzalloc(sizeof *sti, GFP_KERNEL);
|
|
+ if (!sti)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ spin_lock_init(&sti->lock);
|
|
+ init_rwsem(&sti->filesem);
|
|
+ kref_init(&sti->ref);
|
|
+ init_completion(&sti->thread_notifier);
|
|
+
|
|
+ the_sti = sti;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+
|
|
+static int __init sti_init(void)
|
|
+{
|
|
+ int rc;
|
|
+ struct sti_dev *sti;
|
|
+
|
|
+ rc = sti_alloc();
|
|
+ if (rc)
|
|
+ return rc;
|
|
+
|
|
+ sti = the_sti;
|
|
+
|
|
+ rc = usb_gadget_register_driver(&sti_driver);
|
|
+ if (rc)
|
|
+ kref_put(&sti->ref, sti_release);
|
|
+
|
|
+ return rc;
|
|
+}
|
|
+module_init(sti_init);
|
|
+
|
|
+
|
|
+static void __exit sti_cleanup(void)
|
|
+{
|
|
+ struct sti_dev *sti = the_sti;
|
|
+
|
|
+ /* unregister the driver if the thread hasn't already done */
|
|
+ if (test_and_clear_bit(REGISTERED, &sti->atomic_bitflags))
|
|
+ usb_gadget_unregister_driver(&sti_driver);
|
|
+
|
|
+ /* wait for the thread to finish up */
|
|
+ wait_for_completion(&sti->thread_notifier);
|
|
+
|
|
+ kref_put(&sti->ref, sti_release);
|
|
+}
|
|
+module_exit(sti_cleanup);
|
|
diff --git a/drivers/usb/otg/Kconfig b/drivers/usb/otg/Kconfig
|
|
index 3d2d3e5..69ff37b 100644
|
|
--- a/drivers/usb/otg/Kconfig
|
|
+++ b/drivers/usb/otg/Kconfig
|
|
@@ -69,4 +69,18 @@ config NOP_USB_XCEIV
|
|
built-in with usb ip or which are autonomous and doesn't require any
|
|
phy programming such as ISP1x04 etc.
|
|
|
|
+config USB_LANGWELL_OTG
|
|
+ tristate "Intel Langwell USB OTG dual-role support"
|
|
+ depends on USB && X86_MRST
|
|
+ select USB_OTG
|
|
+ select USB_OTG_UTILS
|
|
+ help
|
|
+ Say Y here if you want to build Intel Langwell USB OTG
|
|
+ transciever driver in kernel. This driver implements role
|
|
+ switch between EHCI host driver and Langwell USB OTG
|
|
+ client driver.
|
|
+
|
|
+ To compile this driver as a module, choose M here: the
|
|
+ module will be called langwell_otg.
|
|
+
|
|
endif # USB || OTG
|
|
diff --git a/drivers/usb/otg/Makefile b/drivers/usb/otg/Makefile
|
|
index aeb49a8..b6609db 100644
|
|
--- a/drivers/usb/otg/Makefile
|
|
+++ b/drivers/usb/otg/Makefile
|
|
@@ -9,6 +9,7 @@ obj-$(CONFIG_USB_OTG_UTILS) += otg.o
|
|
obj-$(CONFIG_USB_GPIO_VBUS) += gpio_vbus.o
|
|
obj-$(CONFIG_ISP1301_OMAP) += isp1301_omap.o
|
|
obj-$(CONFIG_TWL4030_USB) += twl4030-usb.o
|
|
+obj-$(CONFIG_USB_LANGWELL_OTG) += langwell_otg.o
|
|
obj-$(CONFIG_NOP_USB_XCEIV) += nop-usb-xceiv.o
|
|
obj-$(CONFIG_USB_ULPI) += ulpi.o
|
|
|
|
diff --git a/drivers/usb/otg/langwell_otg.c b/drivers/usb/otg/langwell_otg.c
|
|
new file mode 100644
|
|
index 0000000..46ae881
|
|
--- /dev/null
|
|
+++ b/drivers/usb/otg/langwell_otg.c
|
|
@@ -0,0 +1,2260 @@
|
|
+/*
|
|
+ * Intel Langwell USB OTG transceiver driver
|
|
+ * Copyright (C) 2008 - 2009, Intel Corporation.
|
|
+ *
|
|
+ * This program is free software; you can redistribute it and/or modify it
|
|
+ * under the terms and conditions of the GNU General Public License,
|
|
+ * version 2, as published by the Free Software Foundation.
|
|
+ *
|
|
+ * This program is distributed in the hope it will be useful, but WITHOUT
|
|
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
+ * more details.
|
|
+ *
|
|
+ * You should have received a copy of the GNU General Public License along with
|
|
+ * this program; if not, write to the Free Software Foundation, Inc.,
|
|
+ * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
|
|
+ *
|
|
+ */
|
|
+/* This driver helps to switch Langwell OTG controller function between host
|
|
+ * and peripheral. It works with EHCI driver and Langwell client controller
|
|
+ * driver together.
|
|
+ */
|
|
+#include <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
|
|
|