diff options
author | David Woodhouse <dwmw2@infradead.org> | 2006-10-01 17:55:53 +0100 |
---|---|---|
committer | David Woodhouse <dwmw2@infradead.org> | 2006-10-01 17:55:53 +0100 |
commit | 8a84fc15ae5cafcc366dd85cf8e1ab2040679abc (patch) | |
tree | 5d8dce194c9667fa92e9ec9f545cec867a9a1e0d /drivers/usb/host | |
parent | 28b79ff9661b22e4c41c0d00d4ab8503e810f13d (diff) | |
parent | 82965addad66fce61a92c5f03104ea90b0b87124 (diff) |
Merge branch 'master' of git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6
Manually resolve conflict in include/mtd/Kbuild
Signed-off-by: David Woodhouse <dwmw2@infradead.org>
Diffstat (limited to 'drivers/usb/host')
35 files changed, 4332 insertions, 334 deletions
diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig index b93d71d28db..cf10cbc98f8 100644 --- a/drivers/usb/host/Kconfig +++ b/drivers/usb/host/Kconfig @@ -83,6 +83,7 @@ config USB_OHCI_HCD tristate "OHCI HCD support" depends on USB && USB_ARCH_HAS_OHCI select ISP1301_OMAP if MACH_OMAP_H2 || MACH_OMAP_H3 + select I2C if ARCH_PNX4008 ---help--- The Open Host Controller Interface (OHCI) is a standard for accessing USB 1.1 host controller hardware. It does more in hardware than Intel's @@ -141,6 +142,34 @@ config USB_UHCI_HCD To compile this driver as a module, choose M here: the module will be called uhci-hcd. +config USB_U132_HCD + tristate "Elan U132 Adapter Host Controller" + depends on USB && USB_FTDI_ELAN + default M + help + The U132 adapter is a USB to CardBus adapter specifically designed + for PC cards that contain an OHCI host controller. Typical PC cards + are the Orange Mobile 3G Option GlobeTrotter Fusion card. The U132 + adapter will *NOT* work with PC cards that do not contain an OHCI + controller. + + For those PC cards that contain multiple OHCI controllers only ther + first one is used. + + The driver consists of two modules, the "ftdi-elan" module is a + USB client driver that interfaces to the FTDI chip within ELAN's + USB-to-PCMCIA adapter, and this "u132-hcd" module is a USB host + controller driver that talks to the OHCI controller within the + CardBus cards that are inserted in the U132 adapter. + + This driver has been tested with a CardBus OHCI USB adapter, and + worked with a USB PEN Drive inserted into the first USB port of + the PCCARD. A rather pointless thing to do, but useful for testing. + + It is safe to say M here. + + See also <http://www.elandigitalsystems.com/support/ufaq/u132linux.php> + config USB_SL811_HCD tristate "SL811HS HCD support" depends on USB diff --git a/drivers/usb/host/Makefile b/drivers/usb/host/Makefile index e3020f4b17b..a2e58c86849 100644 --- a/drivers/usb/host/Makefile +++ b/drivers/usb/host/Makefile @@ -14,4 +14,5 @@ obj-$(CONFIG_USB_OHCI_HCD) += ohci-hcd.o obj-$(CONFIG_USB_UHCI_HCD) += uhci-hcd.o 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_ETRAX_ARCH_V10) += hc_crisv10.o diff --git a/drivers/usb/host/ehci-au1xxx.c b/drivers/usb/host/ehci-au1xxx.c index 26ed757d22a..5d1b12aad77 100644 --- a/drivers/usb/host/ehci-au1xxx.c +++ b/drivers/usb/host/ehci-au1xxx.c @@ -200,6 +200,7 @@ static const struct hc_driver ehci_au1xxx_hc_driver = { .reset = ehci_init, .start = ehci_run, .stop = ehci_stop, + .shutdown = ehci_shutdown, /* * managing i/o requests and associated device resources @@ -268,6 +269,7 @@ MODULE_ALIAS("au1xxx-ehci"); static struct platform_driver ehci_hcd_au1xxx_driver = { .probe = ehci_hcd_au1xxx_drv_probe, .remove = ehci_hcd_au1xxx_drv_remove, + .shutdown = usb_hcd_platform_shutdown, /*.suspend = ehci_hcd_au1xxx_drv_suspend, */ /*.resume = ehci_hcd_au1xxx_drv_resume, */ .driver = { diff --git a/drivers/usb/host/ehci-dbg.c b/drivers/usb/host/ehci-dbg.c index 65ac9fef3a7..23b95b2bfe1 100644 --- a/drivers/usb/host/ehci-dbg.c +++ b/drivers/usb/host/ehci-dbg.c @@ -1,6 +1,6 @@ /* * Copyright (c) 2001-2002 by David Brownell - * + * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or (at your @@ -65,7 +65,7 @@ static void dbg_hcs_params (struct ehci_hcd *ehci, char *label) for (i = 0; i < HCS_N_PORTS (params); i++) { // FIXME MIPS won't readb() ... byte = readb (&ehci->caps->portroute[(i>>1)]); - sprintf(tmp, "%d ", + sprintf(tmp, "%d ", ((i & 0x1) ? ((byte)&0xf) : ((byte>>4)&0xf))); strcat(buf, tmp); } @@ -141,12 +141,12 @@ dbg_qh (const char *label, struct ehci_hcd *ehci, struct ehci_qh *qh) } static void __attribute__((__unused__)) -dbg_itd (const char *label, struct ehci_hcd *ehci, struct ehci_itd *itd) +dbg_itd (const char *label, struct ehci_hcd *ehci, struct ehci_itd *itd) { ehci_dbg (ehci, "%s [%d] itd %p, next %08x, urb %p\n", label, itd->frame, itd, le32_to_cpu(itd->hw_next), itd->urb); ehci_dbg (ehci, - " trans: %08x %08x %08x %08x %08x %08x %08x %08x\n", + " trans: %08x %08x %08x %08x %08x %08x %08x %08x\n", le32_to_cpu(itd->hw_transaction[0]), le32_to_cpu(itd->hw_transaction[1]), le32_to_cpu(itd->hw_transaction[2]), @@ -156,7 +156,7 @@ dbg_itd (const char *label, struct ehci_hcd *ehci, struct ehci_itd *itd) le32_to_cpu(itd->hw_transaction[6]), le32_to_cpu(itd->hw_transaction[7])); ehci_dbg (ehci, - " buf: %08x %08x %08x %08x %08x %08x %08x\n", + " buf: %08x %08x %08x %08x %08x %08x %08x\n", le32_to_cpu(itd->hw_bufp[0]), le32_to_cpu(itd->hw_bufp[1]), le32_to_cpu(itd->hw_bufp[2]), @@ -171,12 +171,12 @@ dbg_itd (const char *label, struct ehci_hcd *ehci, struct ehci_itd *itd) } static void __attribute__((__unused__)) -dbg_sitd (const char *label, struct ehci_hcd *ehci, struct ehci_sitd *sitd) +dbg_sitd (const char *label, struct ehci_hcd *ehci, struct ehci_sitd *sitd) { ehci_dbg (ehci, "%s [%d] sitd %p, next %08x, urb %p\n", label, sitd->frame, sitd, le32_to_cpu(sitd->hw_next), sitd->urb); ehci_dbg (ehci, - " addr %08x sched %04x result %08x buf %08x %08x\n", + " addr %08x sched %04x result %08x buf %08x %08x\n", le32_to_cpu(sitd->hw_fullspeed_ep), le32_to_cpu(sitd->hw_uframe), le32_to_cpu(sitd->hw_results), @@ -451,7 +451,7 @@ show_async (struct class_device *class_dev, char *buf) *buf = 0; bus = class_get_devdata(class_dev); - hcd = bus->hcpriv; + hcd = bus_to_hcd(bus); ehci = hcd_to_ehci (hcd); next = buf; size = PAGE_SIZE; @@ -497,7 +497,7 @@ show_periodic (struct class_device *class_dev, char *buf) seen_count = 0; bus = class_get_devdata(class_dev); - hcd = bus->hcpriv; + hcd = bus_to_hcd(bus); ehci = hcd_to_ehci (hcd); next = buf; size = PAGE_SIZE; @@ -634,7 +634,7 @@ show_registers (struct class_device *class_dev, char *buf) static char label [] = ""; bus = class_get_devdata(class_dev); - hcd = bus->hcpriv; + hcd = bus_to_hcd(bus); ehci = hcd_to_ehci (hcd); next = buf; size = PAGE_SIZE; @@ -754,9 +754,7 @@ show_registers (struct class_device *class_dev, char *buf) } if (ehci->reclaim) { - temp = scnprintf (next, size, "reclaim qh %p%s\n", - ehci->reclaim, - ehci->reclaim_ready ? " ready" : ""); + temp = scnprintf (next, size, "reclaim qh %p\n", ehci->reclaim); size -= temp; next += temp; } @@ -785,10 +783,11 @@ static CLASS_DEVICE_ATTR (registers, S_IRUGO, show_registers, NULL); static inline void create_debug_files (struct ehci_hcd *ehci) { struct class_device *cldev = ehci_to_hcd(ehci)->self.class_dev; + int retval; - class_device_create_file(cldev, &class_device_attr_async); - class_device_create_file(cldev, &class_device_attr_periodic); - class_device_create_file(cldev, &class_device_attr_registers); + retval = class_device_create_file(cldev, &class_device_attr_async); + retval = class_device_create_file(cldev, &class_device_attr_periodic); + retval = class_device_create_file(cldev, &class_device_attr_registers); } static inline void remove_debug_files (struct ehci_hcd *ehci) diff --git a/drivers/usb/host/ehci-fsl.c b/drivers/usb/host/ehci-fsl.c index d030516edfb..1a915e982c1 100644 --- a/drivers/usb/host/ehci-fsl.c +++ b/drivers/usb/host/ehci-fsl.c @@ -285,6 +285,7 @@ static const struct hc_driver ehci_fsl_hc_driver = { .resume = ehci_bus_resume, #endif .stop = ehci_stop, + .shutdown = ehci_shutdown, /* * managing i/o requests and associated device resources @@ -329,6 +330,7 @@ MODULE_ALIAS("fsl-ehci"); static struct platform_driver ehci_fsl_driver = { .probe = ehci_fsl_drv_probe, .remove = ehci_fsl_drv_remove, + .shutdown = usb_hcd_platform_shutdown, .driver = { .name = "fsl-ehci", }, diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c index d63177a8eae..5ac91859113 100644 --- a/drivers/usb/host/ehci-hcd.c +++ b/drivers/usb/host/ehci-hcd.c @@ -1,6 +1,6 @@ /* * Copyright (c) 2000-2004 by David Brownell - * + * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or (at your @@ -70,7 +70,7 @@ * 2002-08-06 Handling for bulk and interrupt transfers is mostly shared; * only scheduling is different, no arbitrary limitations. * 2002-07-25 Sanity check PCI reads, mostly for better cardbus support, - * clean up HC run state handshaking. + * clean up HC run state handshaking. * 2002-05-24 Preliminary FS/LS interrupts, using scheduling shortcuts * 2002-05-11 Clear TT errors for FS/LS ctrl/bulk. Fill in some other * missing pieces: enabling 64bit dma, handoff from BIOS/SMM. @@ -111,7 +111,7 @@ static const char hcd_name [] = "ehci_hcd"; #define EHCI_TUNE_MULT_TT 1 #define EHCI_TUNE_FLS 2 /* (small) 256 frame schedule */ -#define EHCI_IAA_JIFFIES (HZ/100) /* arbitrary; ~10 msec */ +#define EHCI_IAA_MSECS 10 /* arbitrary */ #define EHCI_IO_JIFFIES (HZ/10) /* io watchdog > irq_thresh */ #define EHCI_ASYNC_JIFFIES (HZ/20) /* async idle timeout */ #define EHCI_SHRINK_JIFFIES (HZ/200) /* async qh unlink delay */ @@ -254,6 +254,7 @@ static void ehci_quiesce (struct ehci_hcd *ehci) /*-------------------------------------------------------------------------*/ +static void end_unlink_async (struct ehci_hcd *ehci, struct pt_regs *regs); static void ehci_work(struct ehci_hcd *ehci, struct pt_regs *regs); #include "ehci-hub.c" @@ -263,28 +264,39 @@ static void ehci_work(struct ehci_hcd *ehci, struct pt_regs *regs); /*-------------------------------------------------------------------------*/ -static void ehci_watchdog (unsigned long param) +static void ehci_iaa_watchdog (unsigned long param) { struct ehci_hcd *ehci = (struct ehci_hcd *) param; unsigned long flags; + u32 status; spin_lock_irqsave (&ehci->lock, flags); + WARN_ON(!ehci->reclaim); - /* lost IAA irqs wedge things badly; seen with a vt8235 */ + /* lost IAA irqs wedge things badly; seen first with a vt8235 */ if (ehci->reclaim) { - u32 status = readl (&ehci->regs->status); - + status = readl (&ehci->regs->status); if (status & STS_IAA) { ehci_vdbg (ehci, "lost IAA\n"); COUNT (ehci->stats.lost_iaa); writel (STS_IAA, &ehci->regs->status); - ehci->reclaim_ready = 1; + end_unlink_async (ehci, NULL); } } - /* stop async processing after it's idled a bit */ + spin_unlock_irqrestore (&ehci->lock, flags); +} + +static void ehci_watchdog (unsigned long param) +{ + struct ehci_hcd *ehci = (struct ehci_hcd *) param; + unsigned long flags; + + spin_lock_irqsave (&ehci->lock, flags); + + /* stop async processing after it's idled a bit */ if (test_bit (TIMER_ASYNC_OFF, &ehci->actions)) - start_unlink_async (ehci, ehci->async); + start_unlink_async (ehci, ehci->async); /* ehci could run by timer, without IRQs ... */ ehci_work (ehci, NULL); @@ -292,21 +304,20 @@ static void ehci_watchdog (unsigned long param) spin_unlock_irqrestore (&ehci->lock, flags); } -/* Reboot notifiers kick in for silicon on any bus (not just pci, etc). +/* ehci_shutdown kick in for silicon on any bus (not just pci, etc). * This forcibly disables dma and IRQs, helping kexec and other cases * where the next system software may expect clean state. */ -static int -ehci_reboot (struct notifier_block *self, unsigned long code, void *null) +static void +ehci_shutdown (struct usb_hcd *hcd) { - struct ehci_hcd *ehci; + struct ehci_hcd *ehci; - ehci = container_of (self, struct ehci_hcd, reboot_notifier); + ehci = hcd_to_ehci (hcd); (void) ehci_halt (ehci); /* make BIOS/etc use companion controller during reboot */ writel (0, &ehci->regs->configured_flag); - return 0; } static void ehci_port_power (struct ehci_hcd *ehci, int is_on) @@ -334,8 +345,6 @@ static void ehci_port_power (struct ehci_hcd *ehci, int is_on) static void ehci_work (struct ehci_hcd *ehci, struct pt_regs *regs) { timer_action_done (ehci, TIMER_IO_WATCHDOG); - if (ehci->reclaim_ready) - end_unlink_async (ehci, regs); /* another CPU may drop ehci->lock during a schedule scan while * it reports urb completions. this flag guards against bogus @@ -370,6 +379,7 @@ static void ehci_stop (struct usb_hcd *hcd) /* no more interrupts ... */ del_timer_sync (&ehci->watchdog); + del_timer_sync (&ehci->iaa_watchdog); spin_lock_irq(&ehci->lock); if (HC_IS_RUNNING (hcd->state)) @@ -381,7 +391,6 @@ static void ehci_stop (struct usb_hcd *hcd) /* let companion controllers work when we aren't */ writel (0, &ehci->regs->configured_flag); - unregister_reboot_notifier (&ehci->reboot_notifier); remove_debug_files (ehci); @@ -417,6 +426,10 @@ static int ehci_init(struct usb_hcd *hcd) ehci->watchdog.function = ehci_watchdog; ehci->watchdog.data = (unsigned long) ehci; + init_timer(&ehci->iaa_watchdog); + ehci->iaa_watchdog.function = ehci_iaa_watchdog; + ehci->iaa_watchdog.data = (unsigned long) ehci; + /* * hw default: 1K periodic list heads, one per frame. * periodic_size can shrink by USBCMD update if hcc_params allows. @@ -427,13 +440,12 @@ static int ehci_init(struct usb_hcd *hcd) /* controllers may cache some of the periodic schedule ... */ hcc_params = readl(&ehci->caps->hcc_params); - if (HCC_ISOC_CACHE(hcc_params)) // full frame cache + if (HCC_ISOC_CACHE(hcc_params)) // full frame cache ehci->i_thresh = 8; else // N microframes cached ehci->i_thresh = 2 + HCC_ISOC_THRES(hcc_params); ehci->reclaim = NULL; - ehci->reclaim_ready = 0; ehci->next_uframe = -1; /* @@ -483,9 +495,6 @@ static int ehci_init(struct usb_hcd *hcd) } ehci->command = temp; - ehci->reboot_notifier.notifier_call = ehci_reboot; - register_reboot_notifier(&ehci->reboot_notifier); - return 0; } @@ -499,7 +508,6 @@ static int ehci_run (struct usb_hcd *hcd) /* EHCI spec section 4.1 */ if ((retval = ehci_reset(ehci)) != 0) { - unregister_reboot_notifier(&ehci->reboot_notifier); ehci_mem_cleanup(ehci); return retval; } @@ -611,7 +619,7 @@ static irqreturn_t ehci_irq (struct usb_hcd *hcd, struct pt_regs *regs) /* complete the unlinking of some qh [4.15.2.3] */ if (status & STS_IAA) { COUNT (ehci->stats.reclaim); - ehci->reclaim_ready = 1; + end_unlink_async (ehci, regs); bh = 1; } @@ -715,10 +723,14 @@ static int ehci_urb_enqueue ( static void unlink_async (struct ehci_hcd *ehci, struct ehci_qh *qh) { - /* if we need to use IAA and it's busy, defer */ - if (qh->qh_state == QH_STATE_LINKED - && ehci->reclaim - && HC_IS_RUNNING (ehci_to_hcd(ehci)->state)) { + // BUG_ON(qh->qh_state != QH_STATE_LINKED); + + /* failfast */ + if (!HC_IS_RUNNING (ehci_to_hcd(ehci)->state)) + end_unlink_async (ehci, NULL); + + /* defer till later if busy */ + else if (ehci->reclaim) { struct ehci_qh *last; for (last = ehci->reclaim; @@ -728,12 +740,8 @@ static void unlink_async (struct ehci_hcd *ehci, struct ehci_qh *qh) qh->qh_state = QH_STATE_UNLINK_WAIT; last->reclaim = qh; - /* bypass IAA if the hc can't care */ - } else if (!HC_IS_RUNNING (ehci_to_hcd(ehci)->state) && ehci->reclaim) - end_unlink_async (ehci, NULL); - - /* something else might have unlinked the qh by now */ - if (qh->qh_state == QH_STATE_LINKED) + /* start IAA cycle */ + } else start_unlink_async (ehci, qh); } @@ -755,7 +763,19 @@ static int ehci_urb_dequeue (struct usb_hcd *hcd, struct urb *urb) qh = (struct ehci_qh *) urb->hcpriv; if (!qh) break; - unlink_async (ehci, qh); + switch (qh->qh_state) { + case QH_STATE_LINKED: + case QH_STATE_COMPLETING: + unlink_async (ehci, qh); + break; + case QH_STATE_UNLINK: + case QH_STATE_UNLINK_WAIT: + /* already started */ + break; + case QH_STATE_IDLE: + WARN_ON(1); + break; + } break; case PIPE_INTERRUPT: @@ -847,6 +867,7 @@ rescan: unlink_async (ehci, qh); /* FALL THROUGH */ case QH_STATE_UNLINK: /* wait for hw to finish? */ + case QH_STATE_UNLINK_WAIT: idle_timeout: spin_unlock_irqrestore (&ehci->lock, flags); schedule_timeout_uninterruptible(1); diff --git a/drivers/usb/host/ehci-hub.c b/drivers/usb/host/ehci-hub.c index d03e3cad5ca..b2ee13c5851 100644 --- a/drivers/usb/host/ehci-hub.c +++ b/drivers/usb/host/ehci-hub.c @@ -1,6 +1,6 @@ /* * Copyright (C) 2001-2004 by David Brownell - * + * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or (at your @@ -48,7 +48,7 @@ static int ehci_bus_suspend (struct usb_hcd *hcd) } ehci->command = readl (&ehci->regs->command); if (ehci->reclaim) - ehci->reclaim_ready = 1; + end_unlink_async (ehci, NULL); ehci_work(ehci, NULL); /* suspend any active/unsuspended ports, maybe allow wakeup */ @@ -103,10 +103,10 @@ static int ehci_bus_resume (struct usb_hcd *hcd) /* re-init operational registers in case we lost power */ if (readl (&ehci->regs->intr_enable) == 0) { - /* at least some APM implementations will try to deliver + /* at least some APM implementations will try to deliver * IRQs right away, so delay them until we're ready. - */ - intr_enable = 1; + */ + intr_enable = 1; writel (0, &ehci->regs->segment); writel (ehci->periodic_dma, &ehci->regs->frame_list); writel ((u32)ehci->async->qh_dma, &ehci->regs->async_next); @@ -232,7 +232,7 @@ ehci_hub_status_data (struct usb_hcd *hcd, char *buf) buf [1] = 0; retval++; } - + /* no hub change reports (bit 0) for now (power, ...) */ /* port N changes (bit N)? */ @@ -304,7 +304,7 @@ ehci_hub_descriptor ( /*-------------------------------------------------------------------------*/ -#define PORT_WAKE_BITS (PORT_WKOC_E|PORT_WKDISC_E|PORT_WKCONN_E) +#define PORT_WAKE_BITS (PORT_WKOC_E|PORT_WKDISC_E|PORT_WKCONN_E) static int ehci_hub_control ( struct usb_hcd *hcd, diff --git a/drivers/usb/host/ehci-mem.c b/drivers/usb/host/ehci-mem.c index 766061e0260..a8ba2e1497a 100644 --- a/drivers/usb/host/ehci-mem.c +++ b/drivers/usb/host/ehci-mem.c @@ -1,6 +1,6 @@ /* * Copyright (c) 2001 by David Brownell - * + * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or (at your @@ -25,7 +25,7 @@ * - data used only by the HCD ... kmalloc is fine * - async and periodic schedules, shared by HC and HCD ... these * need to use dma_pool or dma_alloc_coherent - * - driver buffers, read/written by HC ... single shot DMA mapped + * - driver buffers, read/written by HC ... single shot DMA mapped * * There's also PCI "register" data, which is memory mapped. * No memory seen by this driver is pageable. @@ -119,7 +119,7 @@ static inline void qh_put (struct ehci_qh *qh) /*-------------------------------------------------------------------------*/ -/* The queue heads and transfer descriptors are managed from pools tied +/* The queue heads and transfer descriptors are managed from pools tied * to each of the "per device" structures. * This is the initialisation and cleanup code. */ @@ -165,7 +165,7 @@ static int ehci_mem_init (struct ehci_hcd *ehci, gfp_t flags) int i; /* QTDs for control/bulk/intr transfers */ - ehci->qtd_pool = dma_pool_create ("ehci_qtd", + ehci->qtd_pool = dma_pool_create ("ehci_qtd", ehci_to_hcd(ehci)->self.controller, sizeof (struct ehci_qtd), 32 /* byte alignment (for hw parts) */, @@ -175,7 +175,7 @@ static int ehci_mem_init (struct ehci_hcd *ehci, gfp_t flags) } /* QHs for control/bulk/intr transfers */ - ehci->qh_pool = dma_pool_create ("ehci_qh", + ehci->qh_pool = dma_pool_create ("ehci_qh", ehci_to_hcd(ehci)->self.controller, sizeof (struct ehci_qh), 32 /* byte alignment (for hw parts) */, @@ -189,7 +189,7 @@ static int ehci_mem_init (struct ehci_hcd *ehci, gfp_t flags) } /* ITD for high speed ISO transfers */ - ehci->itd_pool = dma_pool_create ("ehci_itd", + ehci->itd_pool = dma_pool_create ("ehci_itd", ehci_to_hcd(ehci)->self.controller, sizeof (struct ehci_itd), 32 /* byte alignment (for hw parts) */, @@ -199,7 +199,7 @@ static int ehci_mem_init (struct ehci_hcd *ehci, gfp_t flags) } /* SITD for full/low speed split ISO transfers */ - ehci->sitd_pool = dma_pool_create ("ehci_sitd", + ehci->sitd_pool = dma_pool_create ("ehci_sitd", ehci_to_hcd(ehci)->self.controller, sizeof (struct ehci_sitd), 32 /* byte alignment (for hw parts) */, diff --git a/drivers/usb/host/ehci-pci.c b/drivers/usb/host/ehci-pci.c index cadffacd945..08d0472d4f5 100644 --- a/drivers/usb/host/ehci-pci.c +++ b/drivers/usb/host/ehci-pci.c @@ -238,6 +238,12 @@ static int ehci_pci_suspend(struct usb_hcd *hcd, pm_message_t message) writel (0, &ehci->regs->intr_enable); (void)readl(&ehci->regs->intr_enable); + /* make sure snapshot being resumed re-enumerates everything */ + if (message.event == PM_EVENT_PRETHAW) { + ehci_halt(ehci); + ehci_reset(ehci); + } + clear_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); bail: spin_unlock_irqrestore (&ehci->lock, flags); @@ -297,7 +303,7 @@ restart: /* emptying the schedule aborts any urbs */ spin_lock_irq(&ehci->lock); if (ehci->reclaim) - ehci->reclaim_ready = 1; + end_unlink_async (ehci, NULL); ehci_work(ehci, NULL); spin_unlock_irq(&ehci->lock); @@ -332,6 +338,7 @@ static const struct hc_driver ehci_pci_hc_driver = { .resume = ehci_pci_resume, #endif .stop = ehci_stop, + .shutdown = ehci_shutdown, /* * managing i/o requests and associated device resources @@ -378,4 +385,5 @@ static struct pci_driver ehci_pci_driver = { .suspend = usb_hcd_pci_suspend, .resume = usb_hcd_pci_resume, #endif + .shutdown = usb_hcd_pci_shutdown, }; diff --git a/drivers/usb/host/ehci-q.c b/drivers/usb/host/ehci-q.c index e469221e7ec..7fc25b6bd7d 100644 --- a/drivers/usb/host/ehci-q.c +++ b/drivers/usb/host/ehci-q.c @@ -1,6 +1,6 @@ /* * Copyright (C) 2001-2004 by David Brownell - * + * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or (at your @@ -31,7 +31,7 @@ * ISO traffic uses "ISO TD" (itd, and sitd) records, and (along with * interrupts) needs careful scheduling. Performance improvements can be * an ongoing challenge. That's in "ehci-sched.c". - * + * * USB 1.1 devices are handled (a) by "companion" OHCI or UHCI root hubs, * or otherwise through transaction translators (TTs) in USB 2.0 hubs using * (b) special fields in qh entries or (c) split iso entries. TTs will @@ -199,7 +199,7 @@ static void qtd_copy_status ( && ((token & QTD_STS_MMF) != 0 || QTD_CERR(token) == 0) && (!ehci_is_TDI(ehci) - || urb->dev->tt->hub != + || urb->dev->tt->hub != ehci_to_hcd(ehci)->self.root_hub)) { #ifdef DEBUG struct usb_device *tt = urb->dev->tt->hub; @@ -364,7 +364,7 @@ qh_completions (struct ehci_hcd *ehci, struct ehci_qh *qh, struct pt_regs *regs) */ if (likely (urb->status == -EINPROGRESS)) continue; - + /* issue status after short control reads */ if (unlikely (do_status != 0) && QTD_PID (token) == 0 /* OUT */) { @@ -388,7 +388,7 @@ halt: wmb (); } } - + /* remove it from the queue */ spin_lock (&urb->lock); qtd_copy_status (ehci, urb, qtd->length, token); @@ -518,7 +518,7 @@ qh_urb_transaction ( /* for zero length DATA stages, STATUS is always IN */ if (len == 0) token |= (1 /* "in" */ << 8); - } + } /* * data transfer stage: buffer setup @@ -759,7 +759,7 @@ qh_make ( } break; default: - dbg ("bogus dev %p speed %d", urb->dev, urb->dev->speed); + dbg ("bogus dev %p speed %d", urb->dev, urb->dev->speed); done: qh_put (qh); return NULL; @@ -967,17 +967,16 @@ static void end_unlink_async (struct ehci_hcd *ehci, struct pt_regs *regs) struct ehci_qh *qh = ehci->reclaim; struct ehci_qh *next; - timer_action_done (ehci, TIMER_IAA_WATCHDOG); + iaa_watchdog_done (ehci); // qh->hw_next = cpu_to_le32 (qh->qh_dma); qh->qh_state = QH_STATE_IDLE; qh->qh_next.qh = NULL; - qh_put (qh); // refcount from reclaim + qh_put (qh); // refcount from reclaim /* other unlink(s) may be pending (in QH_STATE_UNLINK_WAIT) */ next = qh->reclaim; ehci->reclaim = next; - ehci->reclaim_ready = 0; qh->reclaim = NULL; qh_completions (ehci, qh, regs); @@ -1031,7 +1030,7 @@ static void start_unlink_async (struct ehci_hcd *ehci, struct ehci_qh *qh) timer_action_done (ehci, TIMER_ASYNC_OFF); } return; - } + } qh->qh_state = QH_STATE_UNLINK; ehci->reclaim = qh = qh_get (qh); @@ -1046,17 +1045,16 @@ static void start_unlink_async (struct ehci_hcd *ehci, struct ehci_qh *qh) if (unlikely (ehci_to_hcd(ehci)->state == HC_STATE_HALT)) { /* if (unlikely (qh->reclaim != 0)) - * this will recurse, probably not much + * this will recurse, probably not much */ end_unlink_async (ehci, NULL); return; } - ehci->reclaim_ready = 0; cmd |= CMD_IAAD; writel (cmd, &ehci->regs->command); (void) readl (&ehci->regs->command); - timer_action (ehci, TIMER_IAA_WATCHDOG); + iaa_watchdog_start (ehci); } /*-------------------------------------------------------------------------*/ diff --git a/drivers/usb/host/ehci-sched.c b/drivers/usb/host/ehci-sched.c index 4859900bd13..e5e9c653c90 100644 --- a/drivers/usb/host/ehci-sched.c +++ b/drivers/usb/host/ehci-sched.c @@ -1,7 +1,7 @@ /* * Copyright (c) 2001-2004 by David Brownell * Copyright (c) 2003 Michal Sojka, for high-speed iso transfers - * + * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or (at your @@ -613,7 +613,7 @@ static void intr_deschedule (struct ehci_hcd *ehci, struct ehci_qh *qh) /*-------------------------------------------------------------------------*/ static int check_period ( - struct ehci_hcd *ehci, + struct ehci_hcd *ehci, unsigned frame, unsigned uframe, unsigned period, @@ -629,7 +629,7 @@ static int check_period ( /* * 80% periodic == 100 usec/uframe available - * convert "usecs we need" to "max already claimed" + * convert "usecs we need" to "max already claimed" */ usecs = 100 - usecs; @@ -659,14 +659,14 @@ static int check_period ( } static int check_intr_schedule ( - struct ehci_hcd *ehci, + struct ehci_hcd *ehci, unsigned frame, unsigned uframe, const struct ehci_qh *qh, __le32 *c_maskp ) { - int retval = -ENOSPC; + int retval = -ENOSPC; u8 mask = 0; if (qh->c_usecs && uframe >= 6) /* FSTN territory? */ @@ -701,7 +701,7 @@ static int check_intr_schedule ( /* Make sure this tt's buffer is also available for CSPLITs. * We pessimize a bit; probably the typical full speed case * doesn't need the second CSPLIT. - * + * * NOTE: both SPLIT and CSPLIT could be checked in just * one smart pass... */ @@ -728,7 +728,7 @@ done: */ static int qh_schedule (struct ehci_hcd *ehci, struct ehci_qh *qh) { - int status; + int status; unsigned uframe; __le32 c_mask; unsigned frame; /* 0..(qh->period - 1), or NO_FRAME */ @@ -784,7 +784,7 @@ static int qh_schedule (struct ehci_hcd *ehci, struct ehci_qh *qh) ehci_dbg (ehci, "reused qh %p schedule\n", qh); /* stuff into the periodic schedule */ - status = qh_link_periodic (ehci, qh); + status = qh_link_periodic (ehci, qh); done: return status; } @@ -1681,7 +1681,7 @@ static int itd_submit (struct ehci_hcd *ehci, struct urb *urb, status = -ESHUTDOWN; else status = iso_stream_schedule (ehci, urb, stream); - if (likely (status == 0)) + if (likely (status == 0)) itd_link_urb (ehci, urb, ehci->periodic_size << 3, stream); spin_unlock_irqrestore (&ehci->lock, flags); @@ -1738,7 +1738,7 @@ sitd_sched_init ( if (packet->buf1 != (buf & ~(u64)0x0fff)) packet->cross = 1; - /* OUT uses multiple start-splits */ + /* OUT uses multiple start-splits */ if (stream->bEndpointAddress & USB_DIR_IN) continue; length = (length + 187) / 188; @@ -1925,7 +1925,7 @@ sitd_link_urb ( /*-------------------------------------------------------------------------*/ #define SITD_ERRS (SITD_STS_ERR | SITD_STS_DBE | SITD_STS_BABBLE \ - | SITD_STS_XACT | SITD_STS_MMF) + | SITD_STS_XACT | SITD_STS_MMF) static unsigned sitd_complete ( @@ -2043,7 +2043,7 @@ static int sitd_submit (struct ehci_hcd *ehci, struct urb *urb, status = -ESHUTDOWN; else status = iso_stream_schedule (ehci, urb, stream); - if (status == 0) + if (status == 0) sitd_link_urb (ehci, urb, ehci->periodic_size << 3, stream); spin_unlock_irqrestore (&ehci->lock, flags); @@ -2226,5 +2226,5 @@ restart: now_uframe++; now_uframe %= mod; } - } + } } diff --git a/drivers/usb/host/ehci.h b/drivers/usb/host/ehci.h index 679c1cdcc91..6aac39f50e0 100644 --- a/drivers/usb/host/ehci.h +++ b/drivers/usb/host/ehci.h @@ -1,6 +1,6 @@ /* * Copyright (c) 2001-2002 by David Brownell - * + * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or (at your @@ -58,7 +58,6 @@ struct ehci_hcd { /* one per controller */ /* async schedule support */ struct ehci_qh *async; struct ehci_qh *reclaim; - unsigned reclaim_ready : 1; unsigned scanning : 1; /* periodic schedule support */ @@ -81,8 +80,8 @@ struct ehci_hcd { /* one per controller */ struct dma_pool *itd_pool; /* itd per iso urb */ struct dma_pool *sitd_pool; /* sitd per split iso urb */ + struct timer_list iaa_watchdog; struct timer_list watchdog; - struct notifier_block reboot_notifier; unsigned long actions; unsigned stamp; unsigned long next_statechange; @@ -104,7 +103,7 @@ struct ehci_hcd { /* one per controller */ #endif }; -/* convert between an HCD pointer and the corresponding EHCI_HCD */ +/* convert between an HCD pointer and the corresponding EHCI_HCD */ static inline struct ehci_hcd *hcd_to_ehci (struct usb_hcd *hcd) { return (struct ehci_hcd *) (hcd->hcd_priv); @@ -115,9 +114,21 @@ static inline struct usb_hcd *ehci_to_hcd (struct ehci_hcd *ehci) } +static inline void +iaa_watchdog_start (struct ehci_hcd *ehci) +{ + WARN_ON(timer_pending(&ehci->iaa_watchdog)); + mod_timer (&ehci->iaa_watchdog, + jiffies + msecs_to_jiffies(EHCI_IAA_MSECS)); +} + +static inline void iaa_watchdog_done (struct ehci_hcd *ehci) +{ + del_timer (&ehci->iaa_watchdog); +} + enum ehci_timer_action { TIMER_IO_WATCHDOG, - TIMER_IAA_WATCHDOG, TIMER_ASYNC_SHRINK, TIMER_ASYNC_OFF, }; @@ -135,9 +146,6 @@ timer_action (struct ehci_hcd *ehci, enum ehci_timer_action action) unsigned long t; switch (action) { - case TIMER_IAA_WATCHDOG: - t = EHCI_IAA_JIFFIES; - break; case TIMER_IO_WATCHDOG: t = EHCI_IO_JIFFIES; break; @@ -154,8 +162,7 @@ timer_action (struct ehci_hcd *ehci, enum ehci_timer_action action) // 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 (action != TIMER_IAA_WATCHDOG - && t > ehci->watchdog.expires + if (time_before_eq(t, ehci->watchdog.expires) && timer_pending (&ehci->watchdog)) return; mod_timer (&ehci->watchdog, t); @@ -179,8 +186,8 @@ struct ehci_caps { #define HCS_INDICATOR(p) ((p)&(1 << 16)) /* true: has port indicators */ #define HCS_N_CC(p) (((p)>>12)&0xf) /* bits 15:12, #companion HCs */ #define HCS_N_PCC(p) (((p)>>8)&0xf) /* bits 11:8, ports per CC */ -#define HCS_PORTROUTED(p) ((p)&(1 << 7)) /* true: port routing */ -#define HCS_PPC(p) ((p)&(1 << 4)) /* true: port power control */ +#define HCS_PORTROUTED(p) ((p)&(1 << 7)) /* true: port routing */ +#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 */ u32 hcc_params; /* HCCPARAMS - offset 0x8 */ @@ -205,7 +212,7 @@ struct ehci_regs { #define CMD_LRESET (1<<7) /* partial reset (no ports, etc) */ #define CMD_IAAD (1<<6) /* "doorbell" interrupt async advance */ #define CMD_ASE (1<<5) /* async schedule enable */ -#define CMD_PSE (1<<4) /* periodic schedule enable */ +#define CMD_PSE (1<<4) /* periodic schedule enable */ /* 3:2 is periodic frame list size */ #define CMD_RESET (1<<1) /* reset HC not bus */ #define CMD_RUN (1<<0) /* start/stop HC */ @@ -231,9 +238,9 @@ struct ehci_regs { /* FRINDEX: offset 0x0C */ u32 frame_index; /* current microframe number */ /* CTRLDSSEGMENT: offset 0x10 */ - u32 segment; /* address bits 63:32 if needed */ + u32 segment; /* address bits 63:32 if needed */ /* PERIODICLISTBASE: offset 0x14 */ - u32 frame_list; /* points to periodic list */ + u32 frame_list; /* points to periodic list */ /* ASYNCLISTADDR: offset 0x18 */ u32 async_next; /* address of next async queue head */ @@ -302,7 +309,7 @@ struct ehci_dbg_port { /* * EHCI Specification 0.95 Section 3.5 - * QTD: describe data transfer components (buffer, direction, ...) + * QTD: describe data transfer components (buffer, direction, ...) * See Fig 3-6 "Queue Element Transfer Descriptor Block Diagram". * * These are associated only with "QH" (Queue Head) structures, @@ -312,7 +319,7 @@ struct ehci_qtd { /* first part defined by EHCI spec */ __le32 hw_next; /* see EHCI 3.5.1 */ __le32 hw_alt_next; /* see EHCI 3.5.2 */ - __le32 hw_token; /* see EHCI 3.5.3 */ + __le32 hw_token; /* see EHCI 3.5.3 */ #define QTD_TOGGLE (1 << 31) /* data toggle */ #define QTD_LENGTH(tok) (((tok)>>16) & 0x7fff) #define QTD_IOC (1 << 15) /* interrupt on complete */ @@ -349,8 +356,8 @@ struct ehci_qtd { /* values for that type tag */ #define Q_TYPE_ITD __constant_cpu_to_le32 (0 << 1) #define Q_TYPE_QH __constant_cpu_to_le32 (1 << 1) -#define Q_TYPE_SITD __constant_cpu_to_le32 (2 << 1) -#define Q_TYPE_FSTN __constant_cpu_to_le32 (3 << 1) +#define Q_TYPE_SITD __constant_cpu_to_le32 (2 << 1) +#define Q_TYPE_FSTN __constant_cpu_to_le32 (3 << 1) /* next async queue entry, or pointer to interrupt/periodic QH */ #define QH_NEXT(dma) (cpu_to_le32(((u32)dma)&~0x01f)|Q_TYPE_QH) @@ -367,7 +374,7 @@ struct ehci_qtd { * For entries in the async schedule, the type tag always says "qh". */ union ehci_shadow { - struct ehci_qh *qh; /* Q_TYPE_QH */ + struct ehci_qh *qh; /* Q_TYPE_QH */ struct ehci_itd *itd; /* Q_TYPE_ITD */ struct ehci_sitd *sitd; /* Q_TYPE_SITD */ struct ehci_fstn *fstn; /* Q_TYPE_FSTN */ @@ -397,7 +404,7 @@ struct ehci_qh { #define QH_HUBPORT 0x3f800000 #define QH_MULT 0xc0000000 __le32 hw_current; /* qtd list - see EHCI 3.6.4 */ - + /* qtd overlay (hardware parts of a struct ehci_qtd) */ __le32 hw_qtd_next; __le32 hw_alt_next; @@ -472,7 +479,7 @@ struct ehci_iso_stream { struct list_head td_list; /* queued itds/sitds */ struct list_head free_list; /* list of unused itds/sitds */ struct usb_device *udev; - struct usb_host_endpoint *ep; + struct usb_host_endpoint *ep; /* output of (re)scheduling */ unsigned long start; /* jiffies */ @@ -492,8 +499,8 @@ struct ehci_iso_stream { unsigned bandwidth; /* This is used to initialize iTD's hw_bufp fields */ - __le32 buf0; - __le32 buf1; + __le32 buf0; + __le32 buf1; __le32 buf2; /* this is used to initialize sITD's tt info */ @@ -521,7 +528,7 @@ struct ehci_itd { #define ITD_ACTIVE __constant_cpu_to_le32(EHCI_ISOC_ACTIVE) - __le32 hw_bufp [7]; /* see EHCI 3.3.3 */ + __le32 hw_bufp [7]; /* see EHCI 3.3.3 */ __le32 hw_bufp_hi [7]; /* Appendix B */ /* the rest is HCD-private */ @@ -542,7 +549,7 @@ struct ehci_itd { /*-------------------------------------------------------------------------*/ /* - * EHCI Specification 0.95 Section 3.4 + * EHCI Specification 0.95 Section 3.4 * siTD, aka split-transaction isochronous Transfer Descriptor * ... describe full speed iso xfers through TT in hubs * see Figure 3-5 "Split-transaction Isochronous Transaction Descriptor (siTD) diff --git a/drivers/usb/host/isp116x-hcd.c b/drivers/usb/host/isp116x-hcd.c index 5147ed4a666..a72e041df8e 100644 --- a/drivers/usb/host/isp116x-hcd.c +++ b/drivers/usb/host/isp116x-hcd.c @@ -1204,10 +1204,10 @@ static int isp116x_show_dbg(struct seq_file *s, void *unused) static int isp116x_open_seq(struct inode *inode, struct file *file) { - return single_open(file, isp116x_show_dbg, inode->u.generic_ip); + return single_open(file, isp116x_show_dbg, inode->i_private); } -static struct file_operations isp116x_debug_fops = { +static const struct file_operations isp116x_debug_fops = { .open = isp116x_open_seq, .read = seq_read, .llseek = seq_lseek, diff --git a/drivers/usb/host/isp116x.h b/drivers/usb/host/isp116x.h index a1b7c3813d3..b91e2edd9c5 100644 --- a/drivers/usb/host/isp116x.h +++ b/drivers/usb/host/isp116x.h @@ -233,7 +233,7 @@ static const int cc_to_error[16] = { /* Bit Stuff */ -EPROTO, /* Data Togg */ -EILSEQ, /* Stall */ -EPIPE, - /* DevNotResp */ -ETIMEDOUT, + /* DevNotResp */ -ETIME, /* PIDCheck */ -EPROTO, /* UnExpPID */ -EPROTO, /* DataOver */ -EOVERFLOW, diff --git a/drivers/usb/host/ohci-at91.c b/drivers/usb/host/ohci-at91.c index 85cc059705a..b466581beb4 100644 --- a/drivers/usb/host/ohci-at91.c +++ b/drivers/usb/host/ohci-at91.c @@ -193,7 +193,7 @@ ohci_at91_start (struct usb_hcd *hcd) if ((ret = ohci_init(ohci)) < 0) return ret; - root->maxchild = board->ports; + ohci->num_ports = board->ports; if ((ret = ohci_run(ohci)) < 0) { err("can't start %s", hcd->self.bus_name); @@ -221,6 +221,7 @@ static const struct hc_driver ohci_at91_hc_driver = { */ .start = ohci_at91_start, .stop = ohci_stop, + .shutdown = ohci_shutdown, /* * managing i/o requests and associated device resources @@ -239,7 +240,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, @@ -296,6 +297,7 @@ static int ohci_hcd_at91_drv_resume(struct platform_device *pdev) if (!clocked) { clk_enable(iclk); clk_enable(fclk); + clocked = 1; } return 0; @@ -310,6 +312,7 @@ MODULE_ALIAS("at91_ohci"); static struct platform_driver ohci_hcd_at91_driver = { .probe = ohci_hcd_at91_drv_probe, .remove = ohci_hcd_at91_drv_remove, + .shutdown = usb_hcd_platform_shutdown, .suspend = ohci_hcd_at91_drv_suspend, .resume = ohci_hcd_at91_drv_resume, .driver = { diff --git a/drivers/usb/host/ohci-au1xxx.c b/drivers/usb/host/ohci-au1xxx.c index f7a975d5db0..24e23c5783d 100644 --- a/drivers/usb/host/ohci-au1xxx.c +++ b/drivers/usb/host/ohci-au1xxx.c @@ -268,11 +268,8 @@ static const struct hc_driver ohci_au1xxx_hc_driver = { * basic lifecycle operations */ .start = ohci_au1xxx_start, -#ifdef CONFIG_PM - /* suspend: ohci_au1xxx_suspend, -- tbd */ - /* resume: ohci_au1xxx_resume, -- tbd */ -#endif /*CONFIG_PM*/ .stop = ohci_stop, + .shutdown = ohci_shutdown, /* * managing i/o requests and associated device resources @@ -291,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, @@ -338,6 +336,7 @@ static int ohci_hcd_au1xxx_drv_resume(struct platform_device *dev) static struct platform_driver ohci_hcd_au1xxx_driver = { .probe = ohci_hcd_au1xxx_drv_probe, .remove = ohci_hcd_au1xxx_drv_remove, + .shutdown = usb_hcd_platform_shutdown, /*.suspend = ohci_hcd_au1xxx_drv_suspend, */ /*.resume = ohci_hcd_au1xxx_drv_resume, */ .driver = { diff --git a/drivers/usb/host/ohci-dbg.c b/drivers/usb/host/ohci-dbg.c index 7bfffcbbd22..8293c1d4be3 100644 --- a/drivers/usb/host/ohci-dbg.c +++ b/drivers/usb/host/ohci-dbg.c @@ -477,7 +477,7 @@ show_async (struct class_device *class_dev, char *buf) unsigned long flags; bus = class_get_devdata(class_dev); - hcd = bus->hcpriv; + hcd = bus_to_hcd(bus); ohci = hcd_to_ohci(hcd); /* display control and bulk lists together, for simplicity */ @@ -510,7 +510,7 @@ show_periodic (struct class_device *class_dev, char *buf) seen_count = 0; bus = class_get_devdata(class_dev); - hcd = bus->hcpriv; + hcd = bus_to_hcd(bus); ohci = hcd_to_ohci(hcd); next = buf; size = PAGE_SIZE; @@ -607,7 +607,7 @@ show_registers (struct class_device *class_dev, char *buf) u32 rdata; bus = class_get_devdata(class_dev); - hcd = bus->hcpriv; + hcd = bus_to_hcd(bus); ohci = hcd_to_ohci(hcd); regs = ohci->regs; next = buf; @@ -667,6 +667,11 @@ show_registers (struct class_device *class_dev, char *buf) size -= temp; next += temp; + temp = scnprintf (next, size, "hub poll timer %s\n", + ohci_to_hcd(ohci)->poll_rh ? "ON" : "off"); + size -= temp; + next += temp; + /* roothub */ ohci_dump_roothub (ohci, 1, &next, &size); @@ -680,10 +685,11 @@ static CLASS_DEVICE_ATTR (registers, S_IRUGO, show_registers, NULL); static inline void create_debug_files (struct ohci_hcd *ohci) { struct class_device *cldev = ohci_to_hcd(ohci)->self.class_dev; + int retval; - class_device_create_file(cldev, &class_device_attr_async); - class_device_create_file(cldev, &class_device_attr_periodic); - class_device_create_file(cldev, &class_device_attr_registers); + retval = class_device_create_file(cldev, &class_device_attr_async); + retval = class_device_create_file(cldev, &class_device_attr_periodic); + retval = class_device_create_file(cldev, &class_device_attr_registers); ohci_dbg (ohci, "created debug files\n"); } diff --git a/drivers/usb/host/ohci-ep93xx.c b/drivers/usb/host/ohci-ep93xx.c index 6531c4d2652..1bf5e7a4e73 100644 --- a/drivers/usb/host/ohci-ep93xx.c +++ b/drivers/usb/host/ohci-ep93xx.c @@ -128,12 +128,14 @@ static struct hc_driver ohci_ep93xx_hc_driver = { .flags = HCD_USB11 | HCD_MEMORY, .start = ohci_ep93xx_start, .stop = ohci_stop, + .shutdown = ohci_shutdown, .urb_enqueue = ohci_urb_enqueue, .urb_dequeue = ohci_urb_dequeue, .endpoint_disable = ohci_endpoint_disable, .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, @@ -202,6 +204,7 @@ static int ohci_hcd_ep93xx_drv_resume(struct platform_device *pdev) static struct platform_driver ohci_hcd_ep93xx_driver = { .probe = ohci_hcd_ep93xx_drv_probe, .remove = ohci_hcd_ep93xx_drv_remove, + .shutdown = usb_hcd_platform_shutdown, #ifdef CONFIG_PM .suspend = ohci_hcd_ep93xx_drv_suspend, .resume = ohci_hcd_ep93xx_drv_resume, diff --git a/drivers/usb/host/ohci-hcd.c b/drivers/usb/host/ohci-hcd.c index 94d8cf4b36c..d1d68c40225 100644 --- a/drivers/usb/host/ohci-hcd.c +++ b/drivers/usb/host/ohci-hcd.c @@ -88,7 +88,7 @@ #include <linux/timer.h> #include <linux/list.h> #include <linux/usb.h> -#include <linux/usb_otg.h> +#include <linux/usb/otg.h> #include <linux/dma-mapping.h> #include <linux/dmapool.h> #include <linux/reboot.h> @@ -101,7 +101,7 @@ #include "../core/hcd.h" -#define DRIVER_VERSION "2005 April 22" +#define DRIVER_VERSION "2006 August 04" #define DRIVER_AUTHOR "Roman Weissgaerber, David Brownell" #define DRIVER_DESC "USB 1.1 'Open' Host Controller (OHCI) Driver" @@ -110,9 +110,10 @@ #undef OHCI_VERBOSE_DEBUG /* not always helpful */ /* For initializing controller (mask in an HCFS mode too) */ -#define OHCI_CONTROL_INIT OHCI_CTRL_CBSR +#define OHCI_CONTROL_INIT OHCI_CTRL_CBSR #define OHCI_INTR_INIT \ - (OHCI_INTR_MIE | OHCI_INTR_UE | OHCI_INTR_RD | OHCI_INTR_WDH) + (OHCI_INTR_MIE | OHCI_INTR_RHSC | OHCI_INTR_UE \ + | OHCI_INTR_RD | OHCI_INTR_WDH) #ifdef __hppa__ /* On PA-RISC, PDC can leave IR set incorrectly; ignore it there. */ @@ -128,12 +129,13 @@ static const char hcd_name [] = "ohci_hcd"; +#define STATECHANGE_DELAY msecs_to_jiffies(300) + #include "ohci.h" static void ohci_dump (struct ohci_hcd *ohci, int verbose); static int ohci_init (struct ohci_hcd *ohci); static void ohci_stop (struct usb_hcd *hcd); -static int ohci_reboot (struct notifier_block *, unsigned long , void *); #include "ohci-hub.c" #include "ohci-dbg.c" @@ -416,21 +418,20 @@ static void ohci_usb_reset (struct ohci_hcd *ohci) ohci_writel (ohci, ohci->hc_control, &ohci->regs->control); } -/* reboot notifier forcibly disables IRQs and DMA, helping kexec and +/* ohci_shutdown forcibly disables IRQs and DMA, helping kexec and * other cases where the next software may expect clean state from the * "firmware". this is bus-neutral, unlike shutdown() methods. */ -static int -ohci_reboot (struct notifier_block *block, unsigned long code, void *null) +static void +ohci_shutdown (struct usb_hcd *hcd) { struct ohci_hcd *ohci; - ohci = container_of (block, struct ohci_hcd, reboot_notifier); + ohci = hcd_to_ohci (hcd); ohci_writel (ohci, OHCI_INTR_MIE, &ohci->regs->intrdisable); ohci_usb_reset (ohci); /* flush the writes */ (void) ohci_readl (ohci, &ohci->regs->control); - return 0; } /*-------------------------------------------------------------------------* @@ -446,7 +447,6 @@ static int ohci_init (struct ohci_hcd *ohci) disable (ohci); ohci->regs = hcd->regs; - ohci->next_statechange = jiffies; /* REVISIT this BIOS handshake is now moved into PCI "quirks", and * was never needed for most non-PCI systems ... remove the code? @@ -502,7 +502,6 @@ static int ohci_init (struct ohci_hcd *ohci) if ((ret = ohci_mem_init (ohci)) < 0) ohci_stop (hcd); else { - register_reboot_notifier (&ohci->reboot_notifier); create_debug_files (ohci); } @@ -637,10 +636,14 @@ retry: return -EOVERFLOW; } - /* start controller operations */ + /* use rhsc irqs after khubd is fully initialized */ + hcd->poll_rh = 1; + hcd->uses_new_polling = 1; + + /* start controller operations */ ohci->hc_control &= OHCI_CTRL_RWC; - ohci->hc_control |= OHCI_CONTROL_INIT | OHCI_USB_OPER; - ohci_writel (ohci, ohci->hc_control, &ohci->regs->control); + ohci->hc_control |= OHCI_CONTROL_INIT | OHCI_USB_OPER; + ohci_writel (ohci, ohci->hc_control, &ohci->regs->control); hcd->state = HC_STATE_RUNNING; /* wake on ConnectStatusChange, matching external hubs */ @@ -648,7 +651,7 @@ retry: /* Choose the interrupts we care about now, others later on demand */ mask = OHCI_INTR_INIT; - ohci_writel (ohci, mask, &ohci->regs->intrstatus); + ohci_writel (ohci, ~0, &ohci->regs->intrstatus); ohci_writel (ohci, mask, &ohci->regs->intrenable); /* handle root hub init quirks ... */ @@ -672,6 +675,7 @@ retry: // flush those writes (void) ohci_readl (ohci, &ohci->regs->control); + ohci->next_statechange = jiffies + STATECHANGE_DELAY; spin_unlock_irq (&ohci->lock); // POTPGT delay is bits 24-31, in 2 ms units. @@ -709,7 +713,14 @@ static irqreturn_t ohci_irq (struct usb_hcd *hcd, struct pt_regs *ptregs) /* interrupt for some other device? */ } else if ((ints &= ohci_readl (ohci, ®s->intrenable)) == 0) { return IRQ_NOTMINE; - } + } + + if (ints & OHCI_INTR_RHSC) { + ohci_vdbg (ohci, "rhsc\n"); + ohci->next_statechange = jiffies + STATECHANGE_DELAY; + ohci_writel (ohci, OHCI_INTR_RHSC, ®s->intrstatus); + usb_hcd_poll_rh_status(hcd); + } if (ints & OHCI_INTR_UE) { disable (ohci); @@ -723,13 +734,18 @@ static irqreturn_t ohci_irq (struct usb_hcd *hcd, struct pt_regs *ptregs) if (ints & OHCI_INTR_RD) { ohci_vdbg (ohci, "resume detect\n"); ohci_writel (ohci, OHCI_INTR_RD, ®s->intrstatus); - if (hcd->state != HC_STATE_QUIESCING) + hcd->poll_rh = 1; + if (ohci->autostop) { + spin_lock (&ohci->lock); + ohci_rh_resume (ohci); + spin_unlock (&ohci->lock); + } else usb_hcd_resume_root_hub(hcd); } if (ints & OHCI_INTR_WDH) { if (HC_IS_RUNNING(hcd->state)) - ohci_writel (ohci, OHCI_INTR_WDH, ®s->intrdisable); + ohci_writel (ohci, OHCI_INTR_WDH, ®s->intrdisable); spin_lock (&ohci->lock); dl_done_list (ohci, ptregs); spin_unlock (&ohci->lock); @@ -775,9 +791,10 @@ static void ohci_stop (struct usb_hcd *hcd) ohci_usb_reset (ohci); ohci_writel (ohci, OHCI_INTR_MIE, &ohci->regs->intrdisable); - + free_irq(hcd->irq, hcd); + hcd->irq = -1; + remove_debug_files (ohci); - unregister_reboot_notifier (&ohci->reboot_notifier); ohci_mem_cleanup (ohci); if (ohci->hcca) { dma_free_coherent (hcd->self.controller, @@ -917,6 +934,10 @@ MODULE_LICENSE ("GPL"); #include "ohci-at91.c" #endif +#ifdef CONFIG_ARCH_PNX4008 +#include "ohci-pnx4008.c" +#endif + #if !(defined(CONFIG_PCI) \ || defined(CONFIG_SA1111) \ || defined(CONFIG_ARCH_S3C2410) \ @@ -928,6 +949,7 @@ MODULE_LICENSE ("GPL"); || defined (CONFIG_USB_OHCI_HCD_PPC_SOC) \ || defined (CONFIG_ARCH_AT91RM9200) \ || defined (CONFIG_ARCH_AT91SAM9261) \ + || defined (CONFIG_ARCH_PNX4008) \ ) #error "missing bus glue for ohci-hcd" #endif diff --git a/drivers/usb/host/ohci-hub.c b/drivers/usb/host/ohci-hub.c index 5b0a23fd798..ec75774abea 100644 --- a/drivers/usb/host/ohci-hub.c +++ b/drivers/usb/host/ohci-hub.c @@ -36,27 +36,25 @@ /*-------------------------------------------------------------------------*/ -#ifdef CONFIG_PM +/* hcd->hub_irq_enable() */ +static void ohci_rhsc_enable (struct usb_hcd *hcd) +{ + struct ohci_hcd *ohci = hcd_to_ohci (hcd); + + ohci_writel (ohci, OHCI_INTR_RHSC, &ohci->regs->intrenable); +} #define OHCI_SCHED_ENABLES \ (OHCI_CTRL_CLE|OHCI_CTRL_BLE|OHCI_CTRL_PLE|OHCI_CTRL_IE) static void dl_done_list (struct ohci_hcd *, struct pt_regs *); static void finish_unlinks (struct ohci_hcd *, u16 , struct pt_regs *); -static int ohci_restart (struct ohci_hcd *ohci); -static int ohci_bus_suspend (struct usb_hcd *hcd) +static int ohci_rh_suspend (struct ohci_hcd *ohci, int autostop) +__releases(ohci->lock) +__acquires(ohci->lock) { - struct ohci_hcd *ohci = hcd_to_ohci (hcd); int status = 0; - unsigned long flags; - - spin_lock_irqsave (&ohci->lock, flags); - - if (unlikely(!test_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags))) { - spin_unlock_irqrestore (&ohci->lock, flags); - return -ESHUTDOWN; - } ohci->hc_control = ohci_readl (ohci, &ohci->regs->control); switch (ohci->hc_control & OHCI_CTRL_HCFS) { @@ -72,15 +70,16 @@ static int ohci_bus_suspend (struct usb_hcd *hcd) ohci_dbg (ohci, "needs reinit!\n"); goto done; case OHCI_USB_SUSPEND: - ohci_dbg (ohci, "already suspended\n"); - goto done; + if (!ohci->autostop) { + ohci_dbg (ohci, "already suspended\n"); + goto done; + } } - ohci_dbg (ohci, "suspend root hub\n"); + ohci_dbg (ohci, "%s root hub\n", + autostop ? "auto-stop" : "suspend"); /* First stop any processing */ - if (ohci->hc_control & OHCI_SCHED_ENABLES) { - int limit; - + if (!autostop && (ohci->hc_control & OHCI_SCHED_ENABLES)) { ohci->hc_control &= ~OHCI_SCHED_ENABLES; ohci_writel (ohci, ohci->hc_control, &ohci->regs->control); ohci->hc_control = ohci_readl (ohci, &ohci->regs->control); @@ -90,27 +89,22 @@ static int ohci_bus_suspend (struct usb_hcd *hcd) * then the last WDH could take 6+ msec */ ohci_dbg (ohci, "stopping schedules ...\n"); - limit = 2000; - while (limit > 0) { - udelay (250); - limit =- 250; - if (ohci_readl (ohci, &ohci->regs->intrstatus) - & OHCI_INTR_SF) - break; - } - dl_done_list (ohci, NULL); - mdelay (7); + ohci->autostop = 0; + spin_unlock_irq (&ohci->lock); + msleep (8); + spin_lock_irq (&ohci->lock); } dl_done_list (ohci, NULL); finish_unlinks (ohci, ohci_frame_no(ohci), NULL); - ohci_writel (ohci, ohci_readl (ohci, &ohci->regs->intrstatus), - &ohci->regs->intrstatus); /* maybe resume can wake root hub */ - if (device_may_wakeup(&ohci_to_hcd(ohci)->self.root_hub->dev)) + if (device_may_wakeup(&ohci_to_hcd(ohci)->self.root_hub->dev) || + autostop) ohci->hc_control |= OHCI_CTRL_RWE; - else + else { + ohci_writel (ohci, OHCI_INTR_RHSC, &ohci->regs->intrdisable); ohci->hc_control &= ~OHCI_CTRL_RWE; + } /* Suspend hub ... this is the "global (to this bus) suspend" mode, * which doesn't imply ports will first be individually suspended. @@ -121,13 +115,12 @@ static int ohci_bus_suspend (struct usb_hcd *hcd) (void) ohci_readl (ohci, &ohci->regs->control); /* no resumes until devices finish suspending */ - ohci->next_statechange = jiffies + msecs_to_jiffies (5); + if (!autostop) { + ohci->next_statechange = jiffies + msecs_to_jiffies (5); + ohci->autostop = 0; + } done: - /* external suspend vs self autosuspend ... same effect */ - if (status == 0) - usb_hcd_suspend_root_hub(hcd); - spin_unlock_irqrestore (&ohci->lock, flags); return status; } @@ -139,25 +132,19 @@ static inline struct ed *find_head (struct ed *ed) return ed; } +static int ohci_restart (struct ohci_hcd *ohci); + /* caller has locked the root hub */ -static int ohci_bus_resume (struct usb_hcd *hcd) +static int ohci_rh_resume (struct ohci_hcd *ohci) +__releases(ohci->lock) +__acquires(ohci->lock) { - struct ohci_hcd *ohci = hcd_to_ohci (hcd); + struct usb_hcd *hcd = ohci_to_hcd (ohci); u32 temp, enables; int status = -EINPROGRESS; - unsigned long flags; - - if (time_before (jiffies, ohci->next_statechange)) - msleep(5); - - spin_lock_irqsave (&ohci->lock, flags); - - if (unlikely(!test_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags))) { - spin_unlock_irqrestore (&ohci->lock, flags); - return -ESHUTDOWN; - } - + int autostopped = ohci->autostop; + ohci->autostop = 0; ohci->hc_control = ohci_readl (ohci, &ohci->regs->control); if (ohci->hc_control & (OHCI_CTRL_IR | OHCI_SCHED_ENABLES)) { @@ -177,7 +164,8 @@ static int ohci_bus_resume (struct usb_hcd *hcd) ohci->hc_control |= OHCI_USB_RESUME; ohci_writel (ohci, ohci->hc_control, &ohci->regs->control); (void) ohci_readl (ohci, &ohci->regs->control); - ohci_dbg (ohci, "resume root hub\n"); + ohci_dbg (ohci, "%s root hub\n", + autostopped ? "auto-start" : "resume"); break; case OHCI_USB_RESUME: /* HCFS changes sometime after INTR_RD */ @@ -192,16 +180,24 @@ static int ohci_bus_resume (struct usb_hcd *hcd) ohci_dbg (ohci, "lost power\n"); status = -EBUSY; } - spin_unlock_irqrestore (&ohci->lock, flags); +#ifdef CONFIG_PM if (status == -EBUSY) { - (void) ohci_init (ohci); - return ohci_restart (ohci); + if (!autostopped) { + spin_unlock_irq (&ohci->lock); + (void) ohci_init (ohci); + status = ohci_restart (ohci); + spin_lock_irq (&ohci->lock); + } + return status; } +#endif if (status != -EINPROGRESS) return status; + if (autostopped) + goto skip_resume; + spin_unlock_irq (&ohci->lock); temp = ohci->num_ports; - enables = 0; while (temp--) { u32 stat = ohci_readl (ohci, &ohci->regs->roothub.portstatus [temp]); @@ -234,17 +230,21 @@ static int ohci_bus_resume (struct usb_hcd *hcd) /* Sometimes PCI D3 suspend trashes frame timings ... */ periodic_reinit (ohci); + /* the following code is executed with ohci->lock held and + * irqs disabled if and only if autostopped is true + */ + +skip_resume: /* interrupts might have been disabled */ ohci_writel (ohci, OHCI_INTR_INIT, &ohci->regs->intrenable); if (ohci->ed_rm_list) ohci_writel (ohci, OHCI_INTR_SF, &ohci->regs->intrenable); - ohci_writel (ohci, ohci_readl (ohci, &ohci->regs->intrstatus), - &ohci->regs->intrstatus); /* Then re-enable operations */ ohci_writel (ohci, OHCI_USB_OPER, &ohci->regs->control); (void) ohci_readl (ohci, &ohci->regs->control); - msleep (3); + if (!autostopped) + msleep (3); temp = ohci->hc_control; temp &= OHCI_CTRL_RWC; @@ -254,10 +254,14 @@ static int ohci_bus_resume (struct usb_hcd *hcd) (void) ohci_readl (ohci, &ohci->regs->control); /* TRSMRCY */ - msleep (10); + if (!autostopped) { + msleep (10); + spin_lock_irq (&ohci->lock); + } + /* now ohci->lock is always held and irqs are always disabled */ - /* keep it alive for ~5x suspend + resume costs */ - ohci->next_statechange = jiffies + msecs_to_jiffies (250); + /* keep it alive for more than ~5x suspend + resume costs */ + ohci->next_statechange = jiffies + STATECHANGE_DELAY; /* maybe turn schedules back on */ enables = 0; @@ -291,6 +295,45 @@ static int ohci_bus_resume (struct usb_hcd *hcd) return 0; } +#ifdef CONFIG_PM + +static int ohci_bus_suspend (struct usb_hcd *hcd) +{ + struct ohci_hcd *ohci = hcd_to_ohci (hcd); + int rc; + + spin_lock_irq (&ohci->lock); + + if (unlikely(!test_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags))) + rc = -ESHUTDOWN; + else + rc = ohci_rh_suspend (ohci, 0); + spin_unlock_irq (&ohci->lock); + return rc; +} + +static int ohci_bus_resume (struct usb_hcd *hcd) +{ + struct ohci_hcd *ohci = hcd_to_ohci (hcd); + int rc; + + if (time_before (jiffies, ohci->next_statechange)) + msleep(5); + + spin_lock_irq (&ohci->lock); + + if (unlikely(!test_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags))) + rc = -ESHUTDOWN; + else + rc = ohci_rh_resume (ohci); + spin_unlock_irq (&ohci->lock); + + /* poll until we know a device is connected or we autostop */ + if (rc == 0) + usb_hcd_poll_rh_status(hcd); + return rc; +} + #endif /* CONFIG_PM */ /*-------------------------------------------------------------------------*/ @@ -302,20 +345,11 @@ ohci_hub_status_data (struct usb_hcd *hcd, char *buf) { struct ohci_hcd *ohci = hcd_to_ohci (hcd); int i, changed = 0, length = 1; - int can_suspend = device_may_wakeup(&hcd->self.root_hub->dev); + int any_connected = 0, rhsc_enabled = 1; unsigned long flags; spin_lock_irqsave (&ohci->lock, flags); - /* handle autosuspended root: finish resuming before - * letting khubd or root hub timer see state changes. - */ - if (unlikely((ohci->hc_control & OHCI_CTRL_HCFS) != OHCI_USB_OPER - || !HC_IS_RUNNING(hcd->state))) { - can_suspend = 0; - goto done; - } - /* undocumented erratum seen on at least rev D */ if ((ohci->flags & OHCI_QUIRK_AMD756) && (roothub_a (ohci) & RH_A_NDP) > MAX_ROOT_PORTS) { @@ -339,6 +373,9 @@ ohci_hub_status_data (struct usb_hcd *hcd, char *buf) for (i = 0; i < ohci->num_ports; i++) { u32 status = roothub_portstatus (ohci, i); + /* can't autostop if ports are connected */ + any_connected |= (status & RH_PS_CCS); + if (status & (RH_PS_CSC | RH_PS_PESC | RH_PS_PSSC | RH_PS_OCIC | RH_PS_PRSC)) { changed = 1; @@ -346,39 +383,72 @@ ohci_hub_status_data (struct usb_hcd *hcd, char *buf) buf [0] |= 1 << (i + 1); else buf [1] |= 1 << (i - 7); - continue; } - - /* can suspend if no ports are enabled; or if all all - * enabled ports are suspended AND remote wakeup is on. - */ - if (!(status & RH_PS_CCS)) - continue; - if ((status & RH_PS_PSS) && can_suspend) - continue; - can_suspend = 0; } -done: - spin_unlock_irqrestore (&ohci->lock, flags); -#ifdef CONFIG_PM - /* save power by suspending idle root hubs; - * INTR_RD wakes us when there's work + /* NOTE: vendors didn't always make the same implementation + * choices for RHSC. Sometimes it triggers on an edge (like + * setting and maybe clearing a port status change bit); and + * it's level-triggered on other silicon, active until khubd + * clears all active port status change bits. If it's still + * set (level-triggered) we must disable it and rely on + * polling until khubd re-enables it. */ - if (can_suspend - && !changed - && !ohci->ed_rm_list - && ((OHCI_CTRL_HCFS | OHCI_SCHED_ENABLES) - & ohci->hc_control) - == OHCI_USB_OPER - && time_after (jiffies, ohci->next_statechange) - && usb_trylock_device (hcd->self.root_hub) == 0 - ) { - ohci_vdbg (ohci, "autosuspend\n"); - (void) ohci_bus_suspend (hcd); - usb_unlock_device (hcd->self.root_hub); + if (ohci_readl (ohci, &ohci->regs->intrstatus) & OHCI_INTR_RHSC) { + ohci_writel (ohci, OHCI_INTR_RHSC, &ohci->regs->intrdisable); + (void) ohci_readl (ohci, &ohci->regs->intrdisable); + rhsc_enabled = 0; } -#endif + hcd->poll_rh = 1; + + /* carry out appropriate state changes */ + switch (ohci->hc_control & OHCI_CTRL_HCFS) { + + case OHCI_USB_OPER: + /* keep on polling until we know a device is connected + * and RHSC is enabled */ + if (!ohci->autostop) { + if (any_connected) { + if (rhsc_enabled) + hcd->poll_rh = 0; + } else { + ohci->autostop = 1; + ohci->next_statechange = jiffies + HZ; + } + + /* if no devices have been attached for one second, autostop */ + } else { + if (changed || any_connected) { + ohci->autostop = 0; + ohci->next_statechange = jiffies + + STATECHANGE_DELAY; + } else if (time_after_eq (jiffies, + ohci->next_statechange) + && !ohci->ed_rm_list + && !(ohci->hc_control & + OHCI_SCHED_ENABLES)) { + ohci_rh_suspend (ohci, 1); + } + } + break; + + /* if there is a port change, autostart or ask to be resumed */ + case OHCI_USB_SUSPEND: + case OHCI_USB_RESUME: + if (changed) { + if (ohci->autostop) + ohci_rh_resume (ohci); + else + usb_hcd_resume_root_hub (hcd); + } else { + /* everything is idle, no need for polling */ + hcd->poll_rh = 0; + } + break; + } + +done: + spin_unlock_irqrestore (&ohci->lock, flags); return changed ? length : 0; } @@ -550,9 +620,6 @@ static int ohci_hub_control ( break; case USB_PORT_FEAT_SUSPEND: temp = RH_PS_POCI; - if ((ohci->hc_control & OHCI_CTRL_HCFS) - != OHCI_USB_OPER) - usb_hcd_resume_root_hub(hcd); break; case USB_PORT_FEAT_C_SUSPEND: temp = RH_PS_PSSC; diff --git a/drivers/usb/host/ohci-lh7a404.c b/drivers/usb/host/ohci-lh7a404.c index 5602da9bd52..e121d97ed91 100644 --- a/drivers/usb/host/ohci-lh7a404.c +++ b/drivers/usb/host/ohci-lh7a404.c @@ -173,11 +173,8 @@ static const struct hc_driver ohci_lh7a404_hc_driver = { * basic lifecycle operations */ .start = ohci_lh7a404_start, -#ifdef CONFIG_PM - /* suspend: ohci_lh7a404_suspend, -- tbd */ - /* resume: ohci_lh7a404_resume, -- tbd */ -#endif /*CONFIG_PM*/ .stop = ohci_stop, + .shutdown = ohci_shutdown, /* * managing i/o requests and associated device resources @@ -196,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, @@ -244,6 +242,7 @@ static int ohci_hcd_lh7a404_drv_resume(struct platform_device *dev) static struct platform_driver ohci_hcd_lh7a404_driver = { .probe = ohci_hcd_lh7a404_drv_probe, .remove = ohci_hcd_lh7a404_drv_remove, + .shutdown = usb_hcd_platform_shutdown, /*.suspend = ohci_hcd_lh7a404_drv_suspend, */ /*.resume = ohci_hcd_lh7a404_drv_resume, */ .driver = { diff --git a/drivers/usb/host/ohci-mem.c b/drivers/usb/host/ohci-mem.c index bfbe328a478..d976614eebd 100644 --- a/drivers/usb/host/ohci-mem.c +++ b/drivers/usb/host/ohci-mem.c @@ -28,7 +28,6 @@ static void ohci_hcd_init (struct ohci_hcd *ohci) ohci->next_statechange = jiffies; spin_lock_init (&ohci->lock); INIT_LIST_HEAD (&ohci->pending); - ohci->reboot_notifier.notifier_call = ohci_reboot; } /*-------------------------------------------------------------------------*/ diff --git a/drivers/usb/host/ohci-omap.c b/drivers/usb/host/ohci-omap.c index c4c4babd476..9c02177de50 100644 --- a/drivers/usb/host/ohci-omap.c +++ b/drivers/usb/host/ohci-omap.c @@ -4,7 +4,7 @@ * (C) Copyright 1999 Roman Weissgaerber <weissg@vienna.at> * (C) Copyright 2000-2005 David Brownell * (C) Copyright 2002 Hewlett-Packard Company - * + * * OMAP Bus Glue * * Modified for OMAP by Tony Lindgren <tony@atomide.com> @@ -66,15 +66,20 @@ extern int usb_disabled(void); extern int ocpi_enable(void); static struct clk *usb_host_ck; +static struct clk *usb_dc_ck; +static int host_enabled; +static int host_initialized; static void omap_ohci_clock_power(int on) { if (on) { + clk_enable(usb_dc_ck); clk_enable(usb_host_ck); /* guesstimate for T5 == 1x 32K clock + APLL lock time */ udelay(100); } else { clk_disable(usb_host_ck); + clk_disable(usb_dc_ck); } } @@ -87,14 +92,14 @@ static int omap_ohci_transceiver_power(int on) if (on) { if (machine_is_omap_innovator() && cpu_is_omap1510()) fpga_write(fpga_read(INNOVATOR_FPGA_CAM_USB_CONTROL) - | ((1 << 5/*usb1*/) | (1 << 3/*usb2*/)), + | ((1 << 5/*usb1*/) | (1 << 3/*usb2*/)), INNOVATOR_FPGA_CAM_USB_CONTROL); else if (machine_is_omap_osk()) tps65010_set_gpio_out_value(GPIO1, LOW); } else { if (machine_is_omap_innovator() && cpu_is_omap1510()) fpga_write(fpga_read(INNOVATOR_FPGA_CAM_USB_CONTROL) - & ~((1 << 5/*usb1*/) | (1 << 3/*usb2*/)), + & ~((1 << 5/*usb1*/) | (1 << 3/*usb2*/)), INNOVATOR_FPGA_CAM_USB_CONTROL); else if (machine_is_omap_osk()) tps65010_set_gpio_out_value(GPIO1, HIGH); @@ -103,6 +108,7 @@ static int omap_ohci_transceiver_power(int on) return 0; } +#ifdef CONFIG_ARCH_OMAP15XX /* * OMAP-1510 specific Local Bus clock on/off */ @@ -121,8 +127,8 @@ static int omap_1510_local_bus_power(int on) /* * OMAP-1510 specific Local Bus initialization * NOTE: This assumes 32MB memory size in OMAP1510LB_MEMSIZE. - * See also arch/mach-omap/memory.h for __virt_to_dma() and - * __dma_to_virt() which need to match with the physical + * See also arch/mach-omap/memory.h for __virt_to_dma() and + * __dma_to_virt() which need to match with the physical * Local Bus address below. */ static int omap_1510_local_bus_init(void) @@ -130,7 +136,7 @@ static int omap_1510_local_bus_init(void) unsigned int tlb; unsigned long lbaddr, physaddr; - omap_writel((omap_readl(OMAP1510_LB_CLOCK_DIV) & 0xfffffff8) | 0x4, + omap_writel((omap_readl(OMAP1510_LB_CLOCK_DIV) & 0xfffffff8) | 0x4, OMAP1510_LB_CLOCK_DIV); /* Configure the Local Bus MMU table */ @@ -138,7 +144,7 @@ static int omap_1510_local_bus_init(void) lbaddr = tlb * 0x00100000 + OMAP1510_LB_OFFSET; physaddr = tlb * 0x00100000 + PHYS_OFFSET; omap_writel((lbaddr & 0x0fffffff) >> 22, OMAP1510_LB_MMU_CAM_H); - omap_writel(((lbaddr & 0x003ffc00) >> 6) | 0xc, + omap_writel(((lbaddr & 0x003ffc00) >> 6) | 0xc, OMAP1510_LB_MMU_CAM_L); omap_writel(physaddr >> 16, OMAP1510_LB_MMU_RAM_H); omap_writel((physaddr & 0x0000fc00) | 0x300, OMAP1510_LB_MMU_RAM_L); @@ -152,6 +158,10 @@ static int omap_1510_local_bus_init(void) return 0; } +#else +#define omap_1510_local_bus_power(x) {} +#define omap_1510_local_bus_init() {} +#endif #ifdef CONFIG_USB_OTG @@ -173,13 +183,14 @@ static void start_hnp(struct ohci_hcd *ohci) /*-------------------------------------------------------------------------*/ -static int omap_start_hc(struct ohci_hcd *ohci, struct platform_device *pdev) +static int ohci_omap_init(struct usb_hcd *hcd) { - struct omap_usb_config *config = pdev->dev.platform_data; + struct ohci_hcd *ohci = hcd_to_ohci(hcd); + struct omap_usb_config *config = hcd->self.controller->platform_data; int need_transceiver = (config->otg != 0); int ret; - dev_dbg(&pdev->dev, "starting USB Controller\n"); + dev_dbg(hcd->self.controller, "starting USB Controller\n"); if (config->otg) { ohci_to_hcd(ohci)->self.otg_port = config->otg; @@ -200,7 +211,7 @@ static int omap_start_hc(struct ohci_hcd *ohci, struct platform_device *pdev) if (ohci->transceiver) { int status = otg_set_host(ohci->transceiver, &ohci_to_hcd(ohci)->self); - dev_dbg(&pdev->dev, "init %s transceiver, status %d\n", + dev_dbg(hcd->self.controller, "init %s transceiver, status %d\n", ohci->transceiver->label, status); if (status) { if (ohci->transceiver) @@ -208,7 +219,7 @@ static int omap_start_hc(struct ohci_hcd *ohci, struct platform_device *pdev) return status; } } else { - dev_err(&pdev->dev, "can't find transceiver\n"); + dev_err(hcd->self.controller, "can't find transceiver\n"); return -ENODEV; } } @@ -247,6 +258,10 @@ static int omap_start_hc(struct ohci_hcd *ohci, struct platform_device *pdev) } ohci_writel(ohci, rh, &ohci->regs->roothub.a); distrust_firmware = 0; + } else if (machine_is_nokia770()) { + /* We require a self-powered hub, which should have + * plenty of power. */ + ohci_to_hcd(ohci)->power_budget = 0; } /* FIXME khubd hub requests should manage power switching */ @@ -260,21 +275,15 @@ static int omap_start_hc(struct ohci_hcd *ohci, struct platform_device *pdev) return 0; } -static void omap_stop_hc(struct platform_device *pdev) +static void ohci_omap_stop(struct usb_hcd *hcd) { - dev_dbg(&pdev->dev, "stopping USB Controller\n"); + dev_dbg(hcd->self.controller, "stopping USB Controller\n"); omap_ohci_clock_power(0); } /*-------------------------------------------------------------------------*/ -void usb_hcd_omap_remove (struct usb_hcd *, struct platform_device *); - -/* configure so an HC device and id are always provided */ -/* always called with process context; sleeping is OK */ - - /** * usb_hcd_omap_probe - initialize OMAP-based HCDs * Context: !in_interrupt() @@ -283,7 +292,7 @@ void usb_hcd_omap_remove (struct usb_hcd *, struct platform_device *); * then invokes the start() method for the HCD associated with it * through the hotplug entry's driver_data. */ -int usb_hcd_omap_probe (const struct hc_driver *driver, +static int usb_hcd_omap_probe (const struct hc_driver *driver, struct platform_device *pdev) { int retval, irq; @@ -291,12 +300,12 @@ int usb_hcd_omap_probe (const struct hc_driver *driver, struct ohci_hcd *ohci; if (pdev->num_resources != 2) { - printk(KERN_ERR "hcd probe: invalid num_resources: %i\n", + printk(KERN_ERR "hcd probe: invalid num_resources: %i\n", pdev->num_resources); return -ENODEV; } - if (pdev->resource[0].flags != IORESOURCE_MEM + if (pdev->resource[0].flags != IORESOURCE_MEM || pdev->resource[1].flags != IORESOURCE_IRQ) { printk(KERN_ERR "hcd probe: invalid resource type\n"); return -ENODEV; @@ -306,6 +315,17 @@ int usb_hcd_omap_probe (const struct hc_driver *driver, if (IS_ERR(usb_host_ck)) return PTR_ERR(usb_host_ck); + if (!cpu_is_omap1510()) + usb_dc_ck = clk_get(0, "usb_dc_ck"); + else + usb_dc_ck = clk_get(0, "lb_ck"); + + if (IS_ERR(usb_dc_ck)) { + clk_put(usb_host_ck); + return PTR_ERR(usb_dc_ck); + } + + hcd = usb_create_hcd (driver, &pdev->dev, pdev->dev.bus_id); if (!hcd) { retval = -ENOMEM; @@ -325,9 +345,8 @@ int usb_hcd_omap_probe (const struct hc_driver *driver, ohci = hcd_to_ohci(hcd); ohci_hcd_init(ohci); - retval = omap_start_hc(ohci, pdev); - if (retval < 0) - goto err2; + host_initialized = 0; + host_enabled = 1; irq = platform_get_irq(pdev, 0); if (irq < 0) { @@ -335,15 +354,21 @@ int usb_hcd_omap_probe (const struct hc_driver *driver, goto err2; } retval = usb_add_hcd(hcd, irq, IRQF_DISABLED); - if (retval == 0) - return retval; + if (retval) + goto err2; + + host_initialized = 1; + + if (!host_enabled) + omap_ohci_clock_power(0); - omap_stop_hc(pdev); + return 0; err2: release_mem_region(hcd->rsrc_start, hcd->rsrc_len); err1: usb_put_hcd(hcd); err0: + clk_put(usb_dc_ck); clk_put(usb_host_ck); return retval; } @@ -359,31 +384,41 @@ err0: * Reverses the effect of usb_hcd_omap_probe(), first invoking * the HCD's stop() method. It is always called from a thread * context, normally "rmmod", "apmd", or something similar. - * */ -void usb_hcd_omap_remove (struct usb_hcd *hcd, struct platform_device *pdev) +static inline void +usb_hcd_omap_remove (struct usb_hcd *hcd, struct platform_device *pdev) { + struct ohci_hcd *ohci = hcd_to_ohci (hcd); + usb_remove_hcd(hcd); + if (ohci->transceiver) { + (void) otg_set_host(ohci->transceiver, 0); + put_device(ohci->transceiver->dev); + } if (machine_is_omap_osk()) omap_free_gpio(9); - omap_stop_hc(pdev); release_mem_region(hcd->rsrc_start, hcd->rsrc_len); usb_put_hcd(hcd); + clk_put(usb_dc_ck); clk_put(usb_host_ck); } /*-------------------------------------------------------------------------*/ -static int __devinit +static int ohci_omap_start (struct usb_hcd *hcd) { struct omap_usb_config *config; struct ohci_hcd *ohci = hcd_to_ohci (hcd); int ret; + if (!host_enabled) + return 0; config = hcd->self.controller->platform_data; - if (config->otg || config->rwc) + if (config->otg || config->rwc) { + ohci->hc_control = OHCI_CTRL_RWC; writel(OHCI_CTRL_RWC, &ohci->regs->control); + } if ((ret = ohci_run (ohci)) < 0) { dev_err(hcd->self.controller, "can't start\n"); @@ -409,8 +444,10 @@ static const struct hc_driver ohci_omap_hc_driver = { /* * basic lifecycle operations */ + .reset = ohci_omap_init, .start = ohci_omap_start, - .stop = ohci_stop, + .stop = ohci_omap_stop, + .shutdown = ohci_shutdown, /* * managing i/o requests and associated device resources @@ -429,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, @@ -446,13 +484,8 @@ static int ohci_hcd_omap_drv_probe(struct platform_device *dev) static int ohci_hcd_omap_drv_remove(struct platform_device *dev) { struct usb_hcd *hcd = platform_get_drvdata(dev); - struct ohci_hcd *ohci = hcd_to_ohci (hcd); usb_hcd_omap_remove(hcd, dev); - if (ohci->transceiver) { - (void) otg_set_host(ohci->transceiver, 0); - put_device(ohci->transceiver->dev); - } platform_set_drvdata(dev, NULL); return 0; @@ -472,7 +505,7 @@ static int ohci_omap_suspend(struct platform_device *dev, pm_message_t message) omap_ohci_clock_power(0); ohci_to_hcd(ohci)->state = HC_STATE_SUSPENDED; - dev->power.power_state = PMSG_SUSPEND; + dev->dev.power.power_state = PMSG_SUSPEND; return 0; } @@ -485,8 +518,8 @@ static int ohci_omap_resume(struct platform_device *dev) ohci->next_statechange = jiffies; omap_ohci_clock_power(1); - dev->power.power_state = PMSG_ON; - usb_hcd_resume_root_hub(dev_get_drvdata(dev)); + dev->dev.power.power_state = PMSG_ON; + usb_hcd_resume_root_hub(platform_get_drvdata(dev)); return 0; } @@ -500,6 +533,7 @@ static int ohci_omap_resume(struct platform_device *dev) static struct platform_driver ohci_hcd_omap_driver = { .probe = ohci_hcd_omap_drv_probe, .remove = ohci_hcd_omap_drv_remove, + .shutdown = usb_hcd_platform_shutdown, #ifdef CONFIG_PM .suspend = ohci_omap_suspend, .resume = ohci_omap_resume, diff --git a/drivers/usb/host/ohci-pci.c b/drivers/usb/host/ohci-pci.c index b268537e389..87441855278 100644 --- a/drivers/usb/host/ohci-pci.c +++ b/drivers/usb/host/ohci-pci.c @@ -73,13 +73,14 @@ ohci_pci_start (struct usb_hcd *hcd) else if (pdev->vendor == PCI_VENDOR_ID_NS) { struct pci_dev *b; - b = pci_find_slot (pdev->bus->number, + b = pci_get_slot (pdev->bus, PCI_DEVFN (PCI_SLOT (pdev->devfn), 1)); if (b && b->device == PCI_DEVICE_ID_NS_87560_LIO && b->vendor == PCI_VENDOR_ID_NS) { ohci->flags |= OHCI_QUIRK_SUPERIO; ohci_dbg (ohci, "Using NSC SuperIO setup\n"); } + pci_dev_put(b); } /* Check for Compaq's ZFMicro chipset, which needs short @@ -135,6 +136,11 @@ static int ohci_pci_suspend (struct usb_hcd *hcd, pm_message_t message) } ohci_writel(ohci, OHCI_INTR_MIE, &ohci->regs->intrdisable); (void)ohci_readl(ohci, &ohci->regs->intrdisable); + + /* make sure snapshot being resumed re-enumerates everything */ + if (message.event == PM_EVENT_PRETHAW) + ohci_usb_reset(ohci); + clear_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); bail: spin_unlock_irqrestore (&ohci->lock, flags); @@ -171,11 +177,14 @@ static const struct hc_driver ohci_pci_hc_driver = { */ .reset = ohci_pci_reset, .start = ohci_pci_start, + .stop = ohci_stop, + .shutdown = ohci_shutdown, + #ifdef CONFIG_PM + /* these suspend/resume entries are for upstream PCI glue ONLY */ .suspend = ohci_pci_suspend, .resume = ohci_pci_resume, #endif - .stop = ohci_stop, /* * managing i/o requests and associated device resources @@ -194,6 +203,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, @@ -224,6 +234,8 @@ static struct pci_driver ohci_pci_driver = { .suspend = usb_hcd_pci_suspend, .resume = usb_hcd_pci_resume, #endif + + .shutdown = usb_hcd_pci_shutdown, }; diff --git a/drivers/usb/host/ohci-pnx4008.c b/drivers/usb/host/ohci-pnx4008.c new file mode 100644 index 00000000000..82cb22f002e --- /dev/null +++ b/drivers/usb/host/ohci-pnx4008.c @@ -0,0 +1,476 @@ +/* + * drivers/usb/host/ohci-pnx4008.c + * + * driver for Philips PNX4008 USB Host + * + * Authors: Dmitry Chigirev <source@mvista.com> + * Vitaly Wool <vitalywool@gmail.com> + * + * register initialization is based on code examples provided by Philips + * Copyright (c) 2005 Koninklijke Philips Electronics N.V. + * + * NOTE: This driver does not have suspend/resume functionality + * This driver is intended for engineering development purposes only + * + * 2005-2006 (c) MontaVista Software, Inc. This file is licensed under + * the terms of the GNU General Public License version 2. This program + * is licensed "as is" without any warranty of any kind, whether express + * or implied. + */ +#include <linux/clk.h> +#include <linux/platform_device.h> +#include <linux/i2c.h> + +#include <asm/hardware.h> +#include <asm/io.h> +#include <asm/mach-types.h> + +#include <asm/arch/platform.h> +#include <asm/arch/irqs.h> +#include <asm/arch/gpio.h> + +#define USB_CTRL IO_ADDRESS(PNX4008_PWRMAN_BASE + 0x64) + +/* USB_CTRL bit defines */ +#define USB_SLAVE_HCLK_EN (1 << 24) +#define USB_HOST_NEED_CLK_EN (1 << 21) + +#define USB_OTG_CLK_CTRL IO_ADDRESS(PNX4008_USB_CONFIG_BASE + 0xFF4) +#define USB_OTG_CLK_STAT IO_ADDRESS(PNX4008_USB_CONFIG_BASE + 0xFF8) + +/* USB_OTG_CLK_CTRL bit defines */ +#define AHB_M_CLOCK_ON (1 << 4) +#define OTG_CLOCK_ON (1 << 3) +#define I2C_CLOCK_ON (1 << 2) +#define DEV_CLOCK_ON (1 << 1) +#define HOST_CLOCK_ON (1 << 0) + +#define USB_OTG_STAT_CONTROL IO_ADDRESS(PNX4008_USB_CONFIG_BASE + 0x110) + +/* USB_OTG_STAT_CONTROL bit defines */ +#define TRANSPARENT_I2C_EN (1 << 7) +#define HOST_EN (1 << 0) + +/* ISP1301 USB transceiver I2C registers */ +#define ISP1301_MODE_CONTROL_1 0x04 /* u8 read, set, +1 clear */ + +#define MC1_SPEED_REG (1 << 0) +#define MC1_SUSPEND_REG (1 << 1) +#define MC1_DAT_SE0 (1 << 2) +#define MC1_TRANSPARENT (1 << 3) +#define MC1_BDIS_ACON_EN (1 << 4) +#define MC1_OE_INT_EN (1 << 5) +#define MC1_UART_EN (1 << 6) +#define MC1_MASK 0x7f + +#define ISP1301_MODE_CONTROL_2 0x12 /* u8 read, set, +1 clear */ + +#define MC2_GLOBAL_PWR_DN (1 << 0) +#define MC2_SPD_SUSP_CTRL (1 << 1) +#define MC2_BI_DI (1 << 2) +#define MC2_TRANSP_BDIR0 (1 << 3) +#define MC2_TRANSP_BDIR1 (1 << 4) +#define MC2_AUDIO_EN (1 << 5) +#define MC2_PSW_EN (1 << 6) +#define MC2_EN2V7 (1 << 7) + +#define ISP1301_OTG_CONTROL_1 0x06 /* u8 read, set, +1 clear */ +# define OTG1_DP_PULLUP (1 << 0) +# define OTG1_DM_PULLUP (1 << 1) +# define OTG1_DP_PULLDOWN (1 << 2) +# define OTG1_DM_PULLDOWN (1 << 3) +# define OTG1_ID_PULLDOWN (1 << 4) +# define OTG1_VBUS_DRV (1 << 5) +# define OTG1_VBUS_DISCHRG (1 << 6) +# define OTG1_VBUS_CHRG (1 << 7) +#define ISP1301_OTG_STATUS 0x10 /* u8 readonly */ +# define OTG_B_SESS_END (1 << 6) +# define OTG_B_SESS_VLD (1 << 7) + +#define ISP1301_I2C_ADDR 0x2C + +#define ISP1301_I2C_MODE_CONTROL_1 0x4 +#define ISP1301_I2C_MODE_CONTROL_2 0x12 +#define ISP1301_I2C_OTG_CONTROL_1 0x6 +#define ISP1301_I2C_OTG_CONTROL_2 0x10 +#define ISP1301_I2C_INTERRUPT_SOURCE 0x8 +#define ISP1301_I2C_INTERRUPT_LATCH 0xA +#define ISP1301_I2C_INTERRUPT_FALLING 0xC +#define ISP1301_I2C_INTERRUPT_RISING 0xE +#define ISP1301_I2C_REG_CLEAR_ADDR 1 + +struct i2c_driver isp1301_driver; +struct i2c_client *isp1301_i2c_client; + +extern int usb_disabled(void); +extern int ocpi_enable(void); + +static struct clk *usb_clk; + +static int isp1301_probe(struct i2c_adapter *adap); +static int isp1301_detach(struct i2c_client *client); +static int isp1301_command(struct i2c_client *client, unsigned int cmd, + void *arg); + +static unsigned short normal_i2c[] = + { ISP1301_I2C_ADDR, ISP1301_I2C_ADDR + 1, I2C_CLIENT_END }; +static unsigned short dummy_i2c_addrlist[] = { I2C_CLIENT_END }; + +static struct i2c_client_address_data addr_data = { + .normal_i2c = normal_i2c, + .probe = dummy_i2c_addrlist, + .ignore = dummy_i2c_addrlist, +}; + +struct i2c_driver isp1301_driver = { + .id = I2C_DRIVERID_I2CDEV, /* Fake Id */ + .class = I2C_CLASS_HWMON, + .attach_adapter = isp1301_probe, + .detach_client = isp1301_detach, + .command = isp1301_command +}; + +static int isp1301_attach(struct i2c_adapter *adap, int addr, int kind) +{ + struct i2c_client *c; + + c = (struct i2c_client *)kzalloc(sizeof(*c), SLAB_KERNEL); + + if (!c) + return -ENOMEM; + + strcpy(c->name, "isp1301"); + c->flags = 0; + c->addr = addr; + c->adapter = adap; + c->driver = &isp1301_driver; + + isp1301_i2c_client = c; + + return i2c_attach_client(c); +} + +static int isp1301_probe(struct i2c_adapter *adap) +{ + return i2c_probe(adap, &addr_data, isp1301_attach); +} + +static int isp1301_detach(struct i2c_client *client) +{ + i2c_detach_client(client); + kfree(isp1301_i2c_client); + return 0; +} + +/* No commands defined */ +static int isp1301_command(struct i2c_client *client, unsigned int cmd, + void *arg) +{ + return 0; +} + +static void i2c_write(u8 buf, u8 subaddr) +{ + char tmpbuf[2]; + + tmpbuf[0] = subaddr; /*register number */ + tmpbuf[1] = buf; /*register data */ + i2c_master_send(isp1301_i2c_client, &tmpbuf[0], 2); +} + +static void isp1301_configure(void) +{ + /* PNX4008 only supports DAT_SE0 USB mode */ + /* PNX4008 R2A requires setting the MAX603 to output 3.6V */ + /* Power up externel charge-pump */ + + i2c_write(MC1_DAT_SE0 | MC1_SPEED_REG, ISP1301_I2C_MODE_CONTROL_1); + i2c_write(~(MC1_DAT_SE0 | MC1_SPEED_REG), + ISP1301_I2C_MODE_CONTROL_1 | ISP1301_I2C_REG_CLEAR_ADDR); + i2c_write(MC2_BI_DI | MC2_PSW_EN | MC2_SPD_SUSP_CTRL, + ISP1301_I2C_MODE_CONTROL_2); + i2c_write(~(MC2_BI_DI | MC2_PSW_EN | MC2_SPD_SUSP_CTRL), + ISP1301_I2C_MODE_CONTROL_2 | ISP1301_I2C_REG_CLEAR_ADDR); + i2c_write(OTG1_DM_PULLDOWN | OTG1_DP_PULLDOWN, + ISP1301_I2C_OTG_CONTROL_1); + i2c_write(~(OTG1_DM_PULLDOWN | OTG1_DP_PULLDOWN), + ISP1301_I2C_OTG_CONTROL_1 | ISP1301_I2C_REG_CLEAR_ADDR); + i2c_write(0xFF, + ISP1301_I2C_INTERRUPT_LATCH | ISP1301_I2C_REG_CLEAR_ADDR); + i2c_write(0xFF, + ISP1301_I2C_INTERRUPT_FALLING | ISP1301_I2C_REG_CLEAR_ADDR); + i2c_write(0xFF, + ISP1301_I2C_INTERRUPT_RISING | ISP1301_I2C_REG_CLEAR_ADDR); + +} + +static inline void isp1301_vbus_on(void) +{ + i2c_write(OTG1_VBUS_DRV, ISP1301_I2C_OTG_CONTROL_1); +} + +static inline void isp1301_vbus_off(void) +{ + i2c_write(OTG1_VBUS_DRV, + ISP1301_I2C_OTG_CONTROL_1 | ISP1301_I2C_REG_CLEAR_ADDR); +} + +static void pnx4008_start_hc(void) +{ + unsigned long tmp = __raw_readl(USB_OTG_STAT_CONTROL) | HOST_EN; + __raw_writel(tmp, USB_OTG_STAT_CONTROL); + isp1301_vbus_on(); +} + +static void pnx4008_stop_hc(void) +{ + unsigned long tmp; + isp1301_vbus_off(); + tmp = __raw_readl(USB_OTG_STAT_CONTROL) & ~HOST_EN; + __raw_writel(tmp, USB_OTG_STAT_CONTROL); +} + +static int __devinit ohci_pnx4008_start(struct usb_hcd *hcd) +{ + struct ohci_hcd *ohci = hcd_to_ohci(hcd); + int ret; + + if ((ret = ohci_init(ohci)) < 0) + return ret; + + if ((ret = ohci_run(ohci)) < 0) { + dev_err(hcd->self.controller, "can't start\n"); + ohci_stop(hcd); + return ret; + } + return 0; +} + +static const struct hc_driver ohci_pnx4008_hc_driver = { + .description = hcd_name, + .product_desc = "pnx4008 OHCI", + + /* + * generic hardware linkage + */ + .irq = ohci_irq, + .flags = HCD_USB11 | HCD_MEMORY, + + .hcd_priv_size = sizeof(struct ohci_hcd), + /* + * basic lifecycle operations + */ + .start = ohci_pnx4008_start, + .stop = ohci_stop, + + /* + * managing i/o requests and associated device resources + */ + .urb_enqueue = ohci_urb_enqueue, + .urb_dequeue = ohci_urb_dequeue, + .endpoint_disable = ohci_endpoint_disable, + + /* + * scheduling support + */ + .get_frame_number = ohci_get_frame, + + /* + * root hub support + */ + .hub_status_data = ohci_hub_status_data, + .hub_control = ohci_hub_control, + + .start_port_reset = ohci_start_port_reset, +}; + +#define USB_CLOCK_MASK (AHB_M_CLOCK_ON| OTG_CLOCK_ON | HOST_CLOCK_ON | I2C_CLOCK_ON) + +static void pnx4008_set_usb_bits(void) +{ + start_int_set_falling_edge(SE_USB_OTG_ATX_INT_N); + start_int_ack(SE_USB_OTG_ATX_INT_N); + start_int_umask(SE_USB_OTG_ATX_INT_N); + + start_int_set_rising_edge(SE_USB_OTG_TIMER_INT); + start_int_ack(SE_USB_OTG_TIMER_INT); + start_int_umask(SE_USB_OTG_TIMER_INT); + + start_int_set_rising_edge(SE_USB_I2C_INT); + start_int_ack(SE_USB_I2C_INT); + start_int_umask(SE_USB_I2C_INT); + + start_int_set_rising_edge(SE_USB_INT); + start_int_ack(SE_USB_INT); + start_int_umask(SE_USB_INT); + + start_int_set_rising_edge(SE_USB_NEED_CLK_INT); + start_int_ack(SE_USB_NEED_CLK_INT); + start_int_umask(SE_USB_NEED_CLK_INT); + + start_int_set_rising_edge(SE_USB_AHB_NEED_CLK_INT); + start_int_ack(SE_USB_AHB_NEED_CLK_INT); + start_int_umask(SE_USB_AHB_NEED_CLK_INT); +} + +static void pnx4008_unset_usb_bits(void) +{ + start_int_mask(SE_USB_OTG_ATX_INT_N); + start_int_mask(SE_USB_OTG_TIMER_INT); + start_int_mask(SE_USB_I2C_INT); + start_int_mask(SE_USB_INT); + start_int_mask(SE_USB_NEED_CLK_INT); + start_int_mask(SE_USB_AHB_NEED_CLK_INT); +} + +static int __devinit usb_hcd_pnx4008_probe(struct platform_device *pdev) +{ + struct usb_hcd *hcd = 0; + struct ohci_hcd *ohci; + const struct hc_driver *driver = &ohci_pnx4008_hc_driver; + + int ret = 0, irq; + + dev_dbg(&pdev->dev, "%s: " DRIVER_INFO " (pnx4008)\n", hcd_name); + if (usb_disabled()) { + err("USB is disabled"); + ret = -ENODEV; + goto out; + } + + if (pdev->num_resources != 2 + || pdev->resource[0].flags != IORESOURCE_MEM + || pdev->resource[1].flags != IORESOURCE_IRQ) { + err("Invalid resource configuration"); + ret = -ENODEV; + goto out; + } + + /* Enable AHB slave USB clock, needed for further USB clock control */ + __raw_writel(USB_SLAVE_HCLK_EN | (1 << 19), USB_CTRL); + + ret = i2c_add_driver(&isp1301_driver); + if (ret < 0) { + err("failed to connect I2C to ISP1301 USB Transceiver"); + goto out; + } + + isp1301_configure(); + + /* Enable USB PLL */ + usb_clk = clk_get(&pdev->dev, "ck_pll5"); + if (IS_ERR(usb_clk)) { + err("failed to acquire USB PLL"); + ret = PTR_ERR(usb_clk); + goto out1; + } + + ret = clk_enable(usb_clk); + if (ret < 0) { + err("failed to start USB PLL"); + goto out2; + } + + ret = clk_set_rate(usb_clk, 48000); + if (ret < 0) { + err("failed to set USB clock rate"); + goto out3; + } + + __raw_writel(__raw_readl(USB_CTRL) | USB_HOST_NEED_CLK_EN, USB_CTRL); + + /* Set to enable all needed USB clocks */ + __raw_writel(USB_CLOCK_MASK, USB_OTG_CLK_CTRL); + + while ((__raw_readl(USB_OTG_CLK_STAT) & USB_CLOCK_MASK) != + USB_CLOCK_MASK) ; + + hcd = usb_create_hcd (driver, &pdev->dev, pdev->dev.bus_id); + if (!hcd) { + err("Failed to allocate HC buffer"); + ret = -ENOMEM; + goto out3; + } + + /* Set all USB bits in the Start Enable register */ + pnx4008_set_usb_bits(); + + hcd->rsrc_start = pdev->resource[0].start; + hcd->rsrc_len = pdev->resource[0].end - pdev->resource[0].start + 1; + if (!request_mem_region(hcd->rsrc_start, hcd->rsrc_len, hcd_name)) { + dev_dbg(&pdev->dev, "request_mem_region failed\n"); + ret = -ENOMEM; + goto out4; + } + hcd->regs = (void __iomem *)pdev->resource[0].start; + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + ret = -ENXIO; + goto out4; + } + + hcd->self.hcpriv = (void *)hcd; + + pnx4008_start_hc(); + platform_set_drvdata(pdev, hcd); + ohci = hcd_to_ohci(hcd); + ohci_hcd_init(ohci); + + dev_info(&pdev->dev, "at 0x%p, irq %d\n", hcd->regs, hcd->irq); + ret = usb_add_hcd(hcd, irq, SA_INTERRUPT); + if (ret == 0) + return ret; + + pnx4008_stop_hc(); +out4: + pnx4008_unset_usb_bits(); + usb_put_hcd(hcd); +out3: + clk_disable(usb_clk); +out2: + clk_put(usb_clk); +out1: + i2c_del_driver(&isp1301_driver); +out: + return ret; +} + +static int usb_hcd_pnx4008_remove(struct platform_device *pdev) +{ + struct usb_hcd *hcd = platform_get_drvdata(pdev); + + usb_remove_hcd(hcd); + pnx4008_stop_hc(); + release_mem_region(hcd->rsrc_start, hcd->rsrc_len); + usb_put_hcd(hcd); + pnx4008_unset_usb_bits(); + clk_disable(usb_clk); + clk_put(usb_clk); + i2c_del_driver(&isp1301_driver); + + platform_set_drvdata(pdev, NULL); + + return 0; +} + +static struct platform_driver usb_hcd_pnx4008_driver = { + .driver = { + .name = "usb-ohci", + }, + .probe = usb_hcd_pnx4008_probe, + .remove = usb_hcd_pnx4008_remove, +}; + +static int __init usb_hcd_pnx4008_init(void) +{ + return platform_driver_register(&usb_hcd_pnx4008_driver); +} + +static void __exit usb_hcd_pnx4008_cleanup(void) +{ + return platform_driver_unregister(&usb_hcd_pnx4008_driver); +} + +module_init(usb_hcd_pnx4008_init); +module_exit(usb_hcd_pnx4008_cleanup); diff --git a/drivers/usb/host/ohci-ppc-soc.c b/drivers/usb/host/ohci-ppc-soc.c index 9fe56ff1615..d9d1ae236bd 100644 --- a/drivers/usb/host/ohci-ppc-soc.c +++ b/drivers/usb/host/ohci-ppc-soc.c @@ -148,6 +148,7 @@ static const struct hc_driver ohci_ppc_soc_hc_driver = { */ .start = ohci_ppc_soc_start, .stop = ohci_stop, + .shutdown = ohci_shutdown, /* * managing i/o requests and associated device resources @@ -166,6 +167,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, @@ -195,6 +197,7 @@ static int ohci_hcd_ppc_soc_drv_remove(struct platform_device *pdev) static struct platform_driver ohci_hcd_ppc_soc_driver = { .probe = ohci_hcd_ppc_soc_drv_probe, .remove = ohci_hcd_ppc_soc_drv_remove, + .shutdown = usb_hcd_platform_shutdown, #ifdef CONFIG_PM /*.suspend = ohci_hcd_ppc_soc_drv_suspend,*/ /*.resume = ohci_hcd_ppc_soc_drv_resume,*/ diff --git a/drivers/usb/host/ohci-pxa27x.c b/drivers/usb/host/ohci-pxa27x.c index 6f559e10278..e176b04d7ae 100644 --- a/drivers/usb/host/ohci-pxa27x.c +++ b/drivers/usb/host/ohci-pxa27x.c @@ -270,6 +270,7 @@ static const struct hc_driver ohci_pxa27x_hc_driver = { */ .start = ohci_pxa27x_start, .stop = ohci_stop, + .shutdown = ohci_shutdown, /* * managing i/o requests and associated device resources @@ -288,6 +289,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, @@ -357,6 +359,7 @@ static int ohci_hcd_pxa27x_drv_resume(struct platform_device *pdev) static struct platform_driver ohci_hcd_pxa27x_driver = { .probe = ohci_hcd_pxa27x_drv_probe, .remove = ohci_hcd_pxa27x_drv_remove, + .shutdown = usb_hcd_platform_shutdown, #ifdef CONFIG_PM .suspend = ohci_hcd_pxa27x_drv_suspend, .resume = ohci_hcd_pxa27x_drv_resume, diff --git a/drivers/usb/host/ohci-s3c2410.c b/drivers/usb/host/ohci-s3c2410.c index d2fc6969a9f..59e436424d4 100644 --- a/drivers/usb/host/ohci-s3c2410.c +++ b/drivers/usb/host/ohci-s3c2410.c @@ -370,7 +370,7 @@ static int usb_hcd_s3c2410_probe (const struct hc_driver *driver, goto err_mem; } - usb_clk = clk_get(&dev->dev, "upll"); + usb_clk = clk_get(&dev->dev, "usb-bus-host"); if (IS_ERR(usb_clk)) { dev_err(&dev->dev, "cannot get usb-host clock\n"); retval = -ENOENT; @@ -447,6 +447,7 @@ static const struct hc_driver ohci_s3c2410_hc_driver = { */ .start = ohci_s3c2410_start, .stop = ohci_stop, + .shutdown = ohci_shutdown, /* * managing i/o requests and associated device resources @@ -465,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, @@ -490,6 +492,7 @@ static int ohci_hcd_s3c2410_drv_remove(struct platform_device *pdev) static struct platform_driver ohci_hcd_s3c2410_driver = { .probe = ohci_hcd_s3c2410_drv_probe, .remove = ohci_hcd_s3c2410_drv_remove, + .shutdown = usb_hcd_platform_shutdown, /*.suspend = ohci_hcd_s3c2410_drv_suspend, */ /*.resume = ohci_hcd_s3c2410_drv_resume, */ .driver = { diff --git a/drivers/usb/host/ohci-sa1111.c b/drivers/usb/host/ohci-sa1111.c index ce3de106cad..71371de32ad 100644 --- a/drivers/usb/host/ohci-sa1111.c +++ b/drivers/usb/host/ohci-sa1111.c @@ -212,10 +212,6 @@ static const struct hc_driver ohci_sa1111_hc_driver = { * basic lifecycle operations */ .start = ohci_sa1111_start, -#ifdef CONFIG_PM - /* suspend: ohci_sa1111_suspend, -- tbd */ - /* resume: ohci_sa1111_resume, -- tbd */ -#endif .stop = ohci_stop, /* @@ -235,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.h b/drivers/usb/host/ohci.h index caacf14371f..a2f42a2f47c 100644 --- a/drivers/usb/host/ohci.h +++ b/drivers/usb/host/ohci.h @@ -159,7 +159,7 @@ static const int cc_to_error [16] = { /* Bit Stuff */ -EPROTO, /* Data Togg */ -EILSEQ, /* Stall */ -EPIPE, - /* DevNotResp */ -ETIMEDOUT, + /* DevNotResp */ -ETIME, /* PIDCheck */ -EPROTO, /* UnExpPID */ -EPROTO, /* DataOver */ -EOVERFLOW, @@ -388,8 +388,7 @@ struct ohci_hcd { u32 hc_control; /* copy of hc control reg */ unsigned long next_statechange; /* suspend/resume */ u32 fminterval; /* saved register */ - - struct notifier_block reboot_notifier; + unsigned autostop:1; /* rh auto stopping/stopped */ unsigned long flags; /* for HC bugs */ #define OHCI_QUIRK_AMD756 0x01 /* erratum #4 */ diff --git a/drivers/usb/host/sl811-hcd.c b/drivers/usb/host/sl811-hcd.c index fa34092bbcd..3a586aab393 100644 --- a/drivers/usb/host/sl811-hcd.c +++ b/drivers/usb/host/sl811-hcd.c @@ -597,7 +597,7 @@ done(struct sl811 *sl811, struct sl811h_ep *ep, u8 bank, struct pt_regs *regs) /* error? retry, until "3 strikes" */ } else if (++ep->error_count >= 3) { if (status & SL11H_STATMASK_TMOUT) - urbstat = -ETIMEDOUT; + urbstat = -ETIME; else if (status & SL11H_STATMASK_OVF) urbstat = -EOVERFLOW; else @@ -1517,7 +1517,7 @@ static int proc_sl811h_open(struct inode *inode, struct file *file) return single_open(file, proc_sl811h_show, PDE(inode)->data); } -static struct file_operations proc_ops = { +static const struct file_operations proc_ops = { .open = proc_sl811h_open, .read = seq_read, .llseek = seq_lseek, @@ -1783,10 +1783,15 @@ sl811h_suspend(struct platform_device *dev, pm_message_t state) struct sl811 *sl811 = hcd_to_sl811(hcd); int retval = 0; - if (state.event == PM_EVENT_FREEZE) + switch (state.event) { + case PM_EVENT_FREEZE: retval = sl811h_bus_suspend(hcd); - else if (state.event == PM_EVENT_SUSPEND) + break; + case PM_EVENT_SUSPEND: + case PM_EVENT_PRETHAW: /* explicitly discard hw state */ port_power(sl811, 0); + break; + } if (retval == 0) dev->dev.power.power_state = state; return retval; diff --git a/drivers/usb/host/u132-hcd.c b/drivers/usb/host/u132-hcd.c new file mode 100644 index 00000000000..cb2e2a604d1 --- /dev/null +++ b/drivers/usb/host/u132-hcd.c @@ -0,0 +1,3295 @@ +/* +* Host Controller Driver for the Elan Digital Systems U132 adapter +* +* Copyright(C) 2006 Elan Digital Systems Limited +* http://www.elandigitalsystems.com +* +* Author and Maintainer - Tony Olech - Elan Digital Systems +* tony.olech@elandigitalsystems.com +* +* This program is free software;you can redistribute it and/or +* modify it under the terms of the GNU General Public License as +* published by the Free Software Foundation, version 2. +* +* +* This driver was written by Tony Olech(tony.olech@elandigitalsystems.com) +* based on various USB host drivers in the 2.6.15 linux kernel +* with constant reference to the 3rd Edition of Linux Device Drivers +* published by O'Reilly +* +* The U132 adapter is a USB to CardBus adapter specifically designed +* for PC cards that contain an OHCI host controller. Typical PC cards +* are the Orange Mobile 3G Option GlobeTrotter Fusion card. +* +* The U132 adapter will *NOT *work with PC cards that do not contain +* an OHCI controller. A simple way to test whether a PC card has an +* OHCI controller as an interface is to insert the PC card directly +* into a laptop(or desktop) with a CardBus slot and if "lspci" shows +* a new USB controller and "lsusb -v" shows a new OHCI Host Controller +* then there is a good chance that the U132 adapter will support the +* PC card.(you also need the specific client driver for the PC card) +* +* Please inform the Author and Maintainer about any PC cards that +* contain OHCI Host Controller and work when directly connected to +* an embedded CardBus slot but do not work when they are connected +* via an ELAN U132 adapter. +* +*/ +#include <linux/config.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/delay.h> +#include <linux/ioport.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/smp_lock.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/timer.h> +#include <linux/list.h> +#include <linux/interrupt.h> +#include <linux/usb.h> +#include <linux/workqueue.h> +#include <linux/platform_device.h> +#include <linux/pci_ids.h> +#include <asm/io.h> +#include <asm/irq.h> +#include <asm/system.h> +#include <asm/byteorder.h> +#include "../core/hcd.h" +#include "ohci.h" +#define OHCI_CONTROL_INIT OHCI_CTRL_CBSR +#define OHCI_INTR_INIT (OHCI_INTR_MIE | OHCI_INTR_UE | OHCI_INTR_RD | \ + OHCI_INTR_WDH) +MODULE_AUTHOR("Tony Olech - Elan Digital Systems Limited"); +MODULE_DESCRIPTION("U132 USB Host Controller Driver"); +MODULE_LICENSE("GPL"); +#define INT_MODULE_PARM(n, v) static int n = v;module_param(n, int, 0444) +INT_MODULE_PARM(testing, 0); +/* Some boards misreport power switching/overcurrent*/ +static int distrust_firmware = 1; +module_param(distrust_firmware, bool, 0); +MODULE_PARM_DESC(distrust_firmware, "true to distrust firmware power/overcurren" + "t setup"); +DECLARE_WAIT_QUEUE_HEAD(u132_hcd_wait); +/* +* u132_module_lock exists to protect access to global variables +* +*/ +static struct semaphore u132_module_lock; +static int u132_exiting = 0; +static int u132_instances = 0; +static struct list_head u132_static_list; +/* +* end of the global variables protected by u132_module_lock +*/ +static struct workqueue_struct *workqueue; +#define MAX_U132_PORTS 7 +#define MAX_U132_ADDRS 128 +#define MAX_U132_UDEVS 4 +#define MAX_U132_ENDPS 100 +#define MAX_U132_RINGS 4 +static const char *cc_to_text[16] = { + "No Error ", + "CRC Error ", + "Bit Stuff ", + "Data Togg ", + "Stall ", + "DevNotResp ", + "PIDCheck ", + "UnExpPID ", + "DataOver ", + "DataUnder ", + "(for hw) ", + "(for hw) ", + "BufferOver ", + "BuffUnder ", + "(for HCD) ", + "(for HCD) " +}; +struct u132_port { + struct u132 *u132; + int reset; + int enable; + int power; + int Status; +}; +struct u132_addr { + u8 address; +}; +struct u132_udev { + struct kref kref; + struct usb_device *usb_device; + u8 enumeration; + u8 udev_number; + u8 usb_addr; + u8 portnumber; + u8 endp_number_in[16]; + u8 endp_number_out[16]; +}; +#define ENDP_QUEUE_SHIFT 3 +#define ENDP_QUEUE_SIZE (1<<ENDP_QUEUE_SHIFT) +#define ENDP_QUEUE_MASK (ENDP_QUEUE_SIZE-1) +struct u132_urbq { + struct list_head urb_more; + struct urb *urb; +}; +struct u132_spin { + spinlock_t slock; +}; +struct u132_endp { + struct kref kref; + u8 udev_number; + u8 endp_number; + u8 usb_addr; + u8 usb_endp; + struct u132 *u132; + struct list_head endp_ring; + struct u132_ring *ring; + unsigned toggle_bits:2; + unsigned active:1; + unsigned delayed:1; + unsigned input:1; + unsigned output:1; + unsigned pipetype:2; + unsigned dequeueing:1; + unsigned edset_flush:1; + unsigned spare_bits:14; + unsigned long jiffies; + struct usb_host_endpoint *hep; + struct u132_spin queue_lock; + u16 queue_size; + u16 queue_last; + u16 queue_next; + struct urb *urb_list[ENDP_QUEUE_SIZE]; + struct list_head urb_more; + struct work_struct scheduler; +}; +struct u132_ring { + unsigned in_use:1; + unsigned length:7; + u8 number; + struct u132 *u132; + struct u132_endp *curr_endp; + struct work_struct scheduler; +}; +#define OHCI_QUIRK_AMD756 0x01 +#define OHCI_QUIRK_SUPERIO 0x02 +#define OHCI_QUIRK_INITRESET 0x04 +#define OHCI_BIG_ENDIAN 0x08 +#define OHCI_QUIRK_ZFMICRO 0x10 +struct u132 { + struct kref kref; + struct list_head u132_list; + struct semaphore sw_lock; + struct semaphore scheduler_lock; + struct u132_platform_data *board; + struct platform_device *platform_dev; + struct u132_ring ring[MAX_U132_RINGS]; + int sequence_num; + int going; + int power; + int reset; + int num_ports; + u32 hc_control; + u32 hc_fminterval; + u32 hc_roothub_status; + u32 hc_roothub_a; + u32 hc_roothub_portstatus[MAX_ROOT_PORTS]; + int flags; + unsigned long next_statechange; + struct work_struct monitor; + int num_endpoints; + struct u132_addr addr[MAX_U132_ADDRS]; + struct u132_udev udev[MAX_U132_UDEVS]; + struct u132_port port[MAX_U132_PORTS]; + struct u132_endp *endp[MAX_U132_ENDPS]; +}; +int usb_ftdi_elan_read_reg(struct platform_device *pdev, u32 *data); +int usb_ftdi_elan_read_pcimem(struct platform_device *pdev, u8 addressofs, + u8 width, u32 *data); +int usb_ftdi_elan_write_pcimem(struct platform_device *pdev, u8 addressofs, + u8 width, u32 data); +/* +* these can not be inlines because we need the structure offset!! +* Does anyone have a better way????? +*/ +#define u132_read_pcimem(u132, member, data) \ + usb_ftdi_elan_read_pcimem(u132->platform_dev, offsetof(struct \ + ohci_regs, member), 0, data); +#define u132_write_pcimem(u132, member, data) \ + usb_ftdi_elan_write_pcimem(u132->platform_dev, offsetof(struct \ + ohci_regs, member), 0, data); +#define u132_write_pcimem_byte(u132, member, data) \ + usb_ftdi_elan_write_pcimem(u132->platform_dev, offsetof(struct \ + ohci_regs, member), 0x0e, data); +static inline struct u132 *udev_to_u132(struct u132_udev *udev) +{ + u8 udev_number = udev->udev_number; + return container_of(udev, struct u132, udev[udev_number]); +} + +static inline struct u132 *hcd_to_u132(struct usb_hcd *hcd) +{ + return (struct u132 *)(hcd->hcd_priv); +} + +static inline struct usb_hcd *u132_to_hcd(struct u132 *u132) +{ + return container_of((void *)u132, struct usb_hcd, hcd_priv); +} + +static inline void u132_disable(struct u132 *u132) +{ + u132_to_hcd(u132)->state = HC_STATE_HALT; +} + + +#define kref_to_u132(d) container_of(d, struct u132, kref) +#define kref_to_u132_endp(d) container_of(d, struct u132_endp, kref) +#define kref_to_u132_udev(d) container_of(d, struct u132_udev, kref) +#include "../misc/usb_u132.h" +static const char hcd_name[] = "u132_hcd"; +#define PORT_C_MASK ((USB_PORT_STAT_C_CONNECTION | USB_PORT_STAT_C_ENABLE | \ + USB_PORT_STAT_C_SUSPEND | USB_PORT_STAT_C_OVERCURRENT | \ + USB_PORT_STAT_C_RESET) << 16) +static void u132_hcd_delete(struct kref *kref) +{ + struct u132 *u132 = kref_to_u132(kref); + struct platform_device *pdev = u132->platform_dev; + struct usb_hcd *hcd = u132_to_hcd(u132); + u132->going += 1; + down(&u132_module_lock); + list_del_init(&u132->u132_list); + u132_instances -= 1; + up(&u132_module_lock); + dev_warn(&u132->platform_dev->dev, "FREEING the hcd=%p and thus the u13" + "2=%p going=%d pdev=%p\n", hcd, u132, u132->going, pdev); + usb_put_hcd(hcd); +} + +static inline void u132_u132_put_kref(struct u132 *u132) +{ + kref_put(&u132->kref, u132_hcd_delete); +} + +static inline void u132_u132_init_kref(struct u132 *u132) +{ + kref_init(&u132->kref); +} + +static void u132_udev_delete(struct kref *kref) +{ + struct u132_udev *udev = kref_to_u132_udev(kref); + udev->udev_number = 0; + udev->usb_device = NULL; + udev->usb_addr = 0; + udev->enumeration = 0; +} + +static inline void u132_udev_put_kref(struct u132 *u132, struct u132_udev *udev) +{ + kref_put(&udev->kref, u132_udev_delete); +} + +static inline void u132_udev_get_kref(struct u132 *u132, struct u132_udev *udev) +{ + kref_get(&udev->kref); +} + +static inline void u132_udev_init_kref(struct u132 *u132, + struct u132_udev *udev) +{ + kref_init(&udev->kref); +} + +static inline void u132_ring_put_kref(struct u132 *u132, struct u132_ring *ring) +{ + kref_put(&u132->kref, u132_hcd_delete); +} + +static void u132_ring_requeue_work(struct u132 *u132, struct u132_ring *ring, + unsigned int delta) +{ + if (delta > 0) { + if (queue_delayed_work(workqueue, &ring->scheduler, delta)) + return; + } else if (queue_work(workqueue, &ring->scheduler)) + return; + kref_put(&u132->kref, u132_hcd_delete); + return; +} + +static void u132_ring_queue_work(struct u132 *u132, struct u132_ring *ring, + unsigned int delta) +{ + kref_get(&u132->kref); + u132_ring_requeue_work(u132, ring, delta); + return; +} + +static void u132_ring_cancel_work(struct u132 *u132, struct u132_ring *ring) +{ + if (cancel_delayed_work(&ring->scheduler)) { + kref_put(&u132->kref, u132_hcd_delete); + } +} + +static void u132_endp_delete(struct kref *kref) +{ + struct u132_endp *endp = kref_to_u132_endp(kref); + struct u132 *u132 = endp->u132; + u8 usb_addr = endp->usb_addr; + u8 usb_endp = endp->usb_endp; + u8 address = u132->addr[usb_addr].address; + struct u132_udev *udev = &u132->udev[address]; + u8 endp_number = endp->endp_number; + struct usb_host_endpoint *hep = endp->hep; + struct u132_ring *ring = endp->ring; + struct list_head *head = &endp->endp_ring; + ring->length -= 1; + if (endp == ring->curr_endp) { + if (list_empty(head)) { + ring->curr_endp = NULL; + list_del(head); + } else { + struct u132_endp *next_endp = list_entry(head->next, + struct u132_endp, endp_ring); + ring->curr_endp = next_endp; + list_del(head); + }} else + list_del(head); + if (endp->input) { + udev->endp_number_in[usb_endp] = 0; + u132_udev_put_kref(u132, udev); + } + if (endp->output) { + udev->endp_number_out[usb_endp] = 0; + u132_udev_put_kref(u132, udev); + } + u132->endp[endp_number - 1] = NULL; + hep->hcpriv = NULL; + kfree(endp); + u132_u132_put_kref(u132); +} + +static inline void u132_endp_put_kref(struct u132 *u132, struct u132_endp *endp) +{ + kref_put(&endp->kref, u132_endp_delete); +} + +static inline void u132_endp_get_kref(struct u132 *u132, struct u132_endp *endp) +{ + kref_get(&endp->kref); +} + +static inline void u132_endp_init_kref(struct u132 *u132, + struct u132_endp *endp) +{ + kref_init(&endp->kref); + kref_get(&u132->kref); +} + +static void u132_endp_queue_work(struct u132 *u132, struct u132_endp *endp, + unsigned int delta) +{ + if (delta > 0) { + if (queue_delayed_work(workqueue, &endp->scheduler, delta)) + kref_get(&endp->kref); + } else if (queue_work(workqueue, &endp->scheduler)) + kref_get(&endp->kref); + return; +} + +static void u132_endp_cancel_work(struct u132 *u132, struct u132_endp *endp) +{ + if (cancel_delayed_work(&endp->scheduler)) + kref_put(&endp->kref, u132_endp_delete); +} + +static inline void u132_monitor_put_kref(struct u132 *u132) +{ + kref_put(&u132->kref, u132_hcd_delete); +} + +static void u132_monitor_queue_work(struct u132 *u132, unsigned int delta) +{ + if (delta > 0) { + if (queue_delayed_work(workqueue, &u132->monitor, delta)) { + kref_get(&u132->kref); + } + } else if (queue_work(workqueue, &u132->monitor)) + kref_get(&u132->kref); + return; +} + +static void u132_monitor_requeue_work(struct u132 *u132, unsigned int delta) +{ + if (delta > 0) { + if (queue_delayed_work(workqueue, &u132->monitor, delta)) + return; + } else if (queue_work(workqueue, &u132->monitor)) + return; + kref_put(&u132->kref, u132_hcd_delete); + return; +} + +static void u132_monitor_cancel_work(struct u132 *u132) +{ + if (cancel_delayed_work(&u132->monitor)) + kref_put(&u132->kref, u132_hcd_delete); +} + +static int read_roothub_info(struct u132 *u132) +{ + u32 revision; + int retval; + retval = u132_read_pcimem(u132, revision, &revision); + if (retval) { + dev_err(&u132->platform_dev->dev, "error %d accessing device co" + "ntrol\n", retval); + return retval; + } else if ((revision & 0xFF) == 0x10) { + } else if ((revision & 0xFF) == 0x11) { + } else { + dev_err(&u132->platform_dev->dev, "device revision is not valid" + " %08X\n", revision); + return -ENODEV; + } + retval = u132_read_pcimem(u132, control, &u132->hc_control); + if (retval) { + dev_err(&u132->platform_dev->dev, "error %d accessing device co" + "ntrol\n", retval); + return retval; + } + retval = u132_read_pcimem(u132, roothub.status, + &u132->hc_roothub_status); + if (retval) { + dev_err(&u132->platform_dev->dev, "error %d accessing device re" + "g roothub.status\n", retval); + return retval; + } + retval = u132_read_pcimem(u132, roothub.a, &u132->hc_roothub_a); + if (retval) { + dev_err(&u132->platform_dev->dev, "error %d accessing device re" + "g roothub.a\n", retval); + return retval; + } + { + int I = u132->num_ports; + int i = 0; + while (I-- > 0) { + retval = u132_read_pcimem(u132, roothub.portstatus[i], + &u132->hc_roothub_portstatus[i]); + if (retval) { + dev_err(&u132->platform_dev->dev, "error %d acc" + "essing device roothub.portstatus[%d]\n" + , retval, i); + return retval; + } else + i += 1; + } + } + return 0; +} + +static void u132_hcd_monitor_work(void *data) +{ + struct u132 *u132 = data; + if (u132->going > 1) { + dev_err(&u132->platform_dev->dev, "device has been removed %d\n" + , u132->going); + u132_monitor_put_kref(u132); + return; + } else if (u132->going > 0) { + dev_err(&u132->platform_dev->dev, "device is being removed\n"); + u132_monitor_put_kref(u132); + return; + } else { + int retval; + down(&u132->sw_lock); + retval = read_roothub_info(u132); + if (retval) { + struct usb_hcd *hcd = u132_to_hcd(u132); + u132_disable(u132); + u132->going = 1; + up(&u132->sw_lock); + usb_hc_died(hcd); + ftdi_elan_gone_away(u132->platform_dev); + u132_monitor_put_kref(u132); + return; + } else { + u132_monitor_requeue_work(u132, 500); + up(&u132->sw_lock); + return; + } + } +} + +static void u132_hcd_giveback_urb(struct u132 *u132, struct u132_endp *endp, + struct urb *urb, int status) +{ + struct u132_ring *ring; + unsigned long irqs; + struct usb_hcd *hcd = u132_to_hcd(u132); + urb->error_count = 0; + urb->status = status; + urb->hcpriv = NULL; + spin_lock_irqsave(&endp->queue_lock.slock, irqs); + endp->queue_next += 1; + if (ENDP_QUEUE_SIZE > --endp->queue_size) { + endp->active = 0; + spin_unlock_irqrestore(&endp->queue_lock.slock, irqs); + } else { + struct list_head *next = endp->urb_more.next; + struct u132_urbq *urbq = list_entry(next, struct u132_urbq, + urb_more); + list_del(next); + endp->urb_list[ENDP_QUEUE_MASK & endp->queue_last++] = + urbq->urb; + endp->active = 0; + spin_unlock_irqrestore(&endp->queue_lock.slock, irqs); + kfree(urbq); + } down(&u132->scheduler_lock); + ring = endp->ring; + ring->in_use = 0; + u132_ring_cancel_work(u132, ring); + u132_ring_queue_work(u132, ring, 0); + up(&u132->scheduler_lock); + u132_endp_put_kref(u132, endp); + usb_hcd_giveback_urb(hcd, urb, NULL); + return; +} + +static void u132_hcd_forget_urb(struct u132 *u132, struct u132_endp *endp, + struct urb *urb, int status) +{ + u132_endp_put_kref(u132, endp); +} + +static void u132_hcd_abandon_urb(struct u132 *u132, struct u132_endp *endp, + struct urb *urb, int status) +{ + unsigned long irqs; + struct usb_hcd *hcd = u132_to_hcd(u132); + urb->error_count = 0; + urb->status = status; + urb->hcpriv = NULL; + spin_lock_irqsave(&endp->queue_lock.slock, irqs); + endp->queue_next += 1; + if (ENDP_QUEUE_SIZE > --endp->queue_size) { + endp->active = 0; + spin_unlock_irqrestore(&endp->queue_lock.slock, irqs); + } else { + struct list_head *next = endp->urb_more.next; + struct u132_urbq *urbq = list_entry(next, struct u132_urbq, + urb_more); + list_del(next); + endp->urb_list[ENDP_QUEUE_MASK & endp->queue_last++] = + urbq->urb; + endp->active = 0; + spin_unlock_irqrestore(&endp->queue_lock.slock, irqs); + kfree(urbq); + } usb_hcd_giveback_urb(hcd, urb, NULL); + return; +} + +static inline int edset_input(struct u132 *u132, struct u132_ring *ring, + struct u132_endp *endp, struct urb *urb, u8 address, u8 toggle_bits, + void (*callback) (void *endp, struct urb *urb, u8 *buf, int len, + int toggle_bits, int error_count, int condition_code, int repeat_number, + int halted, int skipped, int actual, int non_null)) +{ + return usb_ftdi_elan_edset_input(u132->platform_dev, ring->number, endp, + urb, address, endp->usb_endp, toggle_bits, callback); +} + +static inline int edset_setup(struct u132 *u132, struct u132_ring *ring, + struct u132_endp *endp, struct urb *urb, u8 address, u8 toggle_bits, + void (*callback) (void *endp, struct urb *urb, u8 *buf, int len, + int toggle_bits, int error_count, int condition_code, int repeat_number, + int halted, int skipped, int actual, int non_null)) +{ + return usb_ftdi_elan_edset_setup(u132->platform_dev, ring->number, endp, + urb, address, endp->usb_endp, toggle_bits, callback); +} + +static inline int edset_single(struct u132 *u132, struct u132_ring *ring, + struct u132_endp *endp, struct urb *urb, u8 address, u8 toggle_bits, + void (*callback) (void *endp, struct urb *urb, u8 *buf, int len, + int toggle_bits, int error_count, int condition_code, int repeat_number, + int halted, int skipped, int actual, int non_null)) +{ + return usb_ftdi_elan_edset_single(u132->platform_dev, ring->number, + endp, urb, address, endp->usb_endp, toggle_bits, callback); +} + +static inline int edset_output(struct u132 *u132, struct u132_ring *ring, + struct u132_endp *endp, struct urb *urb, u8 address, u8 toggle_bits, + void (*callback) (void *endp, struct urb *urb, u8 *buf, int len, + int toggle_bits, int error_count, int condition_code, int repeat_number, + int halted, int skipped, int actual, int non_null)) +{ + return usb_ftdi_elan_edset_output(u132->platform_dev, ring->number, + endp, urb, address, endp->usb_endp, toggle_bits, callback); +} + + +/* +* must not LOCK sw_lock +* +*/ +static void u132_hcd_interrupt_recv(void *data, struct urb *urb, u8 *buf, + int len, int toggle_bits, int error_count, int condition_code, + int repeat_number, int halted, int skipped, int actual, int non_null) +{ + struct u132_endp *endp = data; + struct u132 *u132 = endp->u132; + u8 address = u132->addr[endp->usb_addr].address; + struct u132_udev *udev = &u132->udev[address]; + down(&u132->scheduler_lock); + if (u132->going > 1) { + dev_err(&u132->platform_dev->dev, "device has been removed %d\n" + , u132->going); + up(&u132->scheduler_lock); + u132_hcd_forget_urb(u132, endp, urb, -ENODEV); + return; + } else if (endp->dequeueing) { + endp->dequeueing = 0; + up(&u132->scheduler_lock); + u132_hcd_giveback_urb(u132, endp, urb, -EINTR); + return; + } else if (u132->going > 0) { + dev_err(&u132->platform_dev->dev, "device is being removed urb=" + "%p status=%d\n", urb, urb->status); + up(&u132->scheduler_lock); + u132_hcd_giveback_urb(u132, endp, urb, -ENODEV); + return; + } else if (urb->status == -EINPROGRESS) { + struct u132_ring *ring = endp->ring; + u8 *u = urb->transfer_buffer + urb->actual_length; + u8 *b = buf; + int L = len; + while (L-- > 0) { + *u++ = *b++; + } + urb->actual_length += len; + if ((condition_code == TD_CC_NOERROR) && + (urb->transfer_buffer_length > urb->actual_length)) { + endp->toggle_bits = toggle_bits; + usb_settoggle(udev->usb_device, endp->usb_endp, 0, + 1 & toggle_bits); + if (urb->actual_length > 0) { + int retval; + up(&u132->scheduler_lock); + retval = edset_single(u132, ring, endp, urb, + address, endp->toggle_bits, + u132_hcd_interrupt_recv); + if (retval == 0) { + } else + u132_hcd_giveback_urb(u132, endp, urb, + retval); + } else { + ring->in_use = 0; + endp->active = 0; + endp->jiffies = jiffies + + msecs_to_jiffies(urb->interval); + u132_ring_cancel_work(u132, ring); + u132_ring_queue_work(u132, ring, 0); + up(&u132->scheduler_lock); + u132_endp_put_kref(u132, endp); + } + return; + } else if ((condition_code == TD_DATAUNDERRUN) && + ((urb->transfer_flags & URB_SHORT_NOT_OK) == 0)) { + endp->toggle_bits = toggle_bits; + usb_settoggle(udev->usb_device, endp->usb_endp, 0, + 1 & toggle_bits); + up(&u132->scheduler_lock); + u132_hcd_giveback_urb(u132, endp, urb, 0); + return; + } else { + if (condition_code == TD_CC_NOERROR) { + endp->toggle_bits = toggle_bits; + usb_settoggle(udev->usb_device, endp->usb_endp, + 0, 1 & toggle_bits); + } else if (condition_code == TD_CC_STALL) { + endp->toggle_bits = 0x2; + usb_settoggle(udev->usb_device, endp->usb_endp, + 0, 0); + } else { + endp->toggle_bits = 0x2; + usb_settoggle(udev->usb_device, endp->usb_endp, + 0, 0); + dev_err(&u132->platform_dev->dev, "urb=%p givin" + "g back INTERRUPT %s\n", urb, + cc_to_text[condition_code]); + } + up(&u132->scheduler_lock); + u132_hcd_giveback_urb(u132, endp, urb, + cc_to_error[condition_code]); + return; + } + } else { + dev_err(&u132->platform_dev->dev, "CALLBACK called urb=%p statu" + "s=%d\n", urb, urb->status); + up(&u132->scheduler_lock); + u132_hcd_giveback_urb(u132, endp, urb, urb->status); + return; + } +} + +static void u132_hcd_bulk_output_sent(void *data, struct urb *urb, u8 *buf, + int len, int toggle_bits, int error_count, int condition_code, + int repeat_number, int halted, int skipped, int actual, int non_null) +{ + struct u132_endp *endp = data; + struct u132 *u132 = endp->u132; + u8 address = u132->addr[endp->usb_addr].address; + down(&u132->scheduler_lock); + if (u132->going > 1) { + dev_err(&u132->platform_dev->dev, "device has been removed %d\n" + , u132->going); + up(&u132->scheduler_lock); + u132_hcd_forget_urb(u132, endp, urb, -ENODEV); + return; + } else if (endp->dequeueing) { + endp->dequeueing = 0; + up(&u132->scheduler_lock); + u132_hcd_giveback_urb(u132, endp, urb, -EINTR); + return; + } else if (u132->going > 0) { + dev_err(&u132->platform_dev->dev, "device is being removed urb=" + "%p status=%d\n", urb, urb->status); + up(&u132->scheduler_lock); + u132_hcd_giveback_urb(u132, endp, urb, -ENODEV); + return; + } else if (urb->status == -EINPROGRESS) { + struct u132_ring *ring = endp->ring; + urb->actual_length += len; + endp->toggle_bits = toggle_bits; + if (urb->transfer_buffer_length > urb->actual_length) { + int retval; + up(&u132->scheduler_lock); + retval = edset_output(u132, ring, endp, urb, address, + endp->toggle_bits, u132_hcd_bulk_output_sent); + if (retval == 0) { + } else + u132_hcd_giveback_urb(u132, endp, urb, retval); + return; + } else { + up(&u132->scheduler_lock); + u132_hcd_giveback_urb(u132, endp, urb, 0); + return; + } + } else { + dev_err(&u132->platform_dev->dev, "CALLBACK called urb=%p statu" + "s=%d\n", urb, urb->status); + up(&u132->scheduler_lock); + u132_hcd_giveback_urb(u132, endp, urb, urb->status); + return; + } +} + +static void u132_hcd_bulk_input_recv(void *data, struct urb *urb, u8 *buf, + int len, int toggle_bits, int error_count, int condition_code, + int repeat_number, int halted, int skipped, int actual, int non_null) +{ + struct u132_endp *endp = data; + struct u132 *u132 = endp->u132; + u8 address = u132->addr[endp->usb_addr].address; + struct u132_udev *udev = &u132->udev[address]; + down(&u132->scheduler_lock); + if (u132->going > 1) { + dev_err(&u132->platform_dev->dev, "device has been removed %d\n" + , u132->going); + up(&u132->scheduler_lock); + u132_hcd_forget_urb(u132, endp, urb, -ENODEV); + return; + } else if (endp->dequeueing) { + endp->dequeueing = 0; + up(&u132->scheduler_lock); + u132_hcd_giveback_urb(u132, endp, urb, -EINTR); + return; + } else if (u132->going > 0) { + dev_err(&u132->platform_dev->dev, "device is being removed urb=" + "%p status=%d\n", urb, urb->status); + up(&u132->scheduler_lock); + u132_hcd_giveback_urb(u132, endp, urb, -ENODEV); + return; + } else if (urb->status == -EINPROGRESS) { + struct u132_ring *ring = endp->ring; + u8 *u = urb->transfer_buffer + urb->actual_length; + u8 *b = buf; + int L = len; + while (L-- > 0) { + *u++ = *b++; + } + urb->actual_length += len; + if ((condition_code == TD_CC_NOERROR) && + (urb->transfer_buffer_length > urb->actual_length)) { + int retval; + endp->toggle_bits = toggle_bits; + usb_settoggle(udev->usb_device, endp->usb_endp, 0, + 1 & toggle_bits); + up(&u132->scheduler_lock); + retval = usb_ftdi_elan_edset_input(u132->platform_dev, + ring->number, endp, urb, address, + endp->usb_endp, endp->toggle_bits, + u132_hcd_bulk_input_recv); + if (retval == 0) { + } else + u132_hcd_giveback_urb(u132, endp, urb, retval); + return; + } else if (condition_code == TD_CC_NOERROR) { + endp->toggle_bits = toggle_bits; + usb_settoggle(udev->usb_device, endp->usb_endp, 0, + 1 & toggle_bits); + up(&u132->scheduler_lock); + u132_hcd_giveback_urb(u132, endp, urb, + cc_to_error[condition_code]); + return; + } else if ((condition_code == TD_DATAUNDERRUN) && + ((urb->transfer_flags & URB_SHORT_NOT_OK) == 0)) { + endp->toggle_bits = toggle_bits; + usb_settoggle(udev->usb_device, endp->usb_endp, 0, + 1 & toggle_bits); + up(&u132->scheduler_lock); + u132_hcd_giveback_urb(u132, endp, urb, 0); + return; + } else if (condition_code == TD_DATAUNDERRUN) { + endp->toggle_bits = toggle_bits; + usb_settoggle(udev->usb_device, endp->usb_endp, 0, + 1 & toggle_bits); + dev_warn(&u132->platform_dev->dev, "urb=%p(SHORT NOT OK" + ") giving back BULK IN %s\n", urb, + cc_to_text[condition_code]); + up(&u132->scheduler_lock); + u132_hcd_giveback_urb(u132, endp, urb, 0); + return; + } else if (condition_code == TD_CC_STALL) { + endp->toggle_bits = 0x2; + usb_settoggle(udev->usb_device, endp->usb_endp, 0, 0); + up(&u132->scheduler_lock); + u132_hcd_giveback_urb(u132, endp, urb, + cc_to_error[condition_code]); + return; + } else { + endp->toggle_bits = 0x2; + usb_settoggle(udev->usb_device, endp->usb_endp, 0, 0); + dev_err(&u132->platform_dev->dev, "urb=%p giving back B" + "ULK IN code=%d %s\n", urb, condition_code, + cc_to_text[condition_code]); + up(&u132->scheduler_lock); + u132_hcd_giveback_urb(u132, endp, urb, + cc_to_error[condition_code]); + return; + } + } else { + dev_err(&u132->platform_dev->dev, "CALLBACK called urb=%p statu" + "s=%d\n", urb, urb->status); + up(&u132->scheduler_lock); + u132_hcd_giveback_urb(u132, endp, urb, urb->status); + return; + } +} + +static void u132_hcd_configure_empty_sent(void *data, struct urb *urb, u8 *buf, + int len, int toggle_bits, int error_count, int condition_code, + int repeat_number, int halted, int skipped, int actual, int non_null) +{ + struct u132_endp *endp = data; + struct u132 *u132 = endp->u132; + down(&u132->scheduler_lock); + if (u132->going > 1) { + dev_err(&u132->platform_dev->dev, "device has been removed %d\n" + , u132->going); + up(&u132->scheduler_lock); + u132_hcd_forget_urb(u132, endp, urb, -ENODEV); + return; + } else if (endp->dequeueing) { + endp->dequeueing = 0; + up(&u132->scheduler_lock); + u132_hcd_giveback_urb(u132, endp, urb, -EINTR); + return; + } else if (u132->going > 0) { + dev_err(&u132->platform_dev->dev, "device is being removed urb=" + "%p status=%d\n", urb, urb->status); + up(&u132->scheduler_lock); + u132_hcd_giveback_urb(u132, endp, urb, -ENODEV); + return; + } else if (urb->status == -EINPROGRESS) { + up(&u132->scheduler_lock); + u132_hcd_giveback_urb(u132, endp, urb, 0); + return; + } else { + dev_err(&u132->platform_dev->dev, "CALLBACK called urb=%p statu" + "s=%d\n", urb, urb->status); + up(&u132->scheduler_lock); + u132_hcd_giveback_urb(u132, endp, urb, urb->status); + return; + } +} + +static void u132_hcd_configure_input_recv(void *data, struct urb *urb, u8 *buf, + int len, int toggle_bits, int error_count, int condition_code, + int repeat_number, int halted, int skipped, int actual, int non_null) +{ + struct u132_endp *endp = data; + struct u132 *u132 = endp->u132; + u8 address = u132->addr[endp->usb_addr].address; + down(&u132->scheduler_lock); + if (u132->going > 1) { + dev_err(&u132->platform_dev->dev, "device has been removed %d\n" + , u132->going); + up(&u132->scheduler_lock); + u132_hcd_forget_urb(u132, endp, urb, -ENODEV); + return; + } else if (endp->dequeueing) { + endp->dequeueing = 0; + up(&u132->scheduler_lock); + u132_hcd_giveback_urb(u132, endp, urb, -EINTR); + return; + } else if (u132->going > 0) { + dev_err(&u132->platform_dev->dev, "device is being removed urb=" + "%p status=%d\n", urb, urb->status); + up(&u132->scheduler_lock); + u132_hcd_giveback_urb(u132, endp, urb, -ENODEV); + return; + } else if (urb->status == -EINPROGRESS) { + struct u132_ring *ring = endp->ring; + u8 *u = urb->transfer_buffer; + u8 *b = buf; + int L = len; + while (L-- > 0) { + *u++ = *b++; + } + urb->actual_length = len; + if ((condition_code == TD_CC_NOERROR) || ((condition_code == + TD_DATAUNDERRUN) && ((urb->transfer_flags & + URB_SHORT_NOT_OK) == 0))) { + int retval; + up(&u132->scheduler_lock); + retval = usb_ftdi_elan_edset_empty(u132->platform_dev, + ring->number, endp, urb, address, + endp->usb_endp, 0x3, + u132_hcd_configure_empty_sent); + if (retval == 0) { + } else + u132_hcd_giveback_urb(u132, endp, urb, retval); + return; + } else if (condition_code == TD_CC_STALL) { + up(&u132->scheduler_lock); + dev_warn(&u132->platform_dev->dev, "giving back SETUP I" + "NPUT STALL urb %p\n", urb); + u132_hcd_giveback_urb(u132, endp, urb, + cc_to_error[condition_code]); + return; + } else { + up(&u132->scheduler_lock); + dev_err(&u132->platform_dev->dev, "giving back SETUP IN" + "PUT %s urb %p\n", cc_to_text[condition_code], + urb); + u132_hcd_giveback_urb(u132, endp, urb, + cc_to_error[condition_code]); + return; + } + } else { + dev_err(&u132->platform_dev->dev, "CALLBACK called urb=%p statu" + "s=%d\n", urb, urb->status); + up(&u132->scheduler_lock); + u132_hcd_giveback_urb(u132, endp, urb, urb->status); + return; + } +} + +static void u132_hcd_configure_empty_recv(void *data, struct urb *urb, u8 *buf, + int len, int toggle_bits, int error_count, int condition_code, + int repeat_number, int halted, int skipped, int actual, int non_null) +{ + struct u132_endp *endp = data; + struct u132 *u132 = endp->u132; + down(&u132->scheduler_lock); + if (u132->going > 1) { + dev_err(&u132->platform_dev->dev, "device has been removed %d\n" + , u132->going); + up(&u132->scheduler_lock); + u132_hcd_forget_urb(u132, endp, urb, -ENODEV); + return; + } else if (endp->dequeueing) { + endp->dequeueing = 0; + up(&u132->scheduler_lock); + u132_hcd_giveback_urb(u132, endp, urb, -EINTR); + return; + } else if (u132->going > 0) { + dev_err(&u132->platform_dev->dev, "device is being removed urb=" + "%p status=%d\n", urb, urb->status); + up(&u132->scheduler_lock); + u132_hcd_giveback_urb(u132, endp, urb, -ENODEV); + return; + } else if (urb->status == -EINPROGRESS) { + up(&u132->scheduler_lock); + u132_hcd_giveback_urb(u132, endp, urb, 0); + return; + } else { + dev_err(&u132->platform_dev->dev, "CALLBACK called urb=%p statu" + "s=%d\n", urb, urb->status); + up(&u132->scheduler_lock); + u132_hcd_giveback_urb(u132, endp, urb, urb->status); + return; + } +} + +static void u132_hcd_configure_setup_sent(void *data, struct urb *urb, u8 *buf, + int len, int toggle_bits, int error_count, int condition_code, + int repeat_number, int halted, int skipped, int actual, int non_null) +{ + struct u132_endp *endp = data; + struct u132 *u132 = endp->u132; + u8 address = u132->addr[endp->usb_addr].address; + down(&u132->scheduler_lock); + if (u132->going > 1) { + dev_err(&u132->platform_dev->dev, "device has been removed %d\n" + , u132->going); + up(&u132->scheduler_lock); + u132_hcd_forget_urb(u132, endp, urb, -ENODEV); + return; + } else if (endp->dequeueing) { + endp->dequeueing = 0; + up(&u132->scheduler_lock); + u132_hcd_giveback_urb(u132, endp, urb, -EINTR); + return; + } else if (u132->going > 0) { + dev_err(&u132->platform_dev->dev, "device is being removed urb=" + "%p status=%d\n", urb, urb->status); + up(&u132->scheduler_lock); + u132_hcd_giveback_urb(u132, endp, urb, -ENODEV); + return; + } else if (urb->status == -EINPROGRESS) { + if (usb_pipein(urb->pipe)) { + int retval; + struct u132_ring *ring = endp->ring; + up(&u132->scheduler_lock); + retval = usb_ftdi_elan_edset_input(u132->platform_dev, + ring->number, endp, urb, address, + endp->usb_endp, 0, + u132_hcd_configure_input_recv); + if (retval == 0) { + } else + u132_hcd_giveback_urb(u132, endp, urb, retval); + return; + } else { + int retval; + struct u132_ring *ring = endp->ring; + up(&u132->scheduler_lock); + retval = usb_ftdi_elan_edset_input(u132->platform_dev, + ring->number, endp, urb, address, + endp->usb_endp, 0, + u132_hcd_configure_empty_recv); + if (retval == 0) { + } else + u132_hcd_giveback_urb(u132, endp, urb, retval); + return; + } + } else { + dev_err(&u132->platform_dev->dev, "CALLBACK called urb=%p statu" + "s=%d\n", urb, urb->status); + up(&u132->scheduler_lock); + u132_hcd_giveback_urb(u132, endp, urb, urb->status); + return; + } +} + +static void u132_hcd_enumeration_empty_recv(void *data, struct urb *urb, + u8 *buf, int len, int toggle_bits, int error_count, int condition_code, + int repeat_number, int halted, int skipped, int actual, int non_null) +{ + struct u132_endp *endp = data; + struct u132 *u132 = endp->u132; + u8 address = u132->addr[endp->usb_addr].address; + struct u132_udev *udev = &u132->udev[address]; + down(&u132->scheduler_lock); + if (u132->going > 1) { + dev_err(&u132->platform_dev->dev, "device has been removed %d\n" + , u132->going); + up(&u132->scheduler_lock); + u132_hcd_forget_urb(u132, endp, urb, -ENODEV); + return; + } else if (endp->dequeueing) { + endp->dequeueing = 0; + up(&u132->scheduler_lock); + u132_hcd_giveback_urb(u132, endp, urb, -EINTR); + return; + } else if (u132->going > 0) { + dev_err(&u132->platform_dev->dev, "device is being removed urb=" + "%p status=%d\n", urb, urb->status); + up(&u132->scheduler_lock); + u132_hcd_giveback_urb(u132, endp, urb, -ENODEV); + return; + } else if (urb->status == -EINPROGRESS) { + u132->addr[0].address = 0; + endp->usb_addr = udev->usb_addr; + up(&u132->scheduler_lock); + u132_hcd_giveback_urb(u132, endp, urb, 0); + return; + } else { + dev_err(&u132->platform_dev->dev, "CALLBACK called urb=%p statu" + "s=%d\n", urb, urb->status); + up(&u132->scheduler_lock); + u132_hcd_giveback_urb(u132, endp, urb, urb->status); + return; + } +} + +static void u132_hcd_enumeration_address_sent(void *data, struct urb *urb, + u8 *buf, int len, int toggle_bits, int error_count, int condition_code, + int repeat_number, int halted, int skipped, int actual, int non_null) +{ + struct u132_endp *endp = data; + struct u132 *u132 = endp->u132; + down(&u132->scheduler_lock); + if (u132->going > 1) { + dev_err(&u132->platform_dev->dev, "device has been removed %d\n" + , u132->going); + up(&u132->scheduler_lock); + u132_hcd_forget_urb(u132, endp, urb, -ENODEV); + return; + } else if (endp->dequeueing) { + endp->dequeueing = 0; + up(&u132->scheduler_lock); + u132_hcd_giveback_urb(u132, endp, urb, -EINTR); + return; + } else if (u132->going > 0) { + dev_err(&u132->platform_dev->dev, "device is being removed urb=" + "%p status=%d\n", urb, urb->status); + up(&u132->scheduler_lock); + u132_hcd_giveback_urb(u132, endp, urb, -ENODEV); + return; + } else if (urb->status == -EINPROGRESS) { + int retval; + struct u132_ring *ring = endp->ring; + up(&u132->scheduler_lock); + retval = usb_ftdi_elan_edset_input(u132->platform_dev, + ring->number, endp, urb, 0, endp->usb_endp, 0, + u132_hcd_enumeration_empty_recv); + if (retval == 0) { + } else + u132_hcd_giveback_urb(u132, endp, urb, retval); + return; + } else { + dev_err(&u132->platform_dev->dev, "CALLBACK called urb=%p statu" + "s=%d\n", urb, urb->status); + up(&u132->scheduler_lock); + u132_hcd_giveback_urb(u132, endp, urb, urb->status); + return; + } +} + +static void u132_hcd_initial_empty_sent(void *data, struct urb *urb, u8 *buf, + int len, int toggle_bits, int error_count, int condition_code, + int repeat_number, int halted, int skipped, int actual, int non_null) +{ + struct u132_endp *endp = data; + struct u132 *u132 = endp->u132; + down(&u132->scheduler_lock); + if (u132->going > 1) { + dev_err(&u132->platform_dev->dev, "device has been removed %d\n" + , u132->going); + up(&u132->scheduler_lock); + u132_hcd_forget_urb(u132, endp, urb, -ENODEV); + return; + } else if (endp->dequeueing) { + endp->dequeueing = 0; + up(&u132->scheduler_lock); + u132_hcd_giveback_urb(u132, endp, urb, -EINTR); + return; + } else if (u132->going > 0) { + dev_err(&u132->platform_dev->dev, "device is being removed urb=" + "%p status=%d\n", urb, urb->status); + up(&u132->scheduler_lock); + u132_hcd_giveback_urb(u132, endp, urb, -ENODEV); + return; + } else if (urb->status == -EINPROGRESS) { + up(&u132->scheduler_lock); + u132_hcd_giveback_urb(u132, endp, urb, 0); + return; + } else { + dev_err(&u132->platform_dev->dev, "CALLBACK called urb=%p statu" + "s=%d\n", urb, urb->status); + up(&u132->scheduler_lock); + u132_hcd_giveback_urb(u132, endp, urb, urb->status); + return; + } +} + +static void u132_hcd_initial_input_recv(void *data, struct urb *urb, u8 *buf, + int len, int toggle_bits, int error_count, int condition_code, + int repeat_number, int halted, int skipped, int actual, int non_null) +{ + struct u132_endp *endp = data; + struct u132 *u132 = endp->u132; + u8 address = u132->addr[endp->usb_addr].address; + down(&u132->scheduler_lock); + if (u132->going > 1) { + dev_err(&u132->platform_dev->dev, "device has been removed %d\n" + , u132->going); + up(&u132->scheduler_lock); + u132_hcd_forget_urb(u132, endp, urb, -ENODEV); + return; + } else if (endp->dequeueing) { + endp->dequeueing = 0; + up(&u132->scheduler_lock); + u132_hcd_giveback_urb(u132, endp, urb, -EINTR); + return; + } else if (u132->going > 0) { + dev_err(&u132->platform_dev->dev, "device is being removed urb=" + "%p status=%d\n", urb, urb->status); + up(&u132->scheduler_lock); + u132_hcd_giveback_urb(u132, endp, urb, -ENODEV); + return; + } else if (urb->status == -EINPROGRESS) { + int retval; + struct u132_ring *ring = endp->ring; + u8 *u = urb->transfer_buffer; + u8 *b = buf; + int L = len; + while (L-- > 0) { + *u++ = *b++; + } + urb->actual_length = len; + up(&u132->scheduler_lock); + retval = usb_ftdi_elan_edset_empty(u132->platform_dev, + ring->number, endp, urb, address, endp->usb_endp, 0x3, + u132_hcd_initial_empty_sent); + if (retval == 0) { + } else + u132_hcd_giveback_urb(u132, endp, urb, retval); + return; + } else { + dev_err(&u132->platform_dev->dev, "CALLBACK called urb=%p statu" + "s=%d\n", urb, urb->status); + up(&u132->scheduler_lock); + u132_hcd_giveback_urb(u132, endp, urb, urb->status); + return; + } +} + +static void u132_hcd_initial_setup_sent(void *data, struct urb *urb, u8 *buf, + int len, int toggle_bits, int error_count, int condition_code, + int repeat_number, int halted, int skipped, int actual, int non_null) +{ + struct u132_endp *endp = data; + struct u132 *u132 = endp->u132; + u8 address = u132->addr[endp->usb_addr].address; + down(&u132->scheduler_lock); + if (u132->going > 1) { + dev_err(&u132->platform_dev->dev, "device has been removed %d\n" + , u132->going); + up(&u132->scheduler_lock); + u132_hcd_forget_urb(u132, endp, urb, -ENODEV); + return; + } else if (endp->dequeueing) { + endp->dequeueing = 0; + up(&u132->scheduler_lock); + u132_hcd_giveback_urb(u132, endp, urb, -EINTR); + return; + } else if (u132->going > 0) { + dev_err(&u132->platform_dev->dev, "device is being removed urb=" + "%p status=%d\n", urb, urb->status); + up(&u132->scheduler_lock); + u132_hcd_giveback_urb(u132, endp, urb, -ENODEV); + return; + } else if (urb->status == -EINPROGRESS) { + int retval; + struct u132_ring *ring = endp->ring; + up(&u132->scheduler_lock); + retval = usb_ftdi_elan_edset_input(u132->platform_dev, + ring->number, endp, urb, address, endp->usb_endp, 0, + u132_hcd_initial_input_recv); + if (retval == 0) { + } else + u132_hcd_giveback_urb(u132, endp, urb, retval); + return; + } else { + dev_err(&u132->platform_dev->dev, "CALLBACK called urb=%p statu" + "s=%d\n", urb, urb->status); + up(&u132->scheduler_lock); + u132_hcd_giveback_urb(u132, endp, urb, urb->status); + return; + } +} + +static void u132_hcd_ring_work_scheduler(void *data); +static void u132_hcd_endp_work_scheduler(void *data); +/* +* this work function is only executed from the work queue +* +*/ +static void u132_hcd_ring_work_scheduler(void *data) +{ + struct u132_ring *ring = data; + struct u132 *u132 = ring->u132; + down(&u132->scheduler_lock); + if (ring->in_use) { + up(&u132->scheduler_lock); + u132_ring_put_kref(u132, ring); + return; + } else if (ring->curr_endp) { + struct u132_endp *last_endp = ring->curr_endp; + struct list_head *scan; + struct list_head *head = &last_endp->endp_ring; + unsigned long wakeup = 0; + list_for_each(scan, head) { + struct u132_endp *endp = list_entry(scan, + struct u132_endp, endp_ring); + if (endp->queue_next == endp->queue_last) { + } else if ((endp->delayed == 0) + || time_after_eq(jiffies, endp->jiffies)) { + ring->curr_endp = endp; + u132_endp_cancel_work(u132, last_endp); + u132_endp_queue_work(u132, last_endp, 0); + up(&u132->scheduler_lock); + u132_ring_put_kref(u132, ring); + return; + } else { + unsigned long delta = endp->jiffies - jiffies; + if (delta > wakeup) + wakeup = delta; + } + } + if (last_endp->queue_next == last_endp->queue_last) { + } else if ((last_endp->delayed == 0) || time_after_eq(jiffies, + last_endp->jiffies)) { + u132_endp_cancel_work(u132, last_endp); + u132_endp_queue_work(u132, last_endp, 0); + up(&u132->scheduler_lock); + u132_ring_put_kref(u132, ring); + return; + } else { + unsigned long delta = last_endp->jiffies - jiffies; + if (delta > wakeup) + wakeup = delta; + } + if (wakeup > 0) { + u132_ring_requeue_work(u132, ring, wakeup); + up(&u132->scheduler_lock); + return; + } else { + up(&u132->scheduler_lock); + u132_ring_put_kref(u132, ring); + return; + } + } else { + up(&u132->scheduler_lock); + u132_ring_put_kref(u132, ring); + return; + } +} + +static void u132_hcd_endp_work_scheduler(void *data) +{ + struct u132_ring *ring; + struct u132_endp *endp = data; + struct u132 *u132 = endp->u132; + down(&u132->scheduler_lock); + ring = endp->ring; + if (endp->edset_flush) { + endp->edset_flush = 0; + if (endp->dequeueing) + usb_ftdi_elan_edset_flush(u132->platform_dev, + ring->number, endp); + up(&u132->scheduler_lock); + u132_endp_put_kref(u132, endp); + return; + } else if (endp->active) { + up(&u132->scheduler_lock); + u132_endp_put_kref(u132, endp); + return; + } else if (ring->in_use) { + up(&u132->scheduler_lock); + u132_endp_put_kref(u132, endp); + return; + } else if (endp->queue_next == endp->queue_last) { + up(&u132->scheduler_lock); + u132_endp_put_kref(u132, endp); + return; + } else if (endp->pipetype == PIPE_INTERRUPT) { + u8 address = u132->addr[endp->usb_addr].address; + if (ring->in_use) { + up(&u132->scheduler_lock); + u132_endp_put_kref(u132, endp); + return; + } else { + int retval; + struct urb *urb = endp->urb_list[ENDP_QUEUE_MASK & + endp->queue_next]; + endp->active = 1; + ring->curr_endp = endp; + ring->in_use = 1; + up(&u132->scheduler_lock); + retval = edset_single(u132, ring, endp, urb, address, + endp->toggle_bits, u132_hcd_interrupt_recv); + if (retval == 0) { + } else + u132_hcd_giveback_urb(u132, endp, urb, retval); + return; + } + } else if (endp->pipetype == PIPE_CONTROL) { + u8 address = u132->addr[endp->usb_addr].address; + if (ring->in_use) { + up(&u132->scheduler_lock); + u132_endp_put_kref(u132, endp); + return; + } else if (address == 0) { + int retval; + struct urb *urb = endp->urb_list[ENDP_QUEUE_MASK & + endp->queue_next]; + endp->active = 1; + ring->curr_endp = endp; + ring->in_use = 1; + up(&u132->scheduler_lock); + retval = edset_setup(u132, ring, endp, urb, address, + 0x2, u132_hcd_initial_setup_sent); + if (retval == 0) { + } else + u132_hcd_giveback_urb(u132, endp, urb, retval); + return; + } else if (endp->usb_addr == 0) { + int retval; + struct urb *urb = endp->urb_list[ENDP_QUEUE_MASK & + endp->queue_next]; + endp->active = 1; + ring->curr_endp = endp; + ring->in_use = 1; + up(&u132->scheduler_lock); + retval = edset_setup(u132, ring, endp, urb, 0, 0x2, + u132_hcd_enumeration_address_sent); + if (retval == 0) { + } else + u132_hcd_giveback_urb(u132, endp, urb, retval); + return; + } else { + int retval; + u8 address = u132->addr[endp->usb_addr].address; + struct urb *urb = endp->urb_list[ENDP_QUEUE_MASK & + endp->queue_next]; + endp->active = 1; + ring->curr_endp = endp; + ring->in_use = 1; + up(&u132->scheduler_lock); + retval = edset_setup(u132, ring, endp, urb, address, + 0x2, u132_hcd_configure_setup_sent); + if (retval == 0) { + } else + u132_hcd_giveback_urb(u132, endp, urb, retval); + return; + } + } else { + if (endp->input) { + u8 address = u132->addr[endp->usb_addr].address; + if (ring->in_use) { + up(&u132->scheduler_lock); + u132_endp_put_kref(u132, endp); + return; + } else { + int retval; + struct urb *urb = endp->urb_list[ + ENDP_QUEUE_MASK & endp->queue_next]; + endp->active = 1; + ring->curr_endp = endp; + ring->in_use = 1; + up(&u132->scheduler_lock); + retval = edset_input(u132, ring, endp, urb, + address, endp->toggle_bits, + u132_hcd_bulk_input_recv); + if (retval == 0) { + } else + u132_hcd_giveback_urb(u132, endp, urb, + retval); + return; + } + } else { /* output pipe */ + u8 address = u132->addr[endp->usb_addr].address; + if (ring->in_use) { + up(&u132->scheduler_lock); + u132_endp_put_kref(u132, endp); + return; + } else { + int retval; + struct urb *urb = endp->urb_list[ + ENDP_QUEUE_MASK & endp->queue_next]; + endp->active = 1; + ring->curr_endp = endp; + ring->in_use = 1; + up(&u132->scheduler_lock); + retval = edset_output(u132, ring, endp, urb, + address, endp->toggle_bits, + u132_hcd_bulk_output_sent); + if (retval == 0) { + } else + u132_hcd_giveback_urb(u132, endp, urb, + retval); + return; + } + } + } +} + +static void port_power(struct u132 *u132, int pn, int is_on) +{ + u132->port[pn].power = is_on; +} + +static void u132_power(struct u132 *u132, int is_on) +{ + struct usb_hcd *hcd = u132_to_hcd(u132) + ; /* hub is inactive unless the port is powered */ + if (is_on) { + if (u132->power) + return; + u132->power = 1; + hcd->self.controller->power.power_state = PMSG_ON; + } else { + u132->power = 0; + hcd->state = HC_STATE_HALT; + hcd->self.controller->power.power_state = PMSG_SUSPEND; + } +} + +static int u132_periodic_reinit(struct u132 *u132) +{ + int retval; + u32 fi = u132->hc_fminterval & 0x03fff; + u32 fit; + u32 fminterval; + retval = u132_read_pcimem(u132, fminterval, &fminterval); + if (retval) + return retval; + fit = fminterval & FIT; + retval = u132_write_pcimem(u132, fminterval, + (fit ^ FIT) | u132->hc_fminterval); + if (retval) + return retval; + retval = u132_write_pcimem(u132, periodicstart, + ((9 *fi) / 10) & 0x3fff); + if (retval) + return retval; + return 0; +} + +static char *hcfs2string(int state) +{ + switch (state) { + case OHCI_USB_RESET: + return "reset"; + case OHCI_USB_RESUME: + return "resume"; + case OHCI_USB_OPER: + return "operational"; + case OHCI_USB_SUSPEND: + return "suspend"; + } + return "?"; +} + +static int u132_usb_reset(struct u132 *u132) +{ + int retval; + retval = u132_read_pcimem(u132, control, &u132->hc_control); + if (retval) + return retval; + u132->hc_control &= OHCI_CTRL_RWC; + retval = u132_write_pcimem(u132, control, u132->hc_control); + if (retval) + return retval; + return 0; +} + +static int u132_init(struct u132 *u132) +{ + int retval; + u32 control; + u132_disable(u132); + u132->next_statechange = + jiffies; /* SMM owns the HC? not for long! */ { + u32 control; + retval = u132_read_pcimem(u132, control, &control); + if (retval) + return retval; + if (control & OHCI_CTRL_IR) { + u32 temp = 50; + retval = u132_write_pcimem(u132, intrenable, + OHCI_INTR_OC); + if (retval) + return retval; + retval = u132_write_pcimem_byte(u132, cmdstatus, + OHCI_OCR); + if (retval) + return retval; + check:{ + retval = u132_read_pcimem(u132, control, + &control); + if (retval) + return retval; + } + if (control & OHCI_CTRL_IR) { + msleep(10); + if (--temp == 0) { + dev_err(&u132->platform_dev->dev, "USB " + "HC takeover failed!(BIOS/SMM b" + "ug) control=%08X\n", control); + return -EBUSY; + } + goto check; + } + u132_usb_reset(u132); + } + } + retval = u132_write_pcimem(u132, intrdisable, OHCI_INTR_MIE); + if (retval) + return retval; + retval = u132_read_pcimem(u132, control, &control); + if (retval) + return retval; + if (u132->num_ports == 0) { + u32 rh_a = -1; + retval = u132_read_pcimem(u132, roothub.a, &rh_a); + if (retval) + return retval; + u132->num_ports = rh_a & RH_A_NDP; + retval = read_roothub_info(u132); + if (retval) + return retval; + } + if (u132->num_ports > MAX_U132_PORTS) { + return -EINVAL; + } + return 0; +} + + +/* Start an OHCI controller, set the BUS operational +* resets USB and controller +* enable interrupts +*/ +static int u132_run(struct u132 *u132) +{ + int retval; + u32 control; + u32 status; + u32 fminterval; + u32 periodicstart; + u32 cmdstatus; + u32 roothub_a; + int mask = OHCI_INTR_INIT; + int first = u132->hc_fminterval == 0; + int sleep_time = 0; + int reset_timeout = 30; /* ... allow extra time */ + u132_disable(u132); + if (first) { + u32 temp; + retval = u132_read_pcimem(u132, fminterval, &temp); + if (retval) + return retval; + u132->hc_fminterval = temp & 0x3fff; + if (u132->hc_fminterval != FI) { + } + u132->hc_fminterval |= FSMP(u132->hc_fminterval) << 16; + } + retval = u132_read_pcimem(u132, control, &u132->hc_control); + if (retval) + return retval; + dev_info(&u132->platform_dev->dev, "resetting from state '%s', control " + "= %08X\n", hcfs2string(u132->hc_control & OHCI_CTRL_HCFS), + u132->hc_control); + switch (u132->hc_control & OHCI_CTRL_HCFS) { + case OHCI_USB_OPER: + sleep_time = 0; + break; + case OHCI_USB_SUSPEND: + case OHCI_USB_RESUME: + u132->hc_control &= OHCI_CTRL_RWC; + u132->hc_control |= OHCI_USB_RESUME; + sleep_time = 10; + break; + default: + u132->hc_control &= OHCI_CTRL_RWC; + u132->hc_control |= OHCI_USB_RESET; + sleep_time = 50; + break; + } + retval = u132_write_pcimem(u132, control, u132->hc_control); + if (retval) + return retval; + retval = u132_read_pcimem(u132, control, &control); + if (retval) + return retval; + msleep(sleep_time); + retval = u132_read_pcimem(u132, roothub.a, &roothub_a); + if (retval) + return retval; + if (!(roothub_a & RH_A_NPS)) { + int temp; /* power down each port */ + for (temp = 0; temp < u132->num_ports; temp++) { + retval = u132_write_pcimem(u132, + roothub.portstatus[temp], RH_PS_LSDA); + if (retval) + return retval; + } + } + retval = u132_read_pcimem(u132, control, &control); + if (retval) + return retval; + retry:retval = u132_read_pcimem(u132, cmdstatus, &status); + if (retval) + return retval; + retval = u132_write_pcimem_byte(u132, cmdstatus, OHCI_HCR); + if (retval) + return retval; + extra:{ + retval = u132_read_pcimem(u132, cmdstatus, &status); + if (retval) + return retval; + if (0 != (status & OHCI_HCR)) { + if (--reset_timeout == 0) { + dev_err(&u132->platform_dev->dev, "USB HC reset" + " timed out!\n"); + return -ENODEV; + } else { + msleep(5); + goto extra; + } + } + } + if (u132->flags & OHCI_QUIRK_INITRESET) { + retval = u132_write_pcimem(u132, control, u132->hc_control); + if (retval) + return retval; + retval = u132_read_pcimem(u132, control, &control); + if (retval) + return retval; + } + retval = u132_write_pcimem(u132, ed_controlhead, 0x00000000); + if (retval) + return retval; + retval = u132_write_pcimem(u132, ed_bulkhead, 0x11000000); + if (retval) + return retval; + retval = u132_write_pcimem(u132, hcca, 0x00000000); + if (retval) + return retval; + retval = u132_periodic_reinit(u132); + if (retval) + return retval; + retval = u132_read_pcimem(u132, fminterval, &fminterval); + if (retval) + return retval; + retval = u132_read_pcimem(u132, periodicstart, &periodicstart); + if (retval) + return retval; + if (0 == (fminterval & 0x3fff0000) || 0 == periodicstart) { + if (!(u132->flags & OHCI_QUIRK_INITRESET)) { + u132->flags |= OHCI_QUIRK_INITRESET; + goto retry; + } else + dev_err(&u132->platform_dev->dev, "init err(%08x %04x)" + "\n", fminterval, periodicstart); + } /* start controller operations */ + u132->hc_control &= OHCI_CTRL_RWC; + u132->hc_control |= OHCI_CONTROL_INIT | OHCI_CTRL_BLE | OHCI_USB_OPER; + retval = u132_write_pcimem(u132, control, u132->hc_control); + if (retval) + return retval; + retval = u132_write_pcimem_byte(u132, cmdstatus, OHCI_BLF); + if (retval) + return retval; + retval = u132_read_pcimem(u132, cmdstatus, &cmdstatus); + if (retval) + return retval; + retval = u132_read_pcimem(u132, control, &control); + if (retval) + return retval; + u132_to_hcd(u132)->state = HC_STATE_RUNNING; + retval = u132_write_pcimem(u132, roothub.status, RH_HS_DRWE); + if (retval) + return retval; + retval = u132_write_pcimem(u132, intrstatus, mask); + if (retval) + return retval; + retval = u132_write_pcimem(u132, intrdisable, + OHCI_INTR_MIE | OHCI_INTR_OC | OHCI_INTR_RHSC | OHCI_INTR_FNO | + OHCI_INTR_UE | OHCI_INTR_RD | OHCI_INTR_SF | OHCI_INTR_WDH | + OHCI_INTR_SO); + if (retval) + return retval; /* handle root hub init quirks ... */ + retval = u132_read_pcimem(u132, roothub.a, &roothub_a); + if (retval) + return retval; + roothub_a &= ~(RH_A_PSM | RH_A_OCPM); + if (u132->flags & OHCI_QUIRK_SUPERIO) { + roothub_a |= RH_A_NOCP; + roothub_a &= ~(RH_A_POTPGT | RH_A_NPS); + retval = u132_write_pcimem(u132, roothub.a, roothub_a); + if (retval) + return retval; + } else if ((u132->flags & OHCI_QUIRK_AMD756) || distrust_firmware) { + roothub_a |= RH_A_NPS; + retval = u132_write_pcimem(u132, roothub.a, roothub_a); + if (retval) + return retval; + } + retval = u132_write_pcimem(u132, roothub.status, RH_HS_LPSC); + if (retval) + return retval; + retval = u132_write_pcimem(u132, roothub.b, + (roothub_a & RH_A_NPS) ? 0 : RH_B_PPCM); + if (retval) + return retval; + retval = u132_read_pcimem(u132, control, &control); + if (retval) + return retval; + mdelay((roothub_a >> 23) & 0x1fe); + u132_to_hcd(u132)->state = HC_STATE_RUNNING; + return 0; +} + +static void u132_hcd_stop(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 hcd=%p is being remov" + "ed\n", hcd); + } else { + down(&u132->sw_lock); + msleep(100); + u132_power(u132, 0); + up(&u132->sw_lock); + } +} + +static int u132_hcd_start(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); + return -ENODEV; + } else if (u132->going > 0) { + dev_err(&u132->platform_dev->dev, "device is being removed\n"); + return -ESHUTDOWN; + } else if (hcd->self.controller) { + int retval; + struct platform_device *pdev = + to_platform_device(hcd->self.controller); + u16 vendor = ((struct u132_platform_data *) + (pdev->dev.platform_data))->vendor; + u16 device = ((struct u132_platform_data *) + (pdev->dev.platform_data))->device; + down(&u132->sw_lock); + msleep(10); + if (vendor == PCI_VENDOR_ID_AMD && device == 0x740c) { + u132->flags = OHCI_QUIRK_AMD756; + } else if (vendor == PCI_VENDOR_ID_OPTI && device == 0xc861) { + dev_err(&u132->platform_dev->dev, "WARNING: OPTi workar" + "ounds unavailable\n"); + } else if (vendor == PCI_VENDOR_ID_COMPAQ && device == 0xa0f8) + u132->flags |= OHCI_QUIRK_ZFMICRO; + retval = u132_run(u132); + if (retval) { + u132_disable(u132); + u132->going = 1; + } + msleep(100); + up(&u132->sw_lock); + return retval; + } else { + dev_err(&u132->platform_dev->dev, "platform_device missing\n"); + return -ENODEV; + } +} + +static int u132_hcd_reset(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); + return -ENODEV; + } else if (u132->going > 0) { + dev_err(&u132->platform_dev->dev, "device is being removed\n"); + return -ESHUTDOWN; + } else { + int retval; + down(&u132->sw_lock); + retval = u132_init(u132); + if (retval) { + u132_disable(u132); + u132->going = 1; + } + up(&u132->sw_lock); + return retval; + } +} + +static int create_endpoint_and_queue_int(struct u132 *u132, + struct u132_udev *udev, struct usb_host_endpoint *hep, struct urb *urb, + struct usb_device *usb_dev, u8 usb_addr, u8 usb_endp, u8 address, + gfp_t mem_flags) +{ + struct u132_ring *ring; + unsigned long irqs; + u8 endp_number = ++u132->num_endpoints; + struct u132_endp *endp = hep->hcpriv = u132->endp[endp_number - 1] = + kmalloc(sizeof(struct u132_endp), mem_flags); + if (!endp) { + return -ENOMEM; + } + INIT_WORK(&endp->scheduler, u132_hcd_endp_work_scheduler, (void *)endp); + spin_lock_init(&endp->queue_lock.slock); + INIT_LIST_HEAD(&endp->urb_more); + ring = endp->ring = &u132->ring[0]; + if (ring->curr_endp) { + list_add_tail(&endp->endp_ring, &ring->curr_endp->endp_ring); + } else { + INIT_LIST_HEAD(&endp->endp_ring); + ring->curr_endp = endp; + } + ring->length += 1; + endp->dequeueing = 0; + endp->edset_flush = 0; + endp->active = 0; + endp->delayed = 0; + endp->endp_number = endp_number; + endp->u132 = u132; + endp->hep = hep; + endp->pipetype = usb_pipetype(urb->pipe); + u132_endp_init_kref(u132, endp); + if (usb_pipein(urb->pipe)) { + endp->toggle_bits = 0x2; + usb_settoggle(udev->usb_device, usb_endp, 0, 0); + endp->input = 1; + endp->output = 0; + udev->endp_number_in[usb_endp] = endp_number; + u132_udev_get_kref(u132, udev); + } else { + endp->toggle_bits = 0x2; + usb_settoggle(udev->usb_device, usb_endp, 1, 0); + endp->input = 0; + endp->output = 1; + udev->endp_number_out[usb_endp] = endp_number; + u132_udev_get_kref(u132, udev); + } + urb->hcpriv = u132; + spin_lock_irqsave(&endp->queue_lock.slock, irqs); + endp->delayed = 1; + endp->jiffies = jiffies + msecs_to_jiffies(urb->interval); + endp->udev_number = address; + endp->usb_addr = usb_addr; + endp->usb_endp = usb_endp; + endp->queue_size = 1; + endp->queue_last = 0; + endp->queue_next = 0; + endp->urb_list[ENDP_QUEUE_MASK & endp->queue_last++] = urb; + spin_unlock_irqrestore(&endp->queue_lock.slock, irqs); + u132_endp_queue_work(u132, endp, msecs_to_jiffies(urb->interval)); + return 0; +} + +static int queue_int_on_old_endpoint(struct u132 *u132, struct u132_udev *udev, + struct usb_host_endpoint *hep, struct urb *urb, + struct usb_device *usb_dev, struct u132_endp *endp, u8 usb_addr, + u8 usb_endp, u8 address) +{ + urb->hcpriv = u132; + endp->delayed = 1; + endp->jiffies = jiffies + msecs_to_jiffies(urb->interval); + if (endp->queue_size++ < ENDP_QUEUE_SIZE) { + endp->urb_list[ENDP_QUEUE_MASK & endp->queue_last++] = urb; + } else { + struct u132_urbq *urbq = kmalloc(sizeof(struct u132_urbq), + GFP_ATOMIC); + if (urbq == NULL) { + endp->queue_size -= 1; + return -ENOMEM; + } else { + list_add_tail(&urbq->urb_more, &endp->urb_more); + urbq->urb = urb; + } + } + return 0; +} + +static int create_endpoint_and_queue_bulk(struct u132 *u132, + struct u132_udev *udev, struct usb_host_endpoint *hep, struct urb *urb, + struct usb_device *usb_dev, u8 usb_addr, u8 usb_endp, u8 address, + gfp_t mem_flags) +{ + int ring_number; + struct u132_ring *ring; + unsigned long irqs; + u8 endp_number = ++u132->num_endpoints; + struct u132_endp *endp = hep->hcpriv = u132->endp[endp_number - 1] = + kmalloc(sizeof(struct u132_endp), mem_flags); + if (!endp) { + return -ENOMEM; + } + INIT_WORK(&endp->scheduler, u132_hcd_endp_work_scheduler, (void *)endp); + spin_lock_init(&endp->queue_lock.slock); + INIT_LIST_HEAD(&endp->urb_more); + endp->dequeueing = 0; + endp->edset_flush = 0; + endp->active = 0; + endp->delayed = 0; + endp->endp_number = endp_number; + endp->u132 = u132; + endp->hep = hep; + endp->pipetype = usb_pipetype(urb->pipe); + u132_endp_init_kref(u132, endp); + if (usb_pipein(urb->pipe)) { + endp->toggle_bits = 0x2; + usb_settoggle(udev->usb_device, usb_endp, 0, 0); + ring_number = 3; + endp->input = 1; + endp->output = 0; + udev->endp_number_in[usb_endp] = endp_number; + u132_udev_get_kref(u132, udev); + } else { + endp->toggle_bits = 0x2; + usb_settoggle(udev->usb_device, usb_endp, 1, 0); + ring_number = 2; + endp->input = 0; + endp->output = 1; + udev->endp_number_out[usb_endp] = endp_number; + u132_udev_get_kref(u132, udev); + } + ring = endp->ring = &u132->ring[ring_number - 1]; + if (ring->curr_endp) { + list_add_tail(&endp->endp_ring, &ring->curr_endp->endp_ring); + } else { + INIT_LIST_HEAD(&endp->endp_ring); + ring->curr_endp = endp; + } + ring->length += 1; + urb->hcpriv = u132; + spin_lock_irqsave(&endp->queue_lock.slock, irqs); + endp->udev_number = address; + endp->usb_addr = usb_addr; + endp->usb_endp = usb_endp; + endp->queue_size = 1; + endp->queue_last = 0; + endp->queue_next = 0; + endp->urb_list[ENDP_QUEUE_MASK & endp->queue_last++] = urb; + spin_unlock_irqrestore(&endp->queue_lock.slock, irqs); + u132_endp_queue_work(u132, endp, 0); + return 0; +} + +static int queue_bulk_on_old_endpoint(struct u132 *u132, struct u132_udev *udev, + struct usb_host_endpoint *hep, struct urb *urb, + struct usb_device *usb_dev, struct u132_endp *endp, u8 usb_addr, + u8 usb_endp, u8 address) +{ + urb->hcpriv = u132; + if (endp->queue_size++ < ENDP_QUEUE_SIZE) { + endp->urb_list[ENDP_QUEUE_MASK & endp->queue_last++] = urb; + } else { + struct u132_urbq *urbq = kmalloc(sizeof(struct u132_urbq), + GFP_ATOMIC); + if (urbq == NULL) { + endp->queue_size -= 1; + return -ENOMEM; + } else { + list_add_tail(&urbq->urb_more, &endp->urb_more); + urbq->urb = urb; + } + } + return 0; +} + +static int create_endpoint_and_queue_control(struct u132 *u132, + struct usb_host_endpoint *hep, struct urb *urb, + struct usb_device *usb_dev, u8 usb_addr, u8 usb_endp, + gfp_t mem_flags) +{ + struct u132_ring *ring; + u8 endp_number = ++u132->num_endpoints; + struct u132_endp *endp = hep->hcpriv = u132->endp[endp_number - 1] = + kmalloc(sizeof(struct u132_endp), mem_flags); + if (!endp) { + return -ENOMEM; + } + INIT_WORK(&endp->scheduler, u132_hcd_endp_work_scheduler, (void *)endp); + spin_lock_init(&endp->queue_lock.slock); + INIT_LIST_HEAD(&endp->urb_more); + ring = endp->ring = &u132->ring[0]; + if (ring->curr_endp) { + list_add_tail(&endp->endp_ring, &ring->curr_endp->endp_ring); + } else { + INIT_LIST_HEAD(&endp->endp_ring); + ring->curr_endp = endp; + } + ring->length += 1; + endp->dequeueing = 0; + endp->edset_flush = 0; + endp->active = 0; + endp->delayed = 0; + endp->endp_number = endp_number; + endp->u132 = u132; + endp->hep = hep; + u132_endp_init_kref(u132, endp); + u132_endp_get_kref(u132, endp); + if (usb_addr == 0) { + unsigned long irqs; + u8 address = u132->addr[usb_addr].address; + struct u132_udev *udev = &u132->udev[address]; + endp->udev_number = address; + endp->usb_addr = usb_addr; + endp->usb_endp = usb_endp; + endp->input = 1; + endp->output = 1; + endp->pipetype = usb_pipetype(urb->pipe); + u132_udev_init_kref(u132, udev); + u132_udev_get_kref(u132, udev); + udev->endp_number_in[usb_endp] = endp_number; + udev->endp_number_out[usb_endp] = endp_number; + urb->hcpriv = u132; + spin_lock_irqsave(&endp->queue_lock.slock, irqs); + endp->queue_size = 1; + endp->queue_last = 0; + endp->queue_next = 0; + endp->urb_list[ENDP_QUEUE_MASK & endp->queue_last++] = urb; + spin_unlock_irqrestore(&endp->queue_lock.slock, irqs); + u132_endp_queue_work(u132, endp, 0); + return 0; + } else { /*(usb_addr > 0) */ + unsigned long irqs; + u8 address = u132->addr[usb_addr].address; + struct u132_udev *udev = &u132->udev[address]; + endp->udev_number = address; + endp->usb_addr = usb_addr; + endp->usb_endp = usb_endp; + endp->input = 1; + endp->output = 1; + endp->pipetype = usb_pipetype(urb->pipe); + u132_udev_get_kref(u132, udev); + udev->enumeration = 2; + udev->endp_number_in[usb_endp] = endp_number; + udev->endp_number_out[usb_endp] = endp_number; + urb->hcpriv = u132; + spin_lock_irqsave(&endp->queue_lock.slock, irqs); + endp->queue_size = 1; + endp->queue_last = 0; + endp->queue_next = 0; + endp->urb_list[ENDP_QUEUE_MASK & endp->queue_last++] = urb; + spin_unlock_irqrestore(&endp->queue_lock.slock, irqs); + u132_endp_queue_work(u132, endp, 0); + return 0; + } +} + +static int queue_control_on_old_endpoint(struct u132 *u132, + struct usb_host_endpoint *hep, struct urb *urb, + struct usb_device *usb_dev, struct u132_endp *endp, u8 usb_addr, + u8 usb_endp) +{ + if (usb_addr == 0) { + if (usb_pipein(urb->pipe)) { + urb->hcpriv = u132; + if (endp->queue_size++ < ENDP_QUEUE_SIZE) { + endp->urb_list[ENDP_QUEUE_MASK & + endp->queue_last++] = urb; + } else { + struct u132_urbq *urbq = + kmalloc(sizeof(struct u132_urbq), + GFP_ATOMIC); + if (urbq == NULL) { + endp->queue_size -= 1; + return -ENOMEM; + } else { + list_add_tail(&urbq->urb_more, + &endp->urb_more); + urbq->urb = urb; + } + } + return 0; + } else { /* usb_pipeout(urb->pipe) */ + struct u132_addr *addr = &u132->addr[usb_dev->devnum]; + int I = MAX_U132_UDEVS; + int i = 0; + while (--I > 0) { + struct u132_udev *udev = &u132->udev[++i]; + if (udev->usb_device) { + continue; + } else { + udev->enumeration = 1; + u132->addr[0].address = i; + endp->udev_number = i; + udev->udev_number = i; + udev->usb_addr = usb_dev->devnum; + u132_udev_init_kref(u132, udev); + udev->endp_number_in[usb_endp] = + endp->endp_number; + u132_udev_get_kref(u132, udev); + udev->endp_number_out[usb_endp] = + endp->endp_number; + udev->usb_device = usb_dev; + ((u8 *) (urb->setup_packet))[2] = + addr->address = i; + u132_udev_get_kref(u132, udev); + break; + } + } + if (I == 0) { + dev_err(&u132->platform_dev->dev, "run out of d" + "evice space\n"); + return -EINVAL; + } + urb->hcpriv = u132; + if (endp->queue_size++ < ENDP_QUEUE_SIZE) { + endp->urb_list[ENDP_QUEUE_MASK & + endp->queue_last++] = urb; + } else { + struct u132_urbq *urbq = + kmalloc(sizeof(struct u132_urbq), + GFP_ATOMIC); + if (urbq == NULL) { + endp->queue_size -= 1; + return -ENOMEM; + } else { + list_add_tail(&urbq->urb_more, + &endp->urb_more); + urbq->urb = urb; + } + } + return 0; + } + } else { /*(usb_addr > 0) */ + u8 address = u132->addr[usb_addr].address; + struct u132_udev *udev = &u132->udev[address]; + urb->hcpriv = u132; + if (udev->enumeration == 2) { + } else + udev->enumeration = 2; + if (endp->queue_size++ < ENDP_QUEUE_SIZE) { + endp->urb_list[ENDP_QUEUE_MASK & endp->queue_last++] = + urb; + } else { + struct u132_urbq *urbq = + kmalloc(sizeof(struct u132_urbq), GFP_ATOMIC); + if (urbq == NULL) { + endp->queue_size -= 1; + return -ENOMEM; + } else { + list_add_tail(&urbq->urb_more, &endp->urb_more); + urbq->urb = urb; + } + } + return 0; + } +} + +static int u132_urb_enqueue(struct usb_hcd *hcd, struct usb_host_endpoint *hep, + struct urb *urb, gfp_t mem_flags) +{ + struct u132 *u132 = hcd_to_u132(hcd); + if (irqs_disabled()) { + if (__GFP_WAIT & mem_flags) { + printk(KERN_ERR "invalid context for function that migh" + "t sleep\n"); + return -EINVAL; + } + } + if (u132->going > 1) { + dev_err(&u132->platform_dev->dev, "device has been removed %d\n" + , u132->going); + return -ENODEV; + } else if (u132->going > 0) { + dev_err(&u132->platform_dev->dev, "device is being removed urb=" + "%p status=%d\n", urb, urb->status); + return -ESHUTDOWN; + } else { + u8 usb_addr = usb_pipedevice(urb->pipe); + u8 usb_endp = usb_pipeendpoint(urb->pipe); + struct usb_device *usb_dev = urb->dev; + if (usb_pipetype(urb->pipe) == PIPE_INTERRUPT) { + u8 address = u132->addr[usb_addr].address; + struct u132_udev *udev = &u132->udev[address]; + struct u132_endp *endp = hep->hcpriv; + urb->actual_length = 0; + if (endp) { + unsigned long irqs; + int retval; + spin_lock_irqsave(&endp->queue_lock.slock, + irqs); + retval = queue_int_on_old_endpoint(u132, udev, + hep, urb, usb_dev, endp, usb_addr, + usb_endp, address); + spin_unlock_irqrestore(&endp->queue_lock.slock, + irqs); + if (retval) { + return retval; + } else { + u132_endp_queue_work(u132, endp, + msecs_to_jiffies(urb->interval)) + ; + return 0; + } + } else if (u132->num_endpoints == MAX_U132_ENDPS) { + return -EINVAL; + } else { /*(endp == NULL) */ + return create_endpoint_and_queue_int(u132, udev, + hep, urb, usb_dev, usb_addr, usb_endp, + address, mem_flags); + } + } else if (usb_pipetype(urb->pipe) == PIPE_ISOCHRONOUS) { + dev_err(&u132->platform_dev->dev, "the hardware does no" + "t support PIPE_ISOCHRONOUS\n"); + return -EINVAL; + } else if (usb_pipetype(urb->pipe) == PIPE_BULK) { + u8 address = u132->addr[usb_addr].address; + struct u132_udev *udev = &u132->udev[address]; + struct u132_endp *endp = hep->hcpriv; + urb->actual_length = 0; + if (endp) { + unsigned long irqs; + int retval; + spin_lock_irqsave(&endp->queue_lock.slock, + irqs); + retval = queue_bulk_on_old_endpoint(u132, udev, + hep, urb, usb_dev, endp, usb_addr, + usb_endp, address); + spin_unlock_irqrestore(&endp->queue_lock.slock, + irqs); + if (retval) { + return retval; + } else { + u132_endp_queue_work(u132, endp, 0); + return 0; + } + } else if (u132->num_endpoints == MAX_U132_ENDPS) { + return -EINVAL; + } else + return create_endpoint_and_queue_bulk(u132, + udev, hep, urb, usb_dev, usb_addr, + usb_endp, address, mem_flags); + } else { + struct u132_endp *endp = hep->hcpriv; + u16 urb_size = 8; + u8 *b = urb->setup_packet; + int i = 0; + char data[30 *3 + 4]; + char *d = data; + int m = (sizeof(data) - 1) / 3; + int l = 0; + data[0] = 0; + while (urb_size-- > 0) { + if (i > m) { + } else if (i++ < m) { + int w = sprintf(d, " %02X", *b++); + d += w; + l += w; + } else + d += sprintf(d, " .."); + } + if (endp) { + unsigned long irqs; + int retval; + spin_lock_irqsave(&endp->queue_lock.slock, + irqs); + retval = queue_control_on_old_endpoint(u132, + hep, urb, usb_dev, endp, usb_addr, + usb_endp); + spin_unlock_irqrestore(&endp->queue_lock.slock, + irqs); + if (retval) { + return retval; + } else { + u132_endp_queue_work(u132, endp, 0); + return 0; + } + } else if (u132->num_endpoints == MAX_U132_ENDPS) { + return -EINVAL; + } else + return create_endpoint_and_queue_control(u132, + hep, urb, usb_dev, usb_addr, usb_endp, + mem_flags); + } + } +} + +static int dequeue_from_overflow_chain(struct u132 *u132, + struct u132_endp *endp, struct urb *urb) +{ + struct list_head *scan; + struct list_head *head = &endp->urb_more; + list_for_each(scan, head) { + struct u132_urbq *urbq = list_entry(scan, struct u132_urbq, + urb_more); + if (urbq->urb == urb) { + struct usb_hcd *hcd = u132_to_hcd(u132); + list_del(scan); + endp->queue_size -= 1; + urb->error_count = 0; + urb->hcpriv = NULL; + usb_hcd_giveback_urb(hcd, urb, NULL); + return 0; + } else + continue; + } + dev_err(&u132->platform_dev->dev, "urb=%p not found in endp[%d]=%p ring" + "[%d] %c%c usb_endp=%d usb_addr=%d size=%d next=%04X last=%04X" + "\n", urb, endp->endp_number, endp, endp->ring->number, + endp->input ? 'I' : ' ', endp->output ? 'O' : ' ', + endp->usb_endp, endp->usb_addr, endp->queue_size, + endp->queue_next, endp->queue_last); + return -EINVAL; +} + +static int u132_endp_urb_dequeue(struct u132 *u132, struct u132_endp *endp, + struct urb *urb) +{ + unsigned long irqs; + spin_lock_irqsave(&endp->queue_lock.slock, irqs); + if (endp->queue_size == 0) { + dev_err(&u132->platform_dev->dev, "urb=%p not found in endp[%d]" + "=%p ring[%d] %c%c usb_endp=%d usb_addr=%d\n", urb, + endp->endp_number, endp, endp->ring->number, + endp->input ? 'I' : ' ', endp->output ? 'O' : ' ', + endp->usb_endp, endp->usb_addr); + spin_unlock_irqrestore(&endp->queue_lock.slock, irqs); + return -EINVAL; + } + if (urb == endp->urb_list[ENDP_QUEUE_MASK & endp->queue_next]) { + if (endp->active) { + endp->dequeueing = 1; + endp->edset_flush = 1; + u132_endp_queue_work(u132, endp, 0); + spin_unlock_irqrestore(&endp->queue_lock.slock, irqs); + urb->hcpriv = NULL; + return 0; + } else { + spin_unlock_irqrestore(&endp->queue_lock.slock, irqs); + u132_hcd_abandon_urb(u132, endp, urb, urb->status); + return 0; + } + } else { + u16 queue_list = 0; + u16 queue_size = endp->queue_size; + u16 queue_scan = endp->queue_next; + struct urb **urb_slot = NULL; + while (++queue_list < ENDP_QUEUE_SIZE && --queue_size > 0) { + if (urb == endp->urb_list[ENDP_QUEUE_MASK & + ++queue_scan]) { + urb_slot = &endp->urb_list[ENDP_QUEUE_MASK & + queue_scan]; + break; + } else + continue; + } + while (++queue_list < ENDP_QUEUE_SIZE && --queue_size > 0) { + *urb_slot = endp->urb_list[ENDP_QUEUE_MASK & + ++queue_scan]; + urb_slot = &endp->urb_list[ENDP_QUEUE_MASK & + queue_scan]; + } + if (urb_slot) { + struct usb_hcd *hcd = u132_to_hcd(u132); + endp->queue_size -= 1; + if (list_empty(&endp->urb_more)) { + spin_unlock_irqrestore(&endp->queue_lock.slock, + irqs); + } else { + struct list_head *next = endp->urb_more.next; + struct u132_urbq *urbq = list_entry(next, + struct u132_urbq, urb_more); + list_del(next); + *urb_slot = urbq->urb; + spin_unlock_irqrestore(&endp->queue_lock.slock, + irqs); + kfree(urbq); + } urb->error_count = 0; + urb->hcpriv = NULL; + usb_hcd_giveback_urb(hcd, urb, NULL); + return 0; + } else if (list_empty(&endp->urb_more)) { + dev_err(&u132->platform_dev->dev, "urb=%p not found in " + "endp[%d]=%p ring[%d] %c%c usb_endp=%d usb_addr" + "=%d size=%d next=%04X last=%04X\n", urb, + endp->endp_number, endp, endp->ring->number, + endp->input ? 'I' : ' ', + endp->output ? 'O' : ' ', endp->usb_endp, + endp->usb_addr, endp->queue_size, + endp->queue_next, endp->queue_last); + spin_unlock_irqrestore(&endp->queue_lock.slock, irqs); + return -EINVAL; + } else { + int retval = dequeue_from_overflow_chain(u132, endp, + urb); + spin_unlock_irqrestore(&endp->queue_lock.slock, irqs); + return retval; + } + } +} + +static int u132_urb_dequeue(struct usb_hcd *hcd, struct urb *urb) +{ + struct u132 *u132 = hcd_to_u132(hcd); + if (u132->going > 2) { + dev_err(&u132->platform_dev->dev, "device has been removed %d\n" + , u132->going); + return -ENODEV; + } else { + u8 usb_addr = usb_pipedevice(urb->pipe); + u8 usb_endp = usb_pipeendpoint(urb->pipe); + u8 address = u132->addr[usb_addr].address; + struct u132_udev *udev = &u132->udev[address]; + if (usb_pipein(urb->pipe)) { + u8 endp_number = udev->endp_number_in[usb_endp]; + struct u132_endp *endp = u132->endp[endp_number - 1]; + return u132_endp_urb_dequeue(u132, endp, urb); + } else { + u8 endp_number = udev->endp_number_out[usb_endp]; + struct u132_endp *endp = u132->endp[endp_number - 1]; + return u132_endp_urb_dequeue(u132, endp, urb); + } + } +} + +static void u132_endpoint_disable(struct usb_hcd *hcd, + struct usb_host_endpoint *hep) +{ + struct u132 *u132 = hcd_to_u132(hcd); + if (u132->going > 2) { + dev_err(&u132->platform_dev->dev, "device has been removed %d\n" + , u132->going); + } else { + struct u132_endp *endp = hep->hcpriv; + if (endp) + u132_endp_put_kref(u132, endp); + } +} + +static int u132_get_frame(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); + return -ENODEV; + } else if (u132->going > 0) { + dev_err(&u132->platform_dev->dev, "device is being removed\n"); + return -ESHUTDOWN; + } else { + int frame = 0; + dev_err(&u132->platform_dev->dev, "TODO: u132_get_frame\n"); + msleep(100); + return frame; + } +} + +static int u132_roothub_descriptor(struct u132 *u132, + struct usb_hub_descriptor *desc) +{ + int retval; + u16 temp; + u32 rh_a = -1; + u32 rh_b = -1; + retval = u132_read_pcimem(u132, roothub.a, &rh_a); + if (retval) + return retval; + desc->bDescriptorType = 0x29; + desc->bPwrOn2PwrGood = (rh_a & RH_A_POTPGT) >> 24; + desc->bHubContrCurrent = 0; + desc->bNbrPorts = u132->num_ports; + temp = 1 + (u132->num_ports / 8); + desc->bDescLength = 7 + 2 *temp; + temp = 0; + if (rh_a & RH_A_NPS) + temp |= 0x0002; + if (rh_a & RH_A_PSM) + temp |= 0x0001; + if (rh_a & RH_A_NOCP) { + temp |= 0x0010; + } else if (rh_a & RH_A_OCPM) + temp |= 0x0008; + desc->wHubCharacteristics = cpu_to_le16(temp); + retval = u132_read_pcimem(u132, roothub.b, &rh_b); + if (retval) + return retval; + memset(desc->bitmap, 0xff, sizeof(desc->bitmap)); + desc->bitmap[0] = rh_b & RH_B_DR; + if (u132->num_ports > 7) { + desc->bitmap[1] = (rh_b & RH_B_DR) >> 8; + desc->bitmap[2] = 0xff; + } else + desc->bitmap[1] = 0xff; + return 0; +} + +static int u132_roothub_status(struct u132 *u132, __le32 *desc) +{ + u32 rh_status = -1; + int ret_status = u132_read_pcimem(u132, roothub.status, &rh_status); + *desc = cpu_to_le32(rh_status); + return ret_status; +} + +static int u132_roothub_portstatus(struct u132 *u132, __le32 *desc, u16 wIndex) +{ + if (wIndex == 0 || wIndex > u132->num_ports) { + return -EINVAL; + } else { + int port = wIndex - 1; + u32 rh_portstatus = -1; + int ret_portstatus = u132_read_pcimem(u132, + roothub.portstatus[port], &rh_portstatus); + *desc = cpu_to_le32(rh_portstatus); + if (*(u16 *) (desc + 2)) { + dev_info(&u132->platform_dev->dev, "Port %d Status Chan" + "ge = %08X\n", port, *desc); + } + return ret_portstatus; + } +} + + +/* this timer value might be vendor-specific ... */ +#define PORT_RESET_HW_MSEC 10 +#define PORT_RESET_MSEC 10 +/* wrap-aware logic morphed from <linux/jiffies.h> */ +#define tick_before(t1, t2) ((s16)(((s16)(t1))-((s16)(t2))) < 0) +static int u132_roothub_portreset(struct u132 *u132, int port_index) +{ + int retval; + u32 fmnumber; + u16 now; + u16 reset_done; + retval = u132_read_pcimem(u132, fmnumber, &fmnumber); + if (retval) + return retval; + now = fmnumber; + reset_done = now + PORT_RESET_MSEC; + do { + u32 portstat; + do { + retval = u132_read_pcimem(u132, + roothub.portstatus[port_index], &portstat); + if (retval) + return retval; + if (RH_PS_PRS & portstat) { + continue; + } else + break; + } while (tick_before(now, reset_done)); + if (RH_PS_PRS & portstat) + return -ENODEV; + if (RH_PS_CCS & portstat) { + if (RH_PS_PRSC & portstat) { + retval = u132_write_pcimem(u132, + roothub.portstatus[port_index], + RH_PS_PRSC); + if (retval) + return retval; + } + } else + break; /* start the next reset, + sleep till it's probably done */ + retval = u132_write_pcimem(u132, roothub.portstatus[port_index], + RH_PS_PRS); + if (retval) + return retval; + msleep(PORT_RESET_HW_MSEC); + retval = u132_read_pcimem(u132, fmnumber, &fmnumber); + if (retval) + return retval; + now = fmnumber; + } while (tick_before(now, reset_done)); + return 0; +} + +static int u132_roothub_setportfeature(struct u132 *u132, u16 wValue, + u16 wIndex) +{ + if (wIndex == 0 || wIndex > u132->num_ports) { + return -EINVAL; + } else { + int retval; + int port_index = wIndex - 1; + struct u132_port *port = &u132->port[port_index]; + port->Status &= ~(1 << wValue); + switch (wValue) { + case USB_PORT_FEAT_SUSPEND: + retval = u132_write_pcimem(u132, + roothub.portstatus[port_index], RH_PS_PSS); + if (retval) + return retval; + return 0; + case USB_PORT_FEAT_POWER: + retval = u132_write_pcimem(u132, + roothub.portstatus[port_index], RH_PS_PPS); + if (retval) + return retval; + return 0; + case USB_PORT_FEAT_RESET: + retval = u132_roothub_portreset(u132, port_index); + if (retval) + return retval; + return 0; + default: + return -EPIPE; + } + } +} + +static int u132_roothub_clearportfeature(struct u132 *u132, u16 wValue, + u16 wIndex) +{ + if (wIndex == 0 || wIndex > u132->num_ports) { + return -EINVAL; + } else { + int port_index = wIndex - 1; + u32 temp; + int retval; + struct u132_port *port = &u132->port[port_index]; + port->Status &= ~(1 << wValue); + switch (wValue) { + case USB_PORT_FEAT_ENABLE: + temp = RH_PS_CCS; + break; + case USB_PORT_FEAT_C_ENABLE: + temp = RH_PS_PESC; + break; + case USB_PORT_FEAT_SUSPEND: + temp = RH_PS_POCI; + if ((u132->hc_control & OHCI_CTRL_HCFS) + != OHCI_USB_OPER) { + dev_err(&u132->platform_dev->dev, "TODO resume_" + "root_hub\n"); + } + break; + case USB_PORT_FEAT_C_SUSPEND: + temp = RH_PS_PSSC; + break; + case USB_PORT_FEAT_POWER: + temp = RH_PS_LSDA; + break; + case USB_PORT_FEAT_C_CONNECTION: + temp = RH_PS_CSC; + break; + case USB_PORT_FEAT_C_OVER_CURRENT: + temp = RH_PS_OCIC; + break; + case USB_PORT_FEAT_C_RESET: + temp = RH_PS_PRSC; + break; + default: + return -EPIPE; + } + retval = u132_write_pcimem(u132, roothub.portstatus[port_index], + temp); + if (retval) + return retval; + return 0; + } +} + + +/* the virtual root hub timer IRQ checks for hub status*/ +static int u132_hub_status_data(struct usb_hcd *hcd, char *buf) +{ + struct u132 *u132 = hcd_to_u132(hcd); + if (u132->going > 1) { + dev_err(&u132->platform_dev->dev, "device hcd=%p has been remov" + "ed %d\n", hcd, u132->going); + return -ENODEV; + } else if (u132->going > 0) { + dev_err(&u132->platform_dev->dev, "device hcd=%p is being remov" + "ed\n", hcd); + dump_stack(); + return -ESHUTDOWN; + } else { + int i, changed = 0, length = 1; + if (u132->flags & OHCI_QUIRK_AMD756) { + if ((u132->hc_roothub_a & RH_A_NDP) > MAX_ROOT_PORTS) { + dev_err(&u132->platform_dev->dev, "bogus NDP, r" + "ereads as NDP=%d\n", + u132->hc_roothub_a & RH_A_NDP); + goto done; + } + } + if (u132->hc_roothub_status & (RH_HS_LPSC | RH_HS_OCIC)) { + buf[0] = changed = 1; + } else + buf[0] = 0; + if (u132->num_ports > 7) { + buf[1] = 0; + length++; + } + for (i = 0; i < u132->num_ports; i++) { + if (u132->hc_roothub_portstatus[i] & (RH_PS_CSC | + RH_PS_PESC | RH_PS_PSSC | RH_PS_OCIC | + RH_PS_PRSC)) { + changed = 1; + if (i < 7) { + buf[0] |= 1 << (i + 1); + } else + buf[1] |= 1 << (i - 7); + continue; + } + if (!(u132->hc_roothub_portstatus[i] & RH_PS_CCS)) { + continue; + } + if ((u132->hc_roothub_portstatus[i] & RH_PS_PSS)) { + continue; + } + } + done:return changed ? length : 0; + } +} + +static int u132_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, + u16 wIndex, char *buf, u16 wLength) +{ + 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); + return -ENODEV; + } else if (u132->going > 0) { + dev_err(&u132->platform_dev->dev, "device is being removed\n"); + return -ESHUTDOWN; + } else { + int retval = 0; + down(&u132->sw_lock); + switch (typeReq) { + case ClearHubFeature: + switch (wValue) { + case C_HUB_OVER_CURRENT: + case C_HUB_LOCAL_POWER: + break; + default: + goto stall; + } + break; + case SetHubFeature: + switch (wValue) { + case C_HUB_OVER_CURRENT: + case C_HUB_LOCAL_POWER: + break; + default: + goto stall; + } + break; + case ClearPortFeature:{ + retval = u132_roothub_clearportfeature(u132, + wValue, wIndex); + if (retval) + goto error; + break; + } + case GetHubDescriptor:{ + retval = u132_roothub_descriptor(u132, + (struct usb_hub_descriptor *)buf); + if (retval) + goto error; + break; + } + case GetHubStatus:{ + retval = u132_roothub_status(u132, + (__le32 *) buf); + if (retval) + goto error; + break; + } + case GetPortStatus:{ + retval = u132_roothub_portstatus(u132, + (__le32 *) buf, wIndex); + if (retval) + goto error; + break; + } + case SetPortFeature:{ + retval = u132_roothub_setportfeature(u132, + wValue, wIndex); + if (retval) + goto error; + break; + } + default: + goto stall; + error:u132_disable(u132); + u132->going = 1; + break; + stall:retval = -EPIPE; + break; + } + up(&u132->sw_lock); + return retval; + } +} + +static int u132_start_port_reset(struct usb_hcd *hcd, unsigned port_num) +{ + 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); + return -ENODEV; + } else if (u132->going > 0) { + dev_err(&u132->platform_dev->dev, "device is being removed\n"); + return -ESHUTDOWN; + } else + 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_hcd_suspend(struct usb_hcd *hcd, pm_message_t message) +{ + 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); + return -ENODEV; + } else if (u132->going > 0) { + dev_err(&u132->platform_dev->dev, "device is being removed\n"); + return -ESHUTDOWN; + } else + return 0; +} + +static int u132_hcd_resume(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); + return -ENODEV; + } else if (u132->going > 0) { + dev_err(&u132->platform_dev->dev, "device is being removed\n"); + return -ESHUTDOWN; + } else + return 0; +} + +static int u132_bus_suspend(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); + return -ENODEV; + } else if (u132->going > 0) { + dev_err(&u132->platform_dev->dev, "device is being removed\n"); + return -ESHUTDOWN; + } else + return 0; +} + +static int u132_bus_resume(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); + return -ENODEV; + } else if (u132->going > 0) { + dev_err(&u132->platform_dev->dev, "device is being removed\n"); + return -ESHUTDOWN; + } else + return 0; +} + +#else +#define u132_hcd_suspend NULL +#define u132_hcd_resume NULL +#define u132_bus_suspend NULL +#define u132_bus_resume NULL +#endif +static struct hc_driver u132_hc_driver = { + .description = hcd_name, + .hcd_priv_size = sizeof(struct u132), + .irq = NULL, + .flags = HCD_USB11 | HCD_MEMORY, + .reset = u132_hcd_reset, + .start = u132_hcd_start, + .suspend = u132_hcd_suspend, + .resume = u132_hcd_resume, + .stop = u132_hcd_stop, + .urb_enqueue = u132_urb_enqueue, + .urb_dequeue = u132_urb_dequeue, + .endpoint_disable = u132_endpoint_disable, + .get_frame_number = u132_get_frame, + .hub_status_data = u132_hub_status_data, + .hub_control = u132_hub_control, + .bus_suspend = u132_bus_suspend, + .bus_resume = u132_bus_resume, + .start_port_reset = u132_start_port_reset, + .hub_irq_enable = u132_hub_irq_enable, +}; + +/* +* This function may be called by the USB core whilst the "usb_all_devices_rwsem" +* is held for writing, thus this module must not call usb_remove_hcd() +* synchronously - but instead should immediately stop activity to the +* device and ansynchronously call usb_remove_hcd() +*/ +static int __devexit u132_remove(struct platform_device *pdev) +{ + struct usb_hcd *hcd = platform_get_drvdata(pdev); + if (hcd) { + struct u132 *u132 = hcd_to_u132(hcd); + dump_stack(); + if (u132->going++ > 1) { + return -ENODEV; + } else { + int rings = MAX_U132_RINGS; + int endps = MAX_U132_ENDPS; + msleep(100); + down(&u132->sw_lock); + u132_monitor_cancel_work(u132); + while (rings-- > 0) { + struct u132_ring *ring = &u132->ring[rings]; + u132_ring_cancel_work(u132, ring); + } while (endps-- > 0) { + struct u132_endp *endp = u132->endp[endps]; + if (endp) + u132_endp_cancel_work(u132, endp); + } + u132->going += 1; + printk(KERN_INFO "removing device u132.%d\n", + u132->sequence_num); + up(&u132->sw_lock); + usb_remove_hcd(hcd); + u132_u132_put_kref(u132); + return 0; + } + } else + return 0; +} + +static void u132_initialise(struct u132 *u132, struct platform_device *pdev) +{ + int rings = MAX_U132_RINGS; + int ports = MAX_U132_PORTS; + int addrs = MAX_U132_ADDRS; + int udevs = MAX_U132_UDEVS; + int endps = MAX_U132_ENDPS; + u132->board = pdev->dev.platform_data; + u132->platform_dev = pdev; + u132->power = 0; + u132->reset = 0; + init_MUTEX(&u132->sw_lock); + init_MUTEX(&u132->scheduler_lock); + while (rings-- > 0) { + struct u132_ring *ring = &u132->ring[rings]; + ring->u132 = u132; + ring->number = rings + 1; + ring->length = 0; + ring->curr_endp = NULL; + INIT_WORK(&ring->scheduler, u132_hcd_ring_work_scheduler, + (void *)ring); + } down(&u132->sw_lock); + INIT_WORK(&u132->monitor, u132_hcd_monitor_work, (void *)u132); + while (ports-- > 0) { + struct u132_port *port = &u132->port[ports]; + port->u132 = u132; + port->reset = 0; + port->enable = 0; + port->power = 0; + port->Status = 0; + } while (addrs-- > 0) { + struct u132_addr *addr = &u132->addr[addrs]; + addr->address = 0; + } while (udevs-- > 0) { + struct u132_udev *udev = &u132->udev[udevs]; + int i = ARRAY_SIZE(udev->endp_number_in); + int o = ARRAY_SIZE(udev->endp_number_out); + udev->usb_device = NULL; + udev->udev_number = 0; + udev->usb_addr = 0; + udev->portnumber = 0; + while (i-- > 0) { + udev->endp_number_in[i] = 0; + } + while (o-- > 0) { + udev->endp_number_out[o] = 0; + } + } + while (endps-- > 0) { + u132->endp[endps] = NULL; + } + up(&u132->sw_lock); + return; +} + +static int __devinit u132_probe(struct platform_device *pdev) +{ + struct usb_hcd *hcd; + msleep(100); + if (u132_exiting > 0) { + return -ENODEV; + } /* refuse to confuse usbcore */ + if (pdev->dev.dma_mask) { + return -EINVAL; + } + hcd = usb_create_hcd(&u132_hc_driver, &pdev->dev, pdev->dev.bus_id); + if (!hcd) { + printk(KERN_ERR "failed to create the usb hcd struct for U132\n" + ); + ftdi_elan_gone_away(pdev); + return -ENOMEM; + } else { + int retval = 0; + struct u132 *u132 = hcd_to_u132(hcd); + hcd->rsrc_start = 0; + down(&u132_module_lock); + list_add_tail(&u132->u132_list, &u132_static_list); + u132->sequence_num = ++u132_instances; + up(&u132_module_lock); + u132_u132_init_kref(u132); + u132_initialise(u132, pdev); + hcd->product_desc = "ELAN U132 Host Controller"; + retval = usb_add_hcd(hcd, 0, 0); + if (retval != 0) { + dev_err(&u132->platform_dev->dev, "init error %d\n", + retval); + u132_u132_put_kref(u132); + return retval; + } else { + u132_monitor_queue_work(u132, 100); + return 0; + } + } +} + + +#ifdef CONFIG_PM +/* for this device there's no useful distinction between the controller +* and its root hub, except that the root hub only gets direct PM calls +* when CONFIG_USB_SUSPEND is enabled. +*/ +static int u132_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct usb_hcd *hcd = platform_get_drvdata(pdev); + 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); + return -ENODEV; + } else if (u132->going > 0) { + dev_err(&u132->platform_dev->dev, "device is being removed\n"); + return -ESHUTDOWN; + } else { + int retval = 0; + if (state.event == PM_EVENT_FREEZE) { + retval = u132_bus_suspend(hcd); + } else if (state.event == PM_EVENT_SUSPEND) { + int ports = MAX_U132_PORTS; + while (ports-- > 0) { + port_power(u132, ports, 0); + } + } + if (retval == 0) + pdev->dev.power.power_state = state; + return retval; + } +} + +static int u132_resume(struct platform_device *pdev) +{ + struct usb_hcd *hcd = platform_get_drvdata(pdev); + 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); + return -ENODEV; + } else if (u132->going > 0) { + dev_err(&u132->platform_dev->dev, "device is being removed\n"); + return -ESHUTDOWN; + } else { + int retval = 0; + if (pdev->dev.power.power_state.event == PM_EVENT_SUSPEND) { + int ports = MAX_U132_PORTS; + while (ports-- > 0) { + port_power(u132, ports, 1); + } + retval = 0; + } else { + pdev->dev.power.power_state = PMSG_ON; + retval = u132_bus_resume(hcd); + } + return retval; + } +} + +#else +#define u132_suspend NULL +#define u132_resume NULL +#endif +/* +* this driver is loaded explicitely by ftdi_u132 +* +* the platform_driver struct is static because it is per type of module +*/ +static struct platform_driver u132_platform_driver = { + .probe = u132_probe, + .remove = __devexit_p(u132_remove), + .suspend = u132_suspend, + .resume = u132_resume, + .driver = { + .name = (char *)hcd_name, + .owner = THIS_MODULE, + }, +}; +static int __init u132_hcd_init(void) +{ + int retval; + INIT_LIST_HEAD(&u132_static_list); + u132_instances = 0; + u132_exiting = 0; + init_MUTEX(&u132_module_lock); + if (usb_disabled()) + return -ENODEV; + printk(KERN_INFO "driver %s built at %s on %s\n", hcd_name, __TIME__, + __DATE__); + workqueue = create_singlethread_workqueue("u132"); + retval = platform_driver_register(&u132_platform_driver); + return retval; +} + + +module_init(u132_hcd_init); +static void __exit u132_hcd_exit(void) +{ + struct u132 *u132; + struct u132 *temp; + down(&u132_module_lock); + u132_exiting += 1; + up(&u132_module_lock); + list_for_each_entry_safe(u132, temp, &u132_static_list, u132_list) { + platform_device_unregister(u132->platform_dev); + } platform_driver_unregister(&u132_platform_driver); + printk(KERN_INFO "u132-hcd driver deregistered\n"); + wait_event(u132_hcd_wait, u132_instances == 0); + flush_workqueue(workqueue); + destroy_workqueue(workqueue); +} + + +module_exit(u132_hcd_exit); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/host/uhci-debug.c b/drivers/usb/host/uhci-debug.c index dc286a48caf..e345f15b7d8 100644 --- a/drivers/usb/host/uhci-debug.c +++ b/drivers/usb/host/uhci-debug.c @@ -16,7 +16,7 @@ #include "uhci-hcd.h" -#define uhci_debug_operations (* (struct file_operations *) NULL) +#define uhci_debug_operations (* (const struct file_operations *) NULL) static struct dentry *uhci_debugfs_root; #ifdef DEBUG @@ -428,7 +428,7 @@ struct uhci_debug { static int uhci_debug_open(struct inode *inode, struct file *file) { - struct uhci_hcd *uhci = inode->u.generic_ip; + struct uhci_hcd *uhci = inode->i_private; struct uhci_debug *up; int ret = -ENOMEM; unsigned long flags; @@ -500,7 +500,7 @@ static int uhci_debug_release(struct inode *inode, struct file *file) } #undef uhci_debug_operations -static struct file_operations uhci_debug_operations = { +static const struct file_operations uhci_debug_operations = { .owner = THIS_MODULE, .open = uhci_debug_open, .llseek = uhci_debug_lseek, diff --git a/drivers/usb/host/uhci-hcd.c b/drivers/usb/host/uhci-hcd.c index 4151f618602..eb4eab98e8b 100644 --- a/drivers/usb/host/uhci-hcd.c +++ b/drivers/usb/host/uhci-hcd.c @@ -734,6 +734,10 @@ static int uhci_suspend(struct usb_hcd *hcd, pm_message_t message) /* FIXME: Enable non-PME# remote wakeup? */ + /* make sure snapshot being resumed re-enumerates everything */ + if (message.event == PM_EVENT_PRETHAW) + uhci_hc_died(uhci); + done_okay: clear_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); done: @@ -909,8 +913,7 @@ static int __init uhci_hcd_init(void) return 0; init_failed: - if (kmem_cache_destroy(uhci_up_cachep)) - warn("not all urb_privs were freed!"); + kmem_cache_destroy(uhci_up_cachep); up_failed: debugfs_remove(uhci_debugfs_root); @@ -926,10 +929,7 @@ errbuf_failed: static void __exit uhci_hcd_cleanup(void) { pci_unregister_driver(&uhci_pci_driver); - - if (kmem_cache_destroy(uhci_up_cachep)) - warn("not all urb_privs were freed!"); - + kmem_cache_destroy(uhci_up_cachep); debugfs_remove(uhci_debugfs_root); kfree(errbuf); } diff --git a/drivers/usb/host/uhci-hub.c b/drivers/usb/host/uhci-hub.c index c545ef92fe2..16fb72eb6fc 100644 --- a/drivers/usb/host/uhci-hub.c +++ b/drivers/usb/host/uhci-hub.c @@ -84,6 +84,7 @@ static void uhci_finish_suspend(struct uhci_hcd *uhci, int port, unsigned long port_addr) { int status; + int i; if (inw(port_addr) & (USBPORTSC_SUSP | USBPORTSC_RD)) { CLR_RH_PORTSTAT(USBPORTSC_SUSP | USBPORTSC_RD); @@ -92,9 +93,14 @@ static void uhci_finish_suspend(struct uhci_hcd *uhci, int port, /* The controller won't actually turn off the RD bit until * it has had a chance to send a low-speed EOP sequence, - * which takes 3 bit times (= 2 microseconds). We'll delay - * slightly longer for good luck. */ - udelay(4); + * which is supposed to take 3 bit times (= 2 microseconds). + * Experiments show that some controllers take longer, so + * we'll poll for completion. */ + for (i = 0; i < 10; ++i) { + if (!(inw(port_addr) & USBPORTSC_RD)) + break; + udelay(1); + } } clear_bit(port, &uhci->resuming_ports); } |