aboutsummaryrefslogtreecommitdiff
path: root/drivers/usb/host
diff options
context:
space:
mode:
authorDavid Woodhouse <David.Woodhouse@intel.com>2008-07-11 14:36:25 +0100
committerDavid Woodhouse <David.Woodhouse@intel.com>2008-07-11 14:36:25 +0100
commita8931ef380c92d121ae74ecfb03b2d63f72eea6f (patch)
tree980fb6b019e11e6cb1ece55b7faff184721a8053 /drivers/usb/host
parent90574d0a4d4b73308ae54a2a57a4f3f1fa98e984 (diff)
parente5a5816f7875207cb0a0a7032e39a4686c5e10a4 (diff)
Merge branch 'master' of git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6
Diffstat (limited to 'drivers/usb/host')
-rw-r--r--drivers/usb/host/Kconfig47
-rw-r--r--drivers/usb/host/Makefile4
-rw-r--r--drivers/usb/host/ehci-au1xxx.c1
-rw-r--r--drivers/usb/host/ehci-fsl.c7
-rw-r--r--drivers/usb/host/ehci-hub.c16
-rw-r--r--drivers/usb/host/ehci-ixp4xx.c4
-rw-r--r--drivers/usb/host/ehci-orion.c10
-rw-r--r--drivers/usb/host/ehci-pci.c4
-rw-r--r--drivers/usb/host/ehci-ppc-of.c2
-rw-r--r--drivers/usb/host/ehci-ppc-soc.c1
-rw-r--r--drivers/usb/host/ehci-ps3.c1
-rw-r--r--drivers/usb/host/ehci-sched.c67
-rw-r--r--drivers/usb/host/ehci.h24
-rw-r--r--drivers/usb/host/isp1760-hcd.c2231
-rw-r--r--drivers/usb/host/isp1760-hcd.h206
-rw-r--r--drivers/usb/host/isp1760-if.c298
-rw-r--r--drivers/usb/host/ohci-at91.c1
-rw-r--r--drivers/usb/host/ohci-au1xxx.c1
-rw-r--r--drivers/usb/host/ohci-ep93xx.c1
-rw-r--r--drivers/usb/host/ohci-hcd.c15
-rw-r--r--drivers/usb/host/ohci-hub.c55
-rw-r--r--drivers/usb/host/ohci-lh7a404.c1
-rw-r--r--drivers/usb/host/ohci-omap.c1
-rw-r--r--drivers/usb/host/ohci-pci.c1
-rw-r--r--drivers/usb/host/ohci-pnx4008.c1
-rw-r--r--drivers/usb/host/ohci-pnx8550.c1
-rw-r--r--drivers/usb/host/ohci-ppc-of.c1
-rw-r--r--drivers/usb/host/ohci-ppc-soc.c1
-rw-r--r--drivers/usb/host/ohci-ps3.c1
-rw-r--r--drivers/usb/host/ohci-pxa27x.c1
-rw-r--r--drivers/usb/host/ohci-q.c12
-rw-r--r--drivers/usb/host/ohci-s3c2410.c1
-rw-r--r--drivers/usb/host/ohci-sa1111.c1
-rw-r--r--drivers/usb/host/ohci-sh.c1
-rw-r--r--drivers/usb/host/ohci-sm501.c3
-rw-r--r--drivers/usb/host/ohci-ssb.c1
-rw-r--r--drivers/usb/host/u132-hcd.c11
-rw-r--r--drivers/usb/host/uhci-hcd.c74
-rw-r--r--drivers/usb/host/uhci-hcd.h5
39 files changed, 3008 insertions, 106 deletions
diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig
index 0b87480dd71..228797e54f9 100644
--- a/drivers/usb/host/Kconfig
+++ b/drivers/usb/host/Kconfig
@@ -4,6 +4,19 @@
comment "USB Host Controller Drivers"
depends on USB
+config USB_C67X00_HCD
+ tristate "Cypress C67x00 HCD support"
+ depends on USB
+ help
+ The Cypress C67x00 (EZ-Host/EZ-OTG) chips are dual-role
+ host/peripheral/OTG USB controllers.
+
+ Enable this option to support this chip in host controller mode.
+ If unsure, say N.
+
+ To compile this driver as a module, choose M here: the
+ module will be called c67x00.
+
config USB_EHCI_HCD
tristate "EHCI HCD (USB 2.0) support"
depends on USB && USB_ARCH_HAS_EHCI
@@ -95,6 +108,32 @@ config USB_ISP116X_HCD
To compile this driver as a module, choose M here: the
module will be called isp116x-hcd.
+config USB_ISP1760_HCD
+ tristate "ISP 1760 HCD support"
+ depends on USB && EXPERIMENTAL
+ ---help---
+ The ISP1760 chip is a USB 2.0 host controller.
+
+ This driver does not support isochronous transfers or OTG.
+
+ To compile this driver as a module, choose M here: the
+ module will be called isp1760-hcd.
+
+config USB_ISP1760_PCI
+ bool "Support for the PCI bus"
+ depends on USB_ISP1760_HCD && PCI
+ ---help---
+ Enables support for the device present on the PCI bus.
+ This should only be required if you happen to have the eval kit from
+ NXP and you are going to test it.
+
+config USB_ISP1760_OF
+ bool "Support for the OF platform bus"
+ depends on USB_ISP1760_HCD && PPC_OF
+ ---help---
+ Enables support for the device present on the PowerPC
+ OpenFirmware platform bus.
+
config USB_OHCI_HCD
tristate "OHCI HCD support"
depends on USB && USB_ARCH_HAS_OHCI
@@ -261,8 +300,8 @@ config USB_R8A66597_HCD
module will be called r8a66597-hcd.
config SUPERH_ON_CHIP_R8A66597
- boolean "Enable SuperH on-chip USB like the R8A66597"
- depends on USB_R8A66597_HCD && CPU_SUBTYPE_SH7366
+ boolean "Enable SuperH on-chip R8A66597 USB"
+ depends on USB_R8A66597_HCD && (CPU_SUBTYPE_SH7366 || CPU_SUBTYPE_SH7723)
help
- Renesas SuperH processor has USB like the R8A66597.
- This driver supported processor is SH7366.
+ This driver enables support for the on-chip R8A66597 in the
+ SH7366 and SH7723 processors.
diff --git a/drivers/usb/host/Makefile b/drivers/usb/host/Makefile
index bb8e9d44f37..f1edda2dcfd 100644
--- a/drivers/usb/host/Makefile
+++ b/drivers/usb/host/Makefile
@@ -6,6 +6,8 @@ ifeq ($(CONFIG_USB_DEBUG),y)
EXTRA_CFLAGS += -DDEBUG
endif
+isp1760-objs := isp1760-hcd.o isp1760-if.o
+
obj-$(CONFIG_PCI) += pci-quirks.o
obj-$(CONFIG_USB_EHCI_HCD) += ehci-hcd.o
@@ -16,4 +18,4 @@ obj-$(CONFIG_USB_SL811_HCD) += sl811-hcd.o
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
diff --git a/drivers/usb/host/ehci-au1xxx.c b/drivers/usb/host/ehci-au1xxx.c
index 8b5f991e949..08a4335401a 100644
--- a/drivers/usb/host/ehci-au1xxx.c
+++ b/drivers/usb/host/ehci-au1xxx.c
@@ -223,6 +223,7 @@ static const struct hc_driver ehci_au1xxx_hc_driver = {
.bus_suspend = ehci_bus_suspend,
.bus_resume = ehci_bus_resume,
.relinquish_port = ehci_relinquish_port,
+ .port_handed_over = ehci_port_handed_over,
};
/*-------------------------------------------------------------------------*/
diff --git a/drivers/usb/host/ehci-fsl.c b/drivers/usb/host/ehci-fsl.c
index 6d9bed6c1f4..7370d6187c6 100644
--- a/drivers/usb/host/ehci-fsl.c
+++ b/drivers/usb/host/ehci-fsl.c
@@ -269,7 +269,7 @@ static int ehci_fsl_setup(struct usb_hcd *hcd)
if (retval)
return retval;
- ehci->is_tdi_rh_tt = 1;
+ hcd->has_tt = 1;
ehci->sbrn = 0x20;
@@ -295,10 +295,6 @@ static const struct hc_driver ehci_fsl_hc_driver = {
*/
.reset = ehci_fsl_setup,
.start = ehci_run,
-#ifdef CONFIG_PM
- .suspend = ehci_bus_suspend,
- .resume = ehci_bus_resume,
-#endif
.stop = ehci_stop,
.shutdown = ehci_shutdown,
@@ -322,6 +318,7 @@ static const struct hc_driver ehci_fsl_hc_driver = {
.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_fsl_drv_probe(struct platform_device *pdev)
diff --git a/drivers/usb/host/ehci-hub.c b/drivers/usb/host/ehci-hub.c
index 382587c4457..740835bb857 100644
--- a/drivers/usb/host/ehci-hub.c
+++ b/drivers/usb/host/ehci-hub.c
@@ -609,7 +609,7 @@ static int ehci_hub_control (
}
break;
case USB_PORT_FEAT_C_SUSPEND:
- /* we auto-clear this feature */
+ clear_bit(wIndex, &ehci->port_c_suspend);
break;
case USB_PORT_FEAT_POWER:
if (HCS_PPC (ehci->hcs_params))
@@ -688,7 +688,7 @@ static int ehci_hub_control (
/* resume completed? */
else if (time_after_eq(jiffies,
ehci->reset_done[wIndex])) {
- status |= 1 << USB_PORT_FEAT_C_SUSPEND;
+ set_bit(wIndex, &ehci->port_c_suspend);
ehci->reset_done[wIndex] = 0;
/* stop resume signaling */
@@ -765,6 +765,8 @@ static int ehci_hub_control (
status |= 1 << USB_PORT_FEAT_RESET;
if (temp & PORT_POWER)
status |= 1 << USB_PORT_FEAT_POWER;
+ if (test_bit(wIndex, &ehci->port_c_suspend))
+ status |= 1 << USB_PORT_FEAT_C_SUSPEND;
#ifndef VERBOSE_DEBUG
if (status & ~0xffff) /* only if wPortChange is interesting */
@@ -875,3 +877,13 @@ static void ehci_relinquish_port(struct usb_hcd *hcd, int portnum)
set_owner(ehci, --portnum, PORT_OWNER);
}
+static int ehci_port_handed_over(struct usb_hcd *hcd, int portnum)
+{
+ struct ehci_hcd *ehci = hcd_to_ehci(hcd);
+ u32 __iomem *reg;
+
+ if (ehci_is_TDI(ehci))
+ return 0;
+ reg = &ehci->regs->port_status[portnum - 1];
+ return ehci_readl(ehci, reg) & PORT_OWNER;
+}
diff --git a/drivers/usb/host/ehci-ixp4xx.c b/drivers/usb/host/ehci-ixp4xx.c
index 601c8795a85..9d042f22009 100644
--- a/drivers/usb/host/ehci-ixp4xx.c
+++ b/drivers/usb/host/ehci-ixp4xx.c
@@ -26,7 +26,7 @@ static int ixp4xx_ehci_init(struct usb_hcd *hcd)
+ HC_LENGTH(ehci_readl(ehci, &ehci->caps->hc_capbase));
ehci->hcs_params = ehci_readl(ehci, &ehci->caps->hcs_params);
- ehci->is_tdi_rh_tt = 1;
+ hcd->has_tt = 1;
ehci_reset(ehci);
retval = ehci_init(hcd);
@@ -58,6 +58,8 @@ static const struct hc_driver ixp4xx_ehci_hc_driver = {
.bus_suspend = ehci_bus_suspend,
.bus_resume = ehci_bus_resume,
#endif
+ .relinquish_port = ehci_relinquish_port,
+ .port_handed_over = ehci_port_handed_over,
};
static int ixp4xx_ehci_probe(struct platform_device *pdev)
diff --git a/drivers/usb/host/ehci-orion.c b/drivers/usb/host/ehci-orion.c
index d187d031374..ab625f0ba1d 100644
--- a/drivers/usb/host/ehci-orion.c
+++ b/drivers/usb/host/ehci-orion.c
@@ -115,6 +115,8 @@ static int ehci_orion_setup(struct usb_hcd *hcd)
if (retval)
return retval;
+ hcd->has_tt = 1;
+
ehci_reset(ehci);
ehci_port_power(ehci, 0);
@@ -137,10 +139,6 @@ static const struct hc_driver ehci_orion_hc_driver = {
*/
.reset = ehci_orion_setup,
.start = ehci_run,
-#ifdef CONFIG_PM
- .suspend = ehci_bus_suspend,
- .resume = ehci_bus_resume,
-#endif
.stop = ehci_stop,
.shutdown = ehci_shutdown,
@@ -163,6 +161,8 @@ static const struct hc_driver ehci_orion_hc_driver = {
.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 void __init
@@ -248,7 +248,7 @@ static int __init ehci_orion_drv_probe(struct platform_device *pdev)
ehci->regs = hcd->regs + 0x100 +
HC_LENGTH(ehci_readl(ehci, &ehci->caps->hc_capbase));
ehci->hcs_params = ehci_readl(ehci, &ehci->caps->hcs_params);
- ehci->is_tdi_rh_tt = 1;
+ hcd->has_tt = 1;
ehci->sbrn = 0x20;
/*
diff --git a/drivers/usb/host/ehci-pci.c b/drivers/usb/host/ehci-pci.c
index 5bb7f6bb13f..c46a58f9181 100644
--- a/drivers/usb/host/ehci-pci.c
+++ b/drivers/usb/host/ehci-pci.c
@@ -129,7 +129,6 @@ static int ehci_pci_setup(struct usb_hcd *hcd)
switch (pdev->vendor) {
case PCI_VENDOR_ID_TDI:
if (pdev->device == PCI_DEVICE_ID_TDI_EHCI) {
- ehci->is_tdi_rh_tt = 1;
hcd->has_tt = 1;
tdi_reset(ehci);
}
@@ -379,7 +378,8 @@ static const struct hc_driver ehci_pci_hc_driver = {
.hub_control = ehci_hub_control,
.bus_suspend = ehci_bus_suspend,
.bus_resume = ehci_bus_resume,
- .relinquish_port = ehci_relinquish_port,
+ .relinquish_port = ehci_relinquish_port,
+ .port_handed_over = ehci_port_handed_over,
};
/*-------------------------------------------------------------------------*/
diff --git a/drivers/usb/host/ehci-ppc-of.c b/drivers/usb/host/ehci-ppc-of.c
index ee305b1f99f..b018deed2e8 100644
--- a/drivers/usb/host/ehci-ppc-of.c
+++ b/drivers/usb/host/ehci-ppc-of.c
@@ -76,6 +76,8 @@ static const struct hc_driver ehci_ppc_of_hc_driver = {
.bus_suspend = ehci_bus_suspend,
.bus_resume = ehci_bus_resume,
#endif
+ .relinquish_port = ehci_relinquish_port,
+ .port_handed_over = ehci_port_handed_over,
};
diff --git a/drivers/usb/host/ehci-ppc-soc.c b/drivers/usb/host/ehci-ppc-soc.c
index 6c76036783a..529590eb403 100644
--- a/drivers/usb/host/ehci-ppc-soc.c
+++ b/drivers/usb/host/ehci-ppc-soc.c
@@ -163,6 +163,7 @@ static const struct hc_driver ehci_ppc_soc_hc_driver = {
.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)
diff --git a/drivers/usb/host/ehci-ps3.c b/drivers/usb/host/ehci-ps3.c
index 69782221bcf..37e6abeb794 100644
--- a/drivers/usb/host/ehci-ps3.c
+++ b/drivers/usb/host/ehci-ps3.c
@@ -73,6 +73,7 @@ static const struct hc_driver ps3_ehci_hc_driver = {
.bus_resume = ehci_bus_resume,
#endif
.relinquish_port = ehci_relinquish_port,
+ .port_handed_over = ehci_port_handed_over,
};
static int ps3_ehci_probe(struct ps3_system_bus_device *dev)
diff --git a/drivers/usb/host/ehci-sched.c b/drivers/usb/host/ehci-sched.c
index be575e46eac..b7853c8bac0 100644
--- a/drivers/usb/host/ehci-sched.c
+++ b/drivers/usb/host/ehci-sched.c
@@ -1349,18 +1349,27 @@ iso_stream_schedule (
/* when's the last uframe this urb could start? */
max = now + mod;
- /* typical case: reuse current schedule. stream is still active,
- * and no gaps from host falling behind (irq delays etc)
+ /* Typical case: reuse current schedule, stream is still active.
+ * Hopefully there are no gaps from the host falling behind
+ * (irq delays etc), but if there are we'll take the next
+ * slot in the schedule, implicitly assuming URB_ISO_ASAP.
*/
if (likely (!list_empty (&stream->td_list))) {
start = stream->next_uframe;
if (start < now)
start += mod;
- if (likely ((start + sched->span) < max))
- goto ready;
- /* else fell behind; someday, try to reschedule */
- status = -EL2NSYNC;
- goto fail;
+
+ /* Fell behind (by up to twice the slop amount)? */
+ if (start >= max - 2 * 8 * SCHEDULE_SLOP)
+ start += stream->interval * DIV_ROUND_UP(
+ max - start, stream->interval) - mod;
+
+ /* Tried to schedule too far into the future? */
+ if (unlikely((start + sched->span) >= max)) {
+ status = -EFBIG;
+ goto fail;
+ }
+ goto ready;
}
/* need to schedule; when's the next (u)frame we could start?
@@ -1613,6 +1622,9 @@ itd_complete (
} else if (likely ((t & EHCI_ISOC_ACTIVE) == 0)) {
desc->status = 0;
desc->actual_length = EHCI_ITD_LENGTH (t);
+ } else {
+ /* URB was too late */
+ desc->status = -EXDEV;
}
}
@@ -2095,7 +2107,7 @@ done:
static void
scan_periodic (struct ehci_hcd *ehci)
{
- unsigned frame, clock, now_uframe, mod;
+ unsigned now_uframe, frame, clock, clock_frame, mod;
unsigned modified;
mod = ehci->periodic_size << 3;
@@ -2111,6 +2123,7 @@ scan_periodic (struct ehci_hcd *ehci)
else
clock = now_uframe + mod - 1;
clock %= mod;
+ clock_frame = clock >> 3;
for (;;) {
union ehci_shadow q, *q_p;
@@ -2157,22 +2170,26 @@ restart:
case Q_TYPE_ITD:
/* If this ITD is still active, leave it for
* later processing ... check the next entry.
+ * No need to check for activity unless the
+ * frame is current.
*/
- rmb ();
- for (uf = 0; uf < 8 && live; uf++) {
- if (0 == (q.itd->hw_transaction [uf]
- & ITD_ACTIVE(ehci)))
- continue;
- incomplete = true;
- q_p = &q.itd->itd_next;
- hw_p = &q.itd->hw_next;
- type = Q_NEXT_TYPE(ehci,
+ if (frame == clock_frame && live) {
+ rmb();
+ for (uf = 0; uf < 8; uf++) {
+ if (q.itd->hw_transaction[uf] &
+ ITD_ACTIVE(ehci))
+ break;
+ }
+ if (uf < 8) {
+ incomplete = true;
+ q_p = &q.itd->itd_next;
+ hw_p = &q.itd->hw_next;
+ type = Q_NEXT_TYPE(ehci,
q.itd->hw_next);
- q = *q_p;
- break;
+ q = *q_p;
+ break;
+ }
}
- if (uf < 8 && live)
- break;
/* Take finished ITDs out of the schedule
* and process them: recycle, maybe report
@@ -2189,9 +2206,12 @@ restart:
case Q_TYPE_SITD:
/* If this SITD is still active, leave it for
* later processing ... check the next entry.
+ * No need to check for activity unless the
+ * frame is current.
*/
- if ((q.sitd->hw_results & SITD_ACTIVE(ehci))
- && live) {
+ if (frame == clock_frame && live &&
+ (q.sitd->hw_results &
+ SITD_ACTIVE(ehci))) {
incomplete = true;
q_p = &q.sitd->sitd_next;
hw_p = &q.sitd->hw_next;
@@ -2260,6 +2280,7 @@ restart:
/* rescan the rest of this frame, then ... */
clock = now;
+ clock_frame = clock >> 3;
} else {
now_uframe++;
now_uframe %= mod;
diff --git a/drivers/usb/host/ehci.h b/drivers/usb/host/ehci.h
index bf92d209a1a..90245fd8bac 100644
--- a/drivers/usb/host/ehci.h
+++ b/drivers/usb/host/ehci.h
@@ -97,6 +97,8 @@ struct ehci_hcd { /* one per controller */
dedicated to the companion controller */
unsigned long owned_ports; /* which ports are
owned by the companion during a bus suspend */
+ unsigned long port_c_suspend; /* which ports have
+ the change-suspend feature turned on */
/* per-HC memory pools (could be per-bus, but ...) */
struct dma_pool *qh_pool; /* qh per active urb */
@@ -112,7 +114,6 @@ struct ehci_hcd { /* one per controller */
u32 command;
/* SILICON QUIRKS */
- unsigned is_tdi_rh_tt:1; /* TDI roothub with TT */
unsigned no_selective_suspend:1;
unsigned has_fsl_port_bug:1; /* FreeScale */
unsigned big_endian_mmio:1;
@@ -176,6 +177,15 @@ timer_action_done (struct ehci_hcd *ehci, enum ehci_timer_action action)
static inline void
timer_action (struct ehci_hcd *ehci, enum ehci_timer_action action)
{
+ /* Don't override timeouts which shrink or (later) disable
+ * 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;
+
if (!test_and_set_bit (action, &ehci->actions)) {
unsigned long t;
@@ -191,15 +201,7 @@ timer_action (struct ehci_hcd *ehci, enum ehci_timer_action action)
t = EHCI_SHRINK_JIFFIES;
break;
}
- t += jiffies;
- // all timings except IAA watchdog can be overridden.
- // async queue SHRINK often precedes IAA. while it's ready
- // to go OFF neither can matter, and afterwards the IO
- // watchdog stops unless there's still periodic traffic.
- if (time_before_eq(t, ehci->watchdog.expires)
- && timer_pending (&ehci->watchdog))
- return;
- mod_timer (&ehci->watchdog, t);
+ mod_timer(&ehci->watchdog, t + jiffies);
}
}
@@ -678,7 +680,7 @@ struct ehci_fstn {
* needed (mostly in root hub code).
*/
-#define ehci_is_TDI(e) ((e)->is_tdi_rh_tt)
+#define ehci_is_TDI(e) (ehci_to_hcd(e)->has_tt)
/* Returns the speed of a device attached to a port on the root hub. */
static inline unsigned int
diff --git a/drivers/usb/host/isp1760-hcd.c b/drivers/usb/host/isp1760-hcd.c
new file mode 100644
index 00000000000..65aa5ecf569
--- /dev/null
+++ b/drivers/usb/host/isp1760-hcd.c
@@ -0,0 +1,2231 @@
+/*
+ * Driver for the NXP ISP1760 chip
+ *
+ * However, the code might contain some bugs. What doesn't work for sure is:
+ * - ISO
+ * - OTG
+ e The interrupt line is configured as active low, level.
+ *
+ * (c) 2007 Sebastian Siewior <bigeasy@linutronix.de>
+ *
+ */
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/list.h>
+#include <linux/usb.h>
+#include <linux/debugfs.h>
+#include <linux/uaccess.h>
+#include <linux/io.h>
+#include <asm/unaligned.h>
+
+#include "../core/hcd.h"
+#include "isp1760-hcd.h"
+
+static struct kmem_cache *qtd_cachep;
+static struct kmem_cache *qh_cachep;
+
+struct isp1760_hcd {
+ u32 hcs_params;
+ spinlock_t lock;
+ struct inter_packet_info atl_ints[32];
+ struct inter_packet_info int_ints[32];
+ struct memory_chunk memory_pool[BLOCKS];
+
+ /* periodic schedule support */
+#define DEFAULT_I_TDPS 1024
+ unsigned periodic_size;
+ unsigned i_thresh;
+ unsigned long reset_done;
+ unsigned long next_statechange;
+};
+
+static inline struct isp1760_hcd *hcd_to_priv(struct usb_hcd *hcd)
+{
+ return (struct isp1760_hcd *) (hcd->hcd_priv);
+}
+static inline struct usb_hcd *priv_to_hcd(struct isp1760_hcd *priv)
+{
+ return container_of((void *) priv, struct usb_hcd, hcd_priv);
+}
+
+/* Section 2.2 Host Controller Capability Registers */
+#define HC_LENGTH(p) (((p)>>00)&0x00ff) /* bits 7:0 */
+#define HC_VERSION(p) (((p)>>16)&0xffff) /* bits 31:16 */
+#define HCS_INDICATOR(p) ((p)&(1 << 16)) /* true: has port indicators */
+#define HCS_PPC(p) ((p)&(1 << 4)) /* true: port power control */
+#define HCS_N_PORTS(p) (((p)>>0)&0xf) /* bits 3:0, ports on HC */
+#define HCC_ISOC_CACHE(p) ((p)&(1 << 7)) /* true: can cache isoc frame */
+#define HCC_ISOC_THRES(p) (((p)>>4)&0x7) /* bits 6:4, uframes cached */
+
+/* Section 2.3 Host Controller Operational Registers */
+#define CMD_LRESET (1<<7) /* partial reset (no ports, etc) */
+#define CMD_RESET (1<<1) /* reset HC not bus */
+#define CMD_RUN (1<<0) /* start/stop HC */
+#define STS_PCD (1<<2) /* port change detect */
+#define FLAG_CF (1<<0) /* true: we'll support "high speed" */
+
+#define PORT_OWNER (1<<13) /* true: companion hc owns this port */
+#define PORT_POWER (1<<12) /* true: has power (see PPC) */
+#define PORT_USB11(x) (((x) & (3 << 10)) == (1 << 10)) /* USB 1.1 device */
+#define PORT_RESET (1<<8) /* reset port */
+#define PORT_SUSPEND (1<<7) /* suspend port */
+#define PORT_RESUME (1<<6) /* resume it */
+#define PORT_PE (1<<2) /* port enable */
+#define PORT_CSC (1<<1) /* connect status change */
+#define PORT_CONNECT (1<<0) /* device connected */
+#define PORT_RWC_BITS (PORT_CSC)
+
+struct isp1760_qtd {
+ struct isp1760_qtd *hw_next;
+ u8 packet_type;
+ u8 toggle;
+
+ void *data_buffer;
+ /* the rest is HCD-private */
+ struct list_head qtd_list;
+ struct urb *urb;
+ size_t length;
+
+ /* isp special*/
+ u32 status;
+#define URB_COMPLETE_NOTIFY (1 << 0)
+#define URB_ENQUEUED (1 << 1)
+#define URB_TYPE_ATL (1 << 2)
+#define URB_TYPE_INT (1 << 3)
+};
+
+struct isp1760_qh {
+ /* first part defined by EHCI spec */
+ struct list_head qtd_list;
+ struct isp1760_hcd *priv;
+
+ /* periodic schedule info */
+ unsigned short period; /* polling interval */
+ struct usb_device *dev;
+
+ u32 toggle;
+ u32 ping;
+};
+
+#define ehci_port_speed(priv, portsc) (1 << USB_PORT_FEAT_HIGHSPEED)
+
+static unsigned int isp1760_readl(__u32 __iomem *regs)
+{
+ return readl(regs);
+}
+
+static void isp1760_writel(const unsigned int val, __u32 __iomem *regs)
+{
+ writel(val, regs);
+}
+
+/*
+ * The next two copy via MMIO data to/from the device. memcpy_{to|from}io()
+ * doesn't quite work because some people have to enforce 32-bit access
+ */
+static void priv_read_copy(struct isp1760_hcd *priv, u32 *src,
+ __u32 __iomem *dst, u32 offset, u32 len)
+{
+ struct usb_hcd *hcd = priv_to_hcd(priv);
+ u32 val;
+ u8 *buff8;
+
+ if (!src) {
+ printk(KERN_ERR "ERROR: buffer: %p len: %d\n", src, len);
+ return;
+ }
+ isp1760_writel(offset, hcd->regs + HC_MEMORY_REG);
+ /* XXX
+ * 90nsec delay, the spec says something how this could be avoided.
+ */
+ mdelay(1);
+
+ while (len >= 4) {
+ *src = __raw_readl(dst);
+ len -= 4;
+ src++;
+ dst++;
+ }
+
+ if (!len)
+ return;
+
+ /* in case we have 3, 2 or 1 by left. The dst buffer may not be fully
+ * allocated.
+ */
+ val = isp1760_readl(dst);
+
+ buff8 = (u8 *)src;
+ while (len) {
+
+ *buff8 = val;
+ val >>= 8;
+ len--;
+ buff8++;
+ }
+}
+
+static void priv_write_copy(const struct isp1760_hcd *priv, const u32 *src,
+ __u32 __iomem *dst, u32 len)
+{
+ while (len >= 4) {
+ __raw_writel(*src, dst);
+ len -= 4;
+ src++;
+ dst++;
+ }
+
+ if (!len)
+ return;
+ /* in case we have 3, 2 or 1 by left. The buffer is allocated and the
+ * extra bytes should not be read by the HW
+ */
+
+ __raw_writel(*src, dst);
+}
+
+/* memory management of the 60kb on the chip from 0x1000 to 0xffff */
+static void init_memory(struct isp1760_hcd *priv)
+{
+ int i;
+ u32 payload;
+
+ payload = 0x1000;
+ for (i = 0; i < BLOCK_1_NUM; i++) {
+ priv->memory_pool[i].start = payload;
+ priv->memory_pool[i].size = BLOCK_1_SIZE;
+ priv->memory_pool[i].free = 1;
+ payload += priv->memory_pool[i].size;
+ }
+
+
+ for (i = BLOCK_1_NUM; i < BLOCK_1_NUM + BLOCK_2_NUM; i++) {
+ priv->memory_pool[i].start = payload;
+ priv->memory_pool[i].size = BLOCK_2_SIZE;
+ priv->memory_pool[i].free = 1;
+ payload += priv->memory_pool[i].size;
+ }
+
+
+ for (i = BLOCK_1_NUM + BLOCK_2_NUM; i < BLOCKS; i++) {
+ priv->memory_pool[i].start = payload;
+ priv->memory_pool[i].size = BLOCK_3_SIZE;
+ priv->memory_pool[i].free = 1;
+ payload += priv->memory_pool[i].size;
+ }
+
+ BUG_ON(payload - priv->memory_pool[i - 1].size > PAYLOAD_SIZE);
+}
+
+static u32 alloc_mem(struct isp1760_hcd *priv, u32 size)
+{
+ int i;
+
+ if (!size)
+ return ISP1760_NULL_POINTER;
+
+ for (i = 0; i < BLOCKS; i++) {
+ if (priv->memory_pool[i].size >= size &&
+ priv->memory_pool[i].free) {
+
+ priv->memory_pool[i].free = 0;
+ return priv->memory_pool[i].start;
+ }
+ }
+
+ printk(KERN_ERR "ISP1760 MEM: can not allocate %d bytes of memory\n",
+ size);
+ printk(KERN_ERR "Current memory map:\n");
+ for (i = 0; i < BLOCKS; i++) {
+ printk(KERN_ERR "Pool %2d size %4d status: %d\n",
+ i, priv->memory_pool[i].size,
+ priv->memory_pool[i].free);
+ }
+ /* XXX maybe -ENOMEM could be possible */
+ BUG();
+ return 0;
+}
+
+static void free_mem(struct isp1760_hcd *priv, u32 mem)
+{
+ int i;
+
+ if (mem == ISP1760_NULL_POINTER)
+ return;
+
+ for (i = 0; i < BLOCKS; i++) {
+ if (priv->memory_pool[i].start == mem) {
+
+ BUG_ON(priv->memory_pool[i].free);
+
+ priv->memory_pool[i].free = 1;
+ return ;
+ }
+ }
+
+ printk(KERN_ERR "Trying to free not-here-allocated memory :%08x\n",
+ mem);
+ BUG();
+}
+
+static void isp1760_init_regs(struct usb_hcd *hcd)
+{
+ isp1760_writel(0, hcd->regs + HC_BUFFER_STATUS_REG);
+ isp1760_writel(NO_TRANSFER_ACTIVE, hcd->regs +
+ HC_ATL_PTD_SKIPMAP_REG);
+ isp1760_writel(NO_TRANSFER_ACTIVE, hcd->regs +
+ HC_INT_PTD_SKIPMAP_REG);
+ isp1760_writel(NO_TRANSFER_ACTIVE, hcd->regs +
+ HC_ISO_PTD_SKIPMAP_REG);
+
+ isp1760_writel(~NO_TRANSFER_ACTIVE, hcd->regs +
+ HC_ATL_PTD_DONEMAP_REG);
+ isp1760_writel(~NO_TRANSFER_ACTIVE, hcd->regs +
+ HC_INT_PTD_DONEMAP_REG);
+ isp1760_writel(~NO_TRANSFER_ACTIVE, hcd->regs +
+ HC_ISO_PTD_DONEMAP_REG);
+}
+
+static int handshake(struct isp1760_hcd *priv, void __iomem *ptr,
+ u32 mask, u32 done, int usec)
+{
+ u32 result;
+
+ do {
+ result = isp1760_readl(ptr);
+ if (result == ~0)
+ return -ENODEV;
+ result &= mask;
+ if (result == done)
+ return 0;
+ udelay(1);
+ usec--;
+ } while (usec > 0);
+ return -ETIMEDOUT;
+}
+
+/* reset a non-running (STS_HALT == 1) controller */
+static int ehci_reset(struct isp1760_hcd *priv)
+{
+ int retval;
+ struct usb_hcd *hcd = priv_to_hcd(priv);
+ u32 command = isp1760_readl(hcd->regs + HC_USBCMD);
+
+ command |= CMD_RESET;
+ isp1760_writel(command, hcd->regs + HC_USBCMD);
+ hcd->state = HC_STATE_HALT;
+ priv->next_statechange = jiffies;
+ retval = handshake(priv, hcd->regs + HC_USBCMD,
+ CMD_RESET, 0, 250 * 1000);
+ return retval;
+}
+
+static void qh_destroy(struct isp1760_qh *qh)
+{
+ BUG_ON(!list_empty(&qh->qtd_list));
+ kmem_cache_free(qh_cachep, qh);
+}
+
+static struct isp1760_qh *isp1760_qh_alloc(struct isp1760_hcd *priv,
+ gfp_t flags)
+{
+ struct isp1760_qh *qh;
+
+ qh = kmem_cache_zalloc(qh_cachep, flags);
+ if (!qh)
+ return qh;
+
+ INIT_LIST_HEAD(&qh->qtd_list);
+ qh->priv = priv;
+ return qh;
+}
+
+/* magic numbers that can affect system performance */
+#define EHCI_TUNE_CERR 3 /* 0-3 qtd retries; 0 == don't stop */
+#define EHCI_TUNE_RL_HS 4 /* nak throttle; see 4.9 */
+#define EHCI_TUNE_RL_TT 0
+#define EHCI_TUNE_MULT_HS 1 /* 1-3 transactions/uframe; 4.10.3 */
+#define EHCI_TUNE_MULT_TT 1
+#define EHCI_TUNE_FLS 2 /* (small) 256 frame schedule */
+
+/* one-time init, only for memory state */
+static int priv_init(struct usb_hcd *hcd)
+{
+ struct isp1760_hcd *priv = hcd_to_priv(hcd);
+ u32 hcc_params;
+
+ spin_lock_init(&priv->lock);
+
+ /*
+ * hw default: 1K periodic list heads, one per frame.
+ * periodic_size can shrink by USBCMD update if hcc_params allows.
+ */
+ priv->periodic_size = DEFAULT_I_TDPS;
+
+ /* controllers may cache some of the periodic schedule ... */
+ hcc_params = isp1760_readl(hcd->regs + HC_HCCPARAMS);
+ /* full frame cache */
+ if (HCC_ISOC_CACHE(hcc_params))
+ priv->i_thresh = 8;
+ else /* N microframes cached */
+ priv->i_thresh = 2 + HCC_ISOC_THRES(hcc_params);
+
+ return 0;
+}
+
+static int isp1760_hc_setup(struct usb_hcd *hcd)
+{
+ struct isp1760_hcd *priv = hcd_to_priv(hcd);
+ int result;
+ u32 scratch;
+
+ isp1760_writel(0xdeadbabe, hcd->regs + HC_SCRATCH_REG);
+ scratch = isp1760_readl(hcd->regs + HC_SCRATCH_REG);
+ if (scratch != 0xdeadbabe) {
+ printk(KERN_ERR "ISP1760: Scratch test failed.\n");
+ return -ENODEV;
+ }
+
+ /* pre reset */
+ isp1760_init_regs(hcd);
+
+ /* reset */
+ isp1760_writel(SW_RESET_RESET_ALL, hcd->regs + HC_RESET_REG);
+ mdelay(100);
+
+ isp1760_writel(SW_RESET_RESET_HC, hcd->regs + HC_RESET_REG);
+ mdelay(100);
+
+ result = ehci_reset(priv);
+ if (result)
+ return result;
+
+ /* Step 11 passed */
+
+ isp1760_writel(INTERRUPT_ENABLE_MASK, hcd->regs + HC_INTERRUPT_REG);
+ isp1760_writel(INTERRUPT_ENABLE_MASK, hcd->regs + HC_INTERRUPT_ENABLE);
+
+ /* ATL reset */
+ scratch = isp1760_readl(hcd->regs + HC_HW_MODE_CTRL);
+ isp1760_writel(scratch | ALL_ATX_RESET, hcd->regs + HC_HW_MODE_CTRL);
+ mdelay(10);
+ isp1760_writel(scratch, hcd->regs + HC_HW_MODE_CTRL);
+
+ isp1760_writel(PORT1_POWER | PORT1_INIT2, hcd->regs + HC_PORT1_CTRL);
+ mdelay(10);
+
+ priv->hcs_params = isp1760_readl(hcd->regs + HC_HCSPARAMS);
+
+ return priv_init(hcd);
+}
+
+static void isp1760_init_maps(struct usb_hcd *hcd)
+{
+ /*set last maps, for iso its only 1, else 32 tds bitmap*/
+ isp1760_writel(0x80000000, hcd->regs + HC_ATL_PTD_LASTPTD_REG);
+ isp1760_writel(0x80000000, hcd->regs + HC_INT_PTD_LASTPTD_REG);
+ isp1760_writel(0x00000001, hcd->regs + HC_ISO_PTD_LASTPTD_REG);
+}
+
+static void isp1760_enable_interrupts(struct usb_hcd *hcd)
+{
+ isp1760_writel(0, hcd->regs + HC_ATL_IRQ_MASK_AND_REG);
+ isp1760_writel(0, hcd->regs + HC_ATL_IRQ_MASK_OR_REG);
+ isp1760_writel(0, hcd->regs + HC_INT_IRQ_MASK_AND_REG);
+ isp1760_writel(0, hcd->regs + HC_INT_IRQ_MASK_OR_REG);
+ isp1760_writel(0, hcd->regs + HC_ISO_IRQ_MASK_AND_REG);
+ isp1760_writel(0xffffffff, hcd->regs + HC_ISO_IRQ_MASK_OR_REG);
+ /* step 23 passed */
+}
+
+static int isp1760_run(struct usb_hcd *hcd)
+{
+ struct isp1760_hcd *priv = hcd_to_priv(hcd);
+ int retval;
+ u32 temp;
+ u32 command;
+ u32 chipid;
+
+ hcd->uses_new_polling = 1;
+ hcd->poll_rh = 0;
+
+ hcd->state = HC_STATE_RUNNING;
+ isp1760_enable_interrupts(hcd);
+ temp = isp1760_readl(hcd->regs + HC_HW_MODE_CTRL);
+ temp |= FINAL_HW_CONFIG;
+ isp1760_writel(temp, hcd->regs + HC_HW_MODE_CTRL);
+
+ command = isp1760_readl(hcd->regs + HC_USBCMD);
+ command &= ~(CMD_LRESET|CMD_RESET);
+ command |= CMD_RUN;
+ isp1760_writel(command, hcd->regs + HC_USBCMD);
+
+ retval = handshake(priv, hcd->regs + HC_USBCMD, CMD_RUN, CMD_RUN,
+ 250 * 1000);
+ if (retval)
+ return retval;
+
+ /*
+ * XXX
+ * Spec says to write FLAG_CF as last config action, priv code grabs
+ * the semaphore while doing so.
+ */
+ down_write(&ehci_cf_port_reset_rwsem);
+ isp1760_writel(FLAG_CF, hcd->regs + HC_CONFIGFLAG);
+
+ retval = handshake(priv, hcd->regs + HC_CONFIGFLAG, FLAG_CF, FLAG_CF,
+ 250 * 1000);
+ up_write(&ehci_cf_port_reset_rwsem);
+ if (retval)
+ return retval;
+
+ chipid = isp1760_readl(hcd->regs + HC_CHIP_ID_REG);
+ isp1760_info(priv, "USB ISP %04x HW rev. %d started\n", chipid & 0xffff,
+ chipid >> 16);
+
+ /* PTD Register Init Part 2, Step 28 */
+ /* enable INTs */
+ isp1760_init_maps(hcd);
+
+ /* GRR this is run-once init(), being done every time the HC starts.
+ * So long as they're part of class devices, we can't do it init()
+ * since the class device isn't created that early.
+ */
+ return 0;
+}
+
+static u32 base_to_chip(u32 base)
+{
+ return ((base - 0x400) >> 3);
+}
+
+static void transform_into_atl(struct isp1760_hcd *priv, struct isp1760_qh *qh,
+ struct isp1760_qtd *qtd, struct urb *urb,
+ u32 payload, struct ptd *ptd)
+{
+ u32 dw0;
+ u32 dw1;
+ u32 dw2;
+ u32 dw3;
+ u32 maxpacket;
+ u32 multi;
+ u32 pid_code;
+ u32 rl = RL_COUNTER;
+ u32 nak = NAK_COUNTER;
+
+ /* according to 3.6.2, max packet len can not be > 0x400 */
+ maxpacket = usb_maxpacket(urb->dev, urb->pipe, usb_pipeout(urb->pipe));
+ multi = 1 + ((maxpacket >> 11) & 0x3);
+ maxpacket &= 0x7ff;
+
+ /* DW0 */
+ dw0 = PTD_VALID;
+ dw0 |= PTD_LENGTH(qtd->length);
+ dw0 |= PTD_MAXPACKET(maxpacket);
+ dw0 |= PTD_ENDPOINT(usb_pipeendpoint(urb->pipe));
+ dw1 = usb_pipeendpoint(urb->pipe) >> 1;
+
+ /* DW1 */
+ dw1 |= PTD_DEVICE_ADDR(usb_pipedevice(urb->pipe));
+
+ pid_code = qtd->packet_type;
+ dw1 |= PTD_PID_TOKEN(pid_code);
+
+ if (usb_pipebulk(urb->pipe))
+ dw1 |= PTD_TRANS_BULK;
+ else if (usb_pipeint(urb->pipe))
+ dw1 |= PTD_TRANS_INT;
+
+ if (urb->dev->speed != USB_SPEED_HIGH) {
+ /* split transaction */
+
+ dw1 |= PTD_TRANS_SPLIT;
+ if (urb->dev->speed == USB_SPEED_LOW)
+ dw1 |= PTD_SE_USB_LOSPEED;
+
+ dw1 |= PTD_PORT_NUM(urb->dev->ttport);
+ dw1 |= PTD_HUB_NUM(urb->dev->tt->hub->devnum);
+
+ /* SE bit for Split INT transfers */
+ if (usb_pipeint(urb->pipe) &&
+ (urb->dev->speed == USB_SPEED_LOW))
+ dw1 |= 2 << 16;
+
+ dw3 = 0;
+ rl = 0;
+ nak = 0;
+ } else {
+ dw0 |= PTD_MULTI(multi);
+ if (usb_pipecontrol(urb->pipe) || usb_pipebulk(urb->pipe))
+ dw3 = qh->ping;
+ else
+ dw3 = 0;
+ }
+ /* DW2 */
+ dw2 = 0;
+ dw2 |= PTD_DATA_START_ADDR(base_to_chip(payload));
+ dw2 |= PTD_RL_CNT(rl);
+ dw3 |= PTD_NAC_CNT(nak);
+
+ /* DW3 */
+ if (usb_pipecontrol(urb->pipe))
+ dw3 |= PTD_DATA_TOGGLE(qtd->toggle);
+ else
+ dw3 |= qh->toggle;
+
+
+ dw3 |= PTD_ACTIVE;
+ /* Cerr */
+ dw3 |= PTD_CERR(ERR_COUNTER);
+
+ memset(ptd, 0, sizeof(*ptd));
+
+ ptd->dw0 = cpu_to_le32(dw0);
+ ptd->dw1 = cpu_to_le32(dw1);
+ ptd->dw2 = cpu_to_le32(dw2);
+ ptd->dw3 = cpu_to_le32(dw3);
+}
+
+static void transform_add_int(struct isp1760_hcd *priv, struct isp1760_qh *qh,
+ struct isp1760_qtd *qtd, struct urb *urb,
+ u32 payload, struct ptd *ptd)
+{
+ u32 maxpacket;
+ u32 multi;
+ u32 numberofusofs;
+ u32 i;
+ u32 usofmask, usof;
+ u32 period;
+
+ maxpacket = usb_maxpacket(urb->dev, urb->pipe, usb_pipeout(urb->pipe));
+ multi = 1 + ((maxpacket >> 11) & 0x3);
+ maxpacket &= 0x7ff;
+ /* length of the data per uframe */
+ maxpacket = multi * maxpacket;
+
+ numberofusofs = urb->transfer_buffer_length / maxpacket;
+ if (urb->transfer_buffer_length % maxpacket)
+ numberofusofs += 1;
+
+ usofmask = 1;
+ usof = 0;
+ for (i = 0; i < numberofusofs; i++) {
+ usof |= usofmask;
+ usofmask <<= 1;
+ }
+
+ if (urb->dev->speed != USB_SPEED_HIGH) {
+ /* split */
+ ptd->dw5 = __constant_cpu_to_le32(0x1c);
+
+ if (qh->period >= 32)
+ period = qh->period / 2;
+ else
+ period = qh->period;
+
+ } else {
+
+ if (qh->period >= 8)
+ period = qh->period/8;
+ else
+ period = qh->period;
+
+ if (period >= 32)
+ period = 16;
+
+ if (qh->period >= 8) {
+ /* millisecond period */
+ period = (period << 3);
+ } else {
+ /* usof based tranmsfers */
+ /* minimum 4 usofs */
+ usof = 0x11;
+ }
+ }
+
+ ptd->dw2 |= cpu_to_le32(period);
+ ptd->dw4 = cpu_to_le32(usof);
+}
+
+static void transform_into_int(struct isp1760_hcd *priv, struct isp1760_qh *qh,
+ struct isp1760_qtd *qtd, struct urb *urb,
+ u32 payload, struct ptd *ptd)
+{
+ transform_into_atl(priv, qh, qtd, urb, payload, ptd);
+ transform_add_int(priv, qh, qtd, urb, payload, ptd);
+}
+
+static int qtd_fill(struct isp1760_qtd *qtd, void *databuffer, size_t len,
+ u32 token)
+{
+ int count;
+
+ qtd->data_buffer = databuffer;
+ qtd->packet_type = GET_QTD_TOKEN_TYPE(token);
+ qtd->toggle = GET_DATA_TOGGLE(token);
+
+ if (len > HC_ATL_PL_SIZE)
+ count = HC_ATL_PL_SIZE;
+ else
+ count = len;
+
+ qtd->length = count;
+ return count;
+}
+
+static int check_error(struct ptd *ptd)
+{
+ int error = 0;
+ u32 dw3;
+
+ dw3 = le32_to_cpu(ptd->dw3);
+ if (dw3 & DW3_HALT_BIT)
+ error = -EPIPE;
+
+ if (dw3 & DW3_ERROR_BIT) {
+ printk(KERN_ERR "error bit is set in DW3\n");
+ error = -EPIPE;
+ }
+
+ if (dw3 & DW3_QTD_ACTIVE) {
+ printk(KERN_ERR "transfer active bit is set DW3\n");
+ printk(KERN_ERR "nak counter: %d, rl: %d\n", (dw3 >> 19) & 0xf,
+ (le32_to_cpu(ptd->dw2) >> 25) & 0xf);
+ }
+
+ return error;
+}
+
+static void check_int_err_status(u32 dw4)
+{
+ u32 i;
+
+ dw4 >>= 8;
+
+ for (i = 0; i < 8; i++) {
+ switch (dw4 & 0x7) {
+ case INT_UNDERRUN:
+ printk(KERN_ERR "ERROR: under run , %d\n", i);
+ break;
+
+ case INT_EXACT:
+ printk(KERN_ERR "ERROR: transaction error, %d\n", i);
+ break;
+
+ case INT_BABBLE:
+ printk(KERN_ERR "ERROR: babble error, %d\n", i);
+ break;
+ }
+ dw4 >>= 3;
+ }
+}
+
+static void enqueue_one_qtd(struct isp1760_qtd *qtd, struct isp1760_hcd *priv,
+ u32 payload)
+{
+ u32 token;
+ struct usb_hcd *hcd = priv_to_hcd(priv);
+
+ token = qtd->packet_type;
+
+ if (qtd->length && (qtd->length <= HC_ATL_PL_SIZE)) {
+ switch (token) {
+ case IN_PID:
+ break;
+ case OUT_PID:
+ case SETUP_PID:
+ priv_write_copy(priv, qtd->data_buffer,
+ hcd->regs + payload,
+ qtd->length);
+ }
+ }
+}
+
+static void enqueue_one_atl_qtd(u32 atl_regs, u32 payload,
+ struct isp1760_hcd *priv, struct isp1760_qh *qh,
+ struct urb *urb, u32 slot, struct isp1760_qtd *qtd)
+{
+ struct ptd ptd;
+ struct usb_hcd *hcd = priv_to_hcd(priv);
+
+ transform_into_atl(priv, qh, qtd, urb, payload, &ptd);
+ priv_write_copy(priv, (u32 *)&ptd, hcd->regs + atl_regs, sizeof(ptd));
+ enqueue_one_qtd(qtd, priv, payload);
+
+ priv->atl_ints[slot].urb = urb;
+ priv->atl_ints[slot].qh = qh;
+ priv->atl_ints[slot].qtd = qtd;
+ priv->atl_ints[slot].data_buffer = qtd->data_buffer;
+ priv->atl_ints[slot].payload = payload;
+ qtd->status |= URB_ENQUEUED | URB_TYPE_ATL;
+ qtd->status |= slot << 16;
+}
+
+static void enqueue_one_int_qtd(u32 int_regs, u32 payload,
+ struct isp1760_hcd *priv, struct isp1760_qh *qh,
+ struct urb *urb, u32 slot, struct isp1760_qtd *qtd)
+{
+ struct ptd ptd;
+ struct usb_hcd *hcd = priv_to_hcd(priv);
+
+ transform_into_int(priv, qh, qtd, urb, payload, &ptd);
+ priv_write_copy(priv, (u32 *)&ptd, hcd->regs + int_regs, sizeof(ptd));
+ enqueue_one_qtd(qtd, priv, payload);
+
+ priv->int_ints[slot].urb = urb;
+ priv->int_ints[slot].qh = qh;
+ priv->int_ints[slot].qtd = qtd;
+ priv->int_ints[slot].data_buffer = qtd->data_buffer;
+ priv->int_ints[slot].payload = payload;
+ qtd->status |= URB_ENQUEUED | URB_TYPE_INT;
+ qtd->status |= slot << 16;
+}
+
+void enqueue_an_ATL_packet(struct usb_hcd *hcd, struct isp1760_qh *qh,
+ struct isp1760_qtd *qtd)
+{
+ struct isp1760_hcd *priv = hcd_to_priv(hcd);
+ u32 skip_map, or_map;
+ u32 queue_entry;
+ u32 slot;
+ u32 atl_regs, payload;
+ u32 buffstatus;
+
+ skip_map = isp1760_readl(hcd->regs + HC_ATL_PTD_SKIPMAP_REG);
+
+ BUG_ON(!skip_map);
+ slot = __ffs(skip_map);
+ queue_entry = 1 << slot;
+
+ atl_regs = ATL_REGS_OFFSET + slot * sizeof(struct ptd);
+
+ payload = alloc_mem(priv, qtd->length);
+
+ enqueue_one_atl_qtd(atl_regs, payload, priv, qh, qtd->urb, slot, qtd);
+
+ or_map = isp1760_readl(hcd->regs + HC_ATL_IRQ_MASK_OR_REG);
+ or_map |= queue_entry;
+ isp1760_writel(or_map, hcd->regs + HC_ATL_IRQ_MASK_OR_REG);
+
+ skip_map &= ~queue_entry;
+ isp1760_writel(skip_map, hcd->regs + HC_ATL_PTD_SKIPMAP_REG);
+
+ buffstatus = isp1760_readl(hcd->regs + HC_BUFFER_STATUS_REG);
+ buffstatus |= ATL_BUFFER;
+ isp1760_writel(buffstatus, hcd->regs + HC_BUFFER_STATUS_REG);
+}
+
+void enqueue_an_INT_packet(struct usb_hcd *hcd, struct isp1760_qh *qh,
+ struct isp1760_qtd *qtd)
+{
+ struct isp1760_hcd *priv = hcd_to_priv(hcd);
+ u32 skip_map, or_map;
+ u32 queue_entry;
+ u32 slot;
+ u32 int_regs, payload;
+ u32 buffstatus;
+
+ skip_map = isp1760_readl(hcd->regs + HC_INT_PTD_SKIPMAP_REG);
+
+ BUG_ON(!skip_map);
+ slot = __ffs(skip_map);
+ queue_entry = 1 << slot;
+
+ int_regs = INT_REGS_OFFSET + slot * sizeof(struct ptd);
+
+ payload = alloc_mem(priv, qtd->length);
+
+ enqueue_one_int_qtd(int_regs, payload, priv, qh, qtd->urb, slot, qtd);
+
+ or_map = isp1760_readl(hcd->regs + HC_INT_IRQ_MASK_OR_REG);
+ or_map |= queue_entry;
+ isp1760_writel(or_map, hcd->regs + HC_INT_IRQ_MASK_OR_REG);
+
+ skip_map &= ~queue_entry;
+ isp1760_writel(skip_map, hcd->regs + HC_INT_PTD_SKIPMAP_REG);
+
+ buffstatus = isp1760_readl(hcd->regs + HC_BUFFER_STATUS_REG);
+ buffstatus |= INT_BUFFER;
+ isp1760_writel(buffstatus, hcd->regs + HC_BUFFER_STATUS_REG);
+}
+
+static void isp1760_urb_done(struct isp1760_hcd *priv, struct urb *urb, int status)
+__releases(priv->lock)
+__acquires(priv->lock)
+{
+ if (!urb->unlinked) {
+ if (status == -EINPROGRESS)
+ status = 0;
+ }
+
+ /* complete() can reenter this HCD */
+ usb_hcd_unlink_urb_from_ep(priv_to_hcd(priv), urb);
+ spin_unlock(&priv->lock);
+ usb_hcd_giveback_urb(priv_to_hcd(priv), urb, status);
+ spin_lock(&priv->lock);
+}
+
+static void isp1760_qtd_free(struct isp1760_qtd *qtd)
+{
+ kmem_cache_free(qtd_cachep, qtd);
+}
+
+static struct isp1760_qtd *clean_this_qtd(struct isp1760_qtd *qtd)
+{
+ struct isp1760_qtd *tmp_qtd;
+
+ tmp_qtd = qtd->hw_next;
+ list_del(&qtd->qtd_list);
+ isp1760_qtd_free(qtd);
+ return tmp_qtd;
+}
+
+/*
+ * Remove this QTD from the QH list and free its memory. If this QTD
+ * isn't the last one than remove also his successor(s).
+ * Returns the QTD which is part of an new URB and should be enqueued.
+ */
+static struct isp1760_qtd *clean_up_qtdlist(struct isp1760_qtd *qtd)
+{
+ struct isp1760_qtd *tmp_qtd;
+ int last_one;
+
+ do {
+ tmp_qtd = qtd->hw_next;
+ last_one = qtd->status & URB_COMPLETE_NOTIFY;
+ list_del(&qtd->qtd_list);
+ isp1760_qtd_free(qtd);
+ qtd = tmp_qtd;
+ } while (!last_one && qtd);
+
+ return qtd;
+}
+
+static void do_atl_int(struct usb_hcd *usb_hcd)
+{
+ struct isp1760_hcd *priv = hcd_to_priv(usb_hcd);
+ u32 done_map, skip_map;
+ struct ptd ptd;
+ struct urb *urb = NULL;
+ u32 atl_regs_base;
+ u32 atl_regs;
+ u32 queue_entry;
+ u32 payload;
+ u32 length;
+ u32 or_map;
+ u32 status = -EINVAL;
+ int error;
+ struct isp1760_qtd *qtd;
+ struct isp1760_qh *qh;
+ u32 rl;
+ u32 nakcount;
+
+ done_map = isp1760_readl(usb_hcd->regs +
+ HC_ATL_PTD_DONEMAP_REG);
+ skip_map = isp1760_readl(usb_hcd->regs +
+ HC_ATL_PTD_SKIPMAP_REG);
+
+ or_map = isp1760_readl(usb_hcd->regs + HC_ATL_IRQ_MASK_OR_REG);
+ or_map &= ~done_map;
+ isp1760_writel(or_map, usb_hcd->regs + HC_ATL_IRQ_MASK_OR_REG);
+
+ atl_regs_base = ATL_REGS_OFFSET;
+ while (done_map) {
+ u32 dw1;
+ u32 dw2;
+ u32 dw3;
+
+ status = 0;
+
+ queue_entry = __ffs(done_map);
+ done_map &= ~(1 << queue_entry);
+ skip_map |= 1 << queue_entry;
+
+ atl_regs = atl_regs_base + queue_entry * sizeof(struct ptd);
+
+ urb = priv->atl_ints[queue_entry].urb;
+ qtd = priv->atl_ints[queue_entry].qtd;
+ qh = priv->atl_ints[queue_entry].qh;
+ payload = priv->atl_ints[queue_entry].payload;
+
+ if (!qh) {
+ printk(KERN_ERR "qh is 0\n");
+ continue;
+ }
+ priv_read_copy(priv, (u32 *)&ptd, usb_hcd->regs + atl_regs,
+ atl_regs, sizeof(ptd));
+
+ dw1 = le32_to_cpu(ptd.dw1);
+ dw2 = le32_to_cpu(ptd.dw2);
+ dw3 = le32_to_cpu(ptd.dw3);
+ rl = (dw2 >> 25) & 0x0f;
+ nakcount = (dw3 >> 19) & 0xf;
+
+ /* Transfer Error, *but* active and no HALT -> reload */
+ if ((dw3 & DW3_ERROR_BIT) && (dw3 & DW3_QTD_ACTIVE) &&
+ !(dw3 & DW3_HALT_BIT)) {
+
+ /* according to ppriv code, we have to
+ * reload this one if trasfered bytes != requested bytes
+ * else act like everything went smooth..
+ * XXX This just doesn't feel right and hasn't
+ * triggered so far.
+ */
+
+ length = PTD_XFERRED_LENGTH(dw3);
+ printk(KERN_ERR "Should reload now.... transfered %d "
+ "of %zu\n", length, qtd->length);
+ BUG();
+ }
+
+ if (!nakcount && (dw3 & DW3_QTD_ACTIVE)) {
+ u32 buffstatus;
+
+ /* XXX
+ * NAKs are handled in HW by the chip. Usually if the
+ * device is not able to send data fast enough.
+ * This did not trigger for a long time now.
+ */
+ printk(KERN_ERR "Reloading ptd %p/%p... qh %p readed: "
+ "%d of %zu done: %08x cur: %08x\n", qtd,
+ urb, qh, PTD_XFERRED_LENGTH(dw3),
+ qtd->length, done_map,
+ (1 << queue_entry));
+
+ /* RL counter = ERR counter */
+ dw3 &= ~(0xf << 19);
+ dw3 |= rl << 19;
+ dw3 &= ~(3 << (55 - 32));
+ dw3 |= ERR_COUNTER << (55 - 32);
+
+ /*
+ * It is not needed to write skip map back because it
+ * is unchanged. Just make sure that this entry is
+ * unskipped once it gets written to the HW.
+ */
+ skip_map &= ~(1 << queue_entry);
+ or_map = isp1760_readl(usb_hcd->regs +
+ HC_ATL_IRQ_MASK_OR_REG);
+ or_map |= 1 << queue_entry;
+ isp1760_writel(or_map, usb_hcd->regs +
+ HC_ATL_IRQ_MASK_OR_REG);
+
+ ptd.dw3 = cpu_to_le32(dw3);
+ priv_write_copy(priv, (u32 *)&ptd, usb_hcd->regs +
+ atl_regs, sizeof(ptd));
+
+ ptd.dw0 |= __constant_cpu_to_le32(PTD_VALID);
+ priv_write_copy(priv, (u32 *)&ptd, usb_hcd->regs +
+ atl_regs, sizeof(ptd));
+
+ buffstatus = isp1760_readl(usb_hcd->regs +
+ HC_BUFFER_STATUS_REG);
+ buffstatus |= ATL_BUFFER;
+ isp1760_writel(buffstatus, usb_hcd->regs +
+ HC_BUFFER_STATUS_REG);
+ continue;
+ }
+
+ error = check_error(&ptd);
+ if (error) {
+ status = error;
+ priv->atl_ints[queue_entry].qh->toggle = 0;
+ priv->atl_ints[queue_entry].qh->ping = 0;
+ urb->status = -EPIPE;
+
+#if 0
+ printk(KERN_ERR "Error in %s().\n", __func__);
+ printk(KERN_ERR "IN dw0: %08x dw1: %08x dw2: %08x "
+ "dw3: %08x dw4: %08x dw5: %08x dw6: "
+ "%08x dw7: %08x\n",
+ ptd.dw0, ptd.dw1, ptd.dw2, ptd.dw3,
+ ptd.dw4, ptd.dw5, ptd.dw6, ptd.dw7);
+#endif
+ } else {
+ if (usb_pipetype(urb->pipe) == PIPE_BULK) {
+ priv->atl_ints[queue_entry].qh->toggle = dw3 &
+ (1 << 25);
+ priv->atl_ints[queue_entry].qh->ping = dw3 &
+ (1 << 26);
+ }
+ }
+
+ length = PTD_XFERRED_LENGTH(dw3);
+ if (length) {
+ switch (DW1_GET_PID(dw1)) {
+ case IN_PID:
+ priv_read_copy(priv,
+ priv->atl_ints[queue_entry].data_buffer,
+ usb_hcd->regs + payload, payload,
+ length);
+
+ case OUT_PID:
+
+ urb->actual_length += length;
+
+ case SETUP_PID:
+ break;
+ }
+ }
+
+ priv->atl_ints[queue_entry].data_buffer = NULL;
+ priv->atl_ints[queue_entry].urb = NULL;
+ priv->atl_ints[queue_entry].qtd = NULL;
+ priv->atl_ints[queue_entry].qh = NULL;
+
+ free_mem(priv, payload);
+
+ isp1760_writel(skip_map, usb_hcd->regs +
+ HC_ATL_PTD_SKIPMAP_REG);
+
+ if (urb->status == -EPIPE) {
+ /* HALT was received */
+
+ qtd = clean_up_qtdlist(qtd);
+ isp1760_urb_done(priv, urb, urb->status);
+
+ } else if (usb_pipebulk(urb->pipe) && (length < qtd->length)) {
+ /* short BULK received */
+
+ printk(KERN_ERR "short bulk, %d instead %zu\n", length,
+ qtd->length);
+ if (urb->transfer_flags & URB_SHORT_NOT_OK) {
+ urb->status = -EREMOTEIO;
+ printk(KERN_ERR "not okey\n");
+ }
+
+ if (urb->status == -EINPROGRESS)
+ urb->status = 0;
+
+ qtd = clean_up_qtdlist(qtd);
+
+ isp1760_urb_done(priv, urb, urb->status);
+
+ } else if (qtd->status & URB_COMPLETE_NOTIFY) {
+ /* that was the last qtd of that URB */
+
+ if (urb->status == -EINPROGRESS)
+ urb->status = 0;
+
+ qtd = clean_this_qtd(qtd);
+ isp1760_urb_done(priv, urb, urb->status);
+
+ } else {
+ /* next QTD of this URB */
+
+ qtd = clean_this_qtd(qtd);
+ BUG_ON(!qtd);
+ }
+
+ if (qtd)
+ enqueue_an_ATL_packet(usb_hcd, qh, qtd);
+
+ skip_map = isp1760_readl(usb_hcd->regs +
+ HC_ATL_PTD_SKIPMAP_REG);
+ }
+}
+
+static void do_intl_int(struct usb_hcd *usb_hcd)
+{
+ struct isp1760_hcd *priv = hcd_to_priv(usb_hcd);
+ u32 done_map, skip_map;
+ struct ptd ptd;
+ struct urb *urb = NULL;
+ u32 int_regs;
+ u32 int_regs_base;
+ u32 payload;
+ u32 length;
+ u32 or_map;
+ int error;
+ u32 queue_entry;
+ struct isp1760_qtd *qtd;
+ struct isp1760_qh *qh;
+
+ done_map = isp1760_readl(usb_hcd->regs +
+ HC_INT_PTD_DONEMAP_REG);
+ skip_map = isp1760_readl(usb_hcd->regs +
+ HC_INT_PTD_SKIPMAP_REG);
+
+ or_map = isp1760_readl(usb_hcd->regs + HC_INT_IRQ_MASK_OR_REG);
+ or_map &= ~done_map;
+ isp1760_writel(or_map, usb_hcd->regs + HC_INT_IRQ_MASK_OR_REG);
+
+ int_regs_base = INT_REGS_OFFSET;
+
+ while (done_map) {
+ u32 dw1;
+ u32 dw3;
+
+ queue_entry = __ffs(done_map);
+ done_map &= ~(1 << queue_entry);
+ skip_map |= 1 << queue_entry;
+
+ int_regs = int_regs_base + queue_entry * sizeof(struct ptd);
+ urb = priv->int_ints[queue_entry].urb;
+ qtd = priv->int_ints[queue_entry].qtd;
+ qh = priv->int_ints[queue_entry].qh;
+ payload = priv->int_ints[queue_entry].payload;
+
+ if (!qh) {
+ printk(KERN_ERR "(INT) qh is 0\n");
+ continue;
+ }
+
+ priv_read_copy(priv, (u32 *)&ptd, usb_hcd->regs + int_regs,
+ int_regs, sizeof(ptd));
+ dw1 = le32_to_cpu(ptd.dw1);
+ dw3 = le32_to_cpu(ptd.dw3);
+ check_int_err_status(le32_to_cpu(ptd.dw4));
+
+ error = check_error(&ptd);
+ if (error) {
+#if 0
+ printk(KERN_ERR "Error in %s().\n", __func__);
+ printk(KERN_ERR "IN dw0: %08x dw1: %08x dw2: %08x "
+ "dw3: %08x dw4: %08x dw5: %08x dw6: "
+ "%08x dw7: %08x\n",
+ ptd.dw0, ptd.dw1, ptd.dw2, ptd.dw3,
+ ptd.dw4, ptd.dw5, ptd.dw6, ptd.dw7);
+#endif
+ urb->status = -EPIPE;
+ priv->int_ints[queue_entry].qh->toggle = 0;
+ priv->int_ints[queue_entry].qh->ping = 0;
+
+ } else {
+ priv->int_ints[queue_entry].qh->toggle =
+ dw3 & (1 << 25);
+ priv->int_ints[queue_entry].qh->ping = dw3 & (1 << 26);
+ }
+
+ if (urb->dev->speed != USB_SPEED_HIGH)
+ length = PTD_XFERRED_LENGTH_LO(dw3);
+ else
+ length = PTD_XFERRED_LENGTH(dw3);
+
+ if (length) {
+ switch (DW1_GET_PID(dw1)) {
+ case IN_PID:
+ priv_read_copy(priv,
+ priv->int_ints[queue_entry].data_buffer,
+ usb_hcd->regs + payload , payload,
+ length);
+ case OUT_PID:
+
+ urb->actual_length += length;
+
+ case SETUP_PID:
+ break;
+ }
+ }
+
+ priv->int_ints[queue_entry].data_buffer = NULL;
+ priv->int_ints[queue_entry].urb = NULL;
+ priv->int_ints[queue_entry].qtd = NULL;
+ priv->int_ints[queue_entry].qh = NULL;
+
+ isp1760_writel(skip_map, usb_hcd->regs +
+ HC_INT_PTD_SKIPMAP_REG);
+ free_mem(priv, payload);
+
+ if (urb->status == -EPIPE) {
+ /* HALT received */
+
+ qtd = clean_up_qtdlist(qtd);
+ isp1760_urb_done(priv, urb, urb->status);
+
+ } else if (qtd->status & URB_COMPLETE_NOTIFY) {
+
+ if (urb->status == -EINPROGRESS)
+ urb->status = 0;
+
+ qtd = clean_this_qtd(qtd);
+ isp1760_urb_done(priv, urb, urb->status);
+
+ } else {
+ /* next QTD of this URB */
+
+ qtd = clean_this_qtd(qtd);
+ BUG_ON(!qtd);
+ }
+
+ if (qtd)
+ enqueue_an_INT_packet(usb_hcd, qh, qtd);
+
+ skip_map = isp1760_readl(usb_hcd->regs +
+ HC_INT_PTD_SKIPMAP_REG);
+ }
+}
+
+#define max_packet(wMaxPacketSize) ((wMaxPacketSize) & 0x07ff)
+static struct isp1760_qh *qh_make(struct isp1760_hcd *priv, struct urb *urb,
+ gfp_t flags)
+{
+ struct isp1760_qh *qh;
+ int is_input, type;
+
+ qh = isp1760_qh_alloc(priv, flags);
+ if (!qh)
+ return qh;
+
+ /*
+ * init endpoint/device data for this QH
+ */
+ is_input = usb_pipein(urb->pipe);
+ type = usb_pipetype(urb->pipe);
+
+ if (type == PIPE_INTERRUPT) {
+
+ if (urb->dev->speed == USB_SPEED_HIGH) {
+
+ qh->period = urb->interval >> 3;
+ if (qh->period == 0 && urb->interval != 1) {
+ /* NOTE interval 2 or 4 uframes could work.
+ * But interval 1 scheduling is simpler, and
+ * includes high bandwidth.
+ */
+ printk(KERN_ERR "intr period %d uframes, NYET!",
+ urb->interval);
+ qh_destroy(qh);
+ return NULL;
+ }
+ } else {
+ qh->period = urb->interval;
+ }
+ }
+
+ /* support for tt scheduling, and access to toggles */
+ qh->dev = urb->dev;
+
+ if (!usb_pipecontrol(urb->pipe))
+ usb_settoggle(urb->dev, usb_pipeendpoint(urb->pipe), !is_input,
+ 1);
+ return qh;
+}
+
+/*
+ * For control/bulk/interrupt, return QH with these TDs appended.
+ * Allocates and initializes the QH if necessary.
+ * Returns null if it can't allocate a QH it needs to.
+ * If the QH has TDs (urbs) already, that's great.
+ */
+static struct isp1760_qh *qh_append_tds(struct isp1760_hcd *priv,
+ struct urb *urb, struct list_head *qtd_list, int epnum,
+ void **ptr)
+{
+ struct isp1760_qh *qh;
+ struct isp1760_qtd *qtd;
+ struct isp1760_qtd *prev_qtd;
+
+ qh = (struct isp1760_qh *)*ptr;
+ if (!qh) {
+ /* can't sleep here, we have priv->lock... */
+ qh = qh_make(priv, urb, GFP_ATOMIC);
+ if (!qh)
+ return qh;
+ *ptr = qh;
+ }
+
+ qtd = list_entry(qtd_list->next, struct isp1760_qtd,
+ qtd_list);
+ if (!list_empty(&qh->qtd_list))
+ prev_qtd = list_entry(qh->qtd_list.prev,
+ struct isp1760_qtd, qtd_list);
+ else
+ prev_qtd = NULL;
+
+ list_splice(qtd_list, qh->qtd_list.prev);
+ if (prev_qtd) {
+ BUG_ON(prev_qtd->hw_next);
+ prev_qtd->hw_next = qtd;
+ }
+
+ urb->hcpriv = qh;
+ return qh;
+}
+
+static void qtd_list_free(struct isp1760_hcd *priv, struct urb *urb,
+ struct list_head *qtd_list)
+{
+ struct list_head *entry, *temp;
+
+ list_for_each_safe(entry, temp, qtd_list) {
+ struct isp1760_qtd *qtd;
+
+ qtd = list_entry(entry, struct isp1760_qtd, qtd_list);
+ list_del(&qtd->qtd_list);
+ isp1760_qtd_free(qtd);
+ }
+}
+
+static int isp1760_prepare_enqueue(struct isp1760_hcd *priv, struct urb *urb,
+ struct list_head *qtd_list, gfp_t mem_flags, packet_enqueue *p)
+{
+ struct isp1760_qtd *qtd;
+ int epnum;
+ unsigned long flags;
+ struct isp1760_qh *qh = NULL;
+ int rc;
+ int qh_busy;
+
+ qtd = list_entry(qtd_list->next, struct isp1760_qtd, qtd_list);
+ epnum = urb->ep->desc.bEndpointAddress;
+
+ spin_lock_irqsave(&priv->lock, flags);
+ if (!test_bit(HCD_FLAG_HW_ACCESSIBLE, &priv_to_hcd(priv)->flags)) {
+ rc = -ESHUTDOWN;
+ goto done;
+ }
+ rc = usb_hcd_link_urb_to_ep(priv_to_hcd(priv), urb);
+ if (rc)
+ goto done;
+
+ qh = urb->ep->hcpriv;
+ if (qh)
+ qh_busy = !list_empty(&qh->qtd_list);
+ else
+ qh_busy = 0;
+
+ qh = qh_append_tds(priv, urb, qtd_list, epnum, &urb->ep->hcpriv);
+ if (!qh) {
+ usb_hcd_unlink_urb_from_ep(priv_to_hcd(priv), urb);
+ rc = -ENOMEM;
+ goto done;
+ }
+
+ if (!qh_busy)
+ p(priv_to_hcd(priv), qh, qtd);
+
+done:
+ spin_unlock_irqrestore(&priv->lock, flags);
+ if (!qh)
+ qtd_list_free(priv, urb, qtd_list);
+ return rc;
+}
+
+static struct isp1760_qtd *isp1760_qtd_alloc(struct isp1760_hcd *priv,
+ gfp_t flags)
+{
+ struct isp1760_qtd *qtd;
+
+ qtd = kmem_cache_zalloc(qtd_cachep, flags);
+ if (qtd)
+ INIT_LIST_HEAD(&qtd->qtd_list);
+
+ return qtd;
+}
+
+/*
+ * create a list of filled qtds for this URB; won't link into qh.
+ */
+static struct list_head *qh_urb_transaction(struct isp1760_hcd *priv,
+ struct urb *urb, struct list_head *head, gfp_t flags)
+{
+ struct isp1760_qtd *qtd, *qtd_prev;
+ void *buf;
+ int len, maxpacket;
+ int is_input;
+ u32 token;
+
+ /*
+ * URBs map to sequences of QTDs: one logical transaction
+ */
+ qtd = isp1760_qtd_alloc(priv, flags);
+ if (!qtd)
+ return NULL;
+
+ list_add_tail(&qtd->qtd_list, head);
+ qtd->urb = urb;
+ urb->status = -EINPROGRESS;
+
+ token = 0;
+ /* for split transactions, SplitXState initialized to zero */
+
+ len = urb->transfer_buffer_length;
+ is_input = usb_pipein(urb->pipe);
+ if (usb_pipecontrol(urb->pipe)) {
+ /* SETUP pid */
+ qtd_fill(qtd, urb->setup_packet,
+ sizeof(struct usb_ctrlrequest),
+ token | SETUP_PID);
+
+ /* ... and always at least one more pid */
+ token ^= DATA_TOGGLE;
+ qtd_prev = qtd;
+ qtd = isp1760_qtd_alloc(priv, flags);
+ if (!qtd)
+ goto cleanup;
+ qtd->urb = urb;
+ qtd_prev->hw_next = qtd;
+ list_add_tail(&qtd->qtd_list, head);
+
+ /* for zero length DATA stages, STATUS is always IN */
+ if (len == 0)
+ token |= IN_PID;
+ }
+
+ /*
+ * data transfer stage: buffer setup
+ */
+ buf = urb->transfer_buffer;
+
+ if (is_input)
+ token |= IN_PID;
+ else
+ token |= OUT_PID;
+
+ maxpacket = max_packet(usb_maxpacket(urb->dev, urb->pipe, !is_input));
+
+ /*
+ * buffer gets wrapped in one or more qtds;
+ * last one may be "short" (including zero len)
+ * and may serve as a control status ack
+ */
+ for (;;) {
+ int this_qtd_len;
+
+ if (!buf && len) {
+ /* XXX This looks like usb storage / SCSI bug */
+ printk(KERN_ERR "buf is null, dma is %08lx len is %d\n",
+ (long unsigned)urb->transfer_dma, len);
+ WARN_ON(1);
+ }
+
+ this_qtd_len = qtd_fill(qtd, buf, len, token);
+ len -= this_qtd_len;
+ buf += this_qtd_len;
+
+ /* qh makes control packets use qtd toggle; maybe switch it */
+ if ((maxpacket & (this_qtd_len + (maxpacket - 1))) == 0)
+ token ^= DATA_TOGGLE;
+
+ if (len <= 0)
+ break;
+
+ qtd_prev = qtd;
+ qtd = isp1760_qtd_alloc(priv, flags);
+ if (!qtd)
+ goto cleanup;
+ qtd->urb = urb;
+ qtd_prev->hw_next = qtd;
+ list_add_tail(&qtd->qtd_list, head);
+ }
+
+ /*
+ * control requests may need a terminating data "status" ack;
+ * bulk ones may need a terminating short packet (zero length).
+ */
+ if (urb->transfer_buffer_length != 0) {
+ int one_more = 0;
+
+ if (usb_pipecontrol(urb->pipe)) {
+ one_more = 1;
+ /* "in" <--> "out" */
+ token ^= IN_PID;
+ /* force DATA1 */
+ token |= DATA_TOGGLE;
+ } else if (usb_pipebulk(urb->pipe)
+ && (urb->transfer_flags & URB_ZERO_PACKET)
+ && !(urb->transfer_buffer_length % maxpacket)) {
+ one_more = 1;
+ }
+ if (one_more) {
+ qtd_prev = qtd;
+ qtd = isp1760_qtd_alloc(priv, flags);
+ if (!qtd)
+ goto cleanup;
+ qtd->urb = urb;
+ qtd_prev->hw_next = qtd;
+ list_add_tail(&qtd->qtd_list, head);
+
+ /* never any data in such packets */
+ qtd_fill(qtd, NULL, 0, token);
+ }
+ }
+
+ qtd->status = URB_COMPLETE_NOTIFY;
+ return head;
+
+cleanup:
+ qtd_list_free(priv, urb, head);
+ return NULL;
+}
+
+static int isp1760_urb_enqueue(struct usb_hcd *hcd, struct urb *urb,
+ gfp_t mem_flags)
+{
+ struct isp1760_hcd *priv = hcd_to_priv(hcd);
+ struct list_head qtd_list;
+ packet_enqueue *pe;
+
+ INIT_LIST_HEAD(&qtd_list);
+
+ switch (usb_pipetype(urb->pipe)) {
+ case PIPE_CONTROL:
+ case PIPE_BULK:
+
+ if (!qh_urb_transaction(priv, urb, &qtd_list, mem_flags))
+ return -ENOMEM;
+ pe = enqueue_an_ATL_packet;
+ break;
+
+ case PIPE_INTERRUPT:
+ if (!qh_urb_transaction(priv, urb, &qtd_list, mem_flags))
+ return -ENOMEM;
+ pe = enqueue_an_INT_packet;
+ break;
+
+ case PIPE_ISOCHRONOUS:
+ printk(KERN_ERR "PIPE_ISOCHRONOUS ain't supported\n");
+ default:
+ return -EPIPE;
+ }
+
+ isp1760_prepare_enqueue(priv, urb, &qtd_list, mem_flags, pe);
+ return 0;
+}
+
+static int isp1760_urb_dequeue(struct usb_hcd *hcd, struct urb *urb,
+ int status)
+{
+ struct isp1760_hcd *priv = hcd_to_priv(hcd);
+ struct inter_packet_info *ints;
+ u32 i;
+ u32 reg_base, or_reg, skip_reg;
+ int flags;
+ struct ptd ptd;
+
+ switch (usb_pipetype(urb->pipe)) {
+ case PIPE_ISOCHRONOUS:
+ return -EPIPE;
+ break;
+
+ case PIPE_INTERRUPT:
+ ints = priv->int_ints;
+ reg_base = INT_REGS_OFFSET;
+ or_reg = HC_INT_IRQ_MASK_OR_REG;
+ skip_reg = HC_INT_PTD_SKIPMAP_REG;
+ break;
+
+ default:
+ ints = priv->atl_ints;
+ reg_base = ATL_REGS_OFFSET;
+ or_reg = HC_ATL_IRQ_MASK_OR_REG;
+ skip_reg = HC_ATL_PTD_SKIPMAP_REG;
+ break;
+ }
+
+ memset(&ptd, 0, sizeof(ptd));
+ spin_lock_irqsave(&priv->lock, flags);
+
+ for (i = 0; i < 32; i++) {
+ if (ints->urb == urb) {
+ u32 skip_map;
+ u32 or_map;
+ struct isp1760_qtd *qtd;
+
+ skip_map = isp1760_readl(hcd->regs + skip_reg);
+ skip_map |= 1 << i;
+ isp1760_writel(skip_map, hcd->regs + skip_reg);
+
+ or_map = isp1760_readl(hcd->regs + or_reg);
+ or_map &= ~(1 << i);
+ isp1760_writel(or_map, hcd->regs + or_reg);
+
+ priv_write_copy(priv, (u32 *)&ptd, hcd->regs + reg_base
+ + i * sizeof(ptd), sizeof(ptd));
+ qtd = ints->qtd;
+
+ clean_up_qtdlist(qtd);
+
+ free_mem(priv, ints->payload);
+
+ ints->urb = NULL;
+ ints->qh = NULL;
+ ints->qtd = NULL;
+ ints->data_buffer = NULL;
+ ints->payload = 0;
+
+ isp1760_urb_done(priv, urb, status);
+ break;
+ }
+ ints++;
+ }
+
+ spin_unlock_irqrestore(&priv->lock, flags);
+ return 0;
+}
+
+static irqreturn_t isp1760_irq(struct usb_hcd *usb_hcd)
+{
+ struct isp1760_hcd *priv = hcd_to_priv(usb_hcd);
+ u32 imask;
+ irqreturn_t irqret = IRQ_NONE;
+
+ spin_lock(&priv->lock);
+
+ if (!(usb_hcd->state & HC_STATE_RUNNING))
+ goto leave;
+
+ imask = isp1760_readl(usb_hcd->regs + HC_INTERRUPT_REG);
+ if (unlikely(!imask))
+ goto leave;
+
+ isp1760_writel(imask, usb_hcd->regs + HC_INTERRUPT_REG);
+ if (imask & HC_ATL_INT)
+ do_atl_int(usb_hcd);
+
+ if (imask & HC_INTL_INT)
+ do_intl_int(usb_hcd);
+
+ irqret = IRQ_HANDLED;
+leave:
+ spin_unlock(&priv->lock);
+ return irqret;
+}
+
+static int isp1760_hub_status_data(struct usb_hcd *hcd, char *buf)
+{
+ struct isp1760_hcd *priv = hcd_to_priv(hcd);
+ u32 temp, status = 0;
+ u32 mask;
+ int retval = 1;
+ unsigned long flags;
+
+ /* if !USB_SUSPEND, root hub timers won't get shut down ... */
+ if (!HC_IS_RUNNING(hcd->state))
+ return 0;
+
+ /* init status to no-changes */
+ buf[0] = 0;
+ mask = PORT_CSC;
+
+ spin_lock_irqsave(&priv->lock, flags);
+ temp = isp1760_readl(hcd->regs + HC_PORTSC1);
+
+ if (temp & PORT_OWNER) {
+ if (temp & PORT_CSC) {
+ temp &= ~PORT_CSC;
+ isp1760_writel(temp, hcd->regs + HC_PORTSC1);
+ goto done;
+ }
+ }
+
+ /*
+ * Return status information even for ports with OWNER set.
+ * Otherwise khubd wouldn't see the disconnect event when a
+ * high-speed device is switched over to the companion
+ * controller by the user.
+ */
+
+ if ((temp & mask) != 0
+ || ((temp & PORT_RESUME) != 0
+ && time_after_eq(jiffies,
+ priv->reset_done))) {
+ buf [0] |= 1 << (0 + 1);
+ status = STS_PCD;
+ }
+ /* FIXME autosuspend idle root hubs */
+done:
+ spin_unlock_irqrestore(&priv->lock, flags);
+ return status ? retval : 0;
+}
+
+static void isp1760_hub_descriptor(struct isp1760_hcd *priv,
+ struct usb_hub_descriptor *desc)
+{
+ int ports = HCS_N_PORTS(priv->hcs_params);
+ u16 temp;
+
+ desc->bDescriptorType = 0x29;
+ /* priv 1.0, 2.3.9 says 20ms max */
+ desc->bPwrOn2PwrGood = 10;
+ desc->bHubContrCurrent = 0;
+
+ desc->bNbrPorts = ports;
+ temp = 1 + (ports / 8);
+ desc->bDescLength = 7 + 2 * temp;
+
+ /* two bitmaps: ports removable, and usb 1.0 legacy PortPwrCtrlMask */
+ memset(&desc->bitmap[0], 0, temp);
+ memset(&desc->bitmap[temp], 0xff, temp);
+
+ /* per-port overcurrent reporting */
+ temp = 0x0008;
+ if (HCS_PPC(priv->hcs_params))
+ /* per-port power control */
+ temp |= 0x0001;
+ else
+ /* no power switching */
+ temp |= 0x0002;
+ desc->wHubCharacteristics = cpu_to_le16(temp);
+}
+
+#define PORT_WAKE_BITS (PORT_WKOC_E|PORT_WKDISC_E|PORT_WKCONN_E)
+
+static int check_reset_complete(struct isp1760_hcd *priv, int index,
+ u32 __iomem *status_reg, int port_status)
+{
+ if (!(port_status & PORT_CONNECT))
+ return port_status;
+
+ /* if reset finished and it's still not enabled -- handoff */
+ if (!(port_status & PORT_PE)) {
+
+ printk(KERN_ERR "port %d full speed --> companion\n",
+ index + 1);
+
+ port_status |= PORT_OWNER;
+ port_status &= ~PORT_RWC_BITS;
+ isp1760_writel(port_status, status_reg);
+
+ } else
+ printk(KERN_ERR "port %d high speed\n", index + 1);
+
+ return port_status;
+}
+
+static int isp1760_hub_control(struct usb_hcd *hcd, u16 typeReq,
+ u16 wValue, u16 wIndex, char *buf, u16 wLength)
+{
+ struct isp1760_hcd *priv = hcd_to_priv(hcd);
+ int ports = HCS_N_PORTS(priv->hcs_params);
+ u32 __iomem *status_reg = hcd->regs + HC_PORTSC1;
+ u32 temp, status;
+ unsigned long flags;
+ int retval = 0;
+ unsigned selector;
+
+ /*
+ * FIXME: support SetPortFeatures USB_PORT_FEAT_INDICATOR.
+ * HCS_INDICATOR may say we can change LEDs to off/amber/green.
+ * (track current state ourselves) ... blink for diagnostics,
+ * power, "this is the one", etc. EHCI spec supports this.
+ */
+
+ spin_lock_irqsave(&priv->lock, flags);
+ switch (typeReq) {
+ case ClearHubFeature:
+ switch (wValue) {
+ case C_HUB_LOCAL_POWER:
+ case C_HUB_OVER_CURRENT:
+ /* no hub-wide feature/status flags */
+ break;
+ default:
+ goto error;
+ }
+ break;
+ case ClearPortFeature:
+ if (!wIndex || wIndex > ports)
+ goto error;
+ wIndex--;
+ temp = isp1760_readl(status_reg);
+
+ /*
+ * Even if OWNER is set, so the port is owned by the
+ * companion controller, khubd needs to be able to clear
+ * the port-change status bits (especially
+ * USB_PORT_FEAT_C_CONNECTION).
+ */
+
+ switch (wValue) {
+ case USB_PORT_FEAT_ENABLE:
+ isp1760_writel(temp & ~PORT_PE, status_reg);
+ break;
+ case USB_PORT_FEAT_C_ENABLE:
+ /* XXX error? */
+ break;
+ case USB_PORT_FEAT_SUSPEND:
+ if (temp & PORT_RESET)
+ goto error;
+
+ if (temp & PORT_SUSPEND) {
+ if ((temp & PORT_PE) == 0)
+ goto error;
+ /* resume signaling for 20 msec */
+ temp &= ~(PORT_RWC_BITS);
+ isp1760_writel(temp | PORT_RESUME,
+ status_reg);
+ priv->reset_done = jiffies +
+ msecs_to_jiffies(20);
+ }
+ break;
+ case USB_PORT_FEAT_C_SUSPEND:
+ /* we auto-clear this feature */
+ break;
+ case USB_PORT_FEAT_POWER:
+ if (HCS_PPC(priv->hcs_params))
+ isp1760_writel(temp & ~PORT_POWER, status_reg);
+ break;
+ case USB_PORT_FEAT_C_CONNECTION:
+ isp1760_writel(temp | PORT_CSC,
+ status_reg);
+ break;
+ case USB_PORT_FEAT_C_OVER_CURRENT:
+ /* XXX error ?*/
+ break;
+ case USB_PORT_FEAT_C_RESET:
+ /* GetPortStatus clears reset */
+ break;
+ default:
+ goto error;
+ }
+ isp1760_readl(hcd->regs + HC_USBCMD);
+ break;
+ case GetHubDescriptor:
+ isp1760_hub_descriptor(priv, (struct usb_hub_descriptor *)
+ buf);
+ break;
+ case GetHubStatus:
+ /* no hub-wide feature/status flags */
+ memset(buf, 0, 4);
+ break;
+ case GetPortStatus:
+ if (!wIndex || wIndex > ports)
+ goto error;
+ wIndex--;
+ status = 0;
+ temp = isp1760_readl(status_reg);
+
+ /* wPortChange bits */
+ if (temp & PORT_CSC)
+ status |= 1 << USB_PORT_FEAT_C_CONNECTION;
+
+
+ /* whoever resumes must GetPortStatus to complete it!! */
+ if (temp & PORT_RESUME) {
+ printk(KERN_ERR "Port resume should be skipped.\n");
+
+ /* Remote Wakeup received? */
+ if (!priv->reset_done) {
+ /* resume signaling for 20 msec */
+ priv->reset_done = jiffies
+ + msecs_to_jiffies(20);
+ /* check the port again */
+ mod_timer(&priv_to_hcd(priv)->rh_timer,
+ priv->reset_done);
+ }
+
+ /* resume completed? */
+ else if (time_after_eq(jiffies,
+ priv->reset_done)) {
+ status |= 1 << USB_PORT_FEAT_C_SUSPEND;
+ priv->reset_done = 0;
+
+ /* stop resume signaling */
+ temp = isp1760_readl(status_reg);
+ isp1760_writel(
+ temp & ~(PORT_RWC_BITS | PORT_RESUME),
+ status_reg);
+ retval = handshake(priv, status_reg,
+ PORT_RESUME, 0, 2000 /* 2msec */);
+ if (retval != 0) {
+ isp1760_err(priv,
+ "port %d resume error %d\n",
+ wIndex + 1, retval);
+ goto error;
+ }
+ temp &= ~(PORT_SUSPEND|PORT_RESUME|(3<<10));
+ }
+ }
+
+ /* whoever resets must GetPortStatus to complete it!! */
+ if ((temp & PORT_RESET)
+ && time_after_eq(jiffies,
+ priv->reset_done)) {
+ status |= 1 << USB_PORT_FEAT_C_RESET;
+ priv->reset_done = 0;
+
+ /* force reset to complete */
+ isp1760_writel(temp & ~PORT_RESET,
+ status_reg);
+ /* REVISIT: some hardware needs 550+ usec to clear
+ * this bit; seems too long to spin routinely...
+ */
+ retval = handshake(priv, status_reg,
+ PORT_RESET, 0, 750);
+ if (retval != 0) {
+ isp1760_err(priv, "port %d reset error %d\n",
+ wIndex + 1, retval);
+ goto error;
+ }
+
+ /* see what we found out */
+ temp = check_reset_complete(priv, wIndex, status_reg,
+ isp1760_readl(status_reg));
+ }
+ /*
+ * Even if OWNER is set, there's no harm letting khubd
+ * see the wPortStatus values (they should all be 0 except
+ * for PORT_POWER anyway).
+ */
+
+ if (temp & PORT_OWNER)
+ printk(KERN_ERR "Warning: PORT_OWNER is set\n");
+
+ if (temp & PORT_CONNECT) {
+ status |= 1 << USB_PORT_FEAT_CONNECTION;
+ /* status may be from integrated TT */
+ status |= ehci_port_speed(priv, temp);
+ }
+ if (temp & PORT_PE)
+ status |= 1 << USB_PORT_FEAT_ENABLE;
+ if (temp & (PORT_SUSPEND|PORT_RESUME))
+ status |= 1 << USB_PORT_FEAT_SUSPEND;
+ if (temp & PORT_RESET)
+ status |= 1 << USB_PORT_FEAT_RESET;
+ if (temp & PORT_POWER)
+ status |= 1 << USB_PORT_FEAT_POWER;
+
+ put_unaligned(cpu_to_le32(status), (__le32 *) buf);
+ break;
+ case SetHubFeature:
+ switch (wValue) {
+ case C_HUB_LOCAL_POWER:
+ case C_HUB_OVER_CURRENT:
+ /* no hub-wide feature/status flags */
+ break;
+ default:
+ goto error;
+ }
+ break;
+ case SetPortFeature:
+ selector = wIndex >> 8;
+ wIndex &= 0xff;
+ if (!wIndex || wIndex > ports)
+ goto error;
+ wIndex--;
+ temp = isp1760_readl(status_reg);
+ if (temp & PORT_OWNER)
+ break;
+
+/* temp &= ~PORT_RWC_BITS; */
+ switch (wValue) {
+ case USB_PORT_FEAT_ENABLE:
+ isp1760_writel(temp | PORT_PE, status_reg);
+ break;
+
+ case USB_PORT_FEAT_SUSPEND:
+ if ((temp & PORT_PE) == 0
+ || (temp & PORT_RESET) != 0)
+ goto error;
+
+ isp1760_writel(temp | PORT_SUSPEND, status_reg);
+ break;
+ case USB_PORT_FEAT_POWER:
+ if (HCS_PPC(priv->hcs_params))
+ isp1760_writel(temp | PORT_POWER,
+ status_reg);
+ break;
+ case USB_PORT_FEAT_RESET:
+ if (temp & PORT_RESUME)
+ goto error;
+ /* line status bits may report this as low speed,
+ * which can be fine if this root hub has a
+ * transaction translator built in.
+ */
+ if ((temp & (PORT_PE|PORT_CONNECT)) == PORT_CONNECT
+ && PORT_USB11(temp)) {
+ temp |= PORT_OWNER;
+ } else {
+ temp |= PORT_RESET;
+ temp &= ~PORT_PE;
+
+ /*
+ * caller must wait, then call GetPortStatus
+ * usb 2.0 spec says 50 ms resets on root
+ */
+ priv->reset_done = jiffies +
+ msecs_to_jiffies(50);
+ }
+ isp1760_writel(temp, status_reg);
+ break;
+ default:
+ goto error;
+ }
+ isp1760_readl(hcd->regs + HC_USBCMD);
+ break;
+
+ default:
+error:
+ /* "stall" on error */
+ retval = -EPIPE;
+ }
+ spin_unlock_irqrestore(&priv->lock, flags);
+ return retval;
+}
+
+static void isp1760_endpoint_disable(struct usb_hcd *usb_hcd,
+ struct usb_host_endpoint *ep)
+{
+ struct isp1760_hcd *priv = hcd_to_priv(usb_hcd);
+ struct isp1760_qh *qh;
+ struct isp1760_qtd *qtd;
+ u32 flags;
+
+ spin_lock_irqsave(&priv->lock, flags);
+ qh = ep->hcpriv;
+ if (!qh)
+ goto out;
+
+ ep->hcpriv = NULL;
+ do {
+ /* more than entry might get removed */
+ if (list_empty(&qh->qtd_list))
+ break;
+
+ qtd = list_first_entry(&qh->qtd_list, struct isp1760_qtd,
+ qtd_list);
+
+ if (qtd->status & URB_ENQUEUED) {
+
+ spin_unlock_irqrestore(&priv->lock, flags);
+ isp1760_urb_dequeue(usb_hcd, qtd->urb, -ECONNRESET);
+ spin_lock_irqsave(&priv->lock, flags);
+ } else {
+ struct urb *urb;
+
+ urb = qtd->urb;
+ clean_up_qtdlist(qtd);
+ isp1760_urb_done(priv, urb, -ECONNRESET);
+ }
+ } while (1);
+
+ qh_destroy(qh);
+ /* remove requests and leak them.
+ * ATL are pretty fast done, INT could take a while...
+ * The latter shoule be removed
+ */
+out:
+ spin_unlock_irqrestore(&priv->lock, flags);
+}
+
+static int isp1760_get_frame(struct usb_hcd *hcd)
+{
+ struct isp1760_hcd *priv = hcd_to_priv(hcd);
+ u32 fr;
+
+ fr = isp1760_readl(hcd->regs + HC_FRINDEX);
+ return (fr >> 3) % priv->periodic_size;
+}
+
+static void isp1760_stop(struct usb_hcd *hcd)
+{
+ struct isp1760_hcd *priv = hcd_to_priv(hcd);
+
+ isp1760_hub_control(hcd, ClearPortFeature, USB_PORT_FEAT_POWER, 1,
+ NULL, 0);
+ mdelay(20);
+
+ spin_lock_irq(&priv->lock);
+ ehci_reset(priv);
+ /* Disable IRQ */
+ isp1760_writel(HW_DATA_BUS_32BIT, hcd->regs + HC_HW_MODE_CTRL);
+ spin_unlock_irq(&priv->lock);
+
+ isp1760_writel(0, hcd->regs + HC_CONFIGFLAG);
+}
+
+static void isp1760_shutdown(struct usb_hcd *hcd)
+{
+ u32 command;
+
+ isp1760_stop(hcd);
+ isp1760_writel(HW_DATA_BUS_32BIT, hcd->regs + HC_HW_MODE_CTRL);
+
+ command = isp1760_readl(hcd->regs + HC_USBCMD);
+ command &= ~CMD_RUN;
+ isp1760_writel(command, hcd->regs + HC_USBCMD);
+}
+
+static const struct hc_driver isp1760_hc_driver = {
+ .description = "isp1760-hcd",
+ .product_desc = "NXP ISP1760 USB Host Controller",
+ .hcd_priv_size = sizeof(struct isp1760_hcd),
+ .irq = isp1760_irq,
+ .flags = HCD_MEMORY | HCD_USB2,
+ .reset = isp1760_hc_setup,
+ .start = isp1760_run,
+ .stop = isp1760_stop,
+ .shutdown = isp1760_shutdown,
+ .urb_enqueue = isp1760_urb_enqueue,
+ .urb_dequeue = isp1760_urb_dequeue,
+ .endpoint_disable = isp1760_endpoint_disable,
+ .get_frame_number = isp1760_get_frame,
+ .hub_status_data = isp1760_hub_status_data,
+ .hub_control = isp1760_hub_control,
+};
+
+int __init init_kmem_once(void)
+{
+ qtd_cachep = kmem_cache_create("isp1760_qtd",
+ sizeof(struct isp1760_qtd), 0, SLAB_TEMPORARY |
+ SLAB_MEM_SPREAD, NULL);
+
+ if (!qtd_cachep)
+ return -ENOMEM;
+
+ qh_cachep = kmem_cache_create("isp1760_qh", sizeof(struct isp1760_qh),
+ 0, SLAB_TEMPORARY | SLAB_MEM_SPREAD, NULL);
+
+ if (!qh_cachep) {
+ kmem_cache_destroy(qtd_cachep);
+ return -ENOMEM;
+ }
+
+ return 0;
+}
+
+void deinit_kmem_cache(void)
+{
+ kmem_cache_destroy(qtd_cachep);
+ kmem_cache_destroy(qh_cachep);
+}
+
+struct usb_hcd *isp1760_register(u64 res_start, u64 res_len, int irq,
+ u64 irqflags, struct device *dev, const char *busname)
+{
+ struct usb_hcd *hcd;
+ struct isp1760_hcd *priv;
+ int ret;
+
+ if (usb_disabled())
+ return ERR_PTR(-ENODEV);
+
+ /* prevent usb-core allocating DMA pages */
+ dev->dma_mask = NULL;
+
+ hcd = usb_create_hcd(&isp1760_hc_driver, dev, dev->bus_id);
+ if (!hcd)
+ return ERR_PTR(-ENOMEM);
+
+ priv = hcd_to_priv(hcd);
+ init_memory(priv);
+ hcd->regs = ioremap(res_start, res_len);
+ if (!hcd->regs) {
+ ret = -EIO;
+ goto err_put;
+ }
+
+ hcd->irq = irq;
+ hcd->rsrc_start = res_start;
+ hcd->rsrc_len = res_len;
+
+ ret = usb_add_hcd(hcd, irq, irqflags);
+ if (ret)
+ goto err_unmap;
+
+ return hcd;
+
+err_unmap:
+ iounmap(hcd->regs);
+
+err_put:
+ usb_put_hcd(hcd);
+
+ return ERR_PTR(ret);
+}
+
+MODULE_DESCRIPTION("Driver for the ISP1760 USB-controller from NXP");
+MODULE_AUTHOR("Sebastian Siewior <bigeasy@linuxtronix.de>");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/usb/host/isp1760-hcd.h b/drivers/usb/host/isp1760-hcd.h
new file mode 100644
index 00000000000..3d86d0f6b14
--- /dev/null
+++ b/drivers/usb/host/isp1760-hcd.h
@@ -0,0 +1,206 @@
+#ifndef _ISP1760_HCD_H_
+#define _ISP1760_HCD_H_
+
+/* exports for if */
+struct usb_hcd *isp1760_register(u64 res_start, u64 res_len, int irq,
+ u64 irqflags, struct device *dev, const char *busname);
+int init_kmem_once(void);
+void deinit_kmem_cache(void);
+
+/* EHCI capability registers */
+#define HC_CAPLENGTH 0x00
+#define HC_HCSPARAMS 0x04
+#define HC_HCCPARAMS 0x08
+
+/* EHCI operational registers */
+#define HC_USBCMD 0x20
+#define HC_USBSTS 0x24
+#define HC_FRINDEX 0x2c
+#define HC_CONFIGFLAG 0x60
+#define HC_PORTSC1 0x64
+#define HC_ISO_PTD_DONEMAP_REG 0x130
+#define HC_ISO_PTD_SKIPMAP_REG 0x134
+#define HC_ISO_PTD_LASTPTD_REG 0x138
+#define HC_INT_PTD_DONEMAP_REG 0x140
+#define HC_INT_PTD_SKIPMAP_REG 0x144
+#define HC_INT_PTD_LASTPTD_REG 0x148
+#define HC_ATL_PTD_DONEMAP_REG 0x150
+#define HC_ATL_PTD_SKIPMAP_REG 0x154
+#define HC_ATL_PTD_LASTPTD_REG 0x158
+
+/* Configuration Register */
+#define HC_HW_MODE_CTRL 0x300
+#define ALL_ATX_RESET (1 << 31)
+#define HW_DATA_BUS_32BIT (1 << 8)
+#define HW_DACK_POL_HIGH (1 << 6)
+#define HW_DREQ_POL_HIGH (1 << 5)
+#define HW_INTR_HIGH_ACT (1 << 2)
+#define HW_INTR_EDGE_TRIG (1 << 1)
+#define HW_GLOBAL_INTR_EN (1 << 0)
+
+#define HC_CHIP_ID_REG 0x304
+#define HC_SCRATCH_REG 0x308
+
+#define HC_RESET_REG 0x30c
+#define SW_RESET_RESET_HC (1 << 1)
+#define SW_RESET_RESET_ALL (1 << 0)
+
+#define HC_BUFFER_STATUS_REG 0x334
+#define ATL_BUFFER 0x1
+#define INT_BUFFER 0x2
+#define ISO_BUFFER 0x4
+#define BUFFER_MAP 0x7
+
+#define HC_MEMORY_REG 0x33c
+#define HC_PORT1_CTRL 0x374
+#define PORT1_POWER (3 << 3)
+#define PORT1_INIT1 (1 << 7)
+#define PORT1_INIT2 (1 << 23)
+
+/* Interrupt Register */
+#define HC_INTERRUPT_REG 0x310
+
+#define HC_INTERRUPT_ENABLE 0x314
+#define INTERRUPT_ENABLE_MASK (HC_INTL_INT | HC_ATL_INT | HC_EOT_INT)
+#define FINAL_HW_CONFIG (HW_GLOBAL_INTR_EN | HW_DATA_BUS_32BIT)
+
+#define HC_ISO_INT (1 << 9)
+#define HC_ATL_INT (1 << 8)
+#define HC_INTL_INT (1 << 7)
+#define HC_EOT_INT (1 << 3)
+#define HC_SOT_INT (1 << 1)
+
+#define HC_ISO_IRQ_MASK_OR_REG 0x318
+#define HC_INT_IRQ_MASK_OR_REG 0x31C
+#define HC_ATL_IRQ_MASK_OR_REG 0x320
+#define HC_ISO_IRQ_MASK_AND_REG 0x324
+#define HC_INT_IRQ_MASK_AND_REG 0x328
+#define HC_ATL_IRQ_MASK_AND_REG 0x32C
+
+/* Register sets */
+#define HC_BEGIN_OF_ATL 0x0c00
+#define HC_BEGIN_OF_INT 0x0800
+#define HC_BEGIN_OF_ISO 0x0400
+#define HC_BEGIN_OF_PAYLOAD 0x1000
+
+/* urb state*/
+#define DELETE_URB (0x0008)
+#define NO_TRANSFER_ACTIVE (0xffffffff)
+
+#define ATL_REGS_OFFSET (0xc00)
+#define INT_REGS_OFFSET (0x800)
+
+/* Philips Transfer Descriptor (PTD) */
+struct ptd {
+ __le32 dw0;
+ __le32 dw1;
+ __le32 dw2;
+ __le32 dw3;
+ __le32 dw4;
+ __le32 dw5;
+ __le32 dw6;
+ __le32 dw7;
+};
+
+struct inter_packet_info {
+ void *data_buffer;
+ u32 payload;
+#define PTD_FIRE_NEXT (1 << 0)
+#define PTD_URB_FINISHED (1 << 1)
+ struct urb *urb;
+ struct isp1760_qh *qh;
+ struct isp1760_qtd *qtd;
+};
+
+
+typedef void (packet_enqueue)(struct usb_hcd *hcd, struct isp1760_qh *qh,
+ struct isp1760_qtd *qtd);
+
+#define isp1760_info(priv, fmt, args...) \
+ dev_info(priv_to_hcd(priv)->self.controller, fmt, ##args)
+
+#define isp1760_err(priv, fmt, args...) \
+ dev_err(priv_to_hcd(priv)->self.controller, fmt, ##args)
+
+/* chip memory management */
+struct memory_chunk {
+ unsigned int start;
+ unsigned int size;
+ unsigned int free;
+};
+
+/*
+ * 60kb divided in:
+ * - 32 blocks @ 256 bytes
+ * - 20 blocks @ 1024 bytes
+ * - 4 blocks @ 8192 bytes
+ */
+
+#define BLOCK_1_NUM 32
+#define BLOCK_2_NUM 20
+#define BLOCK_3_NUM 4
+
+#define BLOCK_1_SIZE 256
+#define BLOCK_2_SIZE 1024
+#define BLOCK_3_SIZE 8192
+#define BLOCKS (BLOCK_1_NUM + BLOCK_2_NUM + BLOCK_3_NUM)
+#define PAYLOAD_SIZE 0xf000
+
+/* I saw if some reloads if the pointer was negative */
+#define ISP1760_NULL_POINTER (0x400)
+
+/* ATL */
+/* DW0 */
+#define PTD_VALID 1
+#define PTD_LENGTH(x) (((u32) x) << 3)
+#define PTD_MAXPACKET(x) (((u32) x) << 18)
+#define PTD_MULTI(x) (((u32) x) << 29)
+#define PTD_ENDPOINT(x) (((u32) x) << 31)
+/* DW1 */
+#define PTD_DEVICE_ADDR(x) (((u32) x) << 3)
+#define PTD_PID_TOKEN(x) (((u32) x) << 10)
+#define PTD_TRANS_BULK ((u32) 2 << 12)
+#define PTD_TRANS_INT ((u32) 3 << 12)
+#define PTD_TRANS_SPLIT ((u32) 1 << 14)
+#define PTD_SE_USB_LOSPEED ((u32) 2 << 16)
+#define PTD_PORT_NUM(x) (((u32) x) << 18)
+#define PTD_HUB_NUM(x) (((u32) x) << 25)
+#define PTD_PING(x) (((u32) x) << 26)
+/* DW2 */
+#define PTD_RL_CNT(x) (((u32) x) << 25)
+#define PTD_DATA_START_ADDR(x) (((u32) x) << 8)
+#define BASE_ADDR 0x1000
+/* DW3 */
+#define PTD_CERR(x) (((u32) x) << 23)
+#define PTD_NAC_CNT(x) (((u32) x) << 19)
+#define PTD_ACTIVE ((u32) 1 << 31)
+#define PTD_DATA_TOGGLE(x) (((u32) x) << 25)
+
+#define DW3_HALT_BIT (1 << 30)
+#define DW3_ERROR_BIT (1 << 28)
+#define DW3_QTD_ACTIVE (1 << 31)
+
+#define INT_UNDERRUN (1 << 2)
+#define INT_BABBLE (1 << 1)
+#define INT_EXACT (1 << 0)
+
+#define DW1_GET_PID(x) (((x) >> 10) & 0x3)
+#define PTD_XFERRED_LENGTH(x) ((x) & 0x7fff)
+#define PTD_XFERRED_LENGTH_LO(x) ((x) & 0x7ff)
+
+#define SETUP_PID (2)
+#define IN_PID (1)
+#define OUT_PID (0)
+#define GET_QTD_TOKEN_TYPE(x) ((x) & 0x3)
+
+#define DATA_TOGGLE (1 << 31)
+#define GET_DATA_TOGGLE(x) ((x) >> 31)
+
+/* Errata 1 */
+#define RL_COUNTER (0)
+#define NAK_COUNTER (0)
+#define ERR_COUNTER (2)
+
+#define HC_ATL_PL_SIZE (8192)
+
+#endif
diff --git a/drivers/usb/host/isp1760-if.c b/drivers/usb/host/isp1760-if.c
new file mode 100644
index 00000000000..c9db3fe9872
--- /dev/null
+++ b/drivers/usb/host/isp1760-if.c
@@ -0,0 +1,298 @@
+/*
+ * Glue code for the ISP1760 driver and bus
+ * Currently there is support for
+ * - OpenFirmware
+ * - PCI
+ *
+ * (c) 2007 Sebastian Siewior <bigeasy@linutronix.de>
+ *
+ */
+
+#include <linux/usb.h>
+#include <linux/io.h>
+
+#include "../core/hcd.h"
+#include "isp1760-hcd.h"
+
+#ifdef CONFIG_USB_ISP1760_OF
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#endif
+
+#ifdef CONFIG_USB_ISP1760_PCI
+#include <linux/pci.h>
+#endif
+
+#ifdef CONFIG_USB_ISP1760_OF
+static int of_isp1760_probe(struct of_device *dev,
+ const struct of_device_id *match)
+{
+ struct usb_hcd *hcd;
+ struct device_node *dp = dev->node;
+ struct resource *res;
+ struct resource memory;
+ struct of_irq oirq;
+ int virq;
+ u64 res_len;
+ int ret;
+
+ ret = of_address_to_resource(dp, 0, &memory);
+ if (ret)
+ return -ENXIO;
+
+ res = request_mem_region(memory.start, memory.end - memory.start + 1,
+ dev->dev.bus_id);
+ if (!res)
+ return -EBUSY;
+
+ res_len = memory.end - memory.start + 1;
+
+ if (of_irq_map_one(dp, 0, &oirq)) {
+ ret = -ENODEV;
+ goto release_reg;
+ }
+
+ virq = irq_create_of_mapping(oirq.controller, oirq.specifier,
+ oirq.size);
+
+ hcd = isp1760_register(memory.start, res_len, virq,
+ IRQF_SHARED | IRQF_DISABLED, &dev->dev, dev->dev.bus_id);
+ if (IS_ERR(hcd)) {
+ ret = PTR_ERR(hcd);
+ goto release_reg;
+ }
+
+ dev_set_drvdata(&dev->dev, hcd);
+ return ret;
+
+release_reg:
+ release_mem_region(memory.start, memory.end - memory.start + 1);
+ return ret;
+}
+
+static int of_isp1760_remove(struct of_device *dev)
+{
+ struct usb_hcd *hcd = dev_get_drvdata(&dev->dev);
+
+ dev_set_drvdata(&dev->dev, NULL);
+
+ usb_remove_hcd(hcd);
+ iounmap(hcd->regs);
+ release_mem_region(hcd->rsrc_start, hcd->rsrc_len);
+ usb_put_hcd(hcd);
+ return 0;
+}
+
+static struct of_device_id of_isp1760_match[] = {
+ {
+ .compatible = "nxp,usb-isp1760",
+ },
+ { },
+};
+MODULE_DEVICE_TABLE(of, of_isp1760_match);
+
+static struct of_platform_driver isp1760_of_driver = {
+ .name = "nxp-isp1760",
+ .match_table = of_isp1760_match,
+ .probe = of_isp1760_probe,
+ .remove = of_isp1760_remove,
+};
+#endif
+
+#ifdef CONFIG_USB_ISP1760_PCI
+static u32 nxp_pci_io_base;
+static u32 iolength;
+static u32 pci_mem_phy0;
+static u32 length;
+static u8 __iomem *chip_addr;
+static u8 __iomem *iobase;
+
+static int __devinit isp1761_pci_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ u8 latency, limit;
+ __u32 reg_data;
+ int retry_count;
+ int length;
+ int status = 1;
+ struct usb_hcd *hcd;
+
+ if (usb_disabled())
+ return -ENODEV;
+
+ if (pci_enable_device(dev) < 0)
+ return -ENODEV;
+
+ if (!dev->irq)
+ return -ENODEV;
+
+ /* Grab the PLX PCI mem maped port start address we need */
+ nxp_pci_io_base = pci_resource_start(dev, 0);
+ iolength = pci_resource_len(dev, 0);
+
+ if (!request_mem_region(nxp_pci_io_base, iolength, "ISP1761 IO MEM")) {
+ printk(KERN_ERR "request region #1\n");
+ return -EBUSY;
+ }
+
+ iobase = ioremap_nocache(nxp_pci_io_base, iolength);
+ if (!iobase) {
+ printk(KERN_ERR "ioremap #1\n");
+ release_mem_region(nxp_pci_io_base, iolength);
+ return -ENOMEM;
+ }
+ /* Grab the PLX PCI shared memory of the ISP 1761 we need */
+ pci_mem_phy0 = pci_resource_start(dev, 3);
+ length = pci_resource_len(dev, 3);
+
+ if (length < 0xffff) {
+ printk(KERN_ERR "memory length for this resource is less than "
+ "required\n");
+ release_mem_region(nxp_pci_io_base, iolength);
+ iounmap(iobase);
+ return -ENOMEM;
+ }
+
+ if (!request_mem_region(pci_mem_phy0, length, "ISP-PCI")) {
+ printk(KERN_ERR "host controller already in use\n");
+ release_mem_region(nxp_pci_io_base, iolength);
+ iounmap(iobase);
+ return -EBUSY;
+ }
+
+ /* bad pci latencies can contribute to overruns */
+ pci_read_config_byte(dev, PCI_LATENCY_TIMER, &latency);
+ if (latency) {
+ pci_read_config_byte(dev, PCI_MAX_LAT, &limit);
+ if (limit && limit < latency)
+ pci_write_config_byte(dev, PCI_LATENCY_TIMER, limit);
+ }
+
+ /* Try to check whether we can access Scratch Register of
+ * Host Controller or not. The initial PCI access is retried until
+ * local init for the PCI bridge is completed
+ */
+ retry_count = 20;
+ reg_data = 0;
+ while ((reg_data != 0xFACE) && retry_count) {
+ /*by default host is in 16bit mode, so
+ * io operations at this stage must be 16 bit
+ * */
+ writel(0xface, chip_addr + HC_SCRATCH_REG);
+ udelay(100);
+ reg_data = readl(chip_addr + HC_SCRATCH_REG);
+ retry_count--;
+ }
+
+ /* Host Controller presence is detected by writing to scratch register
+ * and reading back and checking the contents are same or not
+ */
+ if (reg_data != 0xFACE) {
+ err("scratch register mismatch %x", reg_data);
+ goto clean;
+ }
+
+ pci_set_master(dev);
+
+ status = readl(iobase + 0x68);
+ status |= 0x900;
+ writel(status, iobase + 0x68);
+
+ dev->dev.dma_mask = NULL;
+ hcd = isp1760_register(pci_mem_phy0, length, dev->irq,
+ IRQF_SHARED | IRQF_DISABLED, &dev->dev, dev->dev.bus_id);
+ pci_set_drvdata(dev, hcd);
+ if (!hcd)
+ return 0;
+clean:
+ status = -ENODEV;
+ iounmap(iobase);
+ release_mem_region(pci_mem_phy0, length);
+ release_mem_region(nxp_pci_io_base, iolength);
+ return status;
+}
+static void isp1761_pci_remove(struct pci_dev *dev)
+{
+ struct usb_hcd *hcd;
+
+ hcd = pci_get_drvdata(dev);
+
+ usb_remove_hcd(hcd);
+ iounmap(hcd->regs);
+ release_mem_region(hcd->rsrc_start, hcd->rsrc_len);
+ usb_put_hcd(hcd);
+
+ pci_disable_device(dev);
+
+ iounmap(iobase);
+ iounmap(chip_addr);
+
+ release_mem_region(nxp_pci_io_base, iolength);
+ release_mem_region(pci_mem_phy0, length);
+}
+
+static void isp1761_pci_shutdown(struct pci_dev *dev)
+{
+ printk(KERN_ERR "ips1761_pci_shutdown\n");
+}
+
+static const struct pci_device_id isp1760_plx [] = { {
+ /* handle any USB 2.0 EHCI controller */
+ PCI_DEVICE_CLASS(((PCI_CLASS_BRIDGE_OTHER << 8) | (0x06 << 16)), ~0),
+ .driver_data = 0,
+},
+{ /* end: all zeroes */ }
+};
+MODULE_DEVICE_TABLE(pci, isp1760_plx);
+
+static struct pci_driver isp1761_pci_driver = {
+ .name = "isp1760",
+ .id_table = isp1760_plx,
+ .probe = isp1761_pci_probe,
+ .remove = isp1761_pci_remove,
+ .shutdown = isp1761_pci_shutdown,
+};
+#endif
+
+static int __init isp1760_init(void)
+{
+ int ret = -ENODEV;
+
+ init_kmem_once();
+
+#ifdef CONFIG_USB_ISP1760_OF
+ ret = of_register_platform_driver(&isp1760_of_driver);
+ if (ret) {
+ deinit_kmem_cache();
+ return ret;
+ }
+#endif
+#ifdef CONFIG_USB_ISP1760_PCI
+ ret = pci_register_driver(&isp1761_pci_driver);
+ if (ret)
+ goto unreg_of;
+#endif
+ return ret;
+
+#ifdef CONFIG_USB_ISP1760_PCI
+unreg_of:
+#endif
+#ifdef CONFIG_USB_ISP1760_OF
+ of_unregister_platform_driver(&isp1760_of_driver);
+#endif
+ deinit_kmem_cache();
+ return ret;
+}
+module_init(isp1760_init);
+
+static void __exit isp1760_exit(void)
+{
+#ifdef CONFIG_USB_ISP1760_OF
+ of_unregister_platform_driver(&isp1760_of_driver);
+#endif
+#ifdef CONFIG_USB_ISP1760_PCI
+ pci_unregister_driver(&isp1761_pci_driver);
+#endif
+ deinit_kmem_cache();
+}
+module_exit(isp1760_exit);
diff --git a/drivers/usb/host/ohci-at91.c b/drivers/usb/host/ohci-at91.c
index c96db1153dc..e534f9de0f0 100644
--- a/drivers/usb/host/ohci-at91.c
+++ b/drivers/usb/host/ohci-at91.c
@@ -261,6 +261,7 @@ static const struct hc_driver ohci_at91_hc_driver = {
*/
.hub_status_data = ohci_hub_status_data,
.hub_control = ohci_hub_control,
+ .hub_irq_enable = ohci_rhsc_enable,
#ifdef CONFIG_PM
.bus_suspend = ohci_bus_suspend,
.bus_resume = ohci_bus_resume,
diff --git a/drivers/usb/host/ohci-au1xxx.c b/drivers/usb/host/ohci-au1xxx.c
index 1b9abdba920..f90fe0c7373 100644
--- a/drivers/usb/host/ohci-au1xxx.c
+++ b/drivers/usb/host/ohci-au1xxx.c
@@ -288,6 +288,7 @@ static const struct hc_driver ohci_au1xxx_hc_driver = {
*/
.hub_status_data = ohci_hub_status_data,
.hub_control = ohci_hub_control,
+ .hub_irq_enable = ohci_rhsc_enable,
#ifdef CONFIG_PM
.bus_suspend = ohci_bus_suspend,
.bus_resume = ohci_bus_resume,
diff --git a/drivers/usb/host/ohci-ep93xx.c b/drivers/usb/host/ohci-ep93xx.c
index 06aadfb0ec2..5adaf36e47d 100644
--- a/drivers/usb/host/ohci-ep93xx.c
+++ b/drivers/usb/host/ohci-ep93xx.c
@@ -135,6 +135,7 @@ static struct hc_driver ohci_ep93xx_hc_driver = {
.get_frame_number = ohci_get_frame,
.hub_status_data = ohci_hub_status_data,
.hub_control = ohci_hub_control,
+ .hub_irq_enable = ohci_rhsc_enable,
#ifdef CONFIG_PM
.bus_suspend = ohci_bus_suspend,
.bus_resume = ohci_bus_resume,
diff --git a/drivers/usb/host/ohci-hcd.c b/drivers/usb/host/ohci-hcd.c
index 33f1c1c32ed..a8160d65f32 100644
--- a/drivers/usb/host/ohci-hcd.c
+++ b/drivers/usb/host/ohci-hcd.c
@@ -1054,7 +1054,7 @@ MODULE_LICENSE ("GPL");
#ifdef CONFIG_MFD_SM501
#include "ohci-sm501.c"
-#define PLATFORM_DRIVER ohci_hcd_sm501_driver
+#define SM501_OHCI_DRIVER ohci_hcd_sm501_driver
#endif
#if !defined(PCI_DRIVER) && \
@@ -1062,6 +1062,7 @@ MODULE_LICENSE ("GPL");
!defined(OF_PLATFORM_DRIVER) && \
!defined(SA1111_DRIVER) && \
!defined(PS3_SYSTEM_BUS_DRIVER) && \
+ !defined(SM501_OHCI_DRIVER) && \
!defined(SSB_OHCI_DRIVER)
#error "missing bus glue for ohci-hcd"
#endif
@@ -1121,9 +1122,18 @@ static int __init ohci_hcd_mod_init(void)
goto error_ssb;
#endif
+#ifdef SM501_OHCI_DRIVER
+ retval = platform_driver_register(&SM501_OHCI_DRIVER);
+ if (retval < 0)
+ goto error_sm501;
+#endif
+
return retval;
/* Error path */
+#ifdef SM501_OHCI_DRIVER
+ error_sm501:
+#endif
#ifdef SSB_OHCI_DRIVER
error_ssb:
#endif
@@ -1159,6 +1169,9 @@ module_init(ohci_hcd_mod_init);
static void __exit ohci_hcd_mod_exit(void)
{
+#ifdef SM501_OHCI_DRIVER
+ platform_driver_unregister(&SM501_OHCI_DRIVER);
+#endif
#ifdef SSB_OHCI_DRIVER
ssb_driver_unregister(&SSB_OHCI_DRIVER);
#endif
diff --git a/drivers/usb/host/ohci-hub.c b/drivers/usb/host/ohci-hub.c
index 17dc2eccda8..b56739221d1 100644
--- a/drivers/usb/host/ohci-hub.c
+++ b/drivers/usb/host/ohci-hub.c
@@ -36,6 +36,18 @@
/*-------------------------------------------------------------------------*/
+/* hcd->hub_irq_enable() */
+static void ohci_rhsc_enable (struct usb_hcd *hcd)
+{
+ struct ohci_hcd *ohci = hcd_to_ohci (hcd);
+
+ spin_lock_irq(&ohci->lock);
+ if (!ohci->autostop)
+ del_timer(&hcd->rh_timer); /* Prevent next poll */
+ ohci_writel(ohci, OHCI_INTR_RHSC, &ohci->regs->intrenable);
+ spin_unlock_irq(&ohci->lock);
+}
+
#define OHCI_SCHED_ENABLES \
(OHCI_CTRL_CLE|OHCI_CTRL_BLE|OHCI_CTRL_PLE|OHCI_CTRL_IE)
@@ -362,28 +374,18 @@ static int ohci_root_hub_state_changes(struct ohci_hcd *ohci, int changed,
int any_connected)
{
int poll_rh = 1;
- int rhsc;
- rhsc = 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);
- }
-
- /* Keep on polling until we know a device is connected
- * and RHSC is enabled, or until we autostop.
- */
+ /* keep on polling until we know a device is connected
+ * and RHSC is enabled */
if (!ohci->autostop) {
if (any_connected ||
!device_may_wakeup(&ohci_to_hcd(ohci)
->self.root_hub->dev)) {
- if (rhsc)
+ if (ohci_readl(ohci, &ohci->regs->intrenable) &
+ OHCI_INTR_RHSC)
poll_rh = 0;
} else {
ohci->autostop = 1;
@@ -396,13 +398,12 @@ 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;
}
}
break;
@@ -416,12 +417,6 @@ static int ohci_root_hub_state_changes(struct ohci_hcd *ohci, int changed,
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 */
poll_rh = 0;
}
@@ -443,16 +438,12 @@ static inline int ohci_rh_resume(struct ohci_hcd *ohci)
static int ohci_root_hub_state_changes(struct ohci_hcd *ohci, int changed,
int any_connected)
{
- /* If RHSC is enabled, don't poll */
- if (ohci_readl(ohci, &ohci->regs->intrenable) & OHCI_INTR_RHSC)
- return 0;
+ int poll_rh = 1;
- /* 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;
+ /* keep on polling until RHSC is enabled */
+ if (ohci_readl(ohci, &ohci->regs->intrenable) & OHCI_INTR_RHSC)
+ poll_rh = 0;
+ return poll_rh;
}
#endif /* CONFIG_PM */
@@ -613,7 +604,7 @@ static void start_hnp(struct ohci_hcd *ohci);
static inline int root_port_reset (struct ohci_hcd *ohci, unsigned port)
{
__hc32 __iomem *portstat = &ohci->regs->roothub.portstatus [port];
- u32 temp;
+ u32 temp = 0;
u16 now = ohci_readl(ohci, &ohci->regs->fmnumber);
u16 reset_done = now + PORT_RESET_MSEC;
int limit_1 = DIV_ROUND_UP(PORT_RESET_MSEC, PORT_RESET_HW_MSEC);
diff --git a/drivers/usb/host/ohci-lh7a404.c b/drivers/usb/host/ohci-lh7a404.c
index 96d14fa1d83..13c12ed2225 100644
--- a/drivers/usb/host/ohci-lh7a404.c
+++ b/drivers/usb/host/ohci-lh7a404.c
@@ -193,6 +193,7 @@ static const struct hc_driver ohci_lh7a404_hc_driver = {
*/
.hub_status_data = ohci_hub_status_data,
.hub_control = ohci_hub_control,
+ .hub_irq_enable = ohci_rhsc_enable,
#ifdef CONFIG_PM
.bus_suspend = ohci_bus_suspend,
.bus_resume = ohci_bus_resume,
diff --git a/drivers/usb/host/ohci-omap.c b/drivers/usb/host/ohci-omap.c
index 6859fb5f1d6..3a7c24c0367 100644
--- a/drivers/usb/host/ohci-omap.c
+++ b/drivers/usb/host/ohci-omap.c
@@ -466,6 +466,7 @@ static const struct hc_driver ohci_omap_hc_driver = {
*/
.hub_status_data = ohci_hub_status_data,
.hub_control = ohci_hub_control,
+ .hub_irq_enable = ohci_rhsc_enable,
#ifdef CONFIG_PM
.bus_suspend = ohci_bus_suspend,
.bus_resume = ohci_bus_resume,
diff --git a/drivers/usb/host/ohci-pci.c b/drivers/usb/host/ohci-pci.c
index 3bf175d95a2..4696cc912e1 100644
--- a/drivers/usb/host/ohci-pci.c
+++ b/drivers/usb/host/ohci-pci.c
@@ -327,6 +327,7 @@ static const struct hc_driver ohci_pci_hc_driver = {
*/
.hub_status_data = ohci_hub_status_data,
.hub_control = ohci_hub_control,
+ .hub_irq_enable = ohci_rhsc_enable,
#ifdef CONFIG_PM
.bus_suspend = ohci_bus_suspend,
.bus_resume = ohci_bus_resume,
diff --git a/drivers/usb/host/ohci-pnx4008.c b/drivers/usb/host/ohci-pnx4008.c
index 664f07ee873..28b458f20cc 100644
--- a/drivers/usb/host/ohci-pnx4008.c
+++ b/drivers/usb/host/ohci-pnx4008.c
@@ -280,6 +280,7 @@ static const struct hc_driver ohci_pnx4008_hc_driver = {
*/
.hub_status_data = ohci_hub_status_data,
.hub_control = ohci_hub_control,
+ .hub_irq_enable = ohci_rhsc_enable,
#ifdef CONFIG_PM
.bus_suspend = ohci_bus_suspend,
.bus_resume = ohci_bus_resume,
diff --git a/drivers/usb/host/ohci-pnx8550.c b/drivers/usb/host/ohci-pnx8550.c
index 28467e288a9..605d59cba28 100644
--- a/drivers/usb/host/ohci-pnx8550.c
+++ b/drivers/usb/host/ohci-pnx8550.c
@@ -201,6 +201,7 @@ static const struct hc_driver ohci_pnx8550_hc_driver = {
*/
.hub_status_data = ohci_hub_status_data,
.hub_control = ohci_hub_control,
+ .hub_irq_enable = ohci_rhsc_enable,
#ifdef CONFIG_PM
.bus_suspend = ohci_bus_suspend,
.bus_resume = ohci_bus_resume,
diff --git a/drivers/usb/host/ohci-ppc-of.c b/drivers/usb/host/ohci-ppc-of.c
index 50e55db1363..a6725279122 100644
--- a/drivers/usb/host/ohci-ppc-of.c
+++ b/drivers/usb/host/ohci-ppc-of.c
@@ -72,6 +72,7 @@ static const struct hc_driver ohci_ppc_of_hc_driver = {
*/
.hub_status_data = ohci_hub_status_data,
.hub_control = ohci_hub_control,
+ .hub_irq_enable = ohci_rhsc_enable,
#ifdef CONFIG_PM
.bus_suspend = ohci_bus_suspend,
.bus_resume = ohci_bus_resume,
diff --git a/drivers/usb/host/ohci-ppc-soc.c b/drivers/usb/host/ohci-ppc-soc.c
index cd3398b675b..523c3012557 100644
--- a/drivers/usb/host/ohci-ppc-soc.c
+++ b/drivers/usb/host/ohci-ppc-soc.c
@@ -172,6 +172,7 @@ static const struct hc_driver ohci_ppc_soc_hc_driver = {
*/
.hub_status_data = ohci_hub_status_data,
.hub_control = ohci_hub_control,
+ .hub_irq_enable = ohci_rhsc_enable,
#ifdef CONFIG_PM
.bus_suspend = ohci_bus_suspend,
.bus_resume = ohci_bus_resume,
diff --git a/drivers/usb/host/ohci-ps3.c b/drivers/usb/host/ohci-ps3.c
index bfdeb0d22d0..c1935ae537f 100644
--- a/drivers/usb/host/ohci-ps3.c
+++ b/drivers/usb/host/ohci-ps3.c
@@ -68,6 +68,7 @@ static const struct hc_driver ps3_ohci_hc_driver = {
.get_frame_number = ohci_get_frame,
.hub_status_data = ohci_hub_status_data,
.hub_control = ohci_hub_control,
+ .hub_irq_enable = ohci_rhsc_enable,
.start_port_reset = ohci_start_port_reset,
#if defined(CONFIG_PM)
.bus_suspend = ohci_bus_suspend,
diff --git a/drivers/usb/host/ohci-pxa27x.c b/drivers/usb/host/ohci-pxa27x.c
index 70b0d4b459e..d4ee27d92be 100644
--- a/drivers/usb/host/ohci-pxa27x.c
+++ b/drivers/usb/host/ohci-pxa27x.c
@@ -298,6 +298,7 @@ static const struct hc_driver ohci_pxa27x_hc_driver = {
*/
.hub_status_data = ohci_hub_status_data,
.hub_control = ohci_hub_control,
+ .hub_irq_enable = ohci_rhsc_enable,
#ifdef CONFIG_PM
.bus_suspend = ohci_bus_suspend,
.bus_resume = ohci_bus_resume,
diff --git a/drivers/usb/host/ohci-q.c b/drivers/usb/host/ohci-q.c
index 9c9f3b59186..9b547407c93 100644
--- a/drivers/usb/host/ohci-q.c
+++ b/drivers/usb/host/ohci-q.c
@@ -952,6 +952,7 @@ rescan_this:
struct urb *urb;
urb_priv_t *urb_priv;
__hc32 savebits;
+ u32 tdINFO;
td = list_entry (entry, struct td, td_list);
urb = td->urb;
@@ -966,6 +967,17 @@ rescan_this:
savebits = *prev & ~cpu_to_hc32 (ohci, TD_MASK);
*prev = td->hwNextTD | savebits;
+ /* If this was unlinked, the TD may not have been
+ * retired ... so manually save the data toggle.
+ * The controller ignores the value we save for
+ * control and ISO endpoints.
+ */
+ tdINFO = hc32_to_cpup(ohci, &td->hwINFO);
+ if ((tdINFO & TD_T) == TD_T_DATA0)
+ ed->hwHeadP &= ~cpu_to_hc32(ohci, ED_C);
+ else if ((tdINFO & TD_T) == TD_T_DATA1)
+ ed->hwHeadP |= cpu_to_hc32(ohci, ED_C);
+
/* HC may have partly processed this TD */
td_done (ohci, urb, td);
urb_priv->td_cnt++;
diff --git a/drivers/usb/host/ohci-s3c2410.c b/drivers/usb/host/ohci-s3c2410.c
index a73d2ff322e..ead4772f0f2 100644
--- a/drivers/usb/host/ohci-s3c2410.c
+++ b/drivers/usb/host/ohci-s3c2410.c
@@ -466,6 +466,7 @@ static const struct hc_driver ohci_s3c2410_hc_driver = {
*/
.hub_status_data = ohci_s3c2410_hub_status_data,
.hub_control = ohci_s3c2410_hub_control,
+ .hub_irq_enable = ohci_rhsc_enable,
#ifdef CONFIG_PM
.bus_suspend = ohci_bus_suspend,
.bus_resume = ohci_bus_resume,
diff --git a/drivers/usb/host/ohci-sa1111.c b/drivers/usb/host/ohci-sa1111.c
index 99438c65981..0f48f2d9922 100644
--- a/drivers/usb/host/ohci-sa1111.c
+++ b/drivers/usb/host/ohci-sa1111.c
@@ -231,6 +231,7 @@ static const struct hc_driver ohci_sa1111_hc_driver = {
*/
.hub_status_data = ohci_hub_status_data,
.hub_control = ohci_hub_control,
+ .hub_irq_enable = ohci_rhsc_enable,
#ifdef CONFIG_PM
.bus_suspend = ohci_bus_suspend,
.bus_resume = ohci_bus_resume,
diff --git a/drivers/usb/host/ohci-sh.c b/drivers/usb/host/ohci-sh.c
index 60f03cc7ec4..e7ee607278f 100644
--- a/drivers/usb/host/ohci-sh.c
+++ b/drivers/usb/host/ohci-sh.c
@@ -68,6 +68,7 @@ static const struct hc_driver ohci_sh_hc_driver = {
*/
.hub_status_data = ohci_hub_status_data,
.hub_control = ohci_hub_control,
+ .hub_irq_enable = ohci_rhsc_enable,
#ifdef CONFIG_PM
.bus_suspend = ohci_bus_suspend,
.bus_resume = ohci_bus_resume,
diff --git a/drivers/usb/host/ohci-sm501.c b/drivers/usb/host/ohci-sm501.c
index 77204f001b9..e610698c6b6 100644
--- a/drivers/usb/host/ohci-sm501.c
+++ b/drivers/usb/host/ohci-sm501.c
@@ -75,6 +75,7 @@ static const struct hc_driver ohci_sm501_hc_driver = {
*/
.hub_status_data = ohci_hub_status_data,
.hub_control = ohci_hub_control,
+ .hub_irq_enable = ohci_rhsc_enable,
#ifdef CONFIG_PM
.bus_suspend = ohci_bus_suspend,
.bus_resume = ohci_bus_resume,
@@ -90,7 +91,7 @@ static int ohci_hcd_sm501_drv_probe(struct platform_device *pdev)
struct device *dev = &pdev->dev;
struct resource *res, *mem;
int retval, irq;
- struct usb_hcd *hcd = 0;
+ struct usb_hcd *hcd = NULL;
irq = retval = platform_get_irq(pdev, 0);
if (retval < 0)
diff --git a/drivers/usb/host/ohci-ssb.c b/drivers/usb/host/ohci-ssb.c
index c4265caec78..7275186db31 100644
--- a/drivers/usb/host/ohci-ssb.c
+++ b/drivers/usb/host/ohci-ssb.c
@@ -81,6 +81,7 @@ static const struct hc_driver ssb_ohci_hc_driver = {
.hub_status_data = ohci_hub_status_data,
.hub_control = ohci_hub_control,
+ .hub_irq_enable = ohci_rhsc_enable,
#ifdef CONFIG_PM
.bus_suspend = ohci_bus_suspend,
.bus_resume = ohci_bus_resume,
diff --git a/drivers/usb/host/u132-hcd.c b/drivers/usb/host/u132-hcd.c
index f29307405bb..9b6323f768b 100644
--- a/drivers/usb/host/u132-hcd.c
+++ b/drivers/usb/host/u132-hcd.c
@@ -2934,6 +2934,16 @@ static int u132_start_port_reset(struct usb_hcd *hcd, unsigned port_num)
return 0;
}
+static void u132_hub_irq_enable(struct usb_hcd *hcd)
+{
+ struct u132 *u132 = hcd_to_u132(hcd);
+ if (u132->going > 1) {
+ dev_err(&u132->platform_dev->dev, "device has been removed %d\n"
+ , u132->going);
+ } else if (u132->going > 0)
+ dev_err(&u132->platform_dev->dev, "device is being removed\n");
+}
+
#ifdef CONFIG_PM
static int u132_bus_suspend(struct usb_hcd *hcd)
@@ -2985,6 +2995,7 @@ static struct hc_driver u132_hc_driver = {
.bus_suspend = u132_bus_suspend,
.bus_resume = u132_bus_resume,
.start_port_reset = u132_start_port_reset,
+ .hub_irq_enable = u132_hub_irq_enable,
};
/*
diff --git a/drivers/usb/host/uhci-hcd.c b/drivers/usb/host/uhci-hcd.c
index d3e0d8aa398..3a7bfe7a887 100644
--- a/drivers/usb/host/uhci-hcd.c
+++ b/drivers/usb/host/uhci-hcd.c
@@ -234,7 +234,7 @@ static int resume_detect_interrupts_are_broken(struct uhci_hcd *uhci)
return 0;
}
-static int remote_wakeup_is_broken(struct uhci_hcd *uhci)
+static int global_suspend_mode_is_broken(struct uhci_hcd *uhci)
{
int port;
const char *sys_info;
@@ -261,27 +261,60 @@ __releases(uhci->lock)
__acquires(uhci->lock)
{
int auto_stop;
- int int_enable, egsm_enable;
+ int int_enable, egsm_enable, wakeup_enable;
struct usb_device *rhdev = uhci_to_hcd(uhci)->self.root_hub;
auto_stop = (new_state == UHCI_RH_AUTO_STOPPED);
dev_dbg(&rhdev->dev, "%s%s\n", __func__,
(auto_stop ? " (auto-stop)" : ""));
- /* Enable resume-detect interrupts if they work.
- * Then enter Global Suspend mode if _it_ works, still configured.
+ /* Start off by assuming Resume-Detect interrupts and EGSM work
+ * and that remote wakeups should be enabled.
*/
egsm_enable = USBCMD_EGSM;
- uhci->working_RD = 1;
+ uhci->RD_enable = 1;
int_enable = USBINTR_RESUME;
- if (remote_wakeup_is_broken(uhci))
- egsm_enable = 0;
- if (resume_detect_interrupts_are_broken(uhci) || !egsm_enable ||
+ wakeup_enable = 1;
+
+ /* In auto-stop mode wakeups must always be detected, but
+ * Resume-Detect interrupts may be prohibited. (In the absence
+ * of CONFIG_PM, they are always disallowed.)
+ */
+ if (auto_stop) {
+ if (!device_may_wakeup(&rhdev->dev))
+ int_enable = 0;
+
+ /* In bus-suspend mode wakeups may be disabled, but if they are
+ * allowed then so are Resume-Detect interrupts.
+ */
+ } else {
#ifdef CONFIG_PM
- (!auto_stop && !rhdev->do_remote_wakeup) ||
+ if (!rhdev->do_remote_wakeup)
+ wakeup_enable = 0;
#endif
- (auto_stop && !device_may_wakeup(&rhdev->dev)))
- uhci->working_RD = int_enable = 0;
+ }
+
+ /* EGSM causes the root hub to echo a 'K' signal (resume) out any
+ * port which requests a remote wakeup. According to the USB spec,
+ * every hub is supposed to do this. But if we are ignoring
+ * remote-wakeup requests anyway then there's no point to it.
+ * We also shouldn't enable EGSM if it's broken.
+ */
+ if (!wakeup_enable || global_suspend_mode_is_broken(uhci))
+ egsm_enable = 0;
+
+ /* If we're ignoring wakeup events then there's no reason to
+ * enable Resume-Detect interrupts. We also shouldn't enable
+ * them if they are broken or disallowed.
+ *
+ * This logic may lead us to enabling RD but not EGSM. The UHCI
+ * spec foolishly says that RD works only when EGSM is on, but
+ * there's no harm in enabling it anyway -- perhaps some chips
+ * will implement it!
+ */
+ if (!wakeup_enable || resume_detect_interrupts_are_broken(uhci) ||
+ !int_enable)
+ uhci->RD_enable = int_enable = 0;
outw(int_enable, uhci->io_addr + USBINTR);
outw(egsm_enable | USBCMD_CF, uhci->io_addr + USBCMD);
@@ -308,7 +341,11 @@ __acquires(uhci->lock)
uhci->rh_state = new_state;
uhci->is_stopped = UHCI_IS_STOPPED;
- uhci_to_hcd(uhci)->poll_rh = !int_enable;
+
+ /* If interrupts don't work and remote wakeup is enabled then
+ * the suspended root hub needs to be polled.
+ */
+ uhci_to_hcd(uhci)->poll_rh = (!int_enable && wakeup_enable);
uhci_scan_schedule(uhci);
uhci_fsbr_off(uhci);
@@ -344,9 +381,12 @@ __acquires(uhci->lock)
* for 20 ms.
*/
if (uhci->rh_state == UHCI_RH_SUSPENDED) {
+ unsigned egsm;
+
+ /* Keep EGSM on if it was set before */
+ egsm = inw(uhci->io_addr + USBCMD) & USBCMD_EGSM;
uhci->rh_state = UHCI_RH_RESUMING;
- outw(USBCMD_FGR | USBCMD_EGSM | USBCMD_CF,
- uhci->io_addr + USBCMD);
+ outw(USBCMD_FGR | USBCMD_CF | egsm, uhci->io_addr + USBCMD);
spin_unlock_irq(&uhci->lock);
msleep(20);
spin_lock_irq(&uhci->lock);
@@ -801,8 +841,10 @@ static int uhci_pci_resume(struct usb_hcd *hcd)
spin_unlock_irq(&uhci->lock);
- if (!uhci->working_RD) {
- /* Suspended root hub needs to be polled */
+ /* If interrupts don't work and remote wakeup is enabled then
+ * the suspended root hub needs to be polled.
+ */
+ if (!uhci->RD_enable && hcd->self.root_hub->do_remote_wakeup) {
hcd->poll_rh = 1;
usb_hcd_poll_rh_status(hcd);
}
diff --git a/drivers/usb/host/uhci-hcd.h b/drivers/usb/host/uhci-hcd.h
index 340d6ed3e6e..7d01c5677f9 100644
--- a/drivers/usb/host/uhci-hcd.h
+++ b/drivers/usb/host/uhci-hcd.h
@@ -400,8 +400,9 @@ struct uhci_hcd {
unsigned int scan_in_progress:1; /* Schedule scan is running */
unsigned int need_rescan:1; /* Redo the schedule scan */
unsigned int dead:1; /* Controller has died */
- unsigned int working_RD:1; /* Suspended root hub doesn't
- need to be polled */
+ unsigned int RD_enable:1; /* Suspended root hub with
+ Resume-Detect interrupts
+ enabled */
unsigned int is_initialized:1; /* Data structure is usable */
unsigned int fsbr_is_on:1; /* FSBR is turned on */
unsigned int fsbr_is_wanted:1; /* Does any URB want FSBR? */