aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/usb/host
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb/host')
-rw-r--r--drivers/usb/host/Kconfig29
-rw-r--r--drivers/usb/host/Makefile3
-rw-r--r--drivers/usb/host/ehci-dbg.c56
-rw-r--r--drivers/usb/host/ehci-hcd.c48
-rw-r--r--drivers/usb/host/ehci-hub.c27
-rw-r--r--drivers/usb/host/ehci-ppc-soc.c201
-rw-r--r--drivers/usb/host/ehci.h23
-rw-r--r--drivers/usb/host/hwa-hc.c925
-rw-r--r--drivers/usb/host/isp116x-hcd.c13
-rw-r--r--drivers/usb/host/isp1760-if.c7
-rw-r--r--drivers/usb/host/ohci-dbg.c2
-rw-r--r--drivers/usb/host/ohci-hcd.c31
-rw-r--r--drivers/usb/host/ohci-hub.c87
-rw-r--r--drivers/usb/host/ohci-omap.c4
-rw-r--r--drivers/usb/host/ohci-pnx4008.c2
-rw-r--r--drivers/usb/host/ohci-tmio.c376
-rw-r--r--drivers/usb/host/ohci.h8
-rw-r--r--drivers/usb/host/r8a66597-hcd.c101
-rw-r--r--drivers/usb/host/sl811-hcd.c15
-rw-r--r--drivers/usb/host/uhci-hcd.c10
-rw-r--r--drivers/usb/host/uhci-q.c17
-rw-r--r--drivers/usb/host/whci/Kbuild11
-rw-r--r--drivers/usb/host/whci/asl.c367
-rw-r--r--drivers/usb/host/whci/hcd.c339
-rw-r--r--drivers/usb/host/whci/hw.c87
-rw-r--r--drivers/usb/host/whci/init.c188
-rw-r--r--drivers/usb/host/whci/int.c95
-rw-r--r--drivers/usb/host/whci/pzl.c398
-rw-r--r--drivers/usb/host/whci/qset.c567
-rw-r--r--drivers/usb/host/whci/whcd.h197
-rw-r--r--drivers/usb/host/whci/whci-hc.h416
-rw-r--r--drivers/usb/host/whci/wusb.c241
32 files changed, 4515 insertions, 376 deletions
diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig
index 228797e54f9c..56f592dc0b36 100644
--- a/drivers/usb/host/Kconfig
+++ b/drivers/usb/host/Kconfig
@@ -138,7 +138,6 @@ config USB_OHCI_HCD
tristate "OHCI HCD support"
depends on USB && USB_ARCH_HAS_OHCI
select ISP1301_OMAP if MACH_OMAP_H2 || MACH_OMAP_H3
- select I2C if ARCH_PNX4008
---help---
The Open Host Controller Interface (OHCI) is a standard for accessing
USB 1.1 host controller hardware. It does more in hardware than Intel's
@@ -305,3 +304,31 @@ config SUPERH_ON_CHIP_R8A66597
help
This driver enables support for the on-chip R8A66597 in the
SH7366 and SH7723 processors.
+
+config USB_WHCI_HCD
+ tristate "Wireless USB Host Controller Interface (WHCI) driver (EXPERIMENTAL)"
+ depends on EXPERIMENTAL
+ depends on PCI && USB
+ select USB_WUSB
+ select UWB_WHCI
+ help
+ A driver for PCI-based Wireless USB Host Controllers that are
+ compliant with the WHCI specification.
+
+ To compile this driver a module, choose M here: the module
+ will be called "whci-hcd".
+
+config USB_HWA_HCD
+ tristate "Host Wire Adapter (HWA) driver (EXPERIMENTAL)"
+ depends on EXPERIMENTAL
+ depends on USB
+ select USB_WUSB
+ select UWB_HWA
+ help
+ This driver enables you to connect Wireless USB devices to
+ your system using a Host Wire Adaptor USB dongle. This is an
+ UWB Radio Controller and WUSB Host Controller connected to
+ your machine via USB (specified in WUSB1.0).
+
+ To compile this driver a module, choose M here: the module
+ will be called "hwa-hc".
diff --git a/drivers/usb/host/Makefile b/drivers/usb/host/Makefile
index f1edda2dcfde..23be22224044 100644
--- a/drivers/usb/host/Makefile
+++ b/drivers/usb/host/Makefile
@@ -8,6 +8,8 @@ endif
isp1760-objs := isp1760-hcd.o isp1760-if.o
+obj-$(CONFIG_USB_WHCI_HCD) += whci/
+
obj-$(CONFIG_PCI) += pci-quirks.o
obj-$(CONFIG_USB_EHCI_HCD) += ehci-hcd.o
@@ -19,3 +21,4 @@ obj-$(CONFIG_USB_SL811_CS) += sl811_cs.o
obj-$(CONFIG_USB_U132_HCD) += u132-hcd.o
obj-$(CONFIG_USB_R8A66597_HCD) += r8a66597-hcd.o
obj-$(CONFIG_USB_ISP1760_HCD) += isp1760.o
+obj-$(CONFIG_USB_HWA_HCD) += hwa-hc.o
diff --git a/drivers/usb/host/ehci-dbg.c b/drivers/usb/host/ehci-dbg.c
index b0f8ed5a7fb9..0cb53ca8d343 100644
--- a/drivers/usb/host/ehci-dbg.c
+++ b/drivers/usb/host/ehci-dbg.c
@@ -358,7 +358,8 @@ struct debug_buffer {
struct usb_bus *bus;
struct mutex mutex; /* protect filling of buffer */
size_t count; /* number of characters filled into buffer */
- char *page;
+ char *output_buf;
+ size_t alloc_size;
};
#define speed_char(info1) ({ char tmp; \
@@ -488,8 +489,8 @@ static ssize_t fill_async_buffer(struct debug_buffer *buf)
hcd = bus_to_hcd(buf->bus);
ehci = hcd_to_ehci (hcd);
- next = buf->page;
- size = PAGE_SIZE;
+ next = buf->output_buf;
+ size = buf->alloc_size;
*next = 0;
@@ -510,7 +511,7 @@ static ssize_t fill_async_buffer(struct debug_buffer *buf)
}
spin_unlock_irqrestore (&ehci->lock, flags);
- return strlen(buf->page);
+ return strlen(buf->output_buf);
}
#define DBG_SCHED_LIMIT 64
@@ -531,8 +532,8 @@ static ssize_t fill_periodic_buffer(struct debug_buffer *buf)
hcd = bus_to_hcd(buf->bus);
ehci = hcd_to_ehci (hcd);
- next = buf->page;
- size = PAGE_SIZE;
+ next = buf->output_buf;
+ size = buf->alloc_size;
temp = scnprintf (next, size, "size = %d\n", ehci->periodic_size);
size -= temp;
@@ -568,14 +569,16 @@ static ssize_t fill_periodic_buffer(struct debug_buffer *buf)
for (temp = 0; temp < seen_count; temp++) {
if (seen [temp].ptr != p.ptr)
continue;
- if (p.qh->qh_next.ptr)
+ if (p.qh->qh_next.ptr) {
temp = scnprintf (next, size,
" ...");
- p.ptr = NULL;
+ size -= temp;
+ next += temp;
+ }
break;
}
/* show more info the first time around */
- if (temp == seen_count && p.ptr) {
+ if (temp == seen_count) {
u32 scratch = hc32_to_cpup(ehci,
&p.qh->hw_info1);
struct ehci_qtd *qtd;
@@ -649,7 +652,7 @@ static ssize_t fill_periodic_buffer(struct debug_buffer *buf)
spin_unlock_irqrestore (&ehci->lock, flags);
kfree (seen);
- return PAGE_SIZE - size;
+ return buf->alloc_size - size;
}
#undef DBG_SCHED_LIMIT
@@ -665,14 +668,14 @@ static ssize_t fill_registers_buffer(struct debug_buffer *buf)
hcd = bus_to_hcd(buf->bus);
ehci = hcd_to_ehci (hcd);
- next = buf->page;
- size = PAGE_SIZE;
+ next = buf->output_buf;
+ size = buf->alloc_size;
spin_lock_irqsave (&ehci->lock, flags);
if (!test_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags)) {
size = scnprintf (next, size,
- "bus %s, device %s (driver " DRIVER_VERSION ")\n"
+ "bus %s, device %s\n"
"%s\n"
"SUSPENDED (no register access)\n",
hcd->self.controller->bus->name,
@@ -684,7 +687,7 @@ static ssize_t fill_registers_buffer(struct debug_buffer *buf)
/* Capability Registers */
i = HC_VERSION(ehci_readl(ehci, &ehci->caps->hc_capbase));
temp = scnprintf (next, size,
- "bus %s, device %s (driver " DRIVER_VERSION ")\n"
+ "bus %s, device %s\n"
"%s\n"
"EHCI %x.%02x, hcd state %d\n",
hcd->self.controller->bus->name,
@@ -808,7 +811,7 @@ static ssize_t fill_registers_buffer(struct debug_buffer *buf)
done:
spin_unlock_irqrestore (&ehci->lock, flags);
- return PAGE_SIZE - size;
+ return buf->alloc_size - size;
}
static struct debug_buffer *alloc_buffer(struct usb_bus *bus,
@@ -822,6 +825,7 @@ static struct debug_buffer *alloc_buffer(struct usb_bus *bus,
buf->bus = bus;
buf->fill_func = fill_func;
mutex_init(&buf->mutex);
+ buf->alloc_size = PAGE_SIZE;
}
return buf;
@@ -831,10 +835,10 @@ static int fill_buffer(struct debug_buffer *buf)
{
int ret = 0;
- if (!buf->page)
- buf->page = (char *)get_zeroed_page(GFP_KERNEL);
+ if (!buf->output_buf)
+ buf->output_buf = (char *)vmalloc(buf->alloc_size);
- if (!buf->page) {
+ if (!buf->output_buf) {
ret = -ENOMEM;
goto out;
}
@@ -867,7 +871,7 @@ static ssize_t debug_output(struct file *file, char __user *user_buf,
mutex_unlock(&buf->mutex);
ret = simple_read_from_buffer(user_buf, len, offset,
- buf->page, buf->count);
+ buf->output_buf, buf->count);
out:
return ret;
@@ -879,8 +883,8 @@ static int debug_close(struct inode *inode, struct file *file)
struct debug_buffer *buf = file->private_data;
if (buf) {
- if (buf->page)
- free_page((unsigned long)buf->page);
+ if (buf->output_buf)
+ vfree(buf->output_buf);
kfree(buf);
}
@@ -895,10 +899,14 @@ static int debug_async_open(struct inode *inode, struct file *file)
static int debug_periodic_open(struct inode *inode, struct file *file)
{
- file->private_data = alloc_buffer(inode->i_private,
- fill_periodic_buffer);
+ struct debug_buffer *buf;
+ buf = alloc_buffer(inode->i_private, fill_periodic_buffer);
+ if (!buf)
+ return -ENOMEM;
- return file->private_data ? 0 : -ENOMEM;
+ buf->alloc_size = (sizeof(void *) == 4 ? 6 : 8)*PAGE_SIZE;
+ file->private_data = buf;
+ return 0;
}
static int debug_registers_open(struct inode *inode, struct file *file)
diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c
index 8409e0705d63..15a803b206b8 100644
--- a/drivers/usb/host/ehci-hcd.c
+++ b/drivers/usb/host/ehci-hcd.c
@@ -24,6 +24,7 @@
#include <linux/ioport.h>
#include <linux/sched.h>
#include <linux/slab.h>
+#include <linux/vmalloc.h>
#include <linux/errno.h>
#include <linux/init.h>
#include <linux/timer.h>
@@ -59,7 +60,6 @@
* providing early devices for those host controllers to talk to!
*/
-#define DRIVER_VERSION "10 Dec 2004"
#define DRIVER_AUTHOR "David Brownell"
#define DRIVER_DESC "USB 2.0 'Enhanced' Host Controller (EHCI) Driver"
@@ -620,9 +620,9 @@ static int ehci_run (struct usb_hcd *hcd)
temp = HC_VERSION(ehci_readl(ehci, &ehci->caps->hc_capbase));
ehci_info (ehci,
- "USB %x.%x started, EHCI %x.%02x, driver %s%s\n",
+ "USB %x.%x started, EHCI %x.%02x%s\n",
((ehci->sbrn & 0xf0)>>4), (ehci->sbrn & 0x0f),
- temp >> 8, temp & 0xff, DRIVER_VERSION,
+ temp >> 8, temp & 0xff,
ignore_oc ? ", overcurrent ignored" : "");
ehci_writel(ehci, INTR_MASK,
@@ -706,7 +706,7 @@ static irqreturn_t ehci_irq (struct usb_hcd *hcd)
pcd_status = status;
/* resume root hub? */
- if (!(ehci_readl(ehci, &ehci->regs->command) & CMD_RUN))
+ if (!(cmd & CMD_RUN))
usb_hcd_resume_root_hub(hcd);
while (i--) {
@@ -715,8 +715,11 @@ static irqreturn_t ehci_irq (struct usb_hcd *hcd)
if (pstatus & PORT_OWNER)
continue;
- if (!(pstatus & PORT_RESUME)
- || ehci->reset_done [i] != 0)
+ if (!(test_bit(i, &ehci->suspended_ports) &&
+ ((pstatus & PORT_RESUME) ||
+ !(pstatus & PORT_SUSPEND)) &&
+ (pstatus & PORT_PE) &&
+ ehci->reset_done[i] == 0))
continue;
/* start 20 msec resume signaling from this port,
@@ -731,9 +734,8 @@ static irqreturn_t ehci_irq (struct usb_hcd *hcd)
/* PCI errors [4.15.2.4] */
if (unlikely ((status & STS_FATAL) != 0)) {
- dbg_cmd (ehci, "fatal", ehci_readl(ehci,
- &ehci->regs->command));
- dbg_status (ehci, "fatal", status);
+ dbg_cmd(ehci, "fatal", cmd);
+ dbg_status(ehci, "fatal", status);
if (status & STS_HALT) {
ehci_err (ehci, "fatal error\n");
dead:
@@ -994,9 +996,7 @@ static int ehci_get_frame (struct usb_hcd *hcd)
/*-------------------------------------------------------------------------*/
-#define DRIVER_INFO DRIVER_VERSION " " DRIVER_DESC
-
-MODULE_DESCRIPTION (DRIVER_INFO);
+MODULE_DESCRIPTION(DRIVER_DESC);
MODULE_AUTHOR (DRIVER_AUTHOR);
MODULE_LICENSE ("GPL");
@@ -1020,11 +1020,6 @@ MODULE_LICENSE ("GPL");
#define PS3_SYSTEM_BUS_DRIVER ps3_ehci_driver
#endif
-#if defined(CONFIG_440EPX) && !defined(CONFIG_PPC_MERGE)
-#include "ehci-ppc-soc.c"
-#define PLATFORM_DRIVER ehci_ppc_soc_driver
-#endif
-
#ifdef CONFIG_USB_EHCI_HCD_PPC_OF
#include "ehci-ppc-of.c"
#define OF_PLATFORM_DRIVER ehci_hcd_ppc_of_driver
@@ -1049,6 +1044,16 @@ static int __init ehci_hcd_init(void)
{
int retval = 0;
+ if (usb_disabled())
+ return -ENODEV;
+
+ printk(KERN_INFO "%s: " DRIVER_DESC "\n", hcd_name);
+ set_bit(USB_EHCI_LOADED, &usb_hcds_loaded);
+ if (test_bit(USB_UHCI_LOADED, &usb_hcds_loaded) ||
+ test_bit(USB_OHCI_LOADED, &usb_hcds_loaded))
+ printk(KERN_WARNING "Warning! ehci_hcd should always be loaded"
+ " before uhci_hcd and ohci_hcd, not after\n");
+
pr_debug("%s: block sizes: qh %Zd qtd %Zd itd %Zd sitd %Zd\n",
hcd_name,
sizeof(struct ehci_qh), sizeof(struct ehci_qtd),
@@ -1056,8 +1061,10 @@ static int __init ehci_hcd_init(void)
#ifdef DEBUG
ehci_debug_root = debugfs_create_dir("ehci", NULL);
- if (!ehci_debug_root)
- return -ENOENT;
+ if (!ehci_debug_root) {
+ retval = -ENOENT;
+ goto err_debug;
+ }
#endif
#ifdef PLATFORM_DRIVER
@@ -1104,7 +1111,9 @@ clean0:
#ifdef DEBUG
debugfs_remove(ehci_debug_root);
ehci_debug_root = NULL;
+err_debug:
#endif
+ clear_bit(USB_EHCI_LOADED, &usb_hcds_loaded);
return retval;
}
module_init(ehci_hcd_init);
@@ -1126,6 +1135,7 @@ static void __exit ehci_hcd_cleanup(void)
#ifdef DEBUG
debugfs_remove(ehci_debug_root);
#endif
+ clear_bit(USB_EHCI_LOADED, &usb_hcds_loaded);
}
module_exit(ehci_hcd_cleanup);
diff --git a/drivers/usb/host/ehci-hub.c b/drivers/usb/host/ehci-hub.c
index 740835bb8575..218f9660d7ee 100644
--- a/drivers/usb/host/ehci-hub.c
+++ b/drivers/usb/host/ehci-hub.c
@@ -236,10 +236,8 @@ static int ehci_bus_resume (struct usb_hcd *hcd)
temp = ehci_readl(ehci, &ehci->regs->port_status [i]);
temp &= ~(PORT_RWC_BITS | PORT_WAKE_BITS);
if (test_bit(i, &ehci->bus_suspended) &&
- (temp & PORT_SUSPEND)) {
- ehci->reset_done [i] = jiffies + msecs_to_jiffies (20);
+ (temp & PORT_SUSPEND))
temp |= PORT_RESUME;
- }
ehci_writel(ehci, temp, &ehci->regs->port_status [i]);
}
i = HCS_N_PORTS (ehci->hcs_params);
@@ -482,10 +480,9 @@ ehci_hub_status_data (struct usb_hcd *hcd, char *buf)
* controller by the user.
*/
- if ((temp & mask) != 0
- || ((temp & PORT_RESUME) != 0
- && time_after_eq(jiffies,
- ehci->reset_done[i]))) {
+ if ((temp & mask) != 0 || test_bit(i, &ehci->port_c_suspend)
+ || (ehci->reset_done[i] && time_after_eq(
+ jiffies, ehci->reset_done[i]))) {
if (i < 7)
buf [0] |= 1 << (i + 1);
else
@@ -688,6 +685,7 @@ static int ehci_hub_control (
/* resume completed? */
else if (time_after_eq(jiffies,
ehci->reset_done[wIndex])) {
+ clear_bit(wIndex, &ehci->suspended_ports);
set_bit(wIndex, &ehci->port_c_suspend);
ehci->reset_done[wIndex] = 0;
@@ -734,6 +732,9 @@ static int ehci_hub_control (
ehci_readl(ehci, status_reg));
}
+ if (!(temp & (PORT_RESUME|PORT_RESET)))
+ ehci->reset_done[wIndex] = 0;
+
/* transfer dedicated ports to the companion hc */
if ((temp & PORT_CONNECT) &&
test_bit(wIndex, &ehci->companion_ports)) {
@@ -757,8 +758,17 @@ static int ehci_hub_control (
}
if (temp & PORT_PE)
status |= 1 << USB_PORT_FEAT_ENABLE;
- if (temp & (PORT_SUSPEND|PORT_RESUME))
+
+ /* maybe the port was unsuspended without our knowledge */
+ if (temp & (PORT_SUSPEND|PORT_RESUME)) {
status |= 1 << USB_PORT_FEAT_SUSPEND;
+ } else if (test_bit(wIndex, &ehci->suspended_ports)) {
+ clear_bit(wIndex, &ehci->suspended_ports);
+ ehci->reset_done[wIndex] = 0;
+ if (temp & PORT_PE)
+ set_bit(wIndex, &ehci->port_c_suspend);
+ }
+
if (temp & PORT_OC)
status |= 1 << USB_PORT_FEAT_OVER_CURRENT;
if (temp & PORT_RESET)
@@ -803,6 +813,7 @@ static int ehci_hub_control (
|| (temp & PORT_RESET) != 0)
goto error;
ehci_writel(ehci, temp | PORT_SUSPEND, status_reg);
+ set_bit(wIndex, &ehci->suspended_ports);
break;
case USB_PORT_FEAT_POWER:
if (HCS_PPC (ehci->hcs_params))
diff --git a/drivers/usb/host/ehci-ppc-soc.c b/drivers/usb/host/ehci-ppc-soc.c
deleted file mode 100644
index 529590eb4037..000000000000
--- a/drivers/usb/host/ehci-ppc-soc.c
+++ /dev/null
@@ -1,201 +0,0 @@
-/*
- * EHCI HCD (Host Controller Driver) for USB.
- *
- * (C) Copyright 2006-2007 Stefan Roese <sr@denx.de>, DENX Software Engineering
- *
- * Bus Glue for PPC On-Chip EHCI driver
- * Tested on AMCC 440EPx
- *
- * Based on "ehci-au1xxx.c" by K.Boge <karsten.boge@amd.com>
- *
- * This file is licenced under the GPL.
- */
-
-#include <linux/platform_device.h>
-
-extern int usb_disabled(void);
-
-/* called during probe() after chip reset completes */
-static int ehci_ppc_soc_setup(struct usb_hcd *hcd)
-{
- struct ehci_hcd *ehci = hcd_to_ehci(hcd);
- int retval;
-
- retval = ehci_halt(ehci);
- if (retval)
- return retval;
-
- retval = ehci_init(hcd);
- if (retval)
- return retval;
-
- ehci->sbrn = 0x20;
- return ehci_reset(ehci);
-}
-
-/**
- * usb_ehci_ppc_soc_probe - initialize PPC-SoC-based HCDs
- * Context: !in_interrupt()
- *
- * Allocates basic resources for this USB host controller, and
- * then invokes the start() method for the HCD associated with it
- * through the hotplug entry's driver_data.
- *
- */
-int usb_ehci_ppc_soc_probe(const struct hc_driver *driver,
- struct usb_hcd **hcd_out,
- struct platform_device *dev)
-{
- int retval;
- struct usb_hcd *hcd;
- struct ehci_hcd *ehci;
-
- if (dev->resource[1].flags != IORESOURCE_IRQ) {
- pr_debug("resource[1] is not IORESOURCE_IRQ");
- retval = -ENOMEM;
- }
- hcd = usb_create_hcd(driver, &dev->dev, "PPC-SOC EHCI");
- if (!hcd)
- return -ENOMEM;
- hcd->rsrc_start = dev->resource[0].start;
- hcd->rsrc_len = dev->resource[0].end - dev->resource[0].start + 1;
-
- if (!request_mem_region(hcd->rsrc_start, hcd->rsrc_len, hcd_name)) {
- pr_debug("request_mem_region failed");
- retval = -EBUSY;
- goto err1;
- }
-
- hcd->regs = ioremap(hcd->rsrc_start, hcd->rsrc_len);
- if (!hcd->regs) {
- pr_debug("ioremap failed");
- retval = -ENOMEM;
- goto err2;
- }
-
- ehci = hcd_to_ehci(hcd);
- ehci->big_endian_mmio = 1;
- ehci->big_endian_desc = 1;
- ehci->caps = hcd->regs;
- ehci->regs = hcd->regs + HC_LENGTH(ehci_readl(ehci, &ehci->caps->hc_capbase));
-
- /* cache this readonly data; minimize chip reads */
- ehci->hcs_params = ehci_readl(ehci, &ehci->caps->hcs_params);
-
-#if defined(CONFIG_440EPX)
- /*
- * 440EPx Errata USBH_3
- * Fix: Enable Break Memory Transfer (BMT) in INSNREG3
- */
- out_be32((void *)((ulong)(&ehci->regs->command) + 0x8c), (1 << 0));
- ehci_dbg(ehci, "Break Memory Transfer (BMT) has beed enabled!\n");
-#endif
-
- retval = usb_add_hcd(hcd, dev->resource[1].start, IRQF_DISABLED);
- if (retval == 0)
- return retval;
-
- iounmap(hcd->regs);
-err2:
- release_mem_region(hcd->rsrc_start, hcd->rsrc_len);
-err1:
- usb_put_hcd(hcd);
- return retval;
-}
-
-/* may be called without controller electrically present */
-/* may be called with controller, bus, and devices active */
-
-/**
- * usb_ehci_hcd_ppc_soc_remove - shutdown processing for PPC-SoC-based HCDs
- * @dev: USB Host Controller being removed
- * Context: !in_interrupt()
- *
- * Reverses the effect of usb_ehci_hcd_ppc_soc_probe(), first invoking
- * the HCD's stop() method. It is always called from a thread
- * context, normally "rmmod", "apmd", or something similar.
- *
- */
-void usb_ehci_ppc_soc_remove(struct usb_hcd *hcd, struct platform_device *dev)
-{
- usb_remove_hcd(hcd);
- iounmap(hcd->regs);
- release_mem_region(hcd->rsrc_start, hcd->rsrc_len);
- usb_put_hcd(hcd);
-}
-
-static const struct hc_driver ehci_ppc_soc_hc_driver = {
- .description = hcd_name,
- .product_desc = "PPC-SOC EHCI",
- .hcd_priv_size = sizeof(struct ehci_hcd),
-
- /*
- * generic hardware linkage
- */
- .irq = ehci_irq,
- .flags = HCD_MEMORY | HCD_USB2,
-
- /*
- * basic lifecycle operations
- */
- .reset = ehci_ppc_soc_setup,
- .start = ehci_run,
- .stop = ehci_stop,
- .shutdown = ehci_shutdown,
-
- /*
- * managing i/o requests and associated device resources
- */
- .urb_enqueue = ehci_urb_enqueue,
- .urb_dequeue = ehci_urb_dequeue,
- .endpoint_disable = ehci_endpoint_disable,
-
- /*
- * scheduling support
- */
- .get_frame_number = ehci_get_frame,
-
- /*
- * root hub support
- */
- .hub_status_data = ehci_hub_status_data,
- .hub_control = ehci_hub_control,
- .bus_suspend = ehci_bus_suspend,
- .bus_resume = ehci_bus_resume,
- .relinquish_port = ehci_relinquish_port,
- .port_handed_over = ehci_port_handed_over,
-};
-
-static int ehci_hcd_ppc_soc_drv_probe(struct platform_device *pdev)
-{
- struct usb_hcd *hcd = NULL;
- int ret;
-
- pr_debug("In ehci_hcd_ppc_soc_drv_probe\n");
-
- if (usb_disabled())
- return -ENODEV;
-
- /* FIXME we only want one one probe() not two */
- ret = usb_ehci_ppc_soc_probe(&ehci_ppc_soc_hc_driver, &hcd, pdev);
- return ret;
-}
-
-static int ehci_hcd_ppc_soc_drv_remove(struct platform_device *pdev)
-{
- struct usb_hcd *hcd = platform_get_drvdata(pdev);
-
- /* FIXME we only want one one remove() not two */
- usb_ehci_ppc_soc_remove(hcd, pdev);
- return 0;
-}
-
-MODULE_ALIAS("platform:ppc-soc-ehci");
-static struct platform_driver ehci_ppc_soc_driver = {
- .probe = ehci_hcd_ppc_soc_drv_probe,
- .remove = ehci_hcd_ppc_soc_drv_remove,
- .shutdown = usb_hcd_platform_shutdown,
- .driver = {
- .name = "ppc-soc-ehci",
- }
-};
diff --git a/drivers/usb/host/ehci.h b/drivers/usb/host/ehci.h
index b697a13364ec..b11798d17ae5 100644
--- a/drivers/usb/host/ehci.h
+++ b/drivers/usb/host/ehci.h
@@ -99,6 +99,8 @@ struct ehci_hcd { /* one per controller */
owned by the companion during a bus suspend */
unsigned long port_c_suspend; /* which ports have
the change-suspend feature turned on */
+ unsigned long suspended_ports; /* which ports are
+ suspended */
/* per-HC memory pools (could be per-bus, but ...) */
struct dma_pool *qh_pool; /* qh per active urb */
@@ -181,14 +183,16 @@ timer_action (struct ehci_hcd *ehci, enum ehci_timer_action action)
* the async ring; just the I/O watchdog. Note that if a
* SHRINK were pending, OFF would never be requested.
*/
- if (timer_pending(&ehci->watchdog)
- && ((BIT(TIMER_ASYNC_SHRINK) | BIT(TIMER_ASYNC_OFF))
- & ehci->actions))
- return;
+ enum ehci_timer_action oldactions = ehci->actions;
if (!test_and_set_bit (action, &ehci->actions)) {
unsigned long t;
+ if (timer_pending(&ehci->watchdog)
+ && ((BIT(TIMER_ASYNC_SHRINK) | BIT(TIMER_ASYNC_OFF))
+ & oldactions))
+ return;
+
switch (action) {
case TIMER_IO_WATCHDOG:
t = EHCI_IO_JIFFIES;
@@ -204,7 +208,7 @@ timer_action (struct ehci_hcd *ehci, enum ehci_timer_action action)
t = DIV_ROUND_UP(EHCI_SHRINK_FRAMES * HZ, 1000) + 1;
break;
}
- mod_timer(&ehci->watchdog, t + jiffies);
+ mod_timer(&ehci->watchdog, round_jiffies(t + jiffies));
}
}
@@ -604,16 +608,7 @@ ehci_port_speed(struct ehci_hcd *ehci, unsigned int portsc)
/*
* Big-endian read/write functions are arch-specific.
* Other arches can be added if/when they're needed.
- *
- * REVISIT: arch/powerpc now has readl/writel_be, so the
- * definition below can die once the 4xx support is
- * finally ported over.
*/
-#if defined(CONFIG_PPC) && !defined(CONFIG_PPC_MERGE)
-#define readl_be(addr) in_be32((__force unsigned *)addr)
-#define writel_be(val, addr) out_be32((__force unsigned *)addr, val)
-#endif
-
#if defined(CONFIG_ARM) && defined(CONFIG_ARCH_IXP4XX)
#define readl_be(addr) __raw_readl((__force unsigned *)addr)
#define writel_be(val, addr) __raw_writel(val, (__force unsigned *)addr)
diff --git a/drivers/usb/host/hwa-hc.c b/drivers/usb/host/hwa-hc.c
new file mode 100644
index 000000000000..64be4d88df11
--- /dev/null
+++ b/drivers/usb/host/hwa-hc.c
@@ -0,0 +1,925 @@
+/*
+ * Host Wire Adapter:
+ * Driver glue, HWA-specific functions, bridges to WAHC and WUSBHC
+ *
+ * Copyright (C) 2005-2006 Intel Corporation
+ * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License version
+ * 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that 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 Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ *
+ *
+ * The HWA driver is a simple layer that forwards requests to the WAHC
+ * (Wire Adater Host Controller) or WUSBHC (Wireless USB Host
+ * Controller) layers.
+ *
+ * Host Wire Adapter is the 'WUSB 1.0 standard' name for Wireless-USB
+ * Host Controller that is connected to your system via USB (a USB
+ * dongle that implements a USB host...). There is also a Device Wired
+ * Adaptor, DWA (Wireless USB hub) that uses the same mechanism for
+ * transferring data (it is after all a USB host connected via
+ * Wireless USB), we have a common layer called Wire Adapter Host
+ * Controller that does all the hard work. The WUSBHC (Wireless USB
+ * Host Controller) is the part common to WUSB Host Controllers, the
+ * HWA and the PCI-based one, that is implemented following the WHCI
+ * spec. All these layers are implemented in ../wusbcore.
+ *
+ * The main functions are hwahc_op_urb_{en,de}queue(), that pass the
+ * job of converting a URB to a Wire Adapter
+ *
+ * Entry points:
+ *
+ * hwahc_driver_*() Driver initialization, registration and
+ * teardown.
+ *
+ * hwahc_probe() New device came up, create an instance for
+ * it [from device enumeration].
+ *
+ * hwahc_disconnect() Remove device instance [from device
+ * enumeration].
+ *
+ * [__]hwahc_op_*() Host-Wire-Adaptor specific functions for
+ * starting/stopping/etc (some might be made also
+ * DWA).
+ */
+#include <linux/kernel.h>
+#include <linux/version.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/workqueue.h>
+#include <linux/wait.h>
+#include <linux/completion.h>
+#include "../wusbcore/wa-hc.h"
+#include "../wusbcore/wusbhc.h"
+
+#define D_LOCAL 0
+#include <linux/uwb/debug.h>
+
+struct hwahc {
+ struct wusbhc wusbhc; /* has to be 1st */
+ struct wahc wa;
+ u8 buffer[16]; /* for misc usb transactions */
+};
+
+/**
+ * FIXME should be wusbhc
+ *
+ * NOTE: we need to cache the Cluster ID because later...there is no
+ * way to get it :)
+ */
+static int __hwahc_set_cluster_id(struct hwahc *hwahc, u8 cluster_id)
+{
+ int result;
+ struct wusbhc *wusbhc = &hwahc->wusbhc;
+ struct wahc *wa = &hwahc->wa;
+ struct device *dev = &wa->usb_iface->dev;
+
+ result = usb_control_msg(wa->usb_dev, usb_sndctrlpipe(wa->usb_dev, 0),
+ WUSB_REQ_SET_CLUSTER_ID,
+ USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
+ cluster_id,
+ wa->usb_iface->cur_altsetting->desc.bInterfaceNumber,
+ NULL, 0, 1000 /* FIXME: arbitrary */);
+ if (result < 0)
+ dev_err(dev, "Cannot set WUSB Cluster ID to 0x%02x: %d\n",
+ cluster_id, result);
+ else
+ wusbhc->cluster_id = cluster_id;
+ dev_info(dev, "Wireless USB Cluster ID set to 0x%02x\n", cluster_id);
+ return result;
+}
+
+static int __hwahc_op_set_num_dnts(struct wusbhc *wusbhc, u8 interval, u8 slots)
+{
+ struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc);
+ struct wahc *wa = &hwahc->wa;
+
+ return usb_control_msg(wa->usb_dev, usb_sndctrlpipe(wa->usb_dev, 0),
+ WUSB_REQ_SET_NUM_DNTS,
+ USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
+ interval << 8 | slots,
+ wa->usb_iface->cur_altsetting->desc.bInterfaceNumber,
+ NULL, 0, 1000 /* FIXME: arbitrary */);
+}
+
+/*
+ * Reset a WUSB host controller and wait for it to complete doing it.
+ *
+ * @usb_hcd: Pointer to WUSB Host Controller instance.
+ *
+ */
+static int hwahc_op_reset(struct usb_hcd *usb_hcd)
+{
+ int result;
+ struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd);
+ struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc);
+ struct device *dev = &hwahc->wa.usb_iface->dev;
+
+ d_fnstart(4, dev, "(hwahc %p)\n", hwahc);
+ mutex_lock(&wusbhc->mutex);
+ wa_nep_disarm(&hwahc->wa);
+ result = __wa_set_feature(&hwahc->wa, WA_RESET);
+ if (result < 0) {
+ dev_err(dev, "error commanding HC to reset: %d\n", result);
+ goto error_unlock;
+ }
+ d_printf(3, dev, "reset: waiting for device to change state\n");
+ result = __wa_wait_status(&hwahc->wa, WA_STATUS_RESETTING, 0);
+ if (result < 0) {
+ dev_err(dev, "error waiting for HC to reset: %d\n", result);
+ goto error_unlock;
+ }
+error_unlock:
+ mutex_unlock(&wusbhc->mutex);
+ d_fnend(4, dev, "(hwahc %p) = %d\n", hwahc, result);
+ return result;
+}
+
+/*
+ * FIXME: break this function up
+ */
+static int hwahc_op_start(struct usb_hcd *usb_hcd)
+{
+ u8 addr;
+ int result;
+ struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd);
+ struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc);
+ struct device *dev = &hwahc->wa.usb_iface->dev;
+
+ /* Set up a Host Info WUSB Information Element */
+ d_fnstart(4, dev, "(hwahc %p)\n", hwahc);
+ result = -ENOSPC;
+ mutex_lock(&wusbhc->mutex);
+ /* Start the numbering from the top so that the bottom
+ * range of the unauth addr space is used for devices,
+ * the top for HCs; use 0xfe - RC# */
+ addr = wusb_cluster_id_get();
+ if (addr == 0)
+ goto error_cluster_id_get;
+ result = __hwahc_set_cluster_id(hwahc, addr);
+ if (result < 0)
+ goto error_set_cluster_id;
+
+ result = wa_nep_arm(&hwahc->wa, GFP_KERNEL);
+ if (result < 0) {
+ dev_err(dev, "cannot listen to notifications: %d\n", result);
+ goto error_stop;
+ }
+ usb_hcd->uses_new_polling = 1;
+ usb_hcd->poll_rh = 1;
+ usb_hcd->state = HC_STATE_RUNNING;
+ result = 0;
+out:
+ mutex_unlock(&wusbhc->mutex);
+ d_fnend(4, dev, "(hwahc %p) = %d\n", hwahc, result);
+ return result;
+
+error_stop:
+ __wa_stop(&hwahc->wa);
+error_set_cluster_id:
+ wusb_cluster_id_put(wusbhc->cluster_id);
+error_cluster_id_get:
+ goto out;
+
+}
+
+/*
+ * FIXME: break this function up
+ */
+static int __hwahc_op_wusbhc_start(struct wusbhc *wusbhc)
+{
+ int result;
+ struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc);
+ struct device *dev = &hwahc->wa.usb_iface->dev;
+
+ /* Set up a Host Info WUSB Information Element */
+ d_fnstart(4, dev, "(hwahc %p)\n", hwahc);
+ result = -ENOSPC;
+
+ result = __wa_set_feature(&hwahc->wa, WA_ENABLE);
+ if (result < 0) {
+ dev_err(dev, "error commanding HC to start: %d\n", result);
+ goto error_stop;
+ }
+ result = __wa_wait_status(&hwahc->wa, WA_ENABLE, WA_ENABLE);
+ if (result < 0) {
+ dev_err(dev, "error waiting for HC to start: %d\n", result);
+ goto error_stop;
+ }
+ result = 0;
+out:
+ d_fnend(4, dev, "(hwahc %p) = %d\n", hwahc, result);
+ return result;
+
+error_stop:
+ result = __wa_clear_feature(&hwahc->wa, WA_ENABLE);
+ goto out;
+}
+
+static int hwahc_op_suspend(struct usb_hcd *usb_hcd, pm_message_t msg)
+{
+ struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd);
+ struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc);
+ dev_err(wusbhc->dev, "%s (%p [%p], 0x%lx) UNIMPLEMENTED\n", __func__,
+ usb_hcd, hwahc, *(unsigned long *) &msg);
+ return -ENOSYS;
+}
+
+static int hwahc_op_resume(struct usb_hcd *usb_hcd)
+{
+ struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd);
+ struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc);
+
+ dev_err(wusbhc->dev, "%s (%p [%p]) UNIMPLEMENTED\n", __func__,
+ usb_hcd, hwahc);
+ return -ENOSYS;
+}
+
+static void __hwahc_op_wusbhc_stop(struct wusbhc *wusbhc)
+{
+ int result;
+ struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc);
+ struct device *dev = &hwahc->wa.usb_iface->dev;
+
+ d_fnstart(4, dev, "(hwahc %p)\n", hwahc);
+ /* Nothing for now */
+ d_fnend(4, dev, "(hwahc %p) = %d\n", hwahc, result);
+ return;
+}
+
+/*
+ * No need to abort pipes, as when this is called, all the children
+ * has been disconnected and that has done it [through
+ * usb_disable_interface() -> usb_disable_endpoint() ->
+ * hwahc_op_ep_disable() - >rpipe_ep_disable()].
+ */
+static void hwahc_op_stop(struct usb_hcd *usb_hcd)
+{
+ int result;
+ struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd);
+ struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc);
+ struct wahc *wa = &hwahc->wa;
+ struct device *dev = &wa->usb_iface->dev;
+
+ d_fnstart(4, dev, "(hwahc %p)\n", hwahc);
+ mutex_lock(&wusbhc->mutex);
+ wusbhc_stop(wusbhc);
+ wa_nep_disarm(&hwahc->wa);
+ result = __wa_stop(&hwahc->wa);
+ wusb_cluster_id_put(wusbhc->cluster_id);
+ mutex_unlock(&wusbhc->mutex);
+ d_fnend(4, dev, "(hwahc %p) = %d\n", hwahc, result);
+ return;
+}
+
+static int hwahc_op_get_frame_number(struct usb_hcd *usb_hcd)
+{
+ struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd);
+ struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc);
+
+ dev_err(wusbhc->dev, "%s (%p [%p]) UNIMPLEMENTED\n", __func__,
+ usb_hcd, hwahc);
+ return -ENOSYS;
+}
+
+static int hwahc_op_urb_enqueue(struct usb_hcd *usb_hcd, struct urb *urb,
+ gfp_t gfp)
+{
+ struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd);
+ struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc);
+
+ return wa_urb_enqueue(&hwahc->wa, urb->ep, urb, gfp);
+}
+
+static int hwahc_op_urb_dequeue(struct usb_hcd *usb_hcd, struct urb *urb,
+ int status)
+{
+ struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd);
+ struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc);
+
+ return wa_urb_dequeue(&hwahc->wa, urb);
+}
+
+/*
+ * Release resources allocated for an endpoint
+ *
+ * If there is an associated rpipe to this endpoint, go ahead and put it.
+ */
+static void hwahc_op_endpoint_disable(struct usb_hcd *usb_hcd,
+ struct usb_host_endpoint *ep)
+{
+ struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd);
+ struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc);
+
+ rpipe_ep_disable(&hwahc->wa, ep);
+}
+
+/*
+ * Set the UWB MAS allocation for the WUSB cluster
+ *
+ * @stream_index: stream to use (-1 for cancelling the allocation)
+ * @mas: mas bitmap to use
+ */
+static int __hwahc_op_bwa_set(struct wusbhc *wusbhc, s8 stream_index,
+ const struct uwb_mas_bm *mas)
+{
+ int result;
+ struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc);
+ struct wahc *wa = &hwahc->wa;
+ struct device *dev = &wa->usb_iface->dev;
+ u8 mas_le[UWB_NUM_MAS/8];
+
+ /* Set the stream index */
+ result = usb_control_msg(wa->usb_dev, usb_sndctrlpipe(wa->usb_dev, 0),
+ WUSB_REQ_SET_STREAM_IDX,
+ USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
+ stream_index,
+ wa->usb_iface->cur_altsetting->desc.bInterfaceNumber,
+ NULL, 0, 1000 /* FIXME: arbitrary */);
+ if (result < 0) {
+ dev_err(dev, "Cannot set WUSB stream index: %d\n", result);
+ goto out;
+ }
+ uwb_mas_bm_copy_le(mas_le, mas);
+ /* Set the MAS allocation */
+ result = usb_control_msg(wa->usb_dev, usb_sndctrlpipe(wa->usb_dev, 0),
+ WUSB_REQ_SET_WUSB_MAS,
+ USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
+ 0, wa->usb_iface->cur_altsetting->desc.bInterfaceNumber,
+ mas_le, 32, 1000 /* FIXME: arbitrary */);
+ if (result < 0)
+ dev_err(dev, "Cannot set WUSB MAS allocation: %d\n", result);
+out:
+ return result;
+}
+
+/*
+ * Add an IE to the host's MMC
+ *
+ * @interval: See WUSB1.0[8.5.3.1]
+ * @repeat_cnt: See WUSB1.0[8.5.3.1]
+ * @handle: See WUSB1.0[8.5.3.1]
+ * @wuie: Pointer to the header of the WUSB IE data to add.
+ * MUST BE allocated in a kmalloc buffer (no stack or
+ * vmalloc).
+ *
+ * NOTE: the format of the WUSB IEs for MMCs are different to the
+ * normal MBOA MAC IEs (IE Id + Length in MBOA MAC vs. Length +
+ * Id in WUSB IEs). Standards...you gotta love'em.
+ */
+static int __hwahc_op_mmcie_add(struct wusbhc *wusbhc, u8 interval,
+ u8 repeat_cnt, u8 handle,
+ struct wuie_hdr *wuie)
+{
+ struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc);
+ struct wahc *wa = &hwahc->wa;
+ u8 iface_no = wa->usb_iface->cur_altsetting->desc.bInterfaceNumber;
+
+ return usb_control_msg(wa->usb_dev, usb_sndctrlpipe(wa->usb_dev, 0),
+ WUSB_REQ_ADD_MMC_IE,
+ USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
+ interval << 8 | repeat_cnt,
+ handle << 8 | iface_no,
+ wuie, wuie->bLength, 1000 /* FIXME: arbitrary */);
+}
+
+/*
+ * Remove an IE to the host's MMC
+ *
+ * @handle: See WUSB1.0[8.5.3.1]
+ */
+static int __hwahc_op_mmcie_rm(struct wusbhc *wusbhc, u8 handle)
+{
+ struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc);
+ struct wahc *wa = &hwahc->wa;
+ u8 iface_no = wa->usb_iface->cur_altsetting->desc.bInterfaceNumber;
+ return usb_control_msg(wa->usb_dev, usb_sndctrlpipe(wa->usb_dev, 0),
+ WUSB_REQ_REMOVE_MMC_IE,
+ USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
+ 0, handle << 8 | iface_no,
+ NULL, 0, 1000 /* FIXME: arbitrary */);
+}
+
+/*
+ * Update device information for a given fake port
+ *
+ * @port_idx: Fake port to which device is connected (wusbhc index, not
+ * USB port number).
+ */
+static int __hwahc_op_dev_info_set(struct wusbhc *wusbhc,
+ struct wusb_dev *wusb_dev)
+{
+ struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc);
+ struct wahc *wa = &hwahc->wa;
+ u8 iface_no = wa->usb_iface->cur_altsetting->desc.bInterfaceNumber;
+ struct hwa_dev_info *dev_info;
+ int ret;
+
+ /* fill out the Device Info buffer and send it */
+ dev_info = kzalloc(sizeof(struct hwa_dev_info), GFP_KERNEL);
+ if (!dev_info)
+ return -ENOMEM;
+ uwb_mas_bm_copy_le(dev_info->bmDeviceAvailability,
+ &wusb_dev->availability);
+ dev_info->bDeviceAddress = wusb_dev->addr;
+
+ /*
+ * If the descriptors haven't been read yet, use a default PHY
+ * rate of 53.3 Mbit/s only. The correct value will be used
+ * when this will be called again as part of the
+ * authentication process (which occurs after the descriptors
+ * have been read).
+ */
+ if (wusb_dev->wusb_cap_descr)
+ dev_info->wPHYRates = wusb_dev->wusb_cap_descr->wPHYRates;
+ else
+ dev_info->wPHYRates = cpu_to_le16(USB_WIRELESS_PHY_53);
+
+ ret = usb_control_msg(wa->usb_dev, usb_sndctrlpipe(wa->usb_dev, 0),
+ WUSB_REQ_SET_DEV_INFO,
+ USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
+ 0, wusb_dev->port_idx << 8 | iface_no,
+ dev_info, sizeof(struct hwa_dev_info),
+ 1000 /* FIXME: arbitrary */);
+ kfree(dev_info);
+ return ret;
+}
+
+/*
+ * Set host's idea of which encryption (and key) method to use when
+ * talking to ad evice on a given port.
+ *
+ * If key is NULL, it means disable encryption for that "virtual port"
+ * (used when we disconnect).
+ */
+static int __hwahc_dev_set_key(struct wusbhc *wusbhc, u8 port_idx, u32 tkid,
+ const void *key, size_t key_size,
+ u8 key_idx)
+{
+ int result = -ENOMEM;
+ struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc);
+ struct wahc *wa = &hwahc->wa;
+ u8 iface_no = wa->usb_iface->cur_altsetting->desc.bInterfaceNumber;
+ struct usb_key_descriptor *keyd;
+ size_t keyd_len;
+
+ keyd_len = sizeof(*keyd) + key_size;
+ keyd = kzalloc(keyd_len, GFP_KERNEL);
+ if (keyd == NULL)
+ return -ENOMEM;
+
+ keyd->bLength = keyd_len;
+ keyd->bDescriptorType = USB_DT_KEY;
+ keyd->tTKID[0] = (tkid >> 0) & 0xff;
+ keyd->tTKID[1] = (tkid >> 8) & 0xff;
+ keyd->tTKID[2] = (tkid >> 16) & 0xff;
+ memcpy(keyd->bKeyData, key, key_size);
+
+ result = usb_control_msg(wa->usb_dev, usb_sndctrlpipe(wa->usb_dev, 0),
+ USB_REQ_SET_DESCRIPTOR,
+ USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
+ USB_DT_KEY << 8 | key_idx,
+ port_idx << 8 | iface_no,
+ keyd, keyd_len, 1000 /* FIXME: arbitrary */);
+
+ memset(keyd, 0, sizeof(*keyd)); /* clear keys etc. */
+ kfree(keyd);
+ return result;
+}
+
+/*
+ * Set host's idea of which encryption (and key) method to use when
+ * talking to ad evice on a given port.
+ *
+ * If key is NULL, it means disable encryption for that "virtual port"
+ * (used when we disconnect).
+ */
+static int __hwahc_op_set_ptk(struct wusbhc *wusbhc, u8 port_idx, u32 tkid,
+ const void *key, size_t key_size)
+{
+ int result = -ENOMEM;
+ struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc);
+ struct wahc *wa = &hwahc->wa;
+ u8 iface_no = wa->usb_iface->cur_altsetting->desc.bInterfaceNumber;
+ u8 encryption_value;
+
+ /* Tell the host which key to use to talk to the device */
+ if (key) {
+ u8 key_idx = wusb_key_index(0, WUSB_KEY_INDEX_TYPE_PTK,
+ WUSB_KEY_INDEX_ORIGINATOR_HOST);
+
+ result = __hwahc_dev_set_key(wusbhc, port_idx, tkid,
+ key, key_size, key_idx);
+ if (result < 0)
+ goto error_set_key;
+ encryption_value = wusbhc->ccm1_etd->bEncryptionValue;
+ } else {
+ /* FIXME: this should come from wusbhc->etd[UNSECURE].value */
+ encryption_value = 0;
+ }
+
+ /* Set the encryption type for commmunicating with the device */
+ result = usb_control_msg(wa->usb_dev, usb_sndctrlpipe(wa->usb_dev, 0),
+ USB_REQ_SET_ENCRYPTION,
+ USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
+ encryption_value, port_idx << 8 | iface_no,
+ NULL, 0, 1000 /* FIXME: arbitrary */);
+ if (result < 0)
+ dev_err(wusbhc->dev, "Can't set host's WUSB encryption for "
+ "port index %u to %s (value %d): %d\n", port_idx,
+ wusb_et_name(wusbhc->ccm1_etd->bEncryptionType),
+ wusbhc->ccm1_etd->bEncryptionValue, result);
+error_set_key:
+ return result;
+}
+
+/*
+ * Set host's GTK key
+ */
+static int __hwahc_op_set_gtk(struct wusbhc *wusbhc, u32 tkid,
+ const void *key, size_t key_size)
+{
+ u8 key_idx = wusb_key_index(0, WUSB_KEY_INDEX_TYPE_GTK,
+ WUSB_KEY_INDEX_ORIGINATOR_HOST);
+
+ return __hwahc_dev_set_key(wusbhc, 0, tkid, key, key_size, key_idx);
+}
+
+/*
+ * Get the Wire Adapter class-specific descriptor
+ *
+ * NOTE: this descriptor comes with the big bundled configuration
+ * descriptor that includes the interfaces' and endpoints', so
+ * we just look for it in the cached copy kept by the USB stack.
+ *
+ * NOTE2: We convert LE fields to CPU order.
+ */
+static int wa_fill_descr(struct wahc *wa)
+{
+ int result;
+ struct device *dev = &wa->usb_iface->dev;
+ char *itr;
+ struct usb_device *usb_dev = wa->usb_dev;
+ struct usb_descriptor_header *hdr;
+ struct usb_wa_descriptor *wa_descr;
+ size_t itr_size, actconfig_idx;
+
+ actconfig_idx = (usb_dev->actconfig - usb_dev->config) /
+ sizeof(usb_dev->config[0]);
+ itr = usb_dev->rawdescriptors[actconfig_idx];
+ itr_size = le16_to_cpu(usb_dev->actconfig->desc.wTotalLength);
+ while (itr_size >= sizeof(*hdr)) {
+ hdr = (struct usb_descriptor_header *) itr;
+ d_printf(3, dev, "Extra device descriptor: "
+ "type %02x/%u bytes @ %zu (%zu left)\n",
+ hdr->bDescriptorType, hdr->bLength,
+ (itr - usb_dev->rawdescriptors[actconfig_idx]),
+ itr_size);
+ if (hdr->bDescriptorType == USB_DT_WIRE_ADAPTER)
+ goto found;
+ itr += hdr->bLength;
+ itr_size -= hdr->bLength;
+ }
+ dev_err(dev, "cannot find Wire Adapter Class descriptor\n");
+ return -ENODEV;
+
+found:
+ result = -EINVAL;
+ if (hdr->bLength > itr_size) { /* is it available? */
+ dev_err(dev, "incomplete Wire Adapter Class descriptor "
+ "(%zu bytes left, %u needed)\n",
+ itr_size, hdr->bLength);
+ goto error;
+ }
+ if (hdr->bLength < sizeof(*wa->wa_descr)) {
+ dev_err(dev, "short Wire Adapter Class descriptor\n");
+ goto error;
+ }
+ wa->wa_descr = wa_descr = (struct usb_wa_descriptor *) hdr;
+ /* Make LE fields CPU order */
+ wa_descr->bcdWAVersion = le16_to_cpu(wa_descr->bcdWAVersion);
+ wa_descr->wNumRPipes = le16_to_cpu(wa_descr->wNumRPipes);
+ wa_descr->wRPipeMaxBlock = le16_to_cpu(wa_descr->wRPipeMaxBlock);
+ if (wa_descr->bcdWAVersion > 0x0100)
+ dev_warn(dev, "Wire Adapter v%d.%d newer than groked v1.0\n",
+ wa_descr->bcdWAVersion & 0xff00 >> 8,
+ wa_descr->bcdWAVersion & 0x00ff);
+ result = 0;
+error:
+ return result;
+}
+
+static struct hc_driver hwahc_hc_driver = {
+ .description = "hwa-hcd",
+ .product_desc = "Wireless USB HWA host controller",
+ .hcd_priv_size = sizeof(struct hwahc) - sizeof(struct usb_hcd),
+ .irq = NULL, /* FIXME */
+ .flags = HCD_USB2, /* FIXME */
+ .reset = hwahc_op_reset,
+ .start = hwahc_op_start,
+ .pci_suspend = hwahc_op_suspend,
+ .pci_resume = hwahc_op_resume,
+ .stop = hwahc_op_stop,
+ .get_frame_number = hwahc_op_get_frame_number,
+ .urb_enqueue = hwahc_op_urb_enqueue,
+ .urb_dequeue = hwahc_op_urb_dequeue,
+ .endpoint_disable = hwahc_op_endpoint_disable,
+
+ .hub_status_data = wusbhc_rh_status_data,
+ .hub_control = wusbhc_rh_control,
+ .bus_suspend = wusbhc_rh_suspend,
+ .bus_resume = wusbhc_rh_resume,
+ .start_port_reset = wusbhc_rh_start_port_reset,
+};
+
+static int hwahc_security_create(struct hwahc *hwahc)
+{
+ int result;
+ struct wusbhc *wusbhc = &hwahc->wusbhc;
+ struct usb_device *usb_dev = hwahc->wa.usb_dev;
+ struct device *dev = &usb_dev->dev;
+ struct usb_security_descriptor *secd;
+ struct usb_encryption_descriptor *etd;
+ void *itr, *top;
+ size_t itr_size, needed, bytes;
+ u8 index;
+ char buf[64];
+
+ /* Find the host's security descriptors in the config descr bundle */
+ index = (usb_dev->actconfig - usb_dev->config) /
+ sizeof(usb_dev->config[0]);
+ itr = usb_dev->rawdescriptors[index];
+ itr_size = le16_to_cpu(usb_dev->actconfig->desc.wTotalLength);
+ top = itr + itr_size;
+ result = __usb_get_extra_descriptor(usb_dev->rawdescriptors[index],
+ le16_to_cpu(usb_dev->actconfig->desc.wTotalLength),
+ USB_DT_SECURITY, (void **) &secd);
+ if (result == -1) {
+ dev_warn(dev, "BUG? WUSB host has no security descriptors\n");
+ return 0;
+ }
+ needed = sizeof(*secd);
+ if (top - (void *)secd < needed) {
+ dev_err(dev, "BUG? Not enough data to process security "
+ "descriptor header (%zu bytes left vs %zu needed)\n",
+ top - (void *) secd, needed);
+ return 0;
+ }
+ needed = le16_to_cpu(secd->wTotalLength);
+ if (top - (void *)secd < needed) {
+ dev_err(dev, "BUG? Not enough data to process security "
+ "descriptors (%zu bytes left vs %zu needed)\n",
+ top - (void *) secd, needed);
+ return 0;
+ }
+ /* Walk over the sec descriptors and store CCM1's on wusbhc */
+ itr = (void *) secd + sizeof(*secd);
+ top = (void *) secd + le16_to_cpu(secd->wTotalLength);
+ index = 0;
+ bytes = 0;
+ while (itr < top) {
+ etd = itr;
+ if (top - itr < sizeof(*etd)) {
+ dev_err(dev, "BUG: bad host security descriptor; "
+ "not enough data (%zu vs %zu left)\n",
+ top - itr, sizeof(*etd));
+ break;
+ }
+ if (etd->bLength < sizeof(*etd)) {
+ dev_err(dev, "BUG: bad host encryption descriptor; "
+ "descriptor is too short "
+ "(%zu vs %zu needed)\n",
+ (size_t)etd->bLength, sizeof(*etd));
+ break;
+ }
+ itr += etd->bLength;
+ bytes += snprintf(buf + bytes, sizeof(buf) - bytes,
+ "%s (0x%02x) ",
+ wusb_et_name(etd->bEncryptionType),
+ etd->bEncryptionValue);
+ wusbhc->ccm1_etd = etd;
+ }
+ dev_info(dev, "supported encryption types: %s\n", buf);
+ if (wusbhc->ccm1_etd == NULL) {
+ dev_err(dev, "E: host doesn't support CCM-1 crypto\n");
+ return 0;
+ }
+ /* Pretty print what we support */
+ return 0;
+}
+
+static void hwahc_security_release(struct hwahc *hwahc)
+{
+ /* nothing to do here so far... */
+}
+
+static int hwahc_create(struct hwahc *hwahc, struct usb_interface *iface)
+{
+ int result;
+ struct device *dev = &iface->dev;
+ struct wusbhc *wusbhc = &hwahc->wusbhc;
+ struct wahc *wa = &hwahc->wa;
+ struct usb_device *usb_dev = interface_to_usbdev(iface);
+
+ wa->usb_dev = usb_get_dev(usb_dev); /* bind the USB device */
+ wa->usb_iface = usb_get_intf(iface);
+ wusbhc->dev = dev;
+ wusbhc->uwb_rc = uwb_rc_get_by_grandpa(iface->dev.parent);
+ if (wusbhc->uwb_rc == NULL) {
+ result = -ENODEV;
+ dev_err(dev, "Cannot get associated UWB Host Controller\n");
+ goto error_rc_get;
+ }
+ result = wa_fill_descr(wa); /* Get the device descriptor */
+ if (result < 0)
+ goto error_fill_descriptor;
+ if (wa->wa_descr->bNumPorts > USB_MAXCHILDREN) {
+ dev_err(dev, "FIXME: USB_MAXCHILDREN too low for WUSB "
+ "adapter (%u ports)\n", wa->wa_descr->bNumPorts);
+ wusbhc->ports_max = USB_MAXCHILDREN;
+ } else {
+ wusbhc->ports_max = wa->wa_descr->bNumPorts;
+ }
+ wusbhc->mmcies_max = wa->wa_descr->bNumMMCIEs;
+ wusbhc->start = __hwahc_op_wusbhc_start;
+ wusbhc->stop = __hwahc_op_wusbhc_stop;
+ wusbhc->mmcie_add = __hwahc_op_mmcie_add;
+ wusbhc->mmcie_rm = __hwahc_op_mmcie_rm;
+ wusbhc->dev_info_set = __hwahc_op_dev_info_set;
+ wusbhc->bwa_set = __hwahc_op_bwa_set;
+ wusbhc->set_num_dnts = __hwahc_op_set_num_dnts;
+ wusbhc->set_ptk = __hwahc_op_set_ptk;
+ wusbhc->set_gtk = __hwahc_op_set_gtk;
+ result = hwahc_security_create(hwahc);
+ if (result < 0) {
+ dev_err(dev, "Can't initialize security: %d\n", result);
+ goto error_security_create;
+ }
+ wa->wusb = wusbhc; /* FIXME: ugly, need to fix */
+ result = wusbhc_create(&hwahc->wusbhc);
+ if (result < 0) {
+ dev_err(dev, "Can't create WUSB HC structures: %d\n", result);
+ goto error_wusbhc_create;
+ }
+ result = wa_create(&hwahc->wa, iface);
+ if (result < 0)
+ goto error_wa_create;
+ return 0;
+
+error_wa_create:
+ wusbhc_destroy(&hwahc->wusbhc);
+error_wusbhc_create:
+ /* WA Descr fill allocs no resources */
+error_security_create:
+error_fill_descriptor:
+ uwb_rc_put(wusbhc->uwb_rc);
+error_rc_get:
+ usb_put_intf(iface);
+ usb_put_dev(usb_dev);
+ return result;
+}
+
+static void hwahc_destroy(struct hwahc *hwahc)
+{
+ struct wusbhc *wusbhc = &hwahc->wusbhc;
+
+ d_fnstart(1, NULL, "(hwahc %p)\n", hwahc);
+ mutex_lock(&wusbhc->mutex);
+ __wa_destroy(&hwahc->wa);
+ wusbhc_destroy(&hwahc->wusbhc);
+ hwahc_security_release(hwahc);
+ hwahc->wusbhc.dev = NULL;
+ uwb_rc_put(wusbhc->uwb_rc);
+ usb_put_intf(hwahc->wa.usb_iface);
+ usb_put_dev(hwahc->wa.usb_dev);
+ mutex_unlock(&wusbhc->mutex);
+ d_fnend(1, NULL, "(hwahc %p) = void\n", hwahc);
+}
+
+static void hwahc_init(struct hwahc *hwahc)
+{
+ wa_init(&hwahc->wa);
+}
+
+static int hwahc_probe(struct usb_interface *usb_iface,
+ const struct usb_device_id *id)
+{
+ int result;
+ struct usb_hcd *usb_hcd;
+ struct wusbhc *wusbhc;
+ struct hwahc *hwahc;
+ struct device *dev = &usb_iface->dev;
+
+ d_fnstart(4, dev, "(%p, %p)\n", usb_iface, id);
+ result = -ENOMEM;
+ usb_hcd = usb_create_hcd(&hwahc_hc_driver, &usb_iface->dev, "wusb-hwa");
+ if (usb_hcd == NULL) {
+ dev_err(dev, "unable to allocate instance\n");
+ goto error_alloc;
+ }
+ usb_hcd->wireless = 1;
+ usb_hcd->flags |= HCD_FLAG_SAW_IRQ;
+ wusbhc = usb_hcd_to_wusbhc(usb_hcd);
+ hwahc = container_of(wusbhc, struct hwahc, wusbhc);
+ hwahc_init(hwahc);
+ result = hwahc_create(hwahc, usb_iface);
+ if (result < 0) {
+ dev_err(dev, "Cannot initialize internals: %d\n", result);
+ goto error_hwahc_create;
+ }
+ result = usb_add_hcd(usb_hcd, 0, 0);
+ if (result < 0) {
+ dev_err(dev, "Cannot add HCD: %d\n", result);
+ goto error_add_hcd;
+ }
+ result = wusbhc_b_create(&hwahc->wusbhc);
+ if (result < 0) {
+ dev_err(dev, "Cannot setup phase B of WUSBHC: %d\n", result);
+ goto error_wusbhc_b_create;
+ }
+ d_fnend(4, dev, "(%p, %p) = 0\n", usb_iface, id);
+ return 0;
+
+error_wusbhc_b_create:
+ usb_remove_hcd(usb_hcd);
+error_add_hcd:
+ hwahc_destroy(hwahc);
+error_hwahc_create:
+ usb_put_hcd(usb_hcd);
+error_alloc:
+ d_fnend(4, dev, "(%p, %p) = %d\n", usb_iface, id, result);
+ return result;
+}
+
+static void hwahc_disconnect(struct usb_interface *usb_iface)
+{
+ struct usb_hcd *usb_hcd;
+ struct wusbhc *wusbhc;
+ struct hwahc *hwahc;
+
+ usb_hcd = usb_get_intfdata(usb_iface);
+ wusbhc = usb_hcd_to_wusbhc(usb_hcd);
+ hwahc = container_of(wusbhc, struct hwahc, wusbhc);
+
+ d_fnstart(1, NULL, "(hwahc %p [usb_iface %p])\n", hwahc, usb_iface);
+ wusbhc_b_destroy(&hwahc->wusbhc);
+ usb_remove_hcd(usb_hcd);
+ hwahc_destroy(hwahc);
+ usb_put_hcd(usb_hcd);
+ d_fnend(1, NULL, "(hwahc %p [usb_iface %p]) = void\n", hwahc,
+ usb_iface);
+}
+
+/** USB device ID's that we handle */
+static struct usb_device_id hwahc_id_table[] = {
+ /* FIXME: use class labels for this */
+ { USB_INTERFACE_INFO(0xe0, 0x02, 0x01), },
+ {},
+};
+MODULE_DEVICE_TABLE(usb, hwahc_id_table);
+
+static struct usb_driver hwahc_driver = {
+ .name = "hwa-hc",
+ .probe = hwahc_probe,
+ .disconnect = hwahc_disconnect,
+ .id_table = hwahc_id_table,
+};
+
+static int __init hwahc_driver_init(void)
+{
+ int result;
+ result = usb_register(&hwahc_driver);
+ if (result < 0) {
+ printk(KERN_ERR "WA-CDS: Cannot register USB driver: %d\n",
+ result);
+ goto error_usb_register;
+ }
+ return 0;
+
+error_usb_register:
+ return result;
+
+}
+module_init(hwahc_driver_init);
+
+static void __exit hwahc_driver_exit(void)
+{
+ usb_deregister(&hwahc_driver);
+}
+module_exit(hwahc_driver_exit);
+
+
+MODULE_AUTHOR("Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>");
+MODULE_DESCRIPTION("Host Wired Adapter USB Host Control Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/usb/host/isp116x-hcd.c b/drivers/usb/host/isp116x-hcd.c
index ce1ca0ba0515..4dda31b26892 100644
--- a/drivers/usb/host/isp116x-hcd.c
+++ b/drivers/usb/host/isp116x-hcd.c
@@ -1562,11 +1562,12 @@ static int __devinit isp116x_probe(struct platform_device *pdev)
{
struct usb_hcd *hcd;
struct isp116x *isp116x;
- struct resource *addr, *data;
+ struct resource *addr, *data, *ires;
void __iomem *addr_reg;
void __iomem *data_reg;
int irq;
int ret = 0;
+ unsigned long irqflags;
if (pdev->num_resources < 3) {
ret = -ENODEV;
@@ -1575,12 +1576,16 @@ static int __devinit isp116x_probe(struct platform_device *pdev)
data = platform_get_resource(pdev, IORESOURCE_MEM, 0);
addr = platform_get_resource(pdev, IORESOURCE_MEM, 1);
- irq = platform_get_irq(pdev, 0);
- if (!addr || !data || irq < 0) {
+ ires = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
+
+ if (!addr || !data || !ires) {
ret = -ENODEV;
goto err1;
}
+ irq = ires->start;
+ irqflags = ires->flags & IRQF_TRIGGER_MASK;
+
if (pdev->dev.dma_mask) {
DBG("DMA not supported\n");
ret = -EINVAL;
@@ -1634,7 +1639,7 @@ static int __devinit isp116x_probe(struct platform_device *pdev)
goto err6;
}
- ret = usb_add_hcd(hcd, irq, IRQF_DISABLED);
+ ret = usb_add_hcd(hcd, irq, irqflags | IRQF_DISABLED);
if (ret)
goto err6;
diff --git a/drivers/usb/host/isp1760-if.c b/drivers/usb/host/isp1760-if.c
index 051ef7b6bdc6..af849f596135 100644
--- a/drivers/usb/host/isp1760-if.c
+++ b/drivers/usb/host/isp1760-if.c
@@ -218,7 +218,7 @@ static int __devinit isp1761_pci_probe(struct pci_dev *dev,
* and reading back and checking the contents are same or not
*/
if (reg_data != 0xFACE) {
- err("scratch register mismatch %x", reg_data);
+ dev_err(&dev->dev, "scratch register mismatch %x\n", reg_data);
goto clean;
}
@@ -232,9 +232,10 @@ static int __devinit isp1761_pci_probe(struct pci_dev *dev,
hcd = isp1760_register(pci_mem_phy0, length, dev->irq,
IRQF_SHARED | IRQF_DISABLED, &dev->dev, dev_name(&dev->dev),
devflags);
- pci_set_drvdata(dev, hcd);
- if (!hcd)
+ if (!IS_ERR(hcd)) {
+ pci_set_drvdata(dev, hcd);
return 0;
+ }
clean:
status = -ENODEV;
iounmap(iobase);
diff --git a/drivers/usb/host/ohci-dbg.c b/drivers/usb/host/ohci-dbg.c
index 7cef1d2f7ccc..d3269656aa4d 100644
--- a/drivers/usb/host/ohci-dbg.c
+++ b/drivers/usb/host/ohci-dbg.c
@@ -649,7 +649,7 @@ static ssize_t fill_registers_buffer(struct debug_buffer *buf)
ohci_dbg_sw (ohci, &next, &size,
"bus %s, device %s\n"
"%s\n"
- "%s version " DRIVER_VERSION "\n",
+ "%s\n",
hcd->self.controller->bus->name,
dev_name(hcd->self.controller),
hcd->product_desc,
diff --git a/drivers/usb/host/ohci-hcd.c b/drivers/usb/host/ohci-hcd.c
index 89901962cbfd..8aa3f4556a32 100644
--- a/drivers/usb/host/ohci-hcd.c
+++ b/drivers/usb/host/ohci-hcd.c
@@ -46,7 +46,6 @@
#include "../core/hcd.h"
-#define DRIVER_VERSION "2006 August 04"
#define DRIVER_AUTHOR "Roman Weissgaerber, David Brownell"
#define DRIVER_DESC "USB 1.1 'Open' Host Controller (OHCI) Driver"
@@ -984,10 +983,8 @@ static int ohci_restart (struct ohci_hcd *ohci)
/*-------------------------------------------------------------------------*/
-#define DRIVER_INFO DRIVER_VERSION " " DRIVER_DESC
-
MODULE_AUTHOR (DRIVER_AUTHOR);
-MODULE_DESCRIPTION (DRIVER_INFO);
+MODULE_DESCRIPTION(DRIVER_DESC);
MODULE_LICENSE ("GPL");
#ifdef CONFIG_PCI
@@ -1078,12 +1075,18 @@ MODULE_LICENSE ("GPL");
#define SM501_OHCI_DRIVER ohci_hcd_sm501_driver
#endif
+#ifdef CONFIG_MFD_TC6393XB
+#include "ohci-tmio.c"
+#define TMIO_OHCI_DRIVER ohci_hcd_tmio_driver
+#endif
+
#if !defined(PCI_DRIVER) && \
!defined(PLATFORM_DRIVER) && \
!defined(OF_PLATFORM_DRIVER) && \
!defined(SA1111_DRIVER) && \
!defined(PS3_SYSTEM_BUS_DRIVER) && \
!defined(SM501_OHCI_DRIVER) && \
+ !defined(TMIO_OHCI_DRIVER) && \
!defined(SSB_OHCI_DRIVER)
#error "missing bus glue for ohci-hcd"
#endif
@@ -1095,9 +1098,10 @@ static int __init ohci_hcd_mod_init(void)
if (usb_disabled())
return -ENODEV;
- printk (KERN_DEBUG "%s: " DRIVER_INFO "\n", hcd_name);
+ printk(KERN_INFO "%s: " DRIVER_DESC "\n", hcd_name);
pr_debug ("%s: block sizes: ed %Zd td %Zd\n", hcd_name,
sizeof (struct ed), sizeof (struct td));
+ set_bit(USB_OHCI_LOADED, &usb_hcds_loaded);
#ifdef DEBUG
ohci_debug_root = debugfs_create_dir("ohci", NULL);
@@ -1149,13 +1153,25 @@ static int __init ohci_hcd_mod_init(void)
goto error_sm501;
#endif
+#ifdef TMIO_OHCI_DRIVER
+ retval = platform_driver_register(&TMIO_OHCI_DRIVER);
+ if (retval < 0)
+ goto error_tmio;
+#endif
+
return retval;
/* Error path */
+#ifdef TMIO_OHCI_DRIVER
+ platform_driver_unregister(&TMIO_OHCI_DRIVER);
+ error_tmio:
+#endif
#ifdef SM501_OHCI_DRIVER
+ platform_driver_unregister(&SM501_OHCI_DRIVER);
error_sm501:
#endif
#ifdef SSB_OHCI_DRIVER
+ ssb_driver_unregister(&SSB_OHCI_DRIVER);
error_ssb:
#endif
#ifdef PCI_DRIVER
@@ -1184,12 +1200,16 @@ static int __init ohci_hcd_mod_init(void)
error_debug:
#endif
+ clear_bit(USB_OHCI_LOADED, &usb_hcds_loaded);
return retval;
}
module_init(ohci_hcd_mod_init);
static void __exit ohci_hcd_mod_exit(void)
{
+#ifdef TMIO_OHCI_DRIVER
+ platform_driver_unregister(&TMIO_OHCI_DRIVER);
+#endif
#ifdef SM501_OHCI_DRIVER
platform_driver_unregister(&SM501_OHCI_DRIVER);
#endif
@@ -1214,6 +1234,7 @@ static void __exit ohci_hcd_mod_exit(void)
#ifdef DEBUG
debugfs_remove(ohci_debug_root);
#endif
+ clear_bit(USB_OHCI_LOADED, &usb_hcds_loaded);
}
module_exit(ohci_hcd_mod_exit);
diff --git a/drivers/usb/host/ohci-hub.c b/drivers/usb/host/ohci-hub.c
index 7ea9a7b31155..32bbce9718f0 100644
--- a/drivers/usb/host/ohci-hub.c
+++ b/drivers/usb/host/ohci-hub.c
@@ -359,21 +359,24 @@ static void ohci_finish_controller_resume(struct usb_hcd *hcd)
/* Carry out polling-, autostop-, and autoresume-related state changes */
static int ohci_root_hub_state_changes(struct ohci_hcd *ohci, int changed,
- int any_connected)
+ int any_connected, int rhsc_status)
{
int poll_rh = 1;
- int rhsc;
+ int rhsc_enable;
- rhsc = ohci_readl(ohci, &ohci->regs->intrenable) & OHCI_INTR_RHSC;
- switch (ohci->hc_control & OHCI_CTRL_HCFS) {
+ /* Some broken controllers never turn off RHCS in the interrupt
+ * status register. For their sake we won't re-enable RHSC
+ * interrupts if the interrupt bit is already active.
+ */
+ rhsc_enable = ohci_readl(ohci, &ohci->regs->intrenable) &
+ OHCI_INTR_RHSC;
+ switch (ohci->hc_control & OHCI_CTRL_HCFS) {
case OHCI_USB_OPER:
- /* If no status changes are pending, enable status-change
- * interrupts.
- */
- if (!rhsc && !changed) {
- rhsc = OHCI_INTR_RHSC;
- ohci_writel(ohci, rhsc, &ohci->regs->intrenable);
+ /* If no status changes are pending, enable RHSC interrupts. */
+ if (!rhsc_enable && !rhsc_status && !changed) {
+ rhsc_enable = OHCI_INTR_RHSC;
+ ohci_writel(ohci, rhsc_enable, &ohci->regs->intrenable);
}
/* Keep on polling until we know a device is connected
@@ -383,7 +386,7 @@ static int ohci_root_hub_state_changes(struct ohci_hcd *ohci, int changed,
if (any_connected ||
!device_may_wakeup(&ohci_to_hcd(ohci)
->self.root_hub->dev)) {
- if (rhsc)
+ if (rhsc_enable)
poll_rh = 0;
} else {
ohci->autostop = 1;
@@ -396,34 +399,45 @@ static int ohci_root_hub_state_changes(struct ohci_hcd *ohci, int changed,
ohci->autostop = 0;
ohci->next_statechange = jiffies +
STATECHANGE_DELAY;
- } else if (rhsc && time_after_eq(jiffies,
+ } else if (time_after_eq(jiffies,
ohci->next_statechange)
&& !ohci->ed_rm_list
&& !(ohci->hc_control &
OHCI_SCHED_ENABLES)) {
ohci_rh_suspend(ohci, 1);
- poll_rh = 0;
+ if (rhsc_enable)
+ poll_rh = 0;
}
}
break;
- /* if there is a port change, autostart or ask to be resumed */
case OHCI_USB_SUSPEND:
case OHCI_USB_RESUME:
+ /* if there is a port change, autostart or ask to be resumed */
if (changed) {
if (ohci->autostop)
ohci_rh_resume(ohci);
else
usb_hcd_resume_root_hub(ohci_to_hcd(ohci));
- } else {
- if (!rhsc && (ohci->autostop ||
- ohci_to_hcd(ohci)->self.root_hub->
- do_remote_wakeup))
- ohci_writel(ohci, OHCI_INTR_RHSC,
- &ohci->regs->intrenable);
- /* everything is idle, no need for polling */
+ /* If remote wakeup is disabled, stop polling */
+ } else if (!ohci->autostop &&
+ !ohci_to_hcd(ohci)->self.root_hub->
+ do_remote_wakeup) {
poll_rh = 0;
+
+ } else {
+ /* If no status changes are pending,
+ * enable RHSC interrupts
+ */
+ if (!rhsc_enable && !rhsc_status) {
+ rhsc_enable = OHCI_INTR_RHSC;
+ ohci_writel(ohci, rhsc_enable,
+ &ohci->regs->intrenable);
+ }
+ /* Keep polling until RHSC is enabled */
+ if (rhsc_enable)
+ poll_rh = 0;
}
break;
}
@@ -441,18 +455,22 @@ static inline int ohci_rh_resume(struct ohci_hcd *ohci)
* autostop isn't used when CONFIG_PM is turned off.
*/
static int ohci_root_hub_state_changes(struct ohci_hcd *ohci, int changed,
- int any_connected)
+ int any_connected, int rhsc_status)
{
/* If RHSC is enabled, don't poll */
if (ohci_readl(ohci, &ohci->regs->intrenable) & OHCI_INTR_RHSC)
return 0;
- /* If no status changes are pending, enable status-change interrupts */
- if (!changed) {
- ohci_writel(ohci, OHCI_INTR_RHSC, &ohci->regs->intrenable);
- return 0;
- }
- return 1;
+ /* If status changes are pending, continue polling.
+ * Conversely, if no status changes are pending but the RHSC
+ * status bit was set, then RHSC may be broken so continue polling.
+ */
+ if (changed || rhsc_status)
+ return 1;
+
+ /* It's safe to re-enable RHSC interrupts */
+ ohci_writel(ohci, OHCI_INTR_RHSC, &ohci->regs->intrenable);
+ return 0;
}
#endif /* CONFIG_PM */
@@ -467,6 +485,7 @@ ohci_hub_status_data (struct usb_hcd *hcd, char *buf)
struct ohci_hcd *ohci = hcd_to_ohci (hcd);
int i, changed = 0, length = 1;
int any_connected = 0;
+ int rhsc_status;
unsigned long flags;
spin_lock_irqsave (&ohci->lock, flags);
@@ -492,12 +511,10 @@ ohci_hub_status_data (struct usb_hcd *hcd, char *buf)
length++;
}
- /* Some broken controllers never turn off RHCS in the interrupt
- * status register. For their sake we won't re-enable RHSC
- * interrupts if the flag is already set.
- */
- if (ohci_readl(ohci, &ohci->regs->intrstatus) & OHCI_INTR_RHSC)
- changed = 1;
+ /* Clear the RHSC status flag before reading the port statuses */
+ ohci_writel(ohci, OHCI_INTR_RHSC, &ohci->regs->intrstatus);
+ rhsc_status = ohci_readl(ohci, &ohci->regs->intrstatus) &
+ OHCI_INTR_RHSC;
/* look at each port */
for (i = 0; i < ohci->num_ports; i++) {
@@ -517,7 +534,7 @@ ohci_hub_status_data (struct usb_hcd *hcd, char *buf)
}
hcd->poll_rh = ohci_root_hub_state_changes(ohci, changed,
- any_connected);
+ any_connected, rhsc_status);
done:
spin_unlock_irqrestore (&ohci->lock, flags);
diff --git a/drivers/usb/host/ohci-omap.c b/drivers/usb/host/ohci-omap.c
index 522185629624..91697bdb399f 100644
--- a/drivers/usb/host/ohci-omap.c
+++ b/drivers/usb/host/ohci-omap.c
@@ -231,7 +231,7 @@ static int ohci_omap_init(struct usb_hcd *hcd)
omap_ohci_clock_power(1);
- if (cpu_is_omap1510()) {
+ if (cpu_is_omap15xx()) {
omap_1510_local_bus_power(1);
omap_1510_local_bus_init();
}
@@ -319,7 +319,7 @@ static int usb_hcd_omap_probe (const struct hc_driver *driver,
if (IS_ERR(usb_host_ck))
return PTR_ERR(usb_host_ck);
- if (!cpu_is_omap1510())
+ if (!cpu_is_omap15xx())
usb_dc_ck = clk_get(0, "usb_dc_ck");
else
usb_dc_ck = clk_get(0, "lb_ck");
diff --git a/drivers/usb/host/ohci-pnx4008.c b/drivers/usb/host/ohci-pnx4008.c
index 658a2a978c32..e306ca6aef3d 100644
--- a/drivers/usb/host/ohci-pnx4008.c
+++ b/drivers/usb/host/ohci-pnx4008.c
@@ -331,7 +331,7 @@ static int __devinit usb_hcd_pnx4008_probe(struct platform_device *pdev)
int ret = 0, irq;
- dev_dbg(&pdev->dev, "%s: " DRIVER_INFO " (pnx4008)\n", hcd_name);
+ dev_dbg(&pdev->dev, "%s: " DRIVER_DESC " (pnx4008)\n", hcd_name);
if (usb_disabled()) {
err("USB is disabled");
ret = -ENODEV;
diff --git a/drivers/usb/host/ohci-tmio.c b/drivers/usb/host/ohci-tmio.c
new file mode 100644
index 000000000000..f9f134af0bd1
--- /dev/null
+++ b/drivers/usb/host/ohci-tmio.c
@@ -0,0 +1,376 @@
+/*
+ * OHCI HCD(Host Controller Driver) for USB.
+ *
+ *(C) Copyright 1999 Roman Weissgaerber <weissg@vienna.at>
+ *(C) Copyright 2000-2002 David Brownell <dbrownell@users.sourceforge.net>
+ *(C) Copyright 2002 Hewlett-Packard Company
+ *
+ * Bus glue for Toshiba Mobile IO(TMIO) Controller's OHCI core
+ * (C) Copyright 2005 Chris Humbert <mahadri-usb@drigon.com>
+ * (C) Copyright 2007, 2008 Dmitry Baryshkov <dbaryshkov@gmail.com>
+ *
+ * This is known to work with the following variants:
+ * TC6393XB revision 3 (32kB SRAM)
+ *
+ * The TMIO's OHCI core DMAs through a small internal buffer that
+ * is directly addressable by the CPU.
+ *
+ * Written from sparse documentation from Toshiba and Sharp's driver
+ * for the 2.4 kernel,
+ * usb-ohci-tc6393.c(C) Copyright 2004 Lineo Solutions, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+/*#include <linux/fs.h>
+#include <linux/mount.h>
+#include <linux/pagemap.h>
+#include <linux/init.h>
+#include <linux/namei.h>
+#include <linux/sched.h>*/
+#include <linux/platform_device.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/tmio.h>
+#include <linux/dma-mapping.h>
+
+/*-------------------------------------------------------------------------*/
+
+/*
+ * USB Host Controller Configuration Register
+ */
+#define CCR_REVID 0x08 /* b Revision ID */
+#define CCR_BASE 0x10 /* l USB Control Register Base Address Low */
+#define CCR_ILME 0x40 /* b Internal Local Memory Enable */
+#define CCR_PM 0x4c /* w Power Management */
+#define CCR_INTC 0x50 /* b INT Control */
+#define CCR_LMW1L 0x54 /* w Local Memory Window 1 LMADRS Low */
+#define CCR_LMW1H 0x56 /* w Local Memory Window 1 LMADRS High */
+#define CCR_LMW1BL 0x58 /* w Local Memory Window 1 Base Address Low */
+#define CCR_LMW1BH 0x5A /* w Local Memory Window 1 Base Address High */
+#define CCR_LMW2L 0x5C /* w Local Memory Window 2 LMADRS Low */
+#define CCR_LMW2H 0x5E /* w Local Memory Window 2 LMADRS High */
+#define CCR_LMW2BL 0x60 /* w Local Memory Window 2 Base Address Low */
+#define CCR_LMW2BH 0x62 /* w Local Memory Window 2 Base Address High */
+#define CCR_MISC 0xFC /* b MISC */
+
+#define CCR_PM_GKEN 0x0001
+#define CCR_PM_CKRNEN 0x0002
+#define CCR_PM_USBPW1 0x0004
+#define CCR_PM_USBPW2 0x0008
+#define CCR_PM_USBPW3 0x0008
+#define CCR_PM_PMEE 0x0100
+#define CCR_PM_PMES 0x8000
+
+/*-------------------------------------------------------------------------*/
+
+struct tmio_hcd {
+ void __iomem *ccr;
+ spinlock_t lock; /* protects RMW cycles */
+};
+
+#define hcd_to_tmio(hcd) ((struct tmio_hcd *)(hcd_to_ohci(hcd) + 1))
+
+/*-------------------------------------------------------------------------*/
+
+static void tmio_write_pm(struct platform_device *dev)
+{
+ struct usb_hcd *hcd = platform_get_drvdata(dev);
+ struct tmio_hcd *tmio = hcd_to_tmio(hcd);
+ u16 pm;
+ unsigned long flags;
+
+ spin_lock_irqsave(&tmio->lock, flags);
+
+ pm = CCR_PM_GKEN | CCR_PM_CKRNEN |
+ CCR_PM_PMEE | CCR_PM_PMES;
+
+ tmio_iowrite16(pm, tmio->ccr + CCR_PM);
+ spin_unlock_irqrestore(&tmio->lock, flags);
+}
+
+static void tmio_stop_hc(struct platform_device *dev)
+{
+ struct usb_hcd *hcd = platform_get_drvdata(dev);
+ struct ohci_hcd *ohci = hcd_to_ohci(hcd);
+ struct tmio_hcd *tmio = hcd_to_tmio(hcd);
+ u16 pm;
+
+ pm = CCR_PM_GKEN | CCR_PM_CKRNEN;
+ switch (ohci->num_ports) {
+ default:
+ dev_err(&dev->dev, "Unsupported amount of ports: %d\n", ohci->num_ports);
+ case 3:
+ pm |= CCR_PM_USBPW3;
+ case 2:
+ pm |= CCR_PM_USBPW2;
+ case 1:
+ pm |= CCR_PM_USBPW1;
+ }
+ tmio_iowrite8(0, tmio->ccr + CCR_INTC);
+ tmio_iowrite8(0, tmio->ccr + CCR_ILME);
+ tmio_iowrite16(0, tmio->ccr + CCR_BASE);
+ tmio_iowrite16(0, tmio->ccr + CCR_BASE + 2);
+ tmio_iowrite16(pm, tmio->ccr + CCR_PM);
+}
+
+static void tmio_start_hc(struct platform_device *dev)
+{
+ struct usb_hcd *hcd = platform_get_drvdata(dev);
+ struct tmio_hcd *tmio = hcd_to_tmio(hcd);
+ unsigned long base = hcd->rsrc_start;
+
+ tmio_write_pm(dev);
+ tmio_iowrite16(base, tmio->ccr + CCR_BASE);
+ tmio_iowrite16(base >> 16, tmio->ccr + CCR_BASE + 2);
+ tmio_iowrite8(1, tmio->ccr + CCR_ILME);
+ tmio_iowrite8(2, tmio->ccr + CCR_INTC);
+
+ dev_info(&dev->dev, "revision %d @ 0x%08llx, irq %d\n",
+ tmio_ioread8(tmio->ccr + CCR_REVID), hcd->rsrc_start, hcd->irq);
+}
+
+static int ohci_tmio_start(struct usb_hcd *hcd)
+{
+ struct ohci_hcd *ohci = hcd_to_ohci(hcd);
+ int ret;
+
+ if ((ret = ohci_init(ohci)) < 0)
+ return ret;
+
+ if ((ret = ohci_run(ohci)) < 0) {
+ err("can't start %s", hcd->self.bus_name);
+ ohci_stop(hcd);
+ return ret;
+ }
+
+ return 0;
+}
+
+static const struct hc_driver ohci_tmio_hc_driver = {
+ .description = hcd_name,
+ .product_desc = "TMIO OHCI USB Host Controller",
+ .hcd_priv_size = sizeof(struct ohci_hcd) + sizeof (struct tmio_hcd),
+
+ /* generic hardware linkage */
+ .irq = ohci_irq,
+ .flags = HCD_USB11 | HCD_MEMORY | HCD_LOCAL_MEM,
+
+ /* basic lifecycle operations */
+ .start = ohci_tmio_start,
+ .stop = ohci_stop,
+ .shutdown = ohci_shutdown,
+
+ /* managing i/o requests and associated device resources */
+ .urb_enqueue = ohci_urb_enqueue,
+ .urb_dequeue = ohci_urb_dequeue,
+ .endpoint_disable = ohci_endpoint_disable,
+
+ /* scheduling support */
+ .get_frame_number = ohci_get_frame,
+
+ /* root hub support */
+ .hub_status_data = ohci_hub_status_data,
+ .hub_control = ohci_hub_control,
+#ifdef CONFIG_PM
+ .bus_suspend = ohci_bus_suspend,
+ .bus_resume = ohci_bus_resume,
+#endif
+ .start_port_reset = ohci_start_port_reset,
+};
+
+/*-------------------------------------------------------------------------*/
+static struct platform_driver ohci_hcd_tmio_driver;
+
+static int __devinit ohci_hcd_tmio_drv_probe(struct platform_device *dev)
+{
+ struct mfd_cell *cell = dev->dev.platform_data;
+ struct resource *regs = platform_get_resource(dev, IORESOURCE_MEM, 0);
+ struct resource *config = platform_get_resource(dev, IORESOURCE_MEM, 1);
+ struct resource *sram = platform_get_resource(dev, IORESOURCE_MEM, 2);
+ int irq = platform_get_irq(dev, 0);
+ struct tmio_hcd *tmio;
+ struct ohci_hcd *ohci;
+ struct usb_hcd *hcd;
+ int ret;
+
+ if (usb_disabled())
+ return -ENODEV;
+
+ if (!cell)
+ return -EINVAL;
+
+ hcd = usb_create_hcd(&ohci_tmio_hc_driver, &dev->dev, dev->dev.bus_id);
+ if (!hcd) {
+ ret = -ENOMEM;
+ goto err_usb_create_hcd;
+ }
+
+ hcd->rsrc_start = regs->start;
+ hcd->rsrc_len = regs->end - regs->start + 1;
+
+ tmio = hcd_to_tmio(hcd);
+
+ spin_lock_init(&tmio->lock);
+
+ tmio->ccr = ioremap(config->start, config->end - config->start + 1);
+ if (!tmio->ccr) {
+ ret = -ENOMEM;
+ goto err_ioremap_ccr;
+ }
+
+ hcd->regs = ioremap(hcd->rsrc_start, hcd->rsrc_len);
+ if (!hcd->regs) {
+ ret = -ENOMEM;
+ goto err_ioremap_regs;
+ }
+
+ if (!dma_declare_coherent_memory(&dev->dev, sram->start,
+ sram->start,
+ sram->end - sram->start + 1,
+ DMA_MEMORY_MAP | DMA_MEMORY_EXCLUSIVE)) {
+ ret = -EBUSY;
+ goto err_dma_declare;
+ }
+
+ if (cell->enable) {
+ ret = cell->enable(dev);
+ if (ret)
+ goto err_enable;
+ }
+
+ tmio_start_hc(dev);
+ ohci = hcd_to_ohci(hcd);
+ ohci_hcd_init(ohci);
+
+ ret = usb_add_hcd(hcd, irq, IRQF_DISABLED);
+ if (ret)
+ goto err_add_hcd;
+
+ if (ret == 0)
+ return ret;
+
+ usb_remove_hcd(hcd);
+
+err_add_hcd:
+ tmio_stop_hc(dev);
+ if (cell->disable)
+ cell->disable(dev);
+err_enable:
+ dma_release_declared_memory(&dev->dev);
+err_dma_declare:
+ iounmap(hcd->regs);
+err_ioremap_regs:
+ iounmap(tmio->ccr);
+err_ioremap_ccr:
+ usb_put_hcd(hcd);
+err_usb_create_hcd:
+
+ return ret;
+}
+
+static int __devexit ohci_hcd_tmio_drv_remove(struct platform_device *dev)
+{
+ struct usb_hcd *hcd = platform_get_drvdata(dev);
+ struct tmio_hcd *tmio = hcd_to_tmio(hcd);
+ struct mfd_cell *cell = dev->dev.platform_data;
+
+ usb_remove_hcd(hcd);
+ tmio_stop_hc(dev);
+ if (cell->disable)
+ cell->disable(dev);
+ dma_release_declared_memory(&dev->dev);
+ iounmap(hcd->regs);
+ iounmap(tmio->ccr);
+ usb_put_hcd(hcd);
+
+ platform_set_drvdata(dev, NULL);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int ohci_hcd_tmio_drv_suspend(struct platform_device *dev, pm_message_t state)
+{
+ struct mfd_cell *cell = dev->dev.platform_data;
+ struct usb_hcd *hcd = platform_get_drvdata(dev);
+ struct ohci_hcd *ohci = hcd_to_ohci(hcd);
+ struct tmio_hcd *tmio = hcd_to_tmio(hcd);
+ unsigned long flags;
+ u8 misc;
+ int ret;
+
+ if (time_before(jiffies, ohci->next_statechange))
+ msleep(5);
+ ohci->next_statechange = jiffies;
+
+ spin_lock_irqsave(&tmio->lock, flags);
+
+ misc = tmio_ioread8(tmio->ccr + CCR_MISC);
+ misc |= 1 << 3; /* USSUSP */
+ tmio_iowrite8(misc, tmio->ccr + CCR_MISC);
+
+ spin_unlock_irqrestore(&tmio->lock, flags);
+
+ if (cell->suspend) {
+ ret = cell->suspend(dev);
+ if (ret)
+ return ret;
+ }
+
+ hcd->state = HC_STATE_SUSPENDED;
+
+ return 0;
+}
+
+static int ohci_hcd_tmio_drv_resume(struct platform_device *dev)
+{
+ struct mfd_cell *cell = dev->dev.platform_data;
+ struct usb_hcd *hcd = platform_get_drvdata(dev);
+ struct ohci_hcd *ohci = hcd_to_ohci(hcd);
+ struct tmio_hcd *tmio = hcd_to_tmio(hcd);
+ unsigned long flags;
+ u8 misc;
+ int ret;
+
+ if (time_before(jiffies, ohci->next_statechange))
+ msleep(5);
+ ohci->next_statechange = jiffies;
+
+ if (cell->resume) {
+ ret = cell->resume(dev);
+ if (ret)
+ return ret;
+ }
+
+ tmio_start_hc(dev);
+
+ spin_lock_irqsave(&tmio->lock, flags);
+
+ misc = tmio_ioread8(tmio->ccr + CCR_MISC);
+ misc &= ~(1 << 3); /* USSUSP */
+ tmio_iowrite8(misc, tmio->ccr + CCR_MISC);
+
+ spin_unlock_irqrestore(&tmio->lock, flags);
+
+ ohci_finish_controller_resume(hcd);
+
+ return 0;
+}
+#else
+#define ohci_hcd_tmio_drv_suspend NULL
+#define ohci_hcd_tmio_drv_resume NULL
+#endif
+
+static struct platform_driver ohci_hcd_tmio_driver = {
+ .probe = ohci_hcd_tmio_drv_probe,
+ .remove = __devexit_p(ohci_hcd_tmio_drv_remove),
+ .shutdown = usb_hcd_platform_shutdown,
+ .suspend = ohci_hcd_tmio_drv_suspend,
+ .resume = ohci_hcd_tmio_drv_resume,
+ .driver = {
+ .name = "tmio-ohci",
+ .owner = THIS_MODULE,
+ },
+};
diff --git a/drivers/usb/host/ohci.h b/drivers/usb/host/ohci.h
index faf622eafce7..222011f6172c 100644
--- a/drivers/usb/host/ohci.h
+++ b/drivers/usb/host/ohci.h
@@ -540,15 +540,7 @@ static inline struct usb_hcd *ohci_to_hcd (const struct ohci_hcd *ohci)
* Big-endian read/write functions are arch-specific.
* Other arches can be added if/when they're needed.
*
- * REVISIT: arch/powerpc now has readl/writel_be, so the
- * definition below can die once the STB04xxx support is
- * finally ported over.
*/
-#if defined(CONFIG_PPC) && !defined(CONFIG_PPC_MERGE)
-#define readl_be(addr) in_be32((__force unsigned *)addr)
-#define writel_be(val, addr) out_be32((__force unsigned *)addr, val)
-#endif
-
static inline unsigned int _ohci_readl (const struct ohci_hcd *ohci,
__hc32 __iomem * regs)
{
diff --git a/drivers/usb/host/r8a66597-hcd.c b/drivers/usb/host/r8a66597-hcd.c
index ea7126f99cab..c18d8790c410 100644
--- a/drivers/usb/host/r8a66597-hcd.c
+++ b/drivers/usb/host/r8a66597-hcd.c
@@ -66,7 +66,7 @@ static unsigned short endian;
module_param(endian, ushort, 0644);
MODULE_PARM_DESC(endian, "data endian: big=256, little=0 (default=0)");
-static unsigned short irq_sense = INTL;
+static unsigned short irq_sense = 0xff;
module_param(irq_sense, ushort, 0644);
MODULE_PARM_DESC(irq_sense, "IRQ sense: low level=32, falling edge=0 "
"(default=32)");
@@ -118,7 +118,7 @@ static int r8a66597_clock_enable(struct r8a66597 *r8a66597)
r8a66597_write(r8a66597, SCKE, SYSCFG0);
tmp = r8a66597_read(r8a66597, SYSCFG0);
if (i++ > 1000) {
- err("register access fail.");
+ printk(KERN_ERR "r8a66597: register access fail.\n");
return -ENXIO;
}
} while ((tmp & SCKE) != SCKE);
@@ -128,7 +128,7 @@ static int r8a66597_clock_enable(struct r8a66597 *r8a66597)
r8a66597_write(r8a66597, USBE, SYSCFG0);
tmp = r8a66597_read(r8a66597, SYSCFG0);
if (i++ > 1000) {
- err("register access fail.");
+ printk(KERN_ERR "r8a66597: register access fail.\n");
return -ENXIO;
}
} while ((tmp & USBE) != USBE);
@@ -141,7 +141,7 @@ static int r8a66597_clock_enable(struct r8a66597 *r8a66597)
msleep(1);
tmp = r8a66597_read(r8a66597, SYSCFG0);
if (i++ > 500) {
- err("register access fail.");
+ printk(KERN_ERR "r8a66597: register access fail.\n");
return -ENXIO;
}
} while ((tmp & SCKE) != SCKE);
@@ -265,7 +265,7 @@ static void get_port_number(char *devpath, u16 *root_port, u16 *hub_port)
if (root_port) {
*root_port = (devpath[0] & 0x0F) - 1;
if (*root_port >= R8A66597_MAX_ROOT_HUB)
- err("illegal root port number");
+ printk(KERN_ERR "r8a66597: Illegal root port number.\n");
}
if (hub_port)
*hub_port = devpath[2] & 0x0F;
@@ -286,7 +286,7 @@ static u16 get_r8a66597_usb_speed(enum usb_device_speed speed)
usbspd = HSMODE;
break;
default:
- err("unknown speed");
+ printk(KERN_ERR "r8a66597: unknown speed\n");
break;
}
@@ -385,7 +385,7 @@ static u8 alloc_usb_address(struct r8a66597 *r8a66597, struct urb *urb)
struct r8a66597_device *dev;
if (is_hub_limit(urb->dev->devpath)) {
- err("Externel hub limit reached.");
+ dev_err(&urb->dev->dev, "External hub limit reached.\n");
return 0;
}
@@ -406,8 +406,9 @@ static u8 alloc_usb_address(struct r8a66597 *r8a66597, struct urb *urb)
return addr;
}
- err("cannot communicate with a USB device more than 10.(%x)",
- r8a66597->address_map);
+ dev_err(&urb->dev->dev,
+ "cannot communicate with a USB device more than 10.(%x)\n",
+ r8a66597->address_map);
return 0;
}
@@ -447,7 +448,8 @@ static void r8a66597_reg_wait(struct r8a66597 *r8a66597, unsigned long reg,
do {
tmp = r8a66597_read(r8a66597, reg);
if (i++ > 1000000) {
- err("register%lx, loop %x is timeout", reg, loop);
+ printk(KERN_ERR "r8a66597: register%lx, loop %x "
+ "is timeout\n", reg, loop);
break;
}
ndelay(1);
@@ -675,7 +677,7 @@ static u16 get_empty_pipenum(struct r8a66597 *r8a66597,
array[i++] = 1;
break;
default:
- err("Illegal type");
+ printk(KERN_ERR "r8a66597: Illegal type\n");
return 0;
}
@@ -705,7 +707,7 @@ static u16 get_r8a66597_type(__u8 type)
r8a66597_type = R8A66597_ISO;
break;
default:
- err("Illegal type");
+ printk(KERN_ERR "r8a66597: Illegal type\n");
r8a66597_type = 0x0000;
break;
}
@@ -724,7 +726,7 @@ static u16 get_bufnum(u16 pipenum)
else if (check_interrupt(pipenum))
bufnum = 4 + (pipenum - 6);
else
- err("Illegal pipenum (%d)", pipenum);
+ printk(KERN_ERR "r8a66597: Illegal pipenum (%d)\n", pipenum);
return bufnum;
}
@@ -740,7 +742,7 @@ static u16 get_buf_bsize(u16 pipenum)
else if (check_interrupt(pipenum))
buf_bsize = 0;
else
- err("Illegal pipenum (%d)", pipenum);
+ printk(KERN_ERR "r8a66597: Illegal pipenum (%d)\n", pipenum);
return buf_bsize;
}
@@ -760,10 +762,12 @@ static void enable_r8a66597_pipe_dma(struct r8a66597 *r8a66597,
if ((r8a66597->dma_map & (1 << i)) != 0)
continue;
- info("address %d, EndpointAddress 0x%02x use DMA FIFO",
- usb_pipedevice(urb->pipe),
- info->dir_in ? USB_ENDPOINT_DIR_MASK + info->epnum
- : info->epnum);
+ dev_info(&dev->udev->dev,
+ "address %d, EndpointAddress 0x%02x use "
+ "DMA FIFO\n", usb_pipedevice(urb->pipe),
+ info->dir_in ?
+ USB_ENDPOINT_DIR_MASK + info->epnum
+ : info->epnum);
r8a66597->dma_map |= 1 << i;
dev->dma_map |= 1 << i;
@@ -1187,7 +1191,7 @@ static int start_transfer(struct r8a66597 *r8a66597, struct r8a66597_td *td)
prepare_status_packet(r8a66597, td);
break;
default:
- err("invalid type.");
+ printk(KERN_ERR "r8a66597: invalid type.\n");
break;
}
@@ -1295,7 +1299,7 @@ static void packet_read(struct r8a66597 *r8a66597, u16 pipenum)
if (unlikely((tmp & FRDY) == 0)) {
pipe_stop(r8a66597, td->pipe);
pipe_irq_disable(r8a66597, pipenum);
- err("in fifo not ready (%d)", pipenum);
+ printk(KERN_ERR "r8a66597: in fifo not ready (%d)\n", pipenum);
finish_request(r8a66597, td, pipenum, td->urb, -EPIPE);
return;
}
@@ -1370,7 +1374,7 @@ static void packet_write(struct r8a66597 *r8a66597, u16 pipenum)
if (unlikely((tmp & FRDY) == 0)) {
pipe_stop(r8a66597, td->pipe);
pipe_irq_disable(r8a66597, pipenum);
- err("out write fifo not ready. (%d)", pipenum);
+ printk(KERN_ERR "r8a66597: out fifo not ready (%d)\n", pipenum);
finish_request(r8a66597, td, pipenum, urb, -EPIPE);
return;
}
@@ -2005,7 +2009,7 @@ static struct r8a66597_device *get_r8a66597_device(struct r8a66597 *r8a66597,
return dev;
}
- err("get_r8a66597_device fail.(%d)\n", addr);
+ printk(KERN_ERR "r8a66597: get_r8a66597_device fail.(%d)\n", addr);
return NULL;
}
@@ -2263,7 +2267,7 @@ static int __init_or_module r8a66597_remove(struct platform_device *pdev)
#define resource_len(r) (((r)->end - (r)->start) + 1)
static int __init r8a66597_probe(struct platform_device *pdev)
{
- struct resource *res = NULL;
+ struct resource *res = NULL, *ires;
int irq = -1;
void __iomem *reg = NULL;
struct usb_hcd *hcd = NULL;
@@ -2274,7 +2278,7 @@ static int __init r8a66597_probe(struct platform_device *pdev)
if (pdev->dev.dma_mask) {
ret = -EINVAL;
- err("dma not support");
+ dev_err(&pdev->dev, "dma not supported\n");
goto clean_up;
}
@@ -2282,21 +2286,25 @@ static int __init r8a66597_probe(struct platform_device *pdev)
(char *)hcd_name);
if (!res) {
ret = -ENODEV;
- err("platform_get_resource_byname error.");
+ dev_err(&pdev->dev, "platform_get_resource_byname error.\n");
goto clean_up;
}
- irq = platform_get_irq(pdev, 0);
- if (irq < 0) {
+ ires = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
+ if (!ires) {
ret = -ENODEV;
- err("platform_get_irq error.");
+ dev_err(&pdev->dev,
+ "platform_get_resource IORESOURCE_IRQ error.\n");
goto clean_up;
}
+ irq = ires->start;
+ irq_trigger = ires->flags & IRQF_TRIGGER_MASK;
+
reg = ioremap(res->start, resource_len(res));
if (reg == NULL) {
ret = -ENOMEM;
- err("ioremap error.");
+ dev_err(&pdev->dev, "ioremap error.\n");
goto clean_up;
}
@@ -2304,7 +2312,7 @@ static int __init r8a66597_probe(struct platform_device *pdev)
hcd = usb_create_hcd(&r8a66597_hc_driver, &pdev->dev, (char *)hcd_name);
if (!hcd) {
ret = -ENOMEM;
- err("Failed to create hcd");
+ dev_err(&pdev->dev, "Failed to create hcd\n");
goto clean_up;
}
r8a66597 = hcd_to_r8a66597(hcd);
@@ -2329,13 +2337,33 @@ static int __init r8a66597_probe(struct platform_device *pdev)
INIT_LIST_HEAD(&r8a66597->child_device);
hcd->rsrc_start = res->start;
- if (irq_sense == INTL)
- irq_trigger = IRQF_TRIGGER_LOW;
- else
- irq_trigger = IRQF_TRIGGER_FALLING;
+
+ /* irq_sense setting on cmdline takes precedence over resource
+ * settings, so the introduction of irqflags in IRQ resourse
+ * won't disturb existing setups */
+ switch (irq_sense) {
+ case INTL:
+ irq_trigger = IRQF_TRIGGER_LOW;
+ break;
+ case 0:
+ irq_trigger = IRQF_TRIGGER_FALLING;
+ break;
+ case 0xff:
+ if (irq_trigger)
+ irq_sense = (irq_trigger & IRQF_TRIGGER_LOW) ?
+ INTL : 0;
+ else {
+ irq_sense = INTL;
+ irq_trigger = IRQF_TRIGGER_LOW;
+ }
+ break;
+ default:
+ dev_err(&pdev->dev, "Unknown irq_sense value.\n");
+ }
+
ret = usb_add_hcd(hcd, irq, IRQF_DISABLED | irq_trigger);
if (ret != 0) {
- err("Failed to add hcd");
+ dev_err(&pdev->dev, "Failed to add hcd\n");
goto clean_up;
}
@@ -2364,7 +2392,8 @@ static int __init r8a66597_init(void)
if (usb_disabled())
return -ENODEV;
- info("driver %s, %s", hcd_name, DRIVER_VERSION);
+ printk(KERN_INFO KBUILD_MODNAME ": driver %s, %s\n", hcd_name,
+ DRIVER_VERSION);
return platform_driver_register(&r8a66597_driver);
}
module_init(r8a66597_init);
diff --git a/drivers/usb/host/sl811-hcd.c b/drivers/usb/host/sl811-hcd.c
index 8a74bbb57d08..e106e9d48d4a 100644
--- a/drivers/usb/host/sl811-hcd.c
+++ b/drivers/usb/host/sl811-hcd.c
@@ -1620,22 +1620,26 @@ sl811h_probe(struct platform_device *dev)
{
struct usb_hcd *hcd;
struct sl811 *sl811;
- struct resource *addr, *data;
+ struct resource *addr, *data, *ires;
int irq;
void __iomem *addr_reg;
void __iomem *data_reg;
int retval;
u8 tmp, ioaddr = 0;
+ unsigned long irqflags;
/* basic sanity checks first. board-specific init logic should
* have initialized these three resources and probably board
* specific platform_data. we don't probe for IRQs, and do only
* minimal sanity checking.
*/
- irq = platform_get_irq(dev, 0);
- if (dev->num_resources < 3 || irq < 0)
+ ires = platform_get_resource(dev, IORESOURCE_IRQ, 0);
+ if (dev->num_resources < 3 || !ires)
return -ENODEV;
+ irq = ires->start;
+ irqflags = ires->flags & IRQF_TRIGGER_MASK;
+
/* refuse to confuse usbcore */
if (dev->dev.dma_mask) {
DBG("no we won't dma\n");
@@ -1717,8 +1721,11 @@ sl811h_probe(struct platform_device *dev)
* triggers (e.g. most ARM CPUs). Initial driver stress testing
* was on a system with single edge triggering, so most sorts of
* triggering arrangement should work.
+ *
+ * Use resource IRQ flags if set by platform device setup.
*/
- retval = usb_add_hcd(hcd, irq, IRQF_DISABLED | IRQF_SHARED);
+ irqflags |= IRQF_SHARED;
+ retval = usb_add_hcd(hcd, irq, IRQF_DISABLED | irqflags);
if (retval != 0)
goto err6;
diff --git a/drivers/usb/host/uhci-hcd.c b/drivers/usb/host/uhci-hcd.c
index 3a7bfe7a8874..cf5e4cf7ea42 100644
--- a/drivers/usb/host/uhci-hcd.c
+++ b/drivers/usb/host/uhci-hcd.c
@@ -53,7 +53,6 @@
/*
* Version Information
*/
-#define DRIVER_VERSION "v3.0"
#define DRIVER_AUTHOR "Linus 'Frodo Rabbit' Torvalds, Johannes Erdfelt, \
Randy Dunlap, Georg Acher, Deti Fliegl, Thomas Sailer, Roman Weissgaerber, \
Alan Stern"
@@ -951,12 +950,13 @@ static int __init uhci_hcd_init(void)
{
int retval = -ENOMEM;
- printk(KERN_INFO DRIVER_DESC " " DRIVER_VERSION "%s\n",
- ignore_oc ? ", overcurrent ignored" : "");
-
if (usb_disabled())
return -ENODEV;
+ printk(KERN_INFO "uhci_hcd: " DRIVER_DESC "%s\n",
+ ignore_oc ? ", overcurrent ignored" : "");
+ set_bit(USB_UHCI_LOADED, &usb_hcds_loaded);
+
if (DEBUG_CONFIGURED) {
errbuf = kmalloc(ERRBUF_LEN, GFP_KERNEL);
if (!errbuf)
@@ -988,6 +988,7 @@ debug_failed:
errbuf_failed:
+ clear_bit(USB_UHCI_LOADED, &usb_hcds_loaded);
return retval;
}
@@ -997,6 +998,7 @@ static void __exit uhci_hcd_cleanup(void)
kmem_cache_destroy(uhci_up_cachep);
debugfs_remove(uhci_debugfs_root);
kfree(errbuf);
+ clear_bit(USB_UHCI_LOADED, &usb_hcds_loaded);
}
module_init(uhci_hcd_init);
diff --git a/drivers/usb/host/uhci-q.c b/drivers/usb/host/uhci-q.c
index 1f0c2cf26e5d..5631d89c8730 100644
--- a/drivers/usb/host/uhci-q.c
+++ b/drivers/usb/host/uhci-q.c
@@ -1065,13 +1065,18 @@ static int uhci_submit_interrupt(struct uhci_hcd *uhci, struct urb *urb,
}
if (exponent < 0)
return -EINVAL;
- qh->period = 1 << exponent;
- qh->skel = SKEL_INDEX(exponent);
- /* For now, interrupt phase is fixed by the layout
- * of the QH lists. */
- qh->phase = (qh->period / 2) & (MAX_PHASE - 1);
- ret = uhci_check_bandwidth(uhci, qh);
+ /* If the slot is full, try a lower period */
+ do {
+ qh->period = 1 << exponent;
+ qh->skel = SKEL_INDEX(exponent);
+
+ /* For now, interrupt phase is fixed by the layout
+ * of the QH lists.
+ */
+ qh->phase = (qh->period / 2) & (MAX_PHASE - 1);
+ ret = uhci_check_bandwidth(uhci, qh);
+ } while (ret != 0 && --exponent >= 0);
if (ret)
return ret;
} else if (qh->period > urb->interval)
diff --git a/drivers/usb/host/whci/Kbuild b/drivers/usb/host/whci/Kbuild
new file mode 100644
index 000000000000..26a3871ea0f9
--- /dev/null
+++ b/drivers/usb/host/whci/Kbuild
@@ -0,0 +1,11 @@
+obj-$(CONFIG_USB_WHCI_HCD) += whci-hcd.o
+
+whci-hcd-y := \
+ asl.o \
+ hcd.o \
+ hw.o \
+ init.o \
+ int.o \
+ pzl.o \
+ qset.o \
+ wusb.o
diff --git a/drivers/usb/host/whci/asl.c b/drivers/usb/host/whci/asl.c
new file mode 100644
index 000000000000..4d7078e50572
--- /dev/null
+++ b/drivers/usb/host/whci/asl.c
@@ -0,0 +1,367 @@
+/*
+ * Wireless Host Controller (WHC) asynchronous schedule management.
+ *
+ * Copyright (C) 2007 Cambridge Silicon Radio Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License version
+ * 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that 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, see <http://www.gnu.org/licenses/>.
+ */
+#include <linux/kernel.h>
+#include <linux/dma-mapping.h>
+#include <linux/uwb/umc.h>
+#include <linux/usb.h>
+#define D_LOCAL 0
+#include <linux/uwb/debug.h>
+
+#include "../../wusbcore/wusbhc.h"
+
+#include "whcd.h"
+
+#if D_LOCAL >= 4
+static void dump_asl(struct whc *whc, const char *tag)
+{
+ struct device *dev = &whc->umc->dev;
+ struct whc_qset *qset;
+
+ d_printf(4, dev, "ASL %s\n", tag);
+
+ list_for_each_entry(qset, &whc->async_list, list_node) {
+ dump_qset(qset, dev);
+ }
+}
+#else
+static inline void dump_asl(struct whc *whc, const char *tag)
+{
+}
+#endif
+
+
+static void qset_get_next_prev(struct whc *whc, struct whc_qset *qset,
+ struct whc_qset **next, struct whc_qset **prev)
+{
+ struct list_head *n, *p;
+
+ BUG_ON(list_empty(&whc->async_list));
+
+ n = qset->list_node.next;
+ if (n == &whc->async_list)
+ n = n->next;
+ p = qset->list_node.prev;
+ if (p == &whc->async_list)
+ p = p->prev;
+
+ *next = container_of(n, struct whc_qset, list_node);
+ *prev = container_of(p, struct whc_qset, list_node);
+
+}
+
+static void asl_qset_insert_begin(struct whc *whc, struct whc_qset *qset)
+{
+ list_move(&qset->list_node, &whc->async_list);
+ qset->in_sw_list = true;
+}
+
+static void asl_qset_insert(struct whc *whc, struct whc_qset *qset)
+{
+ struct whc_qset *next, *prev;
+
+ qset_clear(whc, qset);
+
+ /* Link into ASL. */
+ qset_get_next_prev(whc, qset, &next, &prev);
+ whc_qset_set_link_ptr(&qset->qh.link, next->qset_dma);
+ whc_qset_set_link_ptr(&prev->qh.link, qset->qset_dma);
+ qset->in_hw_list = true;
+}
+
+static void asl_qset_remove(struct whc *whc, struct whc_qset *qset)
+{
+ struct whc_qset *prev, *next;
+
+ qset_get_next_prev(whc, qset, &next, &prev);
+
+ list_move(&qset->list_node, &whc->async_removed_list);
+ qset->in_sw_list = false;
+
+ /*
+ * No more qsets in the ASL? The caller must stop the ASL as
+ * it's no longer valid.
+ */
+ if (list_empty(&whc->async_list))
+ return;
+
+ /* Remove from ASL. */
+ whc_qset_set_link_ptr(&prev->qh.link, next->qset_dma);
+ qset->in_hw_list = false;
+}
+
+/**
+ * process_qset - process any recently inactivated or halted qTDs in a
+ * qset.
+ *
+ * After inactive qTDs are removed, new qTDs can be added if the
+ * urb queue still contains URBs.
+ *
+ * Returns any additional WUSBCMD bits for the ASL sync command (i.e.,
+ * WUSBCMD_ASYNC_QSET_RM if a halted qset was removed).
+ */
+static uint32_t process_qset(struct whc *whc, struct whc_qset *qset)
+{
+ enum whc_update update = 0;
+ uint32_t status = 0;
+
+ while (qset->ntds) {
+ struct whc_qtd *td;
+ int t;
+
+ t = qset->td_start;
+ td = &qset->qtd[qset->td_start];
+ status = le32_to_cpu(td->status);
+
+ /*
+ * Nothing to do with a still active qTD.
+ */
+ if (status & QTD_STS_ACTIVE)
+ break;
+
+ if (status & QTD_STS_HALTED) {
+ /* Ug, an error. */
+ process_halted_qtd(whc, qset, td);
+ goto done;
+ }
+
+ /* Mmm, a completed qTD. */
+ process_inactive_qtd(whc, qset, td);
+ }
+
+ update |= qset_add_qtds(whc, qset);
+
+done:
+ /*
+ * Remove this qset from the ASL if requested, but only if has
+ * no qTDs.
+ */
+ if (qset->remove && qset->ntds == 0) {
+ asl_qset_remove(whc, qset);
+ update |= WHC_UPDATE_REMOVED;
+ }
+ return update;
+}
+
+void asl_start(struct whc *whc)
+{
+ struct whc_qset *qset;
+
+ qset = list_first_entry(&whc->async_list, struct whc_qset, list_node);
+
+ le_writeq(qset->qset_dma | QH_LINK_NTDS(8), whc->base + WUSBASYNCLISTADDR);
+
+ whc_write_wusbcmd(whc, WUSBCMD_ASYNC_EN, WUSBCMD_ASYNC_EN);
+ whci_wait_for(&whc->umc->dev, whc->base + WUSBSTS,
+ WUSBSTS_ASYNC_SCHED, WUSBSTS_ASYNC_SCHED,
+ 1000, "start ASL");
+}
+
+void asl_stop(struct whc *whc)
+{
+ whc_write_wusbcmd(whc, WUSBCMD_ASYNC_EN, 0);
+ whci_wait_for(&whc->umc->dev, whc->base + WUSBSTS,
+ WUSBSTS_ASYNC_SCHED, 0,
+ 1000, "stop ASL");
+}
+
+void asl_update(struct whc *whc, uint32_t wusbcmd)
+{
+ whc_write_wusbcmd(whc, wusbcmd, wusbcmd);
+ wait_event(whc->async_list_wq,
+ (le_readl(whc->base + WUSBCMD) & WUSBCMD_ASYNC_UPDATED) == 0);
+}
+
+/**
+ * scan_async_work - scan the ASL for qsets to process.
+ *
+ * Process each qset in the ASL in turn and then signal the WHC that
+ * the ASL has been updated.
+ *
+ * Then start, stop or update the asynchronous schedule as required.
+ */
+void scan_async_work(struct work_struct *work)
+{
+ struct whc *whc = container_of(work, struct whc, async_work);
+ struct whc_qset *qset, *t;
+ enum whc_update update = 0;
+
+ spin_lock_irq(&whc->lock);
+
+ dump_asl(whc, "before processing");
+
+ /*
+ * Transerve the software list backwards so new qsets can be
+ * safely inserted into the ASL without making it non-circular.
+ */
+ list_for_each_entry_safe_reverse(qset, t, &whc->async_list, list_node) {
+ if (!qset->in_hw_list) {
+ asl_qset_insert(whc, qset);
+ update |= WHC_UPDATE_ADDED;
+ }
+
+ update |= process_qset(whc, qset);
+ }
+
+ dump_asl(whc, "after processing");
+
+ spin_unlock_irq(&whc->lock);
+
+ if (update) {
+ uint32_t wusbcmd = WUSBCMD_ASYNC_UPDATED | WUSBCMD_ASYNC_SYNCED_DB;
+ if (update & WHC_UPDATE_REMOVED)
+ wusbcmd |= WUSBCMD_ASYNC_QSET_RM;
+ asl_update(whc, wusbcmd);
+ }
+
+ /*
+ * Now that the ASL is updated, complete the removal of any
+ * removed qsets.
+ */
+ spin_lock(&whc->lock);
+
+ list_for_each_entry_safe(qset, t, &whc->async_removed_list, list_node) {
+ qset_remove_complete(whc, qset);
+ }
+
+ spin_unlock(&whc->lock);
+}
+
+/**
+ * asl_urb_enqueue - queue an URB onto the asynchronous list (ASL).
+ * @whc: the WHCI host controller
+ * @urb: the URB to enqueue
+ * @mem_flags: flags for any memory allocations
+ *
+ * The qset for the endpoint is obtained and the urb queued on to it.
+ *
+ * Work is scheduled to update the hardware's view of the ASL.
+ */
+int asl_urb_enqueue(struct whc *whc, struct urb *urb, gfp_t mem_flags)
+{
+ struct whc_qset *qset;
+ int err;
+ unsigned long flags;
+
+ spin_lock_irqsave(&whc->lock, flags);
+
+ qset = get_qset(whc, urb, GFP_ATOMIC);
+ if (qset == NULL)
+ err = -ENOMEM;
+ else
+ err = qset_add_urb(whc, qset, urb, GFP_ATOMIC);
+ if (!err) {
+ usb_hcd_link_urb_to_ep(&whc->wusbhc.usb_hcd, urb);
+ if (!qset->in_sw_list)
+ asl_qset_insert_begin(whc, qset);
+ }
+
+ spin_unlock_irqrestore(&whc->lock, flags);
+
+ if (!err)
+ queue_work(whc->workqueue, &whc->async_work);
+
+ return 0;
+}
+
+/**
+ * asl_urb_dequeue - remove an URB (qset) from the async list.
+ * @whc: the WHCI host controller
+ * @urb: the URB to dequeue
+ * @status: the current status of the URB
+ *
+ * URBs that do yet have qTDs can simply be removed from the software
+ * queue, otherwise the qset must be removed from the ASL so the qTDs
+ * can be removed.
+ */
+int asl_urb_dequeue(struct whc *whc, struct urb *urb, int status)
+{
+ struct whc_urb *wurb = urb->hcpriv;
+ struct whc_qset *qset = wurb->qset;
+ struct whc_std *std, *t;
+ int ret;
+ unsigned long flags;
+
+ spin_lock_irqsave(&whc->lock, flags);
+
+ ret = usb_hcd_check_unlink_urb(&whc->wusbhc.usb_hcd, urb, status);
+ if (ret < 0)
+ goto out;
+
+ list_for_each_entry_safe(std, t, &qset->stds, list_node) {
+ if (std->urb == urb)
+ qset_free_std(whc, std);
+ else
+ std->qtd = NULL; /* so this std is re-added when the qset is */
+ }
+
+ asl_qset_remove(whc, qset);
+ wurb->status = status;
+ wurb->is_async = true;
+ queue_work(whc->workqueue, &wurb->dequeue_work);
+
+out:
+ spin_unlock_irqrestore(&whc->lock, flags);
+
+ return ret;
+}
+
+/**
+ * asl_qset_delete - delete a qset from the ASL
+ */
+void asl_qset_delete(struct whc *whc, struct whc_qset *qset)
+{
+ qset->remove = 1;
+ queue_work(whc->workqueue, &whc->async_work);
+ qset_delete(whc, qset);
+}
+
+/**
+ * asl_init - initialize the asynchronous schedule list
+ *
+ * A dummy qset with no qTDs is added to the ASL to simplify removing
+ * qsets (no need to stop the ASL when the last qset is removed).
+ */
+int asl_init(struct whc *whc)
+{
+ struct whc_qset *qset;
+
+ qset = qset_alloc(whc, GFP_KERNEL);
+ if (qset == NULL)
+ return -ENOMEM;
+
+ asl_qset_insert_begin(whc, qset);
+ asl_qset_insert(whc, qset);
+
+ return 0;
+}
+
+/**
+ * asl_clean_up - free ASL resources
+ *
+ * The ASL is stopped and empty except for the dummy qset.
+ */
+void asl_clean_up(struct whc *whc)
+{
+ struct whc_qset *qset;
+
+ if (!list_empty(&whc->async_list)) {
+ qset = list_first_entry(&whc->async_list, struct whc_qset, list_node);
+ list_del(&qset->list_node);
+ qset_free(whc, qset);
+ }
+}
diff --git a/drivers/usb/host/whci/hcd.c b/drivers/usb/host/whci/hcd.c
new file mode 100644
index 000000000000..ef3ad4dca945
--- /dev/null
+++ b/drivers/usb/host/whci/hcd.c
@@ -0,0 +1,339 @@
+/*
+ * Wireless Host Controller (WHC) driver.
+ *
+ * Copyright (C) 2007 Cambridge Silicon Radio Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License version
+ * 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that 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, see <http://www.gnu.org/licenses/>.
+ */
+#include <linux/version.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/uwb/umc.h>
+
+#include "../../wusbcore/wusbhc.h"
+
+#include "whcd.h"
+
+/*
+ * One time initialization.
+ *
+ * Nothing to do here.
+ */
+static int whc_reset(struct usb_hcd *usb_hcd)
+{
+ return 0;
+}
+
+/*
+ * Start the wireless host controller.
+ *
+ * Start device notification.
+ *
+ * Put hc into run state, set DNTS parameters.
+ */
+static int whc_start(struct usb_hcd *usb_hcd)
+{
+ struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd);
+ struct whc *whc = wusbhc_to_whc(wusbhc);
+ u8 bcid;
+ int ret;
+
+ mutex_lock(&wusbhc->mutex);
+
+ le_writel(WUSBINTR_GEN_CMD_DONE
+ | WUSBINTR_HOST_ERR
+ | WUSBINTR_ASYNC_SCHED_SYNCED
+ | WUSBINTR_DNTS_INT
+ | WUSBINTR_ERR_INT
+ | WUSBINTR_INT,
+ whc->base + WUSBINTR);
+
+ /* set cluster ID */
+ bcid = wusb_cluster_id_get();
+ ret = whc_set_cluster_id(whc, bcid);
+ if (ret < 0)
+ goto out;
+ wusbhc->cluster_id = bcid;
+
+ /* start HC */
+ whc_write_wusbcmd(whc, WUSBCMD_RUN, WUSBCMD_RUN);
+
+ usb_hcd->uses_new_polling = 1;
+ usb_hcd->poll_rh = 1;
+ usb_hcd->state = HC_STATE_RUNNING;
+
+out:
+ mutex_unlock(&wusbhc->mutex);
+ return ret;
+}
+
+
+/*
+ * Stop the wireless host controller.
+ *
+ * Stop device notification.
+ *
+ * Wait for pending transfer to stop? Put hc into stop state?
+ */
+static void whc_stop(struct usb_hcd *usb_hcd)
+{
+ struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd);
+ struct whc *whc = wusbhc_to_whc(wusbhc);
+
+ mutex_lock(&wusbhc->mutex);
+
+ wusbhc_stop(wusbhc);
+
+ /* stop HC */
+ le_writel(0, whc->base + WUSBINTR);
+ whc_write_wusbcmd(whc, WUSBCMD_RUN, 0);
+ whci_wait_for(&whc->umc->dev, whc->base + WUSBSTS,
+ WUSBSTS_HCHALTED, WUSBSTS_HCHALTED,
+ 100, "HC to halt");
+
+ wusb_cluster_id_put(wusbhc->cluster_id);
+
+ mutex_unlock(&wusbhc->mutex);
+}
+
+static int whc_get_frame_number(struct usb_hcd *usb_hcd)
+{
+ /* Frame numbers are not applicable to WUSB. */
+ return -ENOSYS;
+}
+
+
+/*
+ * Queue an URB to the ASL or PZL
+ */
+static int whc_urb_enqueue(struct usb_hcd *usb_hcd, struct urb *urb,
+ gfp_t mem_flags)
+{
+ struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd);
+ struct whc *whc = wusbhc_to_whc(wusbhc);
+ int ret;
+
+ switch (usb_pipetype(urb->pipe)) {
+ case PIPE_INTERRUPT:
+ ret = pzl_urb_enqueue(whc, urb, mem_flags);
+ break;
+ case PIPE_ISOCHRONOUS:
+ dev_err(&whc->umc->dev, "isochronous transfers unsupported\n");
+ ret = -ENOTSUPP;
+ break;
+ case PIPE_CONTROL:
+ case PIPE_BULK:
+ default:
+ ret = asl_urb_enqueue(whc, urb, mem_flags);
+ break;
+ };
+
+ return ret;
+}
+
+/*
+ * Remove a queued URB from the ASL or PZL.
+ */
+static int whc_urb_dequeue(struct usb_hcd *usb_hcd, struct urb *urb, int status)
+{
+ struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd);
+ struct whc *whc = wusbhc_to_whc(wusbhc);
+ int ret;
+
+ switch (usb_pipetype(urb->pipe)) {
+ case PIPE_INTERRUPT:
+ ret = pzl_urb_dequeue(whc, urb, status);
+ break;
+ case PIPE_ISOCHRONOUS:
+ ret = -ENOTSUPP;
+ break;
+ case PIPE_CONTROL:
+ case PIPE_BULK:
+ default:
+ ret = asl_urb_dequeue(whc, urb, status);
+ break;
+ };
+
+ return ret;
+}
+
+/*
+ * Wait for all URBs to the endpoint to be completed, then delete the
+ * qset.
+ */
+static void whc_endpoint_disable(struct usb_hcd *usb_hcd,
+ struct usb_host_endpoint *ep)
+{
+ struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd);
+ struct whc *whc = wusbhc_to_whc(wusbhc);
+ struct whc_qset *qset;
+
+ qset = ep->hcpriv;
+ if (qset) {
+ ep->hcpriv = NULL;
+ if (usb_endpoint_xfer_bulk(&ep->desc)
+ || usb_endpoint_xfer_control(&ep->desc))
+ asl_qset_delete(whc, qset);
+ else
+ pzl_qset_delete(whc, qset);
+ }
+}
+
+static struct hc_driver whc_hc_driver = {
+ .description = "whci-hcd",
+ .product_desc = "Wireless host controller",
+ .hcd_priv_size = sizeof(struct whc) - sizeof(struct usb_hcd),
+ .irq = whc_int_handler,
+ .flags = HCD_USB2,
+
+ .reset = whc_reset,
+ .start = whc_start,
+ .stop = whc_stop,
+ .get_frame_number = whc_get_frame_number,
+ .urb_enqueue = whc_urb_enqueue,
+ .urb_dequeue = whc_urb_dequeue,
+ .endpoint_disable = whc_endpoint_disable,
+
+ .hub_status_data = wusbhc_rh_status_data,
+ .hub_control = wusbhc_rh_control,
+ .bus_suspend = wusbhc_rh_suspend,
+ .bus_resume = wusbhc_rh_resume,
+ .start_port_reset = wusbhc_rh_start_port_reset,
+};
+
+static int whc_probe(struct umc_dev *umc)
+{
+ int ret = -ENOMEM;
+ struct usb_hcd *usb_hcd;
+ struct wusbhc *wusbhc = NULL;
+ struct whc *whc = NULL;
+ struct device *dev = &umc->dev;
+
+ usb_hcd = usb_create_hcd(&whc_hc_driver, dev, "whci");
+ if (usb_hcd == NULL) {
+ dev_err(dev, "unable to create hcd\n");
+ goto error;
+ }
+
+ usb_hcd->wireless = 1;
+
+ wusbhc = usb_hcd_to_wusbhc(usb_hcd);
+ whc = wusbhc_to_whc(wusbhc);
+ whc->umc = umc;
+
+ ret = whc_init(whc);
+ if (ret)
+ goto error;
+
+ wusbhc->dev = dev;
+ wusbhc->uwb_rc = uwb_rc_get_by_grandpa(umc->dev.parent);
+ if (!wusbhc->uwb_rc) {
+ ret = -ENODEV;
+ dev_err(dev, "cannot get radio controller\n");
+ goto error;
+ }
+
+ if (whc->n_devices > USB_MAXCHILDREN) {
+ dev_warn(dev, "USB_MAXCHILDREN too low for WUSB adapter (%u ports)\n",
+ whc->n_devices);
+ wusbhc->ports_max = USB_MAXCHILDREN;
+ } else
+ wusbhc->ports_max = whc->n_devices;
+ wusbhc->mmcies_max = whc->n_mmc_ies;
+ wusbhc->start = whc_wusbhc_start;
+ wusbhc->stop = whc_wusbhc_stop;
+ wusbhc->mmcie_add = whc_mmcie_add;
+ wusbhc->mmcie_rm = whc_mmcie_rm;
+ wusbhc->dev_info_set = whc_dev_info_set;
+ wusbhc->bwa_set = whc_bwa_set;
+ wusbhc->set_num_dnts = whc_set_num_dnts;
+ wusbhc->set_ptk = whc_set_ptk;
+ wusbhc->set_gtk = whc_set_gtk;
+
+ ret = wusbhc_create(wusbhc);
+ if (ret)
+ goto error_wusbhc_create;
+
+ ret = usb_add_hcd(usb_hcd, whc->umc->irq, IRQF_SHARED);
+ if (ret) {
+ dev_err(dev, "cannot add HCD: %d\n", ret);
+ goto error_usb_add_hcd;
+ }
+
+ ret = wusbhc_b_create(wusbhc);
+ if (ret) {
+ dev_err(dev, "WUSBHC phase B setup failed: %d\n", ret);
+ goto error_wusbhc_b_create;
+ }
+
+ return 0;
+
+error_wusbhc_b_create:
+ usb_remove_hcd(usb_hcd);
+error_usb_add_hcd:
+ wusbhc_destroy(wusbhc);
+error_wusbhc_create:
+ uwb_rc_put(wusbhc->uwb_rc);
+error:
+ whc_clean_up(whc);
+ if (usb_hcd)
+ usb_put_hcd(usb_hcd);
+ return ret;
+}
+
+
+static void whc_remove(struct umc_dev *umc)
+{
+ struct usb_hcd *usb_hcd = dev_get_drvdata(&umc->dev);
+ struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd);
+ struct whc *whc = wusbhc_to_whc(wusbhc);
+
+ if (usb_hcd) {
+ wusbhc_b_destroy(wusbhc);
+ usb_remove_hcd(usb_hcd);
+ wusbhc_destroy(wusbhc);
+ uwb_rc_put(wusbhc->uwb_rc);
+ whc_clean_up(whc);
+ usb_put_hcd(usb_hcd);
+ }
+}
+
+static struct umc_driver whci_hc_driver = {
+ .name = "whci-hcd",
+ .cap_id = UMC_CAP_ID_WHCI_WUSB_HC,
+ .probe = whc_probe,
+ .remove = whc_remove,
+};
+
+static int __init whci_hc_driver_init(void)
+{
+ return umc_driver_register(&whci_hc_driver);
+}
+module_init(whci_hc_driver_init);
+
+static void __exit whci_hc_driver_exit(void)
+{
+ umc_driver_unregister(&whci_hc_driver);
+}
+module_exit(whci_hc_driver_exit);
+
+/* PCI device ID's that we handle (so it gets loaded) */
+static struct pci_device_id whci_hcd_id_table[] = {
+ { PCI_DEVICE_CLASS(PCI_CLASS_WIRELESS_WHCI, ~0) },
+ { /* empty last entry */ }
+};
+MODULE_DEVICE_TABLE(pci, whci_hcd_id_table);
+
+MODULE_DESCRIPTION("WHCI Wireless USB host controller driver");
+MODULE_AUTHOR("Cambridge Silicon Radio Ltd.");
+MODULE_LICENSE("GPL");
diff --git a/drivers/usb/host/whci/hw.c b/drivers/usb/host/whci/hw.c
new file mode 100644
index 000000000000..ac86e59c1225
--- /dev/null
+++ b/drivers/usb/host/whci/hw.c
@@ -0,0 +1,87 @@
+/*
+ * Wireless Host Controller (WHC) hardware access helpers.
+ *
+ * Copyright (C) 2007 Cambridge Silicon Radio Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License version
+ * 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that 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, see <http://www.gnu.org/licenses/>.
+ */
+#include <linux/kernel.h>
+#include <linux/dma-mapping.h>
+#include <linux/uwb/umc.h>
+
+#include "../../wusbcore/wusbhc.h"
+
+#include "whcd.h"
+
+void whc_write_wusbcmd(struct whc *whc, u32 mask, u32 val)
+{
+ unsigned long flags;
+ u32 cmd;
+
+ spin_lock_irqsave(&whc->lock, flags);
+
+ cmd = le_readl(whc->base + WUSBCMD);
+ cmd = (cmd & ~mask) | val;
+ le_writel(cmd, whc->base + WUSBCMD);
+
+ spin_unlock_irqrestore(&whc->lock, flags);
+}
+
+/**
+ * whc_do_gencmd - start a generic command via the WUSBGENCMDSTS register
+ * @whc: the WHCI HC
+ * @cmd: command to start.
+ * @params: parameters for the command (the WUSBGENCMDPARAMS register value).
+ * @addr: pointer to any data for the command (may be NULL).
+ * @len: length of the data (if any).
+ */
+int whc_do_gencmd(struct whc *whc, u32 cmd, u32 params, void *addr, size_t len)
+{
+ unsigned long flags;
+ dma_addr_t dma_addr;
+ int t;
+
+ mutex_lock(&whc->mutex);
+
+ /* Wait for previous command to complete. */
+ t = wait_event_timeout(whc->cmd_wq,
+ (le_readl(whc->base + WUSBGENCMDSTS) & WUSBGENCMDSTS_ACTIVE) == 0,
+ WHC_GENCMD_TIMEOUT_MS);
+ if (t == 0) {
+ dev_err(&whc->umc->dev, "generic command timeout (%04x/%04x)\n",
+ le_readl(whc->base + WUSBGENCMDSTS),
+ le_readl(whc->base + WUSBGENCMDPARAMS));
+ return -ETIMEDOUT;
+ }
+
+ if (addr) {
+ memcpy(whc->gen_cmd_buf, addr, len);
+ dma_addr = whc->gen_cmd_buf_dma;
+ } else
+ dma_addr = 0;
+
+ /* Poke registers to start cmd. */
+ spin_lock_irqsave(&whc->lock, flags);
+
+ le_writel(params, whc->base + WUSBGENCMDPARAMS);
+ le_writeq(dma_addr, whc->base + WUSBGENADDR);
+
+ le_writel(WUSBGENCMDSTS_ACTIVE | WUSBGENCMDSTS_IOC | cmd,
+ whc->base + WUSBGENCMDSTS);
+
+ spin_unlock_irqrestore(&whc->lock, flags);
+
+ mutex_unlock(&whc->mutex);
+
+ return 0;
+}
diff --git a/drivers/usb/host/whci/init.c b/drivers/usb/host/whci/init.c
new file mode 100644
index 000000000000..34a783cb0133
--- /dev/null
+++ b/drivers/usb/host/whci/init.c
@@ -0,0 +1,188 @@
+/*
+ * Wireless Host Controller (WHC) initialization.
+ *
+ * Copyright (C) 2007 Cambridge Silicon Radio Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License version
+ * 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that 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, see <http://www.gnu.org/licenses/>.
+ */
+#include <linux/kernel.h>
+#include <linux/dma-mapping.h>
+#include <linux/uwb/umc.h>
+
+#include "../../wusbcore/wusbhc.h"
+
+#include "whcd.h"
+
+/*
+ * Reset the host controller.
+ */
+static void whc_hw_reset(struct whc *whc)
+{
+ le_writel(WUSBCMD_WHCRESET, whc->base + WUSBCMD);
+ whci_wait_for(&whc->umc->dev, whc->base + WUSBCMD, WUSBCMD_WHCRESET, 0,
+ 100, "reset");
+}
+
+static void whc_hw_init_di_buf(struct whc *whc)
+{
+ int d;
+
+ /* Disable all entries in the Device Information buffer. */
+ for (d = 0; d < whc->n_devices; d++)
+ whc->di_buf[d].addr_sec_info = WHC_DI_DISABLE;
+
+ le_writeq(whc->di_buf_dma, whc->base + WUSBDEVICEINFOADDR);
+}
+
+static void whc_hw_init_dn_buf(struct whc *whc)
+{
+ /* Clear the Device Notification buffer to ensure the V (valid)
+ * bits are clear. */
+ memset(whc->dn_buf, 0, 4096);
+
+ le_writeq(whc->dn_buf_dma, whc->base + WUSBDNTSBUFADDR);
+}
+
+int whc_init(struct whc *whc)
+{
+ u32 whcsparams;
+ int ret, i;
+ resource_size_t start, len;
+
+ spin_lock_init(&whc->lock);
+ mutex_init(&whc->mutex);
+ init_waitqueue_head(&whc->cmd_wq);
+ init_waitqueue_head(&whc->async_list_wq);
+ init_waitqueue_head(&whc->periodic_list_wq);
+ whc->workqueue = create_singlethread_workqueue(dev_name(&whc->umc->dev));
+ if (whc->workqueue == NULL) {
+ ret = -ENOMEM;
+ goto error;
+ }
+ INIT_WORK(&whc->dn_work, whc_dn_work);
+
+ INIT_WORK(&whc->async_work, scan_async_work);
+ INIT_LIST_HEAD(&whc->async_list);
+ INIT_LIST_HEAD(&whc->async_removed_list);
+
+ INIT_WORK(&whc->periodic_work, scan_periodic_work);
+ for (i = 0; i < 5; i++)
+ INIT_LIST_HEAD(&whc->periodic_list[i]);
+ INIT_LIST_HEAD(&whc->periodic_removed_list);
+
+ /* Map HC registers. */
+ start = whc->umc->resource.start;
+ len = whc->umc->resource.end - start + 1;
+ if (!request_mem_region(start, len, "whci-hc")) {
+ dev_err(&whc->umc->dev, "can't request HC region\n");
+ ret = -EBUSY;
+ goto error;
+ }
+ whc->base_phys = start;
+ whc->base = ioremap(start, len);
+ if (!whc->base) {
+ dev_err(&whc->umc->dev, "ioremap\n");
+ ret = -ENOMEM;
+ goto error;
+ }
+
+ whc_hw_reset(whc);
+
+ /* Read maximum number of devices, keys and MMC IEs. */
+ whcsparams = le_readl(whc->base + WHCSPARAMS);
+ whc->n_devices = WHCSPARAMS_TO_N_DEVICES(whcsparams);
+ whc->n_keys = WHCSPARAMS_TO_N_KEYS(whcsparams);
+ whc->n_mmc_ies = WHCSPARAMS_TO_N_MMC_IES(whcsparams);
+
+ dev_dbg(&whc->umc->dev, "N_DEVICES = %d, N_KEYS = %d, N_MMC_IES = %d\n",
+ whc->n_devices, whc->n_keys, whc->n_mmc_ies);
+
+ whc->qset_pool = dma_pool_create("qset", &whc->umc->dev,
+ sizeof(struct whc_qset), 64, 0);
+ if (whc->qset_pool == NULL) {
+ ret = -ENOMEM;
+ goto error;
+ }
+
+ ret = asl_init(whc);
+ if (ret < 0)
+ goto error;
+ ret = pzl_init(whc);
+ if (ret < 0)
+ goto error;
+
+ /* Allocate and initialize a buffer for generic commands, the
+ Device Information buffer, and the Device Notification
+ buffer. */
+
+ whc->gen_cmd_buf = dma_alloc_coherent(&whc->umc->dev, WHC_GEN_CMD_DATA_LEN,
+ &whc->gen_cmd_buf_dma, GFP_KERNEL);
+ if (whc->gen_cmd_buf == NULL) {
+ ret = -ENOMEM;
+ goto error;
+ }
+
+ whc->dn_buf = dma_alloc_coherent(&whc->umc->dev,
+ sizeof(struct dn_buf_entry) * WHC_N_DN_ENTRIES,
+ &whc->dn_buf_dma, GFP_KERNEL);
+ if (!whc->dn_buf) {
+ ret = -ENOMEM;
+ goto error;
+ }
+ whc_hw_init_dn_buf(whc);
+
+ whc->di_buf = dma_alloc_coherent(&whc->umc->dev,
+ sizeof(struct di_buf_entry) * whc->n_devices,
+ &whc->di_buf_dma, GFP_KERNEL);
+ if (!whc->di_buf) {
+ ret = -ENOMEM;
+ goto error;
+ }
+ whc_hw_init_di_buf(whc);
+
+ return 0;
+
+error:
+ whc_clean_up(whc);
+ return ret;
+}
+
+void whc_clean_up(struct whc *whc)
+{
+ resource_size_t len;
+
+ if (whc->di_buf)
+ dma_free_coherent(&whc->umc->dev, sizeof(struct di_buf_entry) * whc->n_devices,
+ whc->di_buf, whc->di_buf_dma);
+ if (whc->dn_buf)
+ dma_free_coherent(&whc->umc->dev, sizeof(struct dn_buf_entry) * WHC_N_DN_ENTRIES,
+ whc->dn_buf, whc->dn_buf_dma);
+ if (whc->gen_cmd_buf)
+ dma_free_coherent(&whc->umc->dev, WHC_GEN_CMD_DATA_LEN,
+ whc->gen_cmd_buf, whc->gen_cmd_buf_dma);
+
+ pzl_clean_up(whc);
+ asl_clean_up(whc);
+
+ if (whc->qset_pool)
+ dma_pool_destroy(whc->qset_pool);
+
+ len = whc->umc->resource.end - whc->umc->resource.start + 1;
+ if (whc->base)
+ iounmap(whc->base);
+ if (whc->base_phys)
+ release_mem_region(whc->base_phys, len);
+
+ if (whc->workqueue)
+ destroy_workqueue(whc->workqueue);
+}
diff --git a/drivers/usb/host/whci/int.c b/drivers/usb/host/whci/int.c
new file mode 100644
index 000000000000..fce01174aa9b
--- /dev/null
+++ b/drivers/usb/host/whci/int.c
@@ -0,0 +1,95 @@
+/*
+ * Wireless Host Controller (WHC) interrupt handling.
+ *
+ * Copyright (C) 2007 Cambridge Silicon Radio Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License version
+ * 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that 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, see <http://www.gnu.org/licenses/>.
+ */
+#include <linux/version.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/uwb/umc.h>
+
+#include "../../wusbcore/wusbhc.h"
+
+#include "whcd.h"
+
+static void transfer_done(struct whc *whc)
+{
+ queue_work(whc->workqueue, &whc->async_work);
+ queue_work(whc->workqueue, &whc->periodic_work);
+}
+
+irqreturn_t whc_int_handler(struct usb_hcd *hcd)
+{
+ struct wusbhc *wusbhc = usb_hcd_to_wusbhc(hcd);
+ struct whc *whc = wusbhc_to_whc(wusbhc);
+ u32 sts;
+
+ sts = le_readl(whc->base + WUSBSTS);
+ if (!(sts & WUSBSTS_INT_MASK))
+ return IRQ_NONE;
+ le_writel(sts & WUSBSTS_INT_MASK, whc->base + WUSBSTS);
+
+ if (sts & WUSBSTS_GEN_CMD_DONE)
+ wake_up(&whc->cmd_wq);
+
+ if (sts & WUSBSTS_HOST_ERR)
+ dev_err(&whc->umc->dev, "FIXME: host system error\n");
+
+ if (sts & WUSBSTS_ASYNC_SCHED_SYNCED)
+ wake_up(&whc->async_list_wq);
+
+ if (sts & WUSBSTS_PERIODIC_SCHED_SYNCED)
+ wake_up(&whc->periodic_list_wq);
+
+ if (sts & WUSBSTS_DNTS_INT)
+ queue_work(whc->workqueue, &whc->dn_work);
+
+ /*
+ * A transfer completed (see [WHCI] section 4.7.1.2 for when
+ * this occurs).
+ */
+ if (sts & (WUSBSTS_INT | WUSBSTS_ERR_INT))
+ transfer_done(whc);
+
+ return IRQ_HANDLED;
+}
+
+static int process_dn_buf(struct whc *whc)
+{
+ struct wusbhc *wusbhc = &whc->wusbhc;
+ struct dn_buf_entry *dn;
+ int processed = 0;
+
+ for (dn = whc->dn_buf; dn < whc->dn_buf + WHC_N_DN_ENTRIES; dn++) {
+ if (dn->status & WHC_DN_STATUS_VALID) {
+ wusbhc_handle_dn(wusbhc, dn->src_addr,
+ (struct wusb_dn_hdr *)dn->dn_data,
+ dn->msg_size);
+ dn->status &= ~WHC_DN_STATUS_VALID;
+ processed++;
+ }
+ }
+ return processed;
+}
+
+void whc_dn_work(struct work_struct *work)
+{
+ struct whc *whc = container_of(work, struct whc, dn_work);
+ int processed;
+
+ do {
+ processed = process_dn_buf(whc);
+ } while (processed);
+}
diff --git a/drivers/usb/host/whci/pzl.c b/drivers/usb/host/whci/pzl.c
new file mode 100644
index 000000000000..8d62df0c330b
--- /dev/null
+++ b/drivers/usb/host/whci/pzl.c
@@ -0,0 +1,398 @@
+/*
+ * Wireless Host Controller (WHC) periodic schedule management.
+ *
+ * Copyright (C) 2007 Cambridge Silicon Radio Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License version
+ * 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that 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, see <http://www.gnu.org/licenses/>.
+ */
+#include <linux/kernel.h>
+#include <linux/dma-mapping.h>
+#include <linux/uwb/umc.h>
+#include <linux/usb.h>
+#define D_LOCAL 0
+#include <linux/uwb/debug.h>
+
+#include "../../wusbcore/wusbhc.h"
+
+#include "whcd.h"
+
+#if D_LOCAL >= 4
+static void dump_pzl(struct whc *whc, const char *tag)
+{
+ struct device *dev = &whc->umc->dev;
+ struct whc_qset *qset;
+ int period = 0;
+
+ d_printf(4, dev, "PZL %s\n", tag);
+
+ for (period = 0; period < 5; period++) {
+ d_printf(4, dev, "Period %d\n", period);
+ list_for_each_entry(qset, &whc->periodic_list[period], list_node) {
+ dump_qset(qset, dev);
+ }
+ }
+}
+#else
+static inline void dump_pzl(struct whc *whc, const char *tag)
+{
+}
+#endif
+
+static void update_pzl_pointers(struct whc *whc, int period, u64 addr)
+{
+ switch (period) {
+ case 0:
+ whc_qset_set_link_ptr(&whc->pz_list[0], addr);
+ whc_qset_set_link_ptr(&whc->pz_list[2], addr);
+ whc_qset_set_link_ptr(&whc->pz_list[4], addr);
+ whc_qset_set_link_ptr(&whc->pz_list[6], addr);
+ whc_qset_set_link_ptr(&whc->pz_list[8], addr);
+ whc_qset_set_link_ptr(&whc->pz_list[10], addr);
+ whc_qset_set_link_ptr(&whc->pz_list[12], addr);
+ whc_qset_set_link_ptr(&whc->pz_list[14], addr);
+ break;
+ case 1:
+ whc_qset_set_link_ptr(&whc->pz_list[1], addr);
+ whc_qset_set_link_ptr(&whc->pz_list[5], addr);
+ whc_qset_set_link_ptr(&whc->pz_list[9], addr);
+ whc_qset_set_link_ptr(&whc->pz_list[13], addr);
+ break;
+ case 2:
+ whc_qset_set_link_ptr(&whc->pz_list[3], addr);
+ whc_qset_set_link_ptr(&whc->pz_list[11], addr);
+ break;
+ case 3:
+ whc_qset_set_link_ptr(&whc->pz_list[7], addr);
+ break;
+ case 4:
+ whc_qset_set_link_ptr(&whc->pz_list[15], addr);
+ break;
+ }
+}
+
+/*
+ * Return the 'period' to use for this qset. The minimum interval for
+ * the endpoint is used so whatever urbs are submitted the device is
+ * polled often enough.
+ */
+static int qset_get_period(struct whc *whc, struct whc_qset *qset)
+{
+ uint8_t bInterval = qset->ep->desc.bInterval;
+
+ if (bInterval < 6)
+ bInterval = 6;
+ if (bInterval > 10)
+ bInterval = 10;
+ return bInterval - 6;
+}
+
+static void qset_insert_in_sw_list(struct whc *whc, struct whc_qset *qset)
+{
+ int period;
+
+ period = qset_get_period(whc, qset);
+
+ qset_clear(whc, qset);
+ list_move(&qset->list_node, &whc->periodic_list[period]);
+ qset->in_sw_list = true;
+}
+
+static void pzl_qset_remove(struct whc *whc, struct whc_qset *qset)
+{
+ list_move(&qset->list_node, &whc->periodic_removed_list);
+ qset->in_hw_list = false;
+ qset->in_sw_list = false;
+}
+
+/**
+ * pzl_process_qset - process any recently inactivated or halted qTDs
+ * in a qset.
+ *
+ * After inactive qTDs are removed, new qTDs can be added if the
+ * urb queue still contains URBs.
+ *
+ * Returns the schedule updates required.
+ */
+static enum whc_update pzl_process_qset(struct whc *whc, struct whc_qset *qset)
+{
+ enum whc_update update = 0;
+ uint32_t status = 0;
+
+ while (qset->ntds) {
+ struct whc_qtd *td;
+ int t;
+
+ t = qset->td_start;
+ td = &qset->qtd[qset->td_start];
+ status = le32_to_cpu(td->status);
+
+ /*
+ * Nothing to do with a still active qTD.
+ */
+ if (status & QTD_STS_ACTIVE)
+ break;
+
+ if (status & QTD_STS_HALTED) {
+ /* Ug, an error. */
+ process_halted_qtd(whc, qset, td);
+ goto done;
+ }
+
+ /* Mmm, a completed qTD. */
+ process_inactive_qtd(whc, qset, td);
+ }
+
+ update |= qset_add_qtds(whc, qset);
+
+done:
+ /*
+ * If there are no qTDs in this qset, remove it from the PZL.
+ */
+ if (qset->remove && qset->ntds == 0) {
+ pzl_qset_remove(whc, qset);
+ update |= WHC_UPDATE_REMOVED;
+ }
+
+ return update;
+}
+
+/**
+ * pzl_start - start the periodic schedule
+ * @whc: the WHCI host controller
+ *
+ * The PZL must be valid (e.g., all entries in the list should have
+ * the T bit set).
+ */
+void pzl_start(struct whc *whc)
+{
+ le_writeq(whc->pz_list_dma, whc->base + WUSBPERIODICLISTBASE);
+
+ whc_write_wusbcmd(whc, WUSBCMD_PERIODIC_EN, WUSBCMD_PERIODIC_EN);
+ whci_wait_for(&whc->umc->dev, whc->base + WUSBSTS,
+ WUSBSTS_PERIODIC_SCHED, WUSBSTS_PERIODIC_SCHED,
+ 1000, "start PZL");
+}
+
+/**
+ * pzl_stop - stop the periodic schedule
+ * @whc: the WHCI host controller
+ */
+void pzl_stop(struct whc *whc)
+{
+ whc_write_wusbcmd(whc, WUSBCMD_PERIODIC_EN, 0);
+ whci_wait_for(&whc->umc->dev, whc->base + WUSBSTS,
+ WUSBSTS_PERIODIC_SCHED, 0,
+ 1000, "stop PZL");
+}
+
+void pzl_update(struct whc *whc, uint32_t wusbcmd)
+{
+ whc_write_wusbcmd(whc, wusbcmd, wusbcmd);
+ wait_event(whc->periodic_list_wq,
+ (le_readl(whc->base + WUSBCMD) & WUSBCMD_PERIODIC_UPDATED) == 0);
+}
+
+static void update_pzl_hw_view(struct whc *whc)
+{
+ struct whc_qset *qset, *t;
+ int period;
+ u64 tmp_qh = 0;
+
+ for (period = 0; period < 5; period++) {
+ list_for_each_entry_safe(qset, t, &whc->periodic_list[period], list_node) {
+ whc_qset_set_link_ptr(&qset->qh.link, tmp_qh);
+ tmp_qh = qset->qset_dma;
+ qset->in_hw_list = true;
+ }
+ update_pzl_pointers(whc, period, tmp_qh);
+ }
+}
+
+/**
+ * scan_periodic_work - scan the PZL for qsets to process.
+ *
+ * Process each qset in the PZL in turn and then signal the WHC that
+ * the PZL has been updated.
+ *
+ * Then start, stop or update the periodic schedule as required.
+ */
+void scan_periodic_work(struct work_struct *work)
+{
+ struct whc *whc = container_of(work, struct whc, periodic_work);
+ struct whc_qset *qset, *t;
+ enum whc_update update = 0;
+ int period;
+
+ spin_lock_irq(&whc->lock);
+
+ dump_pzl(whc, "before processing");
+
+ for (period = 4; period >= 0; period--) {
+ list_for_each_entry_safe(qset, t, &whc->periodic_list[period], list_node) {
+ if (!qset->in_hw_list)
+ update |= WHC_UPDATE_ADDED;
+ update |= pzl_process_qset(whc, qset);
+ }
+ }
+
+ if (update & (WHC_UPDATE_ADDED | WHC_UPDATE_REMOVED))
+ update_pzl_hw_view(whc);
+
+ dump_pzl(whc, "after processing");
+
+ spin_unlock_irq(&whc->lock);
+
+ if (update) {
+ uint32_t wusbcmd = WUSBCMD_PERIODIC_UPDATED | WUSBCMD_PERIODIC_SYNCED_DB;
+ if (update & WHC_UPDATE_REMOVED)
+ wusbcmd |= WUSBCMD_PERIODIC_QSET_RM;
+ pzl_update(whc, wusbcmd);
+ }
+
+ /*
+ * Now that the PZL is updated, complete the removal of any
+ * removed qsets.
+ */
+ spin_lock(&whc->lock);
+
+ list_for_each_entry_safe(qset, t, &whc->periodic_removed_list, list_node) {
+ qset_remove_complete(whc, qset);
+ }
+
+ spin_unlock(&whc->lock);
+}
+
+/**
+ * pzl_urb_enqueue - queue an URB onto the periodic list (PZL)
+ * @whc: the WHCI host controller
+ * @urb: the URB to enqueue
+ * @mem_flags: flags for any memory allocations
+ *
+ * The qset for the endpoint is obtained and the urb queued on to it.
+ *
+ * Work is scheduled to update the hardware's view of the PZL.
+ */
+int pzl_urb_enqueue(struct whc *whc, struct urb *urb, gfp_t mem_flags)
+{
+ struct whc_qset *qset;
+ int err;
+ unsigned long flags;
+
+ spin_lock_irqsave(&whc->lock, flags);
+
+ qset = get_qset(whc, urb, GFP_ATOMIC);
+ if (qset == NULL)
+ err = -ENOMEM;
+ else
+ err = qset_add_urb(whc, qset, urb, GFP_ATOMIC);
+ if (!err) {
+ usb_hcd_link_urb_to_ep(&whc->wusbhc.usb_hcd, urb);
+ if (!qset->in_sw_list)
+ qset_insert_in_sw_list(whc, qset);
+ }
+
+ spin_unlock_irqrestore(&whc->lock, flags);
+
+ if (!err)
+ queue_work(whc->workqueue, &whc->periodic_work);
+
+ return 0;
+}
+
+/**
+ * pzl_urb_dequeue - remove an URB (qset) from the periodic list
+ * @whc: the WHCI host controller
+ * @urb: the URB to dequeue
+ * @status: the current status of the URB
+ *
+ * URBs that do yet have qTDs can simply be removed from the software
+ * queue, otherwise the qset must be removed so the qTDs can be safely
+ * removed.
+ */
+int pzl_urb_dequeue(struct whc *whc, struct urb *urb, int status)
+{
+ struct whc_urb *wurb = urb->hcpriv;
+ struct whc_qset *qset = wurb->qset;
+ struct whc_std *std, *t;
+ int ret;
+ unsigned long flags;
+
+ spin_lock_irqsave(&whc->lock, flags);
+
+ ret = usb_hcd_check_unlink_urb(&whc->wusbhc.usb_hcd, urb, status);
+ if (ret < 0)
+ goto out;
+
+ list_for_each_entry_safe(std, t, &qset->stds, list_node) {
+ if (std->urb == urb)
+ qset_free_std(whc, std);
+ else
+ std->qtd = NULL; /* so this std is re-added when the qset is */
+ }
+
+ pzl_qset_remove(whc, qset);
+ wurb->status = status;
+ wurb->is_async = false;
+ queue_work(whc->workqueue, &wurb->dequeue_work);
+
+out:
+ spin_unlock_irqrestore(&whc->lock, flags);
+
+ return ret;
+}
+
+/**
+ * pzl_qset_delete - delete a qset from the PZL
+ */
+void pzl_qset_delete(struct whc *whc, struct whc_qset *qset)
+{
+ qset->remove = 1;
+ queue_work(whc->workqueue, &whc->periodic_work);
+ qset_delete(whc, qset);
+}
+
+
+/**
+ * pzl_init - initialize the periodic zone list
+ * @whc: the WHCI host controller
+ */
+int pzl_init(struct whc *whc)
+{
+ int i;
+
+ whc->pz_list = dma_alloc_coherent(&whc->umc->dev, sizeof(u64) * 16,
+ &whc->pz_list_dma, GFP_KERNEL);
+ if (whc->pz_list == NULL)
+ return -ENOMEM;
+
+ /* Set T bit on all elements in PZL. */
+ for (i = 0; i < 16; i++)
+ whc->pz_list[i] = cpu_to_le64(QH_LINK_NTDS(8) | QH_LINK_T);
+
+ le_writeq(whc->pz_list_dma, whc->base + WUSBPERIODICLISTBASE);
+
+ return 0;
+}
+
+/**
+ * pzl_clean_up - free PZL resources
+ * @whc: the WHCI host controller
+ *
+ * The PZL is stopped and empty.
+ */
+void pzl_clean_up(struct whc *whc)
+{
+ if (whc->pz_list)
+ dma_free_coherent(&whc->umc->dev, sizeof(u64) * 16, whc->pz_list,
+ whc->pz_list_dma);
+}
diff --git a/drivers/usb/host/whci/qset.c b/drivers/usb/host/whci/qset.c
new file mode 100644
index 000000000000..0420037d2e18
--- /dev/null
+++ b/drivers/usb/host/whci/qset.c
@@ -0,0 +1,567 @@
+/*
+ * Wireless Host Controller (WHC) qset management.
+ *
+ * Copyright (C) 2007 Cambridge Silicon Radio Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License version
+ * 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that 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, see <http://www.gnu.org/licenses/>.
+ */
+#include <linux/kernel.h>
+#include <linux/dma-mapping.h>
+#include <linux/uwb/umc.h>
+#include <linux/usb.h>
+
+#include "../../wusbcore/wusbhc.h"
+
+#include "whcd.h"
+
+void dump_qset(struct whc_qset *qset, struct device *dev)
+{
+ struct whc_std *std;
+ struct urb *urb = NULL;
+ int i;
+
+ dev_dbg(dev, "qset %08x\n", (u32)qset->qset_dma);
+ dev_dbg(dev, " -> %08x\n", (u32)qset->qh.link);
+ dev_dbg(dev, " info: %08x %08x %08x\n",
+ qset->qh.info1, qset->qh.info2, qset->qh.info3);
+ dev_dbg(dev, " sts: %04x errs: %d\n", qset->qh.status, qset->qh.err_count);
+ dev_dbg(dev, " TD: sts: %08x opts: %08x\n",
+ qset->qh.overlay.qtd.status, qset->qh.overlay.qtd.options);
+
+ for (i = 0; i < WHCI_QSET_TD_MAX; i++) {
+ dev_dbg(dev, " %c%c TD[%d]: sts: %08x opts: %08x ptr: %08x\n",
+ i == qset->td_start ? 'S' : ' ',
+ i == qset->td_end ? 'E' : ' ',
+ i, qset->qtd[i].status, qset->qtd[i].options,
+ (u32)qset->qtd[i].page_list_ptr);
+ }
+ dev_dbg(dev, " ntds: %d\n", qset->ntds);
+ list_for_each_entry(std, &qset->stds, list_node) {
+ if (urb != std->urb) {
+ urb = std->urb;
+ dev_dbg(dev, " urb %p transferred: %d bytes\n", urb,
+ urb->actual_length);
+ }
+ if (std->qtd)
+ dev_dbg(dev, " sTD[%td]: %zu bytes @ %08x\n",
+ std->qtd - &qset->qtd[0],
+ std->len, std->num_pointers ?
+ (u32)(std->pl_virt[0].buf_ptr) : (u32)std->dma_addr);
+ else
+ dev_dbg(dev, " sTD[-]: %zd bytes @ %08x\n",
+ std->len, std->num_pointers ?
+ (u32)(std->pl_virt[0].buf_ptr) : (u32)std->dma_addr);
+ }
+}
+
+struct whc_qset *qset_alloc(struct whc *whc, gfp_t mem_flags)
+{
+ struct whc_qset *qset;
+ dma_addr_t dma;
+
+ qset = dma_pool_alloc(whc->qset_pool, mem_flags, &dma);
+ if (qset == NULL)
+ return NULL;
+ memset(qset, 0, sizeof(struct whc_qset));
+
+ qset->qset_dma = dma;
+ qset->whc = whc;
+
+ INIT_LIST_HEAD(&qset->list_node);
+ INIT_LIST_HEAD(&qset->stds);
+
+ return qset;
+}
+
+/**
+ * qset_fill_qh - fill the static endpoint state in a qset's QHead
+ * @qset: the qset whose QH needs initializing with static endpoint
+ * state
+ * @urb: an urb for a transfer to this endpoint
+ */
+static void qset_fill_qh(struct whc_qset *qset, struct urb *urb)
+{
+ struct usb_device *usb_dev = urb->dev;
+ struct usb_wireless_ep_comp_descriptor *epcd;
+ bool is_out;
+
+ is_out = usb_pipeout(urb->pipe);
+
+ epcd = (struct usb_wireless_ep_comp_descriptor *)qset->ep->extra;
+
+ if (epcd) {
+ qset->max_seq = epcd->bMaxSequence;
+ qset->max_burst = epcd->bMaxBurst;
+ } else {
+ qset->max_seq = 2;
+ qset->max_burst = 1;
+ }
+
+ qset->qh.info1 = cpu_to_le32(
+ QH_INFO1_EP(usb_pipeendpoint(urb->pipe))
+ | (is_out ? QH_INFO1_DIR_OUT : QH_INFO1_DIR_IN)
+ | usb_pipe_to_qh_type(urb->pipe)
+ | QH_INFO1_DEV_INFO_IDX(wusb_port_no_to_idx(usb_dev->portnum))
+ | QH_INFO1_MAX_PKT_LEN(usb_maxpacket(urb->dev, urb->pipe, is_out))
+ );
+ qset->qh.info2 = cpu_to_le32(
+ QH_INFO2_BURST(qset->max_burst)
+ | QH_INFO2_DBP(0)
+ | QH_INFO2_MAX_COUNT(3)
+ | QH_INFO2_MAX_RETRY(3)
+ | QH_INFO2_MAX_SEQ(qset->max_seq - 1)
+ );
+ /* FIXME: where can we obtain these Tx parameters from? Why
+ * doesn't the chip know what Tx power to use? It knows the Rx
+ * strength and can presumably guess the Tx power required
+ * from that? */
+ qset->qh.info3 = cpu_to_le32(
+ QH_INFO3_TX_RATE_53_3
+ | QH_INFO3_TX_PWR(0) /* 0 == max power */
+ );
+}
+
+/**
+ * qset_clear - clear fields in a qset so it may be reinserted into a
+ * schedule
+ */
+void qset_clear(struct whc *whc, struct whc_qset *qset)
+{
+ qset->td_start = qset->td_end = qset->ntds = 0;
+ qset->remove = 0;
+
+ qset->qh.link = cpu_to_le32(QH_LINK_NTDS(8) | QH_LINK_T);
+ qset->qh.status = cpu_to_le16(QH_STATUS_ICUR(qset->td_start));
+ qset->qh.err_count = 0;
+ qset->qh.cur_window = cpu_to_le32((1 << qset->max_burst) - 1);
+ qset->qh.scratch[0] = 0;
+ qset->qh.scratch[1] = 0;
+ qset->qh.scratch[2] = 0;
+
+ memset(&qset->qh.overlay, 0, sizeof(qset->qh.overlay));
+
+ init_completion(&qset->remove_complete);
+}
+
+/**
+ * get_qset - get the qset for an async endpoint
+ *
+ * A new qset is created if one does not already exist.
+ */
+struct whc_qset *get_qset(struct whc *whc, struct urb *urb,
+ gfp_t mem_flags)
+{
+ struct whc_qset *qset;
+
+ qset = urb->ep->hcpriv;
+ if (qset == NULL) {
+ qset = qset_alloc(whc, mem_flags);
+ if (qset == NULL)
+ return NULL;
+
+ qset->ep = urb->ep;
+ urb->ep->hcpriv = qset;
+ qset_fill_qh(qset, urb);
+ }
+ return qset;
+}
+
+void qset_remove_complete(struct whc *whc, struct whc_qset *qset)
+{
+ list_del_init(&qset->list_node);
+ complete(&qset->remove_complete);
+}
+
+/**
+ * qset_add_qtds - add qTDs for an URB to a qset
+ *
+ * Returns true if the list (ASL/PZL) must be updated because (for a
+ * WHCI 0.95 controller) an activated qTD was pointed to be iCur.
+ */
+enum whc_update qset_add_qtds(struct whc *whc, struct whc_qset *qset)
+{
+ struct whc_std *std;
+ enum whc_update update = 0;
+
+ list_for_each_entry(std, &qset->stds, list_node) {
+ struct whc_qtd *qtd;
+ uint32_t status;
+
+ if (qset->ntds >= WHCI_QSET_TD_MAX
+ || (qset->pause_after_urb && std->urb != qset->pause_after_urb))
+ break;
+
+ if (std->qtd)
+ continue; /* already has a qTD */
+
+ qtd = std->qtd = &qset->qtd[qset->td_end];
+
+ /* Fill in setup bytes for control transfers. */
+ if (usb_pipecontrol(std->urb->pipe))
+ memcpy(qtd->setup, std->urb->setup_packet, 8);
+
+ status = QTD_STS_ACTIVE | QTD_STS_LEN(std->len);
+
+ if (whc_std_last(std) && usb_pipeout(std->urb->pipe))
+ status |= QTD_STS_LAST_PKT;
+
+ /*
+ * For an IN transfer the iAlt field should be set so
+ * the h/w will automatically advance to the next
+ * transfer. However, if there are 8 or more TDs
+ * remaining in this transfer then iAlt cannot be set
+ * as it could point to somewhere in this transfer.
+ */
+ if (std->ntds_remaining < WHCI_QSET_TD_MAX) {
+ int ialt;
+ ialt = (qset->td_end + std->ntds_remaining) % WHCI_QSET_TD_MAX;
+ status |= QTD_STS_IALT(ialt);
+ } else if (usb_pipein(std->urb->pipe))
+ qset->pause_after_urb = std->urb;
+
+ if (std->num_pointers)
+ qtd->options = cpu_to_le32(QTD_OPT_IOC);
+ else
+ qtd->options = cpu_to_le32(QTD_OPT_IOC | QTD_OPT_SMALL);
+ qtd->page_list_ptr = cpu_to_le64(std->dma_addr);
+
+ qtd->status = cpu_to_le32(status);
+
+ if (QH_STATUS_TO_ICUR(qset->qh.status) == qset->td_end)
+ update = WHC_UPDATE_UPDATED;
+
+ if (++qset->td_end >= WHCI_QSET_TD_MAX)
+ qset->td_end = 0;
+ qset->ntds++;
+ }
+
+ return update;
+}
+
+/**
+ * qset_remove_qtd - remove the first qTD from a qset.
+ *
+ * The qTD might be still active (if it's part of a IN URB that
+ * resulted in a short read) so ensure it's deactivated.
+ */
+static void qset_remove_qtd(struct whc *whc, struct whc_qset *qset)
+{
+ qset->qtd[qset->td_start].status = 0;
+
+ if (++qset->td_start >= WHCI_QSET_TD_MAX)
+ qset->td_start = 0;
+ qset->ntds--;
+}
+
+/**
+ * qset_free_std - remove an sTD and free it.
+ * @whc: the WHCI host controller
+ * @std: the sTD to remove and free.
+ */
+void qset_free_std(struct whc *whc, struct whc_std *std)
+{
+ list_del(&std->list_node);
+ if (std->num_pointers) {
+ dma_unmap_single(whc->wusbhc.dev, std->dma_addr,
+ std->num_pointers * sizeof(struct whc_page_list_entry),
+ DMA_TO_DEVICE);
+ kfree(std->pl_virt);
+ }
+
+ kfree(std);
+}
+
+/**
+ * qset_remove_qtds - remove an URB's qTDs (and sTDs).
+ */
+static void qset_remove_qtds(struct whc *whc, struct whc_qset *qset,
+ struct urb *urb)
+{
+ struct whc_std *std, *t;
+
+ list_for_each_entry_safe(std, t, &qset->stds, list_node) {
+ if (std->urb != urb)
+ break;
+ if (std->qtd != NULL)
+ qset_remove_qtd(whc, qset);
+ qset_free_std(whc, std);
+ }
+}
+
+/**
+ * qset_free_stds - free any remaining sTDs for an URB.
+ */
+static void qset_free_stds(struct whc_qset *qset, struct urb *urb)
+{
+ struct whc_std *std, *t;
+
+ list_for_each_entry_safe(std, t, &qset->stds, list_node) {
+ if (std->urb == urb)
+ qset_free_std(qset->whc, std);
+ }
+}
+
+static int qset_fill_page_list(struct whc *whc, struct whc_std *std, gfp_t mem_flags)
+{
+ dma_addr_t dma_addr = std->dma_addr;
+ dma_addr_t sp, ep;
+ size_t std_len = std->len;
+ size_t pl_len;
+ int p;
+
+ sp = ALIGN(dma_addr, WHCI_PAGE_SIZE);
+ ep = dma_addr + std_len;
+ std->num_pointers = DIV_ROUND_UP(ep - sp, WHCI_PAGE_SIZE);
+
+ pl_len = std->num_pointers * sizeof(struct whc_page_list_entry);
+ std->pl_virt = kmalloc(pl_len, mem_flags);
+ if (std->pl_virt == NULL)
+ return -ENOMEM;
+ std->dma_addr = dma_map_single(whc->wusbhc.dev, std->pl_virt, pl_len, DMA_TO_DEVICE);
+
+ for (p = 0; p < std->num_pointers; p++) {
+ std->pl_virt[p].buf_ptr = cpu_to_le64(dma_addr);
+ dma_addr = ALIGN(dma_addr + WHCI_PAGE_SIZE, WHCI_PAGE_SIZE);
+ }
+
+ return 0;
+}
+
+/**
+ * urb_dequeue_work - executes asl/pzl update and gives back the urb to the system.
+ */
+static void urb_dequeue_work(struct work_struct *work)
+{
+ struct whc_urb *wurb = container_of(work, struct whc_urb, dequeue_work);
+ struct whc_qset *qset = wurb->qset;
+ struct whc *whc = qset->whc;
+ unsigned long flags;
+
+ if (wurb->is_async == true)
+ asl_update(whc, WUSBCMD_ASYNC_UPDATED
+ | WUSBCMD_ASYNC_SYNCED_DB
+ | WUSBCMD_ASYNC_QSET_RM);
+ else
+ pzl_update(whc, WUSBCMD_PERIODIC_UPDATED
+ | WUSBCMD_PERIODIC_SYNCED_DB
+ | WUSBCMD_PERIODIC_QSET_RM);
+
+ spin_lock_irqsave(&whc->lock, flags);
+ qset_remove_urb(whc, qset, wurb->urb, wurb->status);
+ spin_unlock_irqrestore(&whc->lock, flags);
+}
+
+/**
+ * qset_add_urb - add an urb to the qset's queue.
+ *
+ * The URB is chopped into sTDs, one for each qTD that will required.
+ * At least one qTD (and sTD) is required even if the transfer has no
+ * data (e.g., for some control transfers).
+ */
+int qset_add_urb(struct whc *whc, struct whc_qset *qset, struct urb *urb,
+ gfp_t mem_flags)
+{
+ struct whc_urb *wurb;
+ int remaining = urb->transfer_buffer_length;
+ u64 transfer_dma = urb->transfer_dma;
+ int ntds_remaining;
+
+ ntds_remaining = DIV_ROUND_UP(remaining, QTD_MAX_XFER_SIZE);
+ if (ntds_remaining == 0)
+ ntds_remaining = 1;
+
+ wurb = kzalloc(sizeof(struct whc_urb), mem_flags);
+ if (wurb == NULL)
+ goto err_no_mem;
+ urb->hcpriv = wurb;
+ wurb->qset = qset;
+ wurb->urb = urb;
+ INIT_WORK(&wurb->dequeue_work, urb_dequeue_work);
+
+ while (ntds_remaining) {
+ struct whc_std *std;
+ size_t std_len;
+
+ std = kmalloc(sizeof(struct whc_std), mem_flags);
+ if (std == NULL)
+ goto err_no_mem;
+
+ std_len = remaining;
+ if (std_len > QTD_MAX_XFER_SIZE)
+ std_len = QTD_MAX_XFER_SIZE;
+
+ std->urb = urb;
+ std->dma_addr = transfer_dma;
+ std->len = std_len;
+ std->ntds_remaining = ntds_remaining;
+ std->qtd = NULL;
+
+ INIT_LIST_HEAD(&std->list_node);
+ list_add_tail(&std->list_node, &qset->stds);
+
+ if (std_len > WHCI_PAGE_SIZE) {
+ if (qset_fill_page_list(whc, std, mem_flags) < 0)
+ goto err_no_mem;
+ } else
+ std->num_pointers = 0;
+
+ ntds_remaining--;
+ remaining -= std_len;
+ transfer_dma += std_len;
+ }
+
+ return 0;
+
+err_no_mem:
+ qset_free_stds(qset, urb);
+ return -ENOMEM;
+}
+
+/**
+ * qset_remove_urb - remove an URB from the urb queue.
+ *
+ * The URB is returned to the USB subsystem.
+ */
+void qset_remove_urb(struct whc *whc, struct whc_qset *qset,
+ struct urb *urb, int status)
+{
+ struct wusbhc *wusbhc = &whc->wusbhc;
+ struct whc_urb *wurb = urb->hcpriv;
+
+ usb_hcd_unlink_urb_from_ep(&wusbhc->usb_hcd, urb);
+ /* Drop the lock as urb->complete() may enqueue another urb. */
+ spin_unlock(&whc->lock);
+ wusbhc_giveback_urb(wusbhc, urb, status);
+ spin_lock(&whc->lock);
+
+ kfree(wurb);
+}
+
+/**
+ * get_urb_status_from_qtd - get the completed urb status from qTD status
+ * @urb: completed urb
+ * @status: qTD status
+ */
+static int get_urb_status_from_qtd(struct urb *urb, u32 status)
+{
+ if (status & QTD_STS_HALTED) {
+ if (status & QTD_STS_DBE)
+ return usb_pipein(urb->pipe) ? -ENOSR : -ECOMM;
+ else if (status & QTD_STS_BABBLE)
+ return -EOVERFLOW;
+ else if (status & QTD_STS_RCE)
+ return -ETIME;
+ return -EPIPE;
+ }
+ if (usb_pipein(urb->pipe)
+ && (urb->transfer_flags & URB_SHORT_NOT_OK)
+ && urb->actual_length < urb->transfer_buffer_length)
+ return -EREMOTEIO;
+ return 0;
+}
+
+/**
+ * process_inactive_qtd - process an inactive (but not halted) qTD.
+ *
+ * Update the urb with the transfer bytes from the qTD, if the urb is
+ * completely transfered or (in the case of an IN only) the LPF is
+ * set, then the transfer is complete and the urb should be returned
+ * to the system.
+ */
+void process_inactive_qtd(struct whc *whc, struct whc_qset *qset,
+ struct whc_qtd *qtd)
+{
+ struct whc_std *std = list_first_entry(&qset->stds, struct whc_std, list_node);
+ struct urb *urb = std->urb;
+ uint32_t status;
+ bool complete;
+
+ status = le32_to_cpu(qtd->status);
+
+ urb->actual_length += std->len - QTD_STS_TO_LEN(status);
+
+ if (usb_pipein(urb->pipe) && (status & QTD_STS_LAST_PKT))
+ complete = true;
+ else
+ complete = whc_std_last(std);
+
+ qset_remove_qtd(whc, qset);
+ qset_free_std(whc, std);
+
+ /*
+ * Transfers for this URB are complete? Then return it to the
+ * USB subsystem.
+ */
+ if (complete) {
+ qset_remove_qtds(whc, qset, urb);
+ qset_remove_urb(whc, qset, urb, get_urb_status_from_qtd(urb, status));
+
+ /*
+ * If iAlt isn't valid then the hardware didn't
+ * advance iCur. Adjust the start and end pointers to
+ * match iCur.
+ */
+ if (!(status & QTD_STS_IALT_VALID))
+ qset->td_start = qset->td_end
+ = QH_STATUS_TO_ICUR(le16_to_cpu(qset->qh.status));
+ qset->pause_after_urb = NULL;
+ }
+}
+
+/**
+ * process_halted_qtd - process a qset with a halted qtd
+ *
+ * Remove all the qTDs for the failed URB and return the failed URB to
+ * the USB subsystem. Then remove all other qTDs so the qset can be
+ * removed.
+ *
+ * FIXME: this is the point where rate adaptation can be done. If a
+ * transfer failed because it exceeded the maximum number of retries
+ * then it could be reactivated with a slower rate without having to
+ * remove the qset.
+ */
+void process_halted_qtd(struct whc *whc, struct whc_qset *qset,
+ struct whc_qtd *qtd)
+{
+ struct whc_std *std = list_first_entry(&qset->stds, struct whc_std, list_node);
+ struct urb *urb = std->urb;
+ int urb_status;
+
+ urb_status = get_urb_status_from_qtd(urb, le32_to_cpu(qtd->status));
+
+ qset_remove_qtds(whc, qset, urb);
+ qset_remove_urb(whc, qset, urb, urb_status);
+
+ list_for_each_entry(std, &qset->stds, list_node) {
+ if (qset->ntds == 0)
+ break;
+ qset_remove_qtd(whc, qset);
+ std->qtd = NULL;
+ }
+
+ qset->remove = 1;
+}
+
+void qset_free(struct whc *whc, struct whc_qset *qset)
+{
+ dma_pool_free(whc->qset_pool, qset, qset->qset_dma);
+}
+
+/**
+ * qset_delete - wait for a qset to be unused, then free it.
+ */
+void qset_delete(struct whc *whc, struct whc_qset *qset)
+{
+ wait_for_completion(&qset->remove_complete);
+ qset_free(whc, qset);
+}
diff --git a/drivers/usb/host/whci/whcd.h b/drivers/usb/host/whci/whcd.h
new file mode 100644
index 000000000000..1d2a53bd39fd
--- /dev/null
+++ b/drivers/usb/host/whci/whcd.h
@@ -0,0 +1,197 @@
+/*
+ * Wireless Host Controller (WHC) private header.
+ *
+ * Copyright (C) 2007 Cambridge Silicon Radio Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License version
+ * 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that 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 Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+#ifndef __WHCD_H
+#define __WHCD_H
+
+#include <linux/uwb/whci.h>
+#include <linux/workqueue.h>
+
+#include "whci-hc.h"
+
+/* Generic command timeout. */
+#define WHC_GENCMD_TIMEOUT_MS 100
+
+
+struct whc {
+ struct wusbhc wusbhc;
+ struct umc_dev *umc;
+
+ resource_size_t base_phys;
+ void __iomem *base;
+ int irq;
+
+ u8 n_devices;
+ u8 n_keys;
+ u8 n_mmc_ies;
+
+ u64 *pz_list;
+ struct dn_buf_entry *dn_buf;
+ struct di_buf_entry *di_buf;
+ dma_addr_t pz_list_dma;
+ dma_addr_t dn_buf_dma;
+ dma_addr_t di_buf_dma;
+
+ spinlock_t lock;
+ struct mutex mutex;
+
+ void * gen_cmd_buf;
+ dma_addr_t gen_cmd_buf_dma;
+ wait_queue_head_t cmd_wq;
+
+ struct workqueue_struct *workqueue;
+ struct work_struct dn_work;
+
+ struct dma_pool *qset_pool;
+
+ struct list_head async_list;
+ struct list_head async_removed_list;
+ wait_queue_head_t async_list_wq;
+ struct work_struct async_work;
+
+ struct list_head periodic_list[5];
+ struct list_head periodic_removed_list;
+ wait_queue_head_t periodic_list_wq;
+ struct work_struct periodic_work;
+};
+
+#define wusbhc_to_whc(w) (container_of((w), struct whc, wusbhc))
+
+/**
+ * struct whc_std - a software TD.
+ * @urb: the URB this sTD is for.
+ * @offset: start of the URB's data for this TD.
+ * @len: the length of data in the associated TD.
+ * @ntds_remaining: number of TDs (starting from this one) in this transfer.
+ *
+ * Queued URBs may require more TDs than are available in a qset so we
+ * use a list of these "software TDs" (sTDs) to hold per-TD data.
+ */
+struct whc_std {
+ struct urb *urb;
+ size_t len;
+ int ntds_remaining;
+ struct whc_qtd *qtd;
+
+ struct list_head list_node;
+ int num_pointers;
+ dma_addr_t dma_addr;
+ struct whc_page_list_entry *pl_virt;
+};
+
+/**
+ * struct whc_urb - per URB host controller structure.
+ * @urb: the URB this struct is for.
+ * @qset: the qset associated to the URB.
+ * @dequeue_work: the work to remove the URB when dequeued.
+ * @is_async: the URB belongs to async sheduler or not.
+ * @status: the status to be returned when calling wusbhc_giveback_urb.
+ */
+struct whc_urb {
+ struct urb *urb;
+ struct whc_qset *qset;
+ struct work_struct dequeue_work;
+ bool is_async;
+ int status;
+};
+
+/**
+ * whc_std_last - is this sTD the URB's last?
+ * @std: the sTD to check.
+ */
+static inline bool whc_std_last(struct whc_std *std)
+{
+ return std->ntds_remaining <= 1;
+}
+
+enum whc_update {
+ WHC_UPDATE_ADDED = 0x01,
+ WHC_UPDATE_REMOVED = 0x02,
+ WHC_UPDATE_UPDATED = 0x04,
+};
+
+/* init.c */
+int whc_init(struct whc *whc);
+void whc_clean_up(struct whc *whc);
+
+/* hw.c */
+void whc_write_wusbcmd(struct whc *whc, u32 mask, u32 val);
+int whc_do_gencmd(struct whc *whc, u32 cmd, u32 params, void *addr, size_t len);
+
+/* wusb.c */
+int whc_wusbhc_start(struct wusbhc *wusbhc);
+void whc_wusbhc_stop(struct wusbhc *wusbhc);
+int whc_mmcie_add(struct wusbhc *wusbhc, u8 interval, u8 repeat_cnt,
+ u8 handle, struct wuie_hdr *wuie);
+int whc_mmcie_rm(struct wusbhc *wusbhc, u8 handle);
+int whc_bwa_set(struct wusbhc *wusbhc, s8 stream_index, const struct uwb_mas_bm *mas_bm);
+int whc_dev_info_set(struct wusbhc *wusbhc, struct wusb_dev *wusb_dev);
+int whc_set_num_dnts(struct wusbhc *wusbhc, u8 interval, u8 slots);
+int whc_set_ptk(struct wusbhc *wusbhc, u8 port_idx, u32 tkid,
+ const void *ptk, size_t key_size);
+int whc_set_gtk(struct wusbhc *wusbhc, u32 tkid,
+ const void *gtk, size_t key_size);
+int whc_set_cluster_id(struct whc *whc, u8 bcid);
+
+/* int.c */
+irqreturn_t whc_int_handler(struct usb_hcd *hcd);
+void whc_dn_work(struct work_struct *work);
+
+/* asl.c */
+void asl_start(struct whc *whc);
+void asl_stop(struct whc *whc);
+int asl_init(struct whc *whc);
+void asl_clean_up(struct whc *whc);
+int asl_urb_enqueue(struct whc *whc, struct urb *urb, gfp_t mem_flags);
+int asl_urb_dequeue(struct whc *whc, struct urb *urb, int status);
+void asl_qset_delete(struct whc *whc, struct whc_qset *qset);
+void scan_async_work(struct work_struct *work);
+
+/* pzl.c */
+int pzl_init(struct whc *whc);
+void pzl_clean_up(struct whc *whc);
+void pzl_start(struct whc *whc);
+void pzl_stop(struct whc *whc);
+int pzl_urb_enqueue(struct whc *whc, struct urb *urb, gfp_t mem_flags);
+int pzl_urb_dequeue(struct whc *whc, struct urb *urb, int status);
+void pzl_qset_delete(struct whc *whc, struct whc_qset *qset);
+void scan_periodic_work(struct work_struct *work);
+
+/* qset.c */
+struct whc_qset *qset_alloc(struct whc *whc, gfp_t mem_flags);
+void qset_free(struct whc *whc, struct whc_qset *qset);
+struct whc_qset *get_qset(struct whc *whc, struct urb *urb, gfp_t mem_flags);
+void qset_delete(struct whc *whc, struct whc_qset *qset);
+void qset_clear(struct whc *whc, struct whc_qset *qset);
+int qset_add_urb(struct whc *whc, struct whc_qset *qset, struct urb *urb,
+ gfp_t mem_flags);
+void qset_free_std(struct whc *whc, struct whc_std *std);
+void qset_remove_urb(struct whc *whc, struct whc_qset *qset,
+ struct urb *urb, int status);
+void process_halted_qtd(struct whc *whc, struct whc_qset *qset,
+ struct whc_qtd *qtd);
+void process_inactive_qtd(struct whc *whc, struct whc_qset *qset,
+ struct whc_qtd *qtd);
+enum whc_update qset_add_qtds(struct whc *whc, struct whc_qset *qset);
+void qset_remove_complete(struct whc *whc, struct whc_qset *qset);
+void dump_qset(struct whc_qset *qset, struct device *dev);
+void pzl_update(struct whc *whc, uint32_t wusbcmd);
+void asl_update(struct whc *whc, uint32_t wusbcmd);
+
+#endif /* #ifndef __WHCD_H */
diff --git a/drivers/usb/host/whci/whci-hc.h b/drivers/usb/host/whci/whci-hc.h
new file mode 100644
index 000000000000..bff1eb7a35cf
--- /dev/null
+++ b/drivers/usb/host/whci/whci-hc.h
@@ -0,0 +1,416 @@
+/*
+ * Wireless Host Controller (WHC) data structures.
+ *
+ * Copyright (C) 2007 Cambridge Silicon Radio Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License version
+ * 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that 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 Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+#ifndef _WHCI_WHCI_HC_H
+#define _WHCI_WHCI_HC_H
+
+#include <linux/list.h>
+
+/**
+ * WHCI_PAGE_SIZE - page size use by WHCI
+ *
+ * WHCI assumes that host system uses pages of 4096 octets.
+ */
+#define WHCI_PAGE_SIZE 4096
+
+
+/**
+ * QTD_MAX_TXFER_SIZE - max number of bytes to transfer with a single
+ * qtd.
+ *
+ * This is 2^20 - 1.
+ */
+#define QTD_MAX_XFER_SIZE 1048575
+
+
+/**
+ * struct whc_qtd - Queue Element Transfer Descriptors (qTD)
+ *
+ * This describes the data for a bulk, control or interrupt transfer.
+ *
+ * [WHCI] section 3.2.4
+ */
+struct whc_qtd {
+ __le32 status; /*< remaining transfer len and transfer status */
+ __le32 options;
+ __le64 page_list_ptr; /*< physical pointer to data buffer page list*/
+ __u8 setup[8]; /*< setup data for control transfers */
+} __attribute__((packed));
+
+#define QTD_STS_ACTIVE (1 << 31) /* enable execution of transaction */
+#define QTD_STS_HALTED (1 << 30) /* transfer halted */
+#define QTD_STS_DBE (1 << 29) /* data buffer error */
+#define QTD_STS_BABBLE (1 << 28) /* babble detected */
+#define QTD_STS_RCE (1 << 27) /* retry count exceeded */
+#define QTD_STS_LAST_PKT (1 << 26) /* set Last Packet Flag in WUSB header */
+#define QTD_STS_INACTIVE (1 << 25) /* queue set is marked inactive */
+#define QTD_STS_IALT_VALID (1 << 23) /* iAlt field is valid */
+#define QTD_STS_IALT(i) (QTD_STS_IALT_VALID | ((i) << 20)) /* iAlt field */
+#define QTD_STS_LEN(l) ((l) << 0) /* transfer length */
+#define QTD_STS_TO_LEN(s) ((s) & 0x000fffff)
+
+#define QTD_OPT_IOC (1 << 1) /* page_list_ptr points to buffer directly */
+#define QTD_OPT_SMALL (1 << 0) /* interrupt on complete */
+
+/**
+ * struct whc_itd - Isochronous Queue Element Transfer Descriptors (iTD)
+ *
+ * This describes the data and other parameters for an isochronous
+ * transfer.
+ *
+ * [WHCI] section 3.2.5
+ */
+struct whc_itd {
+ __le16 presentation_time; /*< presentation time for OUT transfers */
+ __u8 num_segments; /*< number of data segments in segment list */
+ __u8 status; /*< command execution status */
+ __le32 options; /*< misc transfer options */
+ __le64 page_list_ptr; /*< physical pointer to data buffer page list */
+ __le64 seg_list_ptr; /*< physical pointer to segment list */
+} __attribute__((packed));
+
+#define ITD_STS_ACTIVE (1 << 7) /* enable execution of transaction */
+#define ITD_STS_DBE (1 << 5) /* data buffer error */
+#define ITD_STS_BABBLE (1 << 4) /* babble detected */
+#define ITD_STS_INACTIVE (1 << 1) /* queue set is marked inactive */
+
+#define ITD_OPT_IOC (1 << 1) /* interrupt on complete */
+#define ITD_OPT_SMALL (1 << 0) /* page_list_ptr points to buffer directly */
+
+/**
+ * Page list entry.
+ *
+ * A TD's page list must contain sufficient page list entries for the
+ * total data length in the TD.
+ *
+ * [WHCI] section 3.2.4.3
+ */
+struct whc_page_list_entry {
+ __le64 buf_ptr; /*< physical pointer to buffer */
+} __attribute__((packed));
+
+/**
+ * struct whc_seg_list_entry - Segment list entry.
+ *
+ * Describes a portion of the data buffer described in the containing
+ * qTD's page list.
+ *
+ * seg_ptr = qtd->page_list_ptr[qtd->seg_list_ptr[seg].idx].buf_ptr
+ * + qtd->seg_list_ptr[seg].offset;
+ *
+ * Segments can't cross page boundries.
+ *
+ * [WHCI] section 3.2.5.5
+ */
+struct whc_seg_list_entry {
+ __le16 len; /*< segment length */
+ __u8 idx; /*< index into page list */
+ __u8 status; /*< segment status */
+ __le16 offset; /*< 12 bit offset into page */
+} __attribute__((packed));
+
+/**
+ * struct whc_qhead - endpoint and status information for a qset.
+ *
+ * [WHCI] section 3.2.6
+ */
+struct whc_qhead {
+ __le64 link; /*< next qset in list */
+ __le32 info1;
+ __le32 info2;
+ __le32 info3;
+ __le16 status;
+ __le16 err_count; /*< transaction error count */
+ __le32 cur_window;
+ __le32 scratch[3]; /*< h/w scratch area */
+ union {
+ struct whc_qtd qtd;
+ struct whc_itd itd;
+ } overlay;
+} __attribute__((packed));
+
+#define QH_LINK_PTR_MASK (~0x03Full)
+#define QH_LINK_PTR(ptr) ((ptr) & QH_LINK_PTR_MASK)
+#define QH_LINK_IQS (1 << 4) /* isochronous queue set */
+#define QH_LINK_NTDS(n) (((n) - 1) << 1) /* number of TDs in queue set */
+#define QH_LINK_T (1 << 0) /* last queue set in periodic schedule list */
+
+#define QH_INFO1_EP(e) ((e) << 0) /* endpoint number */
+#define QH_INFO1_DIR_IN (1 << 4) /* IN transfer */
+#define QH_INFO1_DIR_OUT (0 << 4) /* OUT transfer */
+#define QH_INFO1_TR_TYPE_CTRL (0x0 << 5) /* control transfer */
+#define QH_INFO1_TR_TYPE_ISOC (0x1 << 5) /* isochronous transfer */
+#define QH_INFO1_TR_TYPE_BULK (0x2 << 5) /* bulk transfer */
+#define QH_INFO1_TR_TYPE_INT (0x3 << 5) /* interrupt */
+#define QH_INFO1_TR_TYPE_LP_INT (0x7 << 5) /* low power interrupt */
+#define QH_INFO1_DEV_INFO_IDX(i) ((i) << 8) /* index into device info buffer */
+#define QH_INFO1_SET_INACTIVE (1 << 15) /* set inactive after transfer */
+#define QH_INFO1_MAX_PKT_LEN(l) ((l) << 16) /* maximum packet length */
+
+#define QH_INFO2_BURST(b) ((b) << 0) /* maximum burst length */
+#define QH_INFO2_DBP(p) ((p) << 5) /* data burst policy (see [WUSB] table 5-7) */
+#define QH_INFO2_MAX_COUNT(c) ((c) << 8) /* max isoc/int pkts per zone */
+#define QH_INFO2_RQS (1 << 15) /* reactivate queue set */
+#define QH_INFO2_MAX_RETRY(r) ((r) << 16) /* maximum transaction retries */
+#define QH_INFO2_MAX_SEQ(s) ((s) << 20) /* maximum sequence number */
+#define QH_INFO3_MAX_DELAY(d) ((d) << 0) /* maximum stream delay in 125 us units (isoc only) */
+#define QH_INFO3_INTERVAL(i) ((i) << 16) /* segment interval in 125 us units (isoc only) */
+
+#define QH_INFO3_TX_RATE_53_3 (0 << 24)
+#define QH_INFO3_TX_RATE_80 (1 << 24)
+#define QH_INFO3_TX_RATE_106_7 (2 << 24)
+#define QH_INFO3_TX_RATE_160 (3 << 24)
+#define QH_INFO3_TX_RATE_200 (4 << 24)
+#define QH_INFO3_TX_RATE_320 (5 << 24)
+#define QH_INFO3_TX_RATE_400 (6 << 24)
+#define QH_INFO3_TX_RATE_480 (7 << 24)
+#define QH_INFO3_TX_PWR(p) ((p) << 29) /* transmit power (see [WUSB] section 5.2.1.2) */
+
+#define QH_STATUS_FLOW_CTRL (1 << 15)
+#define QH_STATUS_ICUR(i) ((i) << 5)
+#define QH_STATUS_TO_ICUR(s) (((s) >> 5) & 0x7)
+
+/**
+ * usb_pipe_to_qh_type - USB core pipe type to QH transfer type
+ *
+ * Returns the QH type field for a USB core pipe type.
+ */
+static inline unsigned usb_pipe_to_qh_type(unsigned pipe)
+{
+ static const unsigned type[] = {
+ [PIPE_ISOCHRONOUS] = QH_INFO1_TR_TYPE_ISOC,
+ [PIPE_INTERRUPT] = QH_INFO1_TR_TYPE_INT,
+ [PIPE_CONTROL] = QH_INFO1_TR_TYPE_CTRL,
+ [PIPE_BULK] = QH_INFO1_TR_TYPE_BULK,
+ };
+ return type[usb_pipetype(pipe)];
+}
+
+/**
+ * Maxiumum number of TDs in a qset.
+ */
+#define WHCI_QSET_TD_MAX 8
+
+/**
+ * struct whc_qset - WUSB data transfers to a specific endpoint
+ * @qh: the QHead of this qset
+ * @qtd: up to 8 qTDs (for qsets for control, bulk and interrupt
+ * transfers)
+ * @itd: up to 8 iTDs (for qsets for isochronous transfers)
+ * @qset_dma: DMA address for this qset
+ * @whc: WHCI HC this qset is for
+ * @ep: endpoint
+ * @stds: list of sTDs queued to this qset
+ * @ntds: number of qTDs queued (not necessarily the same as nTDs
+ * field in the QH)
+ * @td_start: index of the first qTD in the list
+ * @td_end: index of next free qTD in the list (provided
+ * ntds < WHCI_QSET_TD_MAX)
+ *
+ * Queue Sets (qsets) are added to the asynchronous schedule list
+ * (ASL) or the periodic zone list (PZL).
+ *
+ * qsets may contain up to 8 TDs (either qTDs or iTDs as appropriate).
+ * Each TD may refer to at most 1 MiB of data. If a single transfer
+ * has > 8MiB of data, TDs can be reused as they are completed since
+ * the TD list is used as a circular buffer. Similarly, several
+ * (smaller) transfers may be queued in a qset.
+ *
+ * WHCI controllers may cache portions of the qsets in the ASL and
+ * PZL, requiring the WHCD to inform the WHC that the lists have been
+ * updated (fields changed or qsets inserted or removed). For safe
+ * insertion and removal of qsets from the lists the schedule must be
+ * stopped to avoid races in updating the QH link pointers.
+ *
+ * Since the HC is free to execute qsets in any order, all transfers
+ * to an endpoint should use the same qset to ensure transfers are
+ * executed in the order they're submitted.
+ *
+ * [WHCI] section 3.2.3
+ */
+struct whc_qset {
+ struct whc_qhead qh;
+ union {
+ struct whc_qtd qtd[WHCI_QSET_TD_MAX];
+ struct whc_itd itd[WHCI_QSET_TD_MAX];
+ };
+
+ /* private data for WHCD */
+ dma_addr_t qset_dma;
+ struct whc *whc;
+ struct usb_host_endpoint *ep;
+ struct list_head stds;
+ int ntds;
+ int td_start;
+ int td_end;
+ struct list_head list_node;
+ unsigned in_sw_list:1;
+ unsigned in_hw_list:1;
+ unsigned remove:1;
+ struct urb *pause_after_urb;
+ struct completion remove_complete;
+ int max_burst;
+ int max_seq;
+};
+
+static inline void whc_qset_set_link_ptr(u64 *ptr, u64 target)
+{
+ if (target)
+ *ptr = (*ptr & ~(QH_LINK_PTR_MASK | QH_LINK_T)) | QH_LINK_PTR(target);
+ else
+ *ptr = QH_LINK_T;
+}
+
+/**
+ * struct di_buf_entry - Device Information (DI) buffer entry.
+ *
+ * There's one of these per connected device.
+ */
+struct di_buf_entry {
+ __le32 availability_info[8]; /*< MAS availability information, one MAS per bit */
+ __le32 addr_sec_info; /*< addressing and security info */
+ __le32 reserved[7];
+} __attribute__((packed));
+
+#define WHC_DI_SECURE (1 << 31)
+#define WHC_DI_DISABLE (1 << 30)
+#define WHC_DI_KEY_IDX(k) ((k) << 8)
+#define WHC_DI_KEY_IDX_MASK 0x0000ff00
+#define WHC_DI_DEV_ADDR(a) ((a) << 0)
+#define WHC_DI_DEV_ADDR_MASK 0x000000ff
+
+/**
+ * struct dn_buf_entry - Device Notification (DN) buffer entry.
+ *
+ * [WHCI] section 3.2.8
+ */
+struct dn_buf_entry {
+ __u8 msg_size; /*< number of octets of valid DN data */
+ __u8 reserved1;
+ __u8 src_addr; /*< source address */
+ __u8 status; /*< buffer entry status */
+ __le32 tkid; /*< TKID for source device, valid if secure bit is set */
+ __u8 dn_data[56]; /*< up to 56 octets of DN data */
+} __attribute__((packed));
+
+#define WHC_DN_STATUS_VALID (1 << 7) /* buffer entry is valid */
+#define WHC_DN_STATUS_SECURE (1 << 6) /* notification received using secure frame */
+
+#define WHC_N_DN_ENTRIES (4096 / sizeof(struct dn_buf_entry))
+
+/* The Add MMC IE WUSB Generic Command may take up to 256 bytes of
+ data. [WHCI] section 2.4.7. */
+#define WHC_GEN_CMD_DATA_LEN 256
+
+/*
+ * HC registers.
+ *
+ * [WHCI] section 2.4
+ */
+
+#define WHCIVERSION 0x00
+
+#define WHCSPARAMS 0x04
+# define WHCSPARAMS_TO_N_MMC_IES(p) (((p) >> 16) & 0xff)
+# define WHCSPARAMS_TO_N_KEYS(p) (((p) >> 8) & 0xff)
+# define WHCSPARAMS_TO_N_DEVICES(p) (((p) >> 0) & 0x7f)
+
+#define WUSBCMD 0x08
+# define WUSBCMD_BCID(b) ((b) << 16)
+# define WUSBCMD_BCID_MASK (0xff << 16)
+# define WUSBCMD_ASYNC_QSET_RM (1 << 12)
+# define WUSBCMD_PERIODIC_QSET_RM (1 << 11)
+# define WUSBCMD_WUSBSI(s) ((s) << 8)
+# define WUSBCMD_WUSBSI_MASK (0x7 << 8)
+# define WUSBCMD_ASYNC_SYNCED_DB (1 << 7)
+# define WUSBCMD_PERIODIC_SYNCED_DB (1 << 6)
+# define WUSBCMD_ASYNC_UPDATED (1 << 5)
+# define WUSBCMD_PERIODIC_UPDATED (1 << 4)
+# define WUSBCMD_ASYNC_EN (1 << 3)
+# define WUSBCMD_PERIODIC_EN (1 << 2)
+# define WUSBCMD_WHCRESET (1 << 1)
+# define WUSBCMD_RUN (1 << 0)
+
+#define WUSBSTS 0x0c
+# define WUSBSTS_ASYNC_SCHED (1 << 15)
+# define WUSBSTS_PERIODIC_SCHED (1 << 14)
+# define WUSBSTS_DNTS_SCHED (1 << 13)
+# define WUSBSTS_HCHALTED (1 << 12)
+# define WUSBSTS_GEN_CMD_DONE (1 << 9)
+# define WUSBSTS_CHAN_TIME_ROLLOVER (1 << 8)
+# define WUSBSTS_DNTS_OVERFLOW (1 << 7)
+# define WUSBSTS_BPST_ADJUSTMENT_CHANGED (1 << 6)
+# define WUSBSTS_HOST_ERR (1 << 5)
+# define WUSBSTS_ASYNC_SCHED_SYNCED (1 << 4)
+# define WUSBSTS_PERIODIC_SCHED_SYNCED (1 << 3)
+# define WUSBSTS_DNTS_INT (1 << 2)
+# define WUSBSTS_ERR_INT (1 << 1)
+# define WUSBSTS_INT (1 << 0)
+# define WUSBSTS_INT_MASK 0x3ff
+
+#define WUSBINTR 0x10
+# define WUSBINTR_GEN_CMD_DONE (1 << 9)
+# define WUSBINTR_CHAN_TIME_ROLLOVER (1 << 8)
+# define WUSBINTR_DNTS_OVERFLOW (1 << 7)
+# define WUSBINTR_BPST_ADJUSTMENT_CHANGED (1 << 6)
+# define WUSBINTR_HOST_ERR (1 << 5)
+# define WUSBINTR_ASYNC_SCHED_SYNCED (1 << 4)
+# define WUSBINTR_PERIODIC_SCHED_SYNCED (1 << 3)
+# define WUSBINTR_DNTS_INT (1 << 2)
+# define WUSBINTR_ERR_INT (1 << 1)
+# define WUSBINTR_INT (1 << 0)
+# define WUSBINTR_ALL 0x3ff
+
+#define WUSBGENCMDSTS 0x14
+# define WUSBGENCMDSTS_ACTIVE (1 << 31)
+# define WUSBGENCMDSTS_ERROR (1 << 24)
+# define WUSBGENCMDSTS_IOC (1 << 23)
+# define WUSBGENCMDSTS_MMCIE_ADD 0x01
+# define WUSBGENCMDSTS_MMCIE_RM 0x02
+# define WUSBGENCMDSTS_SET_MAS 0x03
+# define WUSBGENCMDSTS_CHAN_STOP 0x04
+# define WUSBGENCMDSTS_RWP_EN 0x05
+
+#define WUSBGENCMDPARAMS 0x18
+#define WUSBGENADDR 0x20
+#define WUSBASYNCLISTADDR 0x28
+#define WUSBDNTSBUFADDR 0x30
+#define WUSBDEVICEINFOADDR 0x38
+
+#define WUSBSETSECKEYCMD 0x40
+# define WUSBSETSECKEYCMD_SET (1 << 31)
+# define WUSBSETSECKEYCMD_ERASE (1 << 30)
+# define WUSBSETSECKEYCMD_GTK (1 << 8)
+# define WUSBSETSECKEYCMD_IDX(i) ((i) << 0)
+
+#define WUSBTKID 0x44
+#define WUSBSECKEY 0x48
+#define WUSBPERIODICLISTBASE 0x58
+#define WUSBMASINDEX 0x60
+
+#define WUSBDNTSCTRL 0x64
+# define WUSBDNTSCTRL_ACTIVE (1 << 31)
+# define WUSBDNTSCTRL_INTERVAL(i) ((i) << 8)
+# define WUSBDNTSCTRL_SLOTS(s) ((s) << 0)
+
+#define WUSBTIME 0x68
+#define WUSBBPST 0x6c
+#define WUSBDIBUPDATED 0x70
+
+#endif /* #ifndef _WHCI_WHCI_HC_H */
diff --git a/drivers/usb/host/whci/wusb.c b/drivers/usb/host/whci/wusb.c
new file mode 100644
index 000000000000..66e4ddcd961d
--- /dev/null
+++ b/drivers/usb/host/whci/wusb.c
@@ -0,0 +1,241 @@
+/*
+ * Wireless Host Controller (WHC) WUSB operations.
+ *
+ * Copyright (C) 2007 Cambridge Silicon Radio Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License version
+ * 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that 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, see <http://www.gnu.org/licenses/>.
+ */
+#include <linux/version.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/uwb/umc.h>
+#define D_LOCAL 1
+#include <linux/uwb/debug.h>
+
+#include "../../wusbcore/wusbhc.h"
+
+#include "whcd.h"
+
+#if D_LOCAL >= 1
+static void dump_di(struct whc *whc, int idx)
+{
+ struct di_buf_entry *di = &whc->di_buf[idx];
+ struct device *dev = &whc->umc->dev;
+ char buf[128];
+
+ bitmap_scnprintf(buf, sizeof(buf), (unsigned long *)di->availability_info, UWB_NUM_MAS);
+
+ d_printf(1, dev, "DI[%d]\n", idx);
+ d_printf(1, dev, " availability: %s\n", buf);
+ d_printf(1, dev, " %c%c key idx: %d dev addr: %d\n",
+ (di->addr_sec_info & WHC_DI_SECURE) ? 'S' : ' ',
+ (di->addr_sec_info & WHC_DI_DISABLE) ? 'D' : ' ',
+ (di->addr_sec_info & WHC_DI_KEY_IDX_MASK) >> 8,
+ (di->addr_sec_info & WHC_DI_DEV_ADDR_MASK));
+}
+#else
+static inline void dump_di(struct whc *whc, int idx)
+{
+}
+#endif
+
+static int whc_update_di(struct whc *whc, int idx)
+{
+ int offset = idx / 32;
+ u32 bit = 1 << (idx % 32);
+
+ dump_di(whc, idx);
+
+ le_writel(bit, whc->base + WUSBDIBUPDATED + offset);
+
+ return whci_wait_for(&whc->umc->dev,
+ whc->base + WUSBDIBUPDATED + offset, bit, 0,
+ 100, "DI update");
+}
+
+/*
+ * WHCI starts and stops MMCs based on there being a valid GTK so
+ * these need only start/stop the asynchronous and periodic schedules.
+ */
+
+int whc_wusbhc_start(struct wusbhc *wusbhc)
+{
+ struct whc *whc = wusbhc_to_whc(wusbhc);
+
+ asl_start(whc);
+ pzl_start(whc);
+
+ return 0;
+}
+
+void whc_wusbhc_stop(struct wusbhc *wusbhc)
+{
+ struct whc *whc = wusbhc_to_whc(wusbhc);
+
+ pzl_stop(whc);
+ asl_stop(whc);
+}
+
+int whc_mmcie_add(struct wusbhc *wusbhc, u8 interval, u8 repeat_cnt,
+ u8 handle, struct wuie_hdr *wuie)
+{
+ struct whc *whc = wusbhc_to_whc(wusbhc);
+ u32 params;
+
+ params = (interval << 24)
+ | (repeat_cnt << 16)
+ | (wuie->bLength << 8)
+ | handle;
+
+ return whc_do_gencmd(whc, WUSBGENCMDSTS_MMCIE_ADD, params, wuie, wuie->bLength);
+}
+
+int whc_mmcie_rm(struct wusbhc *wusbhc, u8 handle)
+{
+ struct whc *whc = wusbhc_to_whc(wusbhc);
+ u32 params;
+
+ params = handle;
+
+ return whc_do_gencmd(whc, WUSBGENCMDSTS_MMCIE_RM, params, NULL, 0);
+}
+
+int whc_bwa_set(struct wusbhc *wusbhc, s8 stream_index, const struct uwb_mas_bm *mas_bm)
+{
+ struct whc *whc = wusbhc_to_whc(wusbhc);
+
+ if (stream_index >= 0)
+ whc_write_wusbcmd(whc, WUSBCMD_WUSBSI_MASK, WUSBCMD_WUSBSI(stream_index));
+
+ return whc_do_gencmd(whc, WUSBGENCMDSTS_SET_MAS, 0, (void *)mas_bm, sizeof(*mas_bm));
+}
+
+int whc_dev_info_set(struct wusbhc *wusbhc, struct wusb_dev *wusb_dev)
+{
+ struct whc *whc = wusbhc_to_whc(wusbhc);
+ int idx = wusb_dev->port_idx;
+ struct di_buf_entry *di = &whc->di_buf[idx];
+ int ret;
+
+ mutex_lock(&whc->mutex);
+
+ uwb_mas_bm_copy_le(di->availability_info, &wusb_dev->availability);
+ di->addr_sec_info &= ~(WHC_DI_DISABLE | WHC_DI_DEV_ADDR_MASK);
+ di->addr_sec_info |= WHC_DI_DEV_ADDR(wusb_dev->addr);
+
+ ret = whc_update_di(whc, idx);
+
+ mutex_unlock(&whc->mutex);
+
+ return ret;
+}
+
+/*
+ * Set the number of Device Notification Time Slots (DNTS) and enable
+ * device notifications.
+ */
+int whc_set_num_dnts(struct wusbhc *wusbhc, u8 interval, u8 slots)
+{
+ struct whc *whc = wusbhc_to_whc(wusbhc);
+ u32 dntsctrl;
+
+ dntsctrl = WUSBDNTSCTRL_ACTIVE
+ | WUSBDNTSCTRL_INTERVAL(interval)
+ | WUSBDNTSCTRL_SLOTS(slots);
+
+ le_writel(dntsctrl, whc->base + WUSBDNTSCTRL);
+
+ return 0;
+}
+
+static int whc_set_key(struct whc *whc, u8 key_index, uint32_t tkid,
+ const void *key, size_t key_size, bool is_gtk)
+{
+ uint32_t setkeycmd;
+ uint32_t seckey[4];
+ int i;
+ int ret;
+
+ memcpy(seckey, key, key_size);
+ setkeycmd = WUSBSETSECKEYCMD_SET | WUSBSETSECKEYCMD_IDX(key_index);
+ if (is_gtk)
+ setkeycmd |= WUSBSETSECKEYCMD_GTK;
+
+ le_writel(tkid, whc->base + WUSBTKID);
+ for (i = 0; i < 4; i++)
+ le_writel(seckey[i], whc->base + WUSBSECKEY + 4*i);
+ le_writel(setkeycmd, whc->base + WUSBSETSECKEYCMD);
+
+ ret = whci_wait_for(&whc->umc->dev, whc->base + WUSBSETSECKEYCMD,
+ WUSBSETSECKEYCMD_SET, 0, 100, "set key");
+
+ return ret;
+}
+
+/**
+ * whc_set_ptk - set the PTK to use for a device.
+ *
+ * The index into the key table for this PTK is the same as the
+ * device's port index.
+ */
+int whc_set_ptk(struct wusbhc *wusbhc, u8 port_idx, u32 tkid,
+ const void *ptk, size_t key_size)
+{
+ struct whc *whc = wusbhc_to_whc(wusbhc);
+ struct di_buf_entry *di = &whc->di_buf[port_idx];
+ int ret;
+
+ mutex_lock(&whc->mutex);
+
+ if (ptk) {
+ ret = whc_set_key(whc, port_idx, tkid, ptk, key_size, false);
+ if (ret)
+ goto out;
+
+ di->addr_sec_info &= ~WHC_DI_KEY_IDX_MASK;
+ di->addr_sec_info |= WHC_DI_SECURE | WHC_DI_KEY_IDX(port_idx);
+ } else
+ di->addr_sec_info &= ~WHC_DI_SECURE;
+
+ ret = whc_update_di(whc, port_idx);
+out:
+ mutex_unlock(&whc->mutex);
+ return ret;
+}
+
+/**
+ * whc_set_gtk - set the GTK for subsequent broadcast packets
+ *
+ * The GTK is stored in the last entry in the key table (the previous
+ * N_DEVICES entries are for the per-device PTKs).
+ */
+int whc_set_gtk(struct wusbhc *wusbhc, u32 tkid,
+ const void *gtk, size_t key_size)
+{
+ struct whc *whc = wusbhc_to_whc(wusbhc);
+ int ret;
+
+ mutex_lock(&whc->mutex);
+
+ ret = whc_set_key(whc, whc->n_devices, tkid, gtk, key_size, true);
+
+ mutex_unlock(&whc->mutex);
+
+ return ret;
+}
+
+int whc_set_cluster_id(struct whc *whc, u8 bcid)
+{
+ whc_write_wusbcmd(whc, WUSBCMD_BCID_MASK, WUSBCMD_BCID(bcid));
+ return 0;
+}