From a5e6ced58d423cb09c4fc0087dcfdb0b5deb5e1c Mon Sep 17 00:00:00 2001 From: David Vrabel <david.vrabel@csr.com> Date: Wed, 7 Jan 2009 10:54:22 +0000 Subject: wusb: timeout when waiting for ASL/PZL updates in whci-hcd Timeout if an ASL or PZL update doesn't not complete and reset the hardware. Signed-off-by: David Vrabel <david.vrabel@csr.com> --- drivers/usb/host/whci/asl.c | 9 +++++++-- drivers/usb/host/whci/hw.c | 15 +++++++++++++++ drivers/usb/host/whci/pzl.c | 9 +++++++-- drivers/usb/host/whci/whcd.h | 1 + 4 files changed, 30 insertions(+), 4 deletions(-) (limited to 'drivers/usb/host') diff --git a/drivers/usb/host/whci/asl.c b/drivers/usb/host/whci/asl.c index 577c0d29849d..2291c5f5af51 100644 --- a/drivers/usb/host/whci/asl.c +++ b/drivers/usb/host/whci/asl.c @@ -170,12 +170,17 @@ void asl_stop(struct whc *whc) void asl_update(struct whc *whc, uint32_t wusbcmd) { struct wusbhc *wusbhc = &whc->wusbhc; + long t; mutex_lock(&wusbhc->mutex); if (wusbhc->active) { whc_write_wusbcmd(whc, wusbcmd, wusbcmd); - wait_event(whc->async_list_wq, - (le_readl(whc->base + WUSBCMD) & WUSBCMD_ASYNC_UPDATED) == 0); + t = wait_event_timeout( + whc->async_list_wq, + (le_readl(whc->base + WUSBCMD) & WUSBCMD_ASYNC_UPDATED) == 0, + msecs_to_jiffies(1000)); + if (t == 0) + whc_hw_error(whc, "ASL update timeout"); } mutex_unlock(&wusbhc->mutex); } diff --git a/drivers/usb/host/whci/hw.c b/drivers/usb/host/whci/hw.c index d498e7203217..6afa2e379160 100644 --- a/drivers/usb/host/whci/hw.c +++ b/drivers/usb/host/whci/hw.c @@ -87,3 +87,18 @@ out: return ret; } + +/** + * whc_hw_error - recover from a hardware error + * @whc: the WHCI HC that broke. + * @reason: a description of the failure. + * + * Recover from broken hardware with a full reset. + */ +void whc_hw_error(struct whc *whc, const char *reason) +{ + struct wusbhc *wusbhc = &whc->wusbhc; + + dev_err(&whc->umc->dev, "hardware error: %s\n", reason); + wusbhc_reset_all(wusbhc); +} diff --git a/drivers/usb/host/whci/pzl.c b/drivers/usb/host/whci/pzl.c index 2ae5abf69a6a..7dc85a0bee7c 100644 --- a/drivers/usb/host/whci/pzl.c +++ b/drivers/usb/host/whci/pzl.c @@ -183,12 +183,17 @@ void pzl_stop(struct whc *whc) void pzl_update(struct whc *whc, uint32_t wusbcmd) { struct wusbhc *wusbhc = &whc->wusbhc; + long t; mutex_lock(&wusbhc->mutex); if (wusbhc->active) { whc_write_wusbcmd(whc, wusbcmd, wusbcmd); - wait_event(whc->periodic_list_wq, - (le_readl(whc->base + WUSBCMD) & WUSBCMD_PERIODIC_UPDATED) == 0); + t = wait_event_timeout( + whc->periodic_list_wq, + (le_readl(whc->base + WUSBCMD) & WUSBCMD_PERIODIC_UPDATED) == 0, + msecs_to_jiffies(1000)); + if (t == 0) + whc_hw_error(whc, "PZL update timeout"); } mutex_unlock(&wusbhc->mutex); } diff --git a/drivers/usb/host/whci/whcd.h b/drivers/usb/host/whci/whcd.h index 0f3540f04f53..d3543a181dc9 100644 --- a/drivers/usb/host/whci/whcd.h +++ b/drivers/usb/host/whci/whcd.h @@ -137,6 +137,7 @@ void whc_clean_up(struct whc *whc); /* hw.c */ void whc_write_wusbcmd(struct whc *whc, u32 mask, u32 val); int whc_do_gencmd(struct whc *whc, u32 cmd, u32 params, void *addr, size_t len); +void whc_hw_error(struct whc *whc, const char *reason); /* wusb.c */ int whc_wusbhc_start(struct wusbhc *wusbhc); -- cgit v1.2.2 From 953a7e8476bbd7367cebdb868c326ba42968bc13 Mon Sep 17 00:00:00 2001 From: Russell King <rmk@dyn-67.arm.linux.org.uk> Date: Sun, 18 Jan 2009 12:52:38 +0000 Subject: [ARM] omap: ensure OMAP drivers pass a struct device to clk_get() Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk> --- drivers/usb/host/ohci-omap.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'drivers/usb/host') diff --git a/drivers/usb/host/ohci-omap.c b/drivers/usb/host/ohci-omap.c index 4bbddb73abd9..f3aaba35e912 100644 --- a/drivers/usb/host/ohci-omap.c +++ b/drivers/usb/host/ohci-omap.c @@ -315,14 +315,14 @@ static int usb_hcd_omap_probe (const struct hc_driver *driver, return -ENODEV; } - usb_host_ck = clk_get(0, "usb_hhc_ck"); + usb_host_ck = clk_get(&pdev->dev, "usb_hhc_ck"); if (IS_ERR(usb_host_ck)) return PTR_ERR(usb_host_ck); if (!cpu_is_omap15xx()) - usb_dc_ck = clk_get(0, "usb_dc_ck"); + usb_dc_ck = clk_get(&pdev->dev, "usb_dc_ck"); else - usb_dc_ck = clk_get(0, "lb_ck"); + usb_dc_ck = clk_get(&pdev->dev, "lb_ck"); if (IS_ERR(usb_dc_ck)) { clk_put(usb_host_ck); -- cgit v1.2.2 From bcca06efea883bdf3803a0bb0ffa60f26730387d Mon Sep 17 00:00:00 2001 From: Alan Stern <stern@rowland.harvard.edu> Date: Tue, 13 Jan 2009 11:35:54 -0500 Subject: USB: don't enable wakeup by default for PCI host controllers This patch (as1199) changes the initial wakeup settings for PCI USB host controllers. The controllers are marked as capable of waking the system, but wakeup is not enabled by default. It turns out that enabling wakeup for USB host controllers has a lot of bad consequences. As the simplest example, if a USB mouse or keyboard is unplugged immediately after the computer is put to sleep, the unplug will cause the system to wake back up again! We are better off marking them as wakeup-capable and leaving wakeup disabled. Signed-off-by: Alan Stern <stern@rowland.harvard.edu> Reported-by: Rafael J. Wysocki <rjw@sisk.pl> CC: David Brownell <david-b@pacbell.net> Cc: stable <stable@kernel.org> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/host/ehci-pci.c | 2 +- drivers/usb/host/ohci-hcd.c | 8 +++----- 2 files changed, 4 insertions(+), 6 deletions(-) (limited to 'drivers/usb/host') diff --git a/drivers/usb/host/ehci-pci.c b/drivers/usb/host/ehci-pci.c index bdc6e86e1f8b..9faa5c8fe025 100644 --- a/drivers/usb/host/ehci-pci.c +++ b/drivers/usb/host/ehci-pci.c @@ -230,7 +230,7 @@ static int ehci_pci_setup(struct usb_hcd *hcd) pci_read_config_word(pdev, 0x62, &port_wake); if (port_wake & 0x0001) { dev_warn(&pdev->dev, "Enabling legacy PCI PM\n"); - device_init_wakeup(&pdev->dev, 1); + device_set_wakeup_capable(&pdev->dev, 1); } } diff --git a/drivers/usb/host/ohci-hcd.c b/drivers/usb/host/ohci-hcd.c index 65a9609f4ad6..5cf5f1eca4f4 100644 --- a/drivers/usb/host/ohci-hcd.c +++ b/drivers/usb/host/ohci-hcd.c @@ -593,12 +593,10 @@ static int ohci_run (struct ohci_hcd *ohci) * to be checked in case boot firmware (BIOS/SMM/...) has set up * wakeup in a way the bus isn't aware of (e.g., legacy PCI PM). * If the bus glue detected wakeup capability then it should - * already be enabled. Either way, if wakeup should be enabled - * but isn't, we'll enable it now. + * already be enabled; if so we'll just enable it again. */ - if ((ohci->hc_control & OHCI_CTRL_RWC) != 0 - && !device_can_wakeup(hcd->self.controller)) - device_init_wakeup(hcd->self.controller, 1); + if ((ohci->hc_control & OHCI_CTRL_RWC) != 0) + device_set_wakeup_capable(hcd->self.controller, 1); switch (ohci->hc_control & OHCI_CTRL_HCFS) { case OHCI_USB_OPER: -- cgit v1.2.2 From a15d95a003fae154121733f049dd25e9c13dbef3 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" <rjw@sisk.pl> Date: Tue, 20 Jan 2009 01:26:56 +0100 Subject: USB: Fix suspend-resume of PCI USB controllers Commit a0d4922da2e4ccb0973095d8d29f36f6b1b5f703 (USB: fix up suspend and resume for PCI host controllers) attempted to fix the suspend-resume of PCI USB controllers, but unfortunately it did that incorrectly and interrupts are left enabled by the USB controllers' ->suspend_late() callback as a result. This leads to serious problems during suspend which are very difficult to debug. Fix the issue by removing the ->suspend_late() callback of PCI USB controllers and moving the code from there to the ->suspend() callback executed with interrupts enabled. Additionally, make the ->resume() callback of PCI USB controllers execute pci_enable_wake(dev, PCI_D0, false) to disable wake-up from the full power state (PCI_D0). Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl> Tested-by: Andrey Borzenkov <arvidjaar@mail.ru> Tested-by: "Jeff Chua" <jeff.chua.linux@gmail.com> Cc: Alan Stern <stern@rowland.harvard.edu> Cc: Andrew Morton <akpm@linux-foundation.org> Cc: Christian Borntraeger <borntraeger@de.ibm.com> Cc: "Zdenek Kabelac" <zdenek.kabelac@gmail.com> Cc: Ingo Molnar <mingo@elte.hu> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/host/ehci-pci.c | 1 - drivers/usb/host/ohci-pci.c | 1 - drivers/usb/host/uhci-hcd.c | 1 - 3 files changed, 3 deletions(-) (limited to 'drivers/usb/host') diff --git a/drivers/usb/host/ehci-pci.c b/drivers/usb/host/ehci-pci.c index 9faa5c8fe025..bb21fb0a4969 100644 --- a/drivers/usb/host/ehci-pci.c +++ b/drivers/usb/host/ehci-pci.c @@ -432,7 +432,6 @@ static struct pci_driver ehci_pci_driver = { #ifdef CONFIG_PM .suspend = usb_hcd_pci_suspend, - .suspend_late = usb_hcd_pci_suspend_late, .resume_early = usb_hcd_pci_resume_early, .resume = usb_hcd_pci_resume, #endif diff --git a/drivers/usb/host/ohci-pci.c b/drivers/usb/host/ohci-pci.c index 8b28ae7865ba..5d625c3fd423 100644 --- a/drivers/usb/host/ohci-pci.c +++ b/drivers/usb/host/ohci-pci.c @@ -487,7 +487,6 @@ static struct pci_driver ohci_pci_driver = { #ifdef CONFIG_PM .suspend = usb_hcd_pci_suspend, - .suspend_late = usb_hcd_pci_suspend_late, .resume_early = usb_hcd_pci_resume_early, .resume = usb_hcd_pci_resume, #endif diff --git a/drivers/usb/host/uhci-hcd.c b/drivers/usb/host/uhci-hcd.c index 4e221060f58c..944f7e0ca4df 100644 --- a/drivers/usb/host/uhci-hcd.c +++ b/drivers/usb/host/uhci-hcd.c @@ -942,7 +942,6 @@ static struct pci_driver uhci_pci_driver = { #ifdef CONFIG_PM .suspend = usb_hcd_pci_suspend, - .suspend_late = usb_hcd_pci_suspend_late, .resume_early = usb_hcd_pci_resume_early, .resume = usb_hcd_pci_resume, #endif /* PM */ -- cgit v1.2.2 From 2bf5fa13fc8e34d7b86307b99f64a24cb7a83852 Mon Sep 17 00:00:00 2001 From: David Brownell <dbrownell@users.sourceforge.net> Date: Sat, 24 Jan 2009 17:55:57 -0800 Subject: USB: omap1 ohci buildfix (otg related) > > drivers/built-in.o: In function `ohci_omap_init': > > hid-quirks.c:(.text+0x6c608): undefined reference to `otg_get_transceiver' > > drivers/built-in.o: In function `omap_udc_probe': > > hid-quirks.c:(.init.text+0x34c0): undefined reference to `otg_get_transceiver' > > hid-quirks.c:(.init.text+0x3d40): undefined reference to `otg_put_transceiver' Reported-by: Russell King <linux@arm.linux.org.uk> Signed-off-by: David Brownell <dbrownell@users.sourceforge.net> Acked-by: Tony Lindgren <tony@atomide.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/host/Kconfig | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers/usb/host') diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig index 2b476b6b3d4d..5a988f4cdf87 100644 --- a/drivers/usb/host/Kconfig +++ b/drivers/usb/host/Kconfig @@ -140,6 +140,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 USB_OTG_UTILS if ARCH_OMAP ---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 -- cgit v1.2.2 From 236dd4d18f293e3c9798f35c08272196826a980d Mon Sep 17 00:00:00 2001 From: Anton Vorontsov <avorontsov@ru.mvista.com> Date: Sat, 10 Jan 2009 05:03:21 +0300 Subject: USB: Driver for Freescale QUICC Engine USB Host Controller This patch adds support for the FHCI USB controller, as found in the Freescale MPC836x and MPC832x processors. It can support Full or Low speed modes. Quite a lot the hardware is doing by itself (SOF generation, CRC generation and checking), though scheduling and retransmission is on software's shoulders. This controller does not integrate the root hub, so this driver also fakes one-port hub. External hub is required to support more than one device. Signed-off-by: Anton Vorontsov <avorontsov@ru.mvista.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/host/Kconfig | 17 + drivers/usb/host/Makefile | 6 + drivers/usb/host/fhci-dbg.c | 139 +++++++ drivers/usb/host/fhci-hcd.c | 836 +++++++++++++++++++++++++++++++++++++++ drivers/usb/host/fhci-hub.c | 345 ++++++++++++++++ drivers/usb/host/fhci-mem.c | 113 ++++++ drivers/usb/host/fhci-q.c | 284 ++++++++++++++ drivers/usb/host/fhci-sched.c | 888 ++++++++++++++++++++++++++++++++++++++++++ drivers/usb/host/fhci-tds.c | 626 +++++++++++++++++++++++++++++ drivers/usb/host/fhci.h | 607 +++++++++++++++++++++++++++++ 10 files changed, 3861 insertions(+) create mode 100644 drivers/usb/host/fhci-dbg.c create mode 100644 drivers/usb/host/fhci-hcd.c create mode 100644 drivers/usb/host/fhci-hub.c create mode 100644 drivers/usb/host/fhci-mem.c create mode 100644 drivers/usb/host/fhci-q.c create mode 100644 drivers/usb/host/fhci-sched.c create mode 100644 drivers/usb/host/fhci-tds.c create mode 100644 drivers/usb/host/fhci.h (limited to 'drivers/usb/host') diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig index 5a988f4cdf87..2c63bfb1f8d9 100644 --- a/drivers/usb/host/Kconfig +++ b/drivers/usb/host/Kconfig @@ -239,6 +239,23 @@ config USB_UHCI_HCD To compile this driver as a module, choose M here: the module will be called uhci-hcd. +config USB_FHCI_HCD + tristate "Freescale QE USB Host Controller support" + depends on USB && OF_GPIO && QE_GPIO && QUICC_ENGINE + select FSL_GTM + select QE_USB + help + This driver enables support for Freescale QE USB Host Controller + (as found on MPC8360 and MPC8323 processors), the driver supports + Full and Low Speed USB. + +config FHCI_DEBUG + bool "Freescale QE USB Host Controller debug support" + depends on USB_FHCI_HCD && DEBUG_FS + help + Say "y" to see some FHCI debug information and statistics + throught debugfs. + config USB_U132_HCD tristate "Elan U132 Adapter Host Controller" depends on USB && USB_FTDI_ELAN diff --git a/drivers/usb/host/Makefile b/drivers/usb/host/Makefile index e5f3f20787e4..f163571e33d8 100644 --- a/drivers/usb/host/Makefile +++ b/drivers/usb/host/Makefile @@ -7,6 +7,11 @@ ifeq ($(CONFIG_USB_DEBUG),y) endif isp1760-objs := isp1760-hcd.o isp1760-if.o +fhci-objs := fhci-hcd.o fhci-hub.o fhci-q.o fhci-mem.o \ + fhci-tds.o fhci-sched.o +ifeq ($(CONFIG_FHCI_DEBUG),y) +fhci-objs += fhci-dbg.o +endif obj-$(CONFIG_USB_WHCI_HCD) += whci/ @@ -17,6 +22,7 @@ obj-$(CONFIG_USB_OXU210HP_HCD) += oxu210hp-hcd.o obj-$(CONFIG_USB_ISP116X_HCD) += isp116x-hcd.o obj-$(CONFIG_USB_OHCI_HCD) += ohci-hcd.o obj-$(CONFIG_USB_UHCI_HCD) += uhci-hcd.o +obj-$(CONFIG_USB_FHCI_HCD) += fhci.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 diff --git a/drivers/usb/host/fhci-dbg.c b/drivers/usb/host/fhci-dbg.c new file mode 100644 index 000000000000..34e14edf390b --- /dev/null +++ b/drivers/usb/host/fhci-dbg.c @@ -0,0 +1,139 @@ +/* + * Freescale QUICC Engine USB Host Controller Driver + * + * Copyright (c) Freescale Semicondutor, Inc. 2006. + * Shlomi Gridish <gridish@freescale.com> + * Jerry Huang <Chang-Ming.Huang@freescale.com> + * Copyright (c) Logic Product Development, Inc. 2007 + * Peter Barada <peterb@logicpd.com> + * Copyright (c) MontaVista Software, Inc. 2008. + * Anton Vorontsov <avorontsov@ru.mvista.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; either version 2 of the License, or (at your + * option) any later version. + */ + +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/debugfs.h> +#include <linux/seq_file.h> +#include <linux/usb.h> +#include "../core/hcd.h" +#include "fhci.h" + +void fhci_dbg_isr(struct fhci_hcd *fhci, int usb_er) +{ + int i; + + if (usb_er == -1) { + fhci->usb_irq_stat[12]++; + return; + } + + for (i = 0; i < 12; ++i) { + if (usb_er & (1 << i)) + fhci->usb_irq_stat[i]++; + } +} + +static int fhci_dfs_regs_show(struct seq_file *s, void *v) +{ + struct fhci_hcd *fhci = s->private; + struct fhci_regs __iomem *regs = fhci->regs; + + seq_printf(s, + "mode: 0x%x\n" "addr: 0x%x\n" + "command: 0x%x\n" "ep0: 0x%x\n" + "event: 0x%x\n" "mask: 0x%x\n" + "status: 0x%x\n" "SOF timer: %d\n" + "frame number: %d\n" + "lines status: 0x%x\n", + in_8(®s->usb_mod), in_8(®s->usb_addr), + in_8(®s->usb_comm), in_be16(®s->usb_ep[0]), + in_be16(®s->usb_event), in_be16(®s->usb_mask), + in_8(®s->usb_status), in_be16(®s->usb_sof_tmr), + in_be16(®s->usb_frame_num), + fhci_ioports_check_bus_state(fhci)); + + return 0; +} + +static int fhci_dfs_irq_stat_show(struct seq_file *s, void *v) +{ + struct fhci_hcd *fhci = s->private; + int *usb_irq_stat = fhci->usb_irq_stat; + + seq_printf(s, + "RXB: %d\n" "TXB: %d\n" "BSY: %d\n" + "SOF: %d\n" "TXE0: %d\n" "TXE1: %d\n" + "TXE2: %d\n" "TXE3: %d\n" "IDLE: %d\n" + "RESET: %d\n" "SFT: %d\n" "MSF: %d\n" + "IDLE_ONLY: %d\n", + usb_irq_stat[0], usb_irq_stat[1], usb_irq_stat[2], + usb_irq_stat[3], usb_irq_stat[4], usb_irq_stat[5], + usb_irq_stat[6], usb_irq_stat[7], usb_irq_stat[8], + usb_irq_stat[9], usb_irq_stat[10], usb_irq_stat[11], + usb_irq_stat[12]); + + return 0; +} + +static int fhci_dfs_regs_open(struct inode *inode, struct file *file) +{ + return single_open(file, fhci_dfs_regs_show, inode->i_private); +} + +static int fhci_dfs_irq_stat_open(struct inode *inode, struct file *file) +{ + return single_open(file, fhci_dfs_irq_stat_show, inode->i_private); +} + +static const struct file_operations fhci_dfs_regs_fops = { + .open = fhci_dfs_regs_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static const struct file_operations fhci_dfs_irq_stat_fops = { + .open = fhci_dfs_irq_stat_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +void fhci_dfs_create(struct fhci_hcd *fhci) +{ + struct device *dev = fhci_to_hcd(fhci)->self.controller; + + fhci->dfs_root = debugfs_create_dir(dev->bus_id, NULL); + if (!fhci->dfs_root) { + WARN_ON(1); + return; + } + + fhci->dfs_regs = debugfs_create_file("regs", S_IFREG | S_IRUGO, + fhci->dfs_root, fhci, &fhci_dfs_regs_fops); + + fhci->dfs_irq_stat = debugfs_create_file("irq_stat", + S_IFREG | S_IRUGO, fhci->dfs_root, fhci, + &fhci_dfs_irq_stat_fops); + + WARN_ON(!fhci->dfs_regs || !fhci->dfs_irq_stat); +} + +void fhci_dfs_destroy(struct fhci_hcd *fhci) +{ + if (!fhci->dfs_root) + return; + + if (fhci->dfs_irq_stat) + debugfs_remove(fhci->dfs_irq_stat); + + if (fhci->dfs_regs) + debugfs_remove(fhci->dfs_regs); + + debugfs_remove(fhci->dfs_root); +} diff --git a/drivers/usb/host/fhci-hcd.c b/drivers/usb/host/fhci-hcd.c new file mode 100644 index 000000000000..ba622cc8a9ba --- /dev/null +++ b/drivers/usb/host/fhci-hcd.c @@ -0,0 +1,836 @@ +/* + * Freescale QUICC Engine USB Host Controller Driver + * + * Copyright (c) Freescale Semicondutor, Inc. 2006. + * Shlomi Gridish <gridish@freescale.com> + * Jerry Huang <Chang-Ming.Huang@freescale.com> + * Copyright (c) Logic Product Development, Inc. 2007 + * Peter Barada <peterb@logicpd.com> + * Copyright (c) MontaVista Software, Inc. 2008. + * Anton Vorontsov <avorontsov@ru.mvista.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; either version 2 of the License, or (at your + * option) any later version. + */ + +#include <linux/module.h> +#include <linux/types.h> +#include <linux/spinlock.h> +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/list.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/usb.h> +#include <linux/of_platform.h> +#include <linux/of_gpio.h> +#include <asm/qe.h> +#include <asm/fsl_gtm.h> +#include "../core/hcd.h" +#include "fhci.h" + +void fhci_start_sof_timer(struct fhci_hcd *fhci) +{ + fhci_dbg(fhci, "-> %s\n", __func__); + + /* clear frame_n */ + out_be16(&fhci->pram->frame_num, 0); + + out_be16(&fhci->regs->usb_sof_tmr, 0); + setbits8(&fhci->regs->usb_mod, USB_MODE_SFTE); + + fhci_dbg(fhci, "<- %s\n", __func__); +} + +void fhci_stop_sof_timer(struct fhci_hcd *fhci) +{ + fhci_dbg(fhci, "-> %s\n", __func__); + + clrbits8(&fhci->regs->usb_mod, USB_MODE_SFTE); + gtm_stop_timer16(fhci->timer); + + fhci_dbg(fhci, "<- %s\n", __func__); +} + +u16 fhci_get_sof_timer_count(struct fhci_usb *usb) +{ + return be16_to_cpu(in_be16(&usb->fhci->regs->usb_sof_tmr) / 12); +} + +/* initialize the endpoint zero */ +static u32 endpoint_zero_init(struct fhci_usb *usb, + enum fhci_mem_alloc data_mem, + u32 ring_len) +{ + u32 rc; + + rc = fhci_create_ep(usb, data_mem, ring_len); + if (rc) + return rc; + + /* inilialize endpoint registers */ + fhci_init_ep_registers(usb, usb->ep0, data_mem); + + return 0; +} + +/* enable the USB interrupts */ +void fhci_usb_enable_interrupt(struct fhci_usb *usb) +{ + struct fhci_hcd *fhci = usb->fhci; + + if (usb->intr_nesting_cnt == 1) { + /* initialize the USB interrupt */ + enable_irq(fhci_to_hcd(fhci)->irq); + + /* initialize the event register and mask register */ + out_be16(&usb->fhci->regs->usb_event, 0xffff); + out_be16(&usb->fhci->regs->usb_mask, usb->saved_msk); + + /* enable the timer interrupts */ + enable_irq(fhci->timer->irq); + } else if (usb->intr_nesting_cnt > 1) + fhci_info(fhci, "unbalanced USB interrupts nesting\n"); + usb->intr_nesting_cnt--; +} + +/* diable the usb interrupt */ +void fhci_usb_disable_interrupt(struct fhci_usb *usb) +{ + struct fhci_hcd *fhci = usb->fhci; + + if (usb->intr_nesting_cnt == 0) { + /* diable the timer interrupt */ + disable_irq_nosync(fhci->timer->irq); + + /* disable the usb interrupt */ + disable_irq_nosync(fhci_to_hcd(fhci)->irq); + out_be16(&usb->fhci->regs->usb_mask, 0); + } + usb->intr_nesting_cnt++; +} + +/* enable the USB controller */ +static u32 fhci_usb_enable(struct fhci_hcd *fhci) +{ + struct fhci_usb *usb = fhci->usb_lld; + + out_be16(&usb->fhci->regs->usb_event, 0xffff); + out_be16(&usb->fhci->regs->usb_mask, usb->saved_msk); + setbits8(&usb->fhci->regs->usb_mod, USB_MODE_EN); + + mdelay(100); + + return 0; +} + +/* disable the USB controller */ +static u32 fhci_usb_disable(struct fhci_hcd *fhci) +{ + struct fhci_usb *usb = fhci->usb_lld; + + fhci_usb_disable_interrupt(usb); + fhci_port_disable(fhci); + + /* disable the usb controller */ + if (usb->port_status == FHCI_PORT_FULL || + usb->port_status == FHCI_PORT_LOW) + fhci_device_disconnected_interrupt(fhci); + + clrbits8(&usb->fhci->regs->usb_mod, USB_MODE_EN); + + return 0; +} + +/* check the bus state by polling the QE bit on the IO ports */ +int fhci_ioports_check_bus_state(struct fhci_hcd *fhci) +{ + u8 bits = 0; + + /* check USBOE,if transmitting,exit */ + if (!gpio_get_value(fhci->gpios[GPIO_USBOE])) + return -1; + + /* check USBRP */ + if (gpio_get_value(fhci->gpios[GPIO_USBRP])) + bits |= 0x2; + + /* check USBRN */ + if (gpio_get_value(fhci->gpios[GPIO_USBRN])) + bits |= 0x1; + + return bits; +} + +static void fhci_mem_free(struct fhci_hcd *fhci) +{ + struct ed *ed; + struct ed *next_ed; + struct td *td; + struct td *next_td; + + list_for_each_entry_safe(ed, next_ed, &fhci->empty_eds, node) { + list_del(&ed->node); + kfree(ed); + } + + list_for_each_entry_safe(td, next_td, &fhci->empty_tds, node) { + list_del(&td->node); + kfree(td); + } + + kfree(fhci->vroot_hub); + fhci->vroot_hub = NULL; + + kfree(fhci->hc_list); + fhci->hc_list = NULL; +} + +static int fhci_mem_init(struct fhci_hcd *fhci) +{ + int i; + + fhci->hc_list = kzalloc(sizeof(*fhci->hc_list), GFP_KERNEL); + if (!fhci->hc_list) + goto err; + + INIT_LIST_HEAD(&fhci->hc_list->ctrl_list); + INIT_LIST_HEAD(&fhci->hc_list->bulk_list); + INIT_LIST_HEAD(&fhci->hc_list->iso_list); + INIT_LIST_HEAD(&fhci->hc_list->intr_list); + INIT_LIST_HEAD(&fhci->hc_list->done_list); + + fhci->vroot_hub = kzalloc(sizeof(*fhci->vroot_hub), GFP_KERNEL); + if (!fhci->vroot_hub) + goto err; + + INIT_LIST_HEAD(&fhci->empty_eds); + INIT_LIST_HEAD(&fhci->empty_tds); + + /* initialize work queue to handle done list */ + fhci_tasklet.data = (unsigned long)fhci; + fhci->process_done_task = &fhci_tasklet; + + for (i = 0; i < MAX_TDS; i++) { + struct td *td; + + td = kmalloc(sizeof(*td), GFP_KERNEL); + if (!td) + goto err; + fhci_recycle_empty_td(fhci, td); + } + for (i = 0; i < MAX_EDS; i++) { + struct ed *ed; + + ed = kmalloc(sizeof(*ed), GFP_KERNEL); + if (!ed) + goto err; + fhci_recycle_empty_ed(fhci, ed); + } + + fhci->active_urbs = 0; + return 0; +err: + fhci_mem_free(fhci); + return -ENOMEM; +} + +/* destroy the fhci_usb structure */ +static void fhci_usb_free(void *lld) +{ + struct fhci_usb *usb = lld; + struct fhci_hcd *fhci = usb->fhci; + + if (usb) { + fhci_config_transceiver(fhci, FHCI_PORT_POWER_OFF); + fhci_ep0_free(usb); + kfree(usb->actual_frame); + kfree(usb); + } +} + +/* initialize the USB */ +static int fhci_usb_init(struct fhci_hcd *fhci) +{ + struct fhci_usb *usb = fhci->usb_lld; + + memset_io(usb->fhci->pram, 0, FHCI_PRAM_SIZE); + + usb->port_status = FHCI_PORT_DISABLED; + usb->max_frame_usage = FRAME_TIME_USAGE; + usb->sw_transaction_time = SW_FIX_TIME_BETWEEN_TRANSACTION; + + usb->actual_frame = kzalloc(sizeof(*usb->actual_frame), GFP_KERNEL); + if (!usb->actual_frame) { + fhci_usb_free(usb); + return -ENOMEM; + } + + INIT_LIST_HEAD(&usb->actual_frame->tds_list); + + /* initializing registers on chip, clear frame number */ + out_be16(&fhci->pram->frame_num, 0); + + /* clear rx state */ + out_be32(&fhci->pram->rx_state, 0); + + /* set mask register */ + usb->saved_msk = (USB_E_TXB_MASK | + USB_E_TXE1_MASK | + USB_E_IDLE_MASK | + USB_E_RESET_MASK | USB_E_SFT_MASK | USB_E_MSF_MASK); + + out_8(&usb->fhci->regs->usb_mod, USB_MODE_HOST | USB_MODE_EN); + + /* clearing the mask register */ + out_be16(&usb->fhci->regs->usb_mask, 0); + + /* initialing the event register */ + out_be16(&usb->fhci->regs->usb_event, 0xffff); + + if (endpoint_zero_init(usb, DEFAULT_DATA_MEM, DEFAULT_RING_LEN) != 0) { + fhci_usb_free(usb); + return -EINVAL; + } + + return 0; +} + +/* initialize the fhci_usb struct and the corresponding data staruct */ +static struct fhci_usb *fhci_create_lld(struct fhci_hcd *fhci) +{ + struct fhci_usb *usb; + + /* allocate memory for SCC data structure */ + usb = kzalloc(sizeof(*usb), GFP_KERNEL); + if (!usb) { + fhci_err(fhci, "no memory for SCC data struct\n"); + return NULL; + } + + usb->fhci = fhci; + usb->hc_list = fhci->hc_list; + usb->vroot_hub = fhci->vroot_hub; + + usb->transfer_confirm = fhci_transfer_confirm_callback; + + return usb; +} + +static int fhci_start(struct usb_hcd *hcd) +{ + int ret; + struct fhci_hcd *fhci = hcd_to_fhci(hcd); + + ret = fhci_mem_init(fhci); + if (ret) { + fhci_err(fhci, "failed to allocate memory\n"); + goto err; + } + + fhci->usb_lld = fhci_create_lld(fhci); + if (!fhci->usb_lld) { + fhci_err(fhci, "low level driver config failed\n"); + ret = -ENOMEM; + goto err; + } + + ret = fhci_usb_init(fhci); + if (ret) { + fhci_err(fhci, "low level driver initialize failed\n"); + goto err; + } + + spin_lock_init(&fhci->lock); + + /* connect the virtual root hub */ + fhci->vroot_hub->dev_num = 1; /* this field may be needed to fix */ + fhci->vroot_hub->hub.wHubStatus = 0; + fhci->vroot_hub->hub.wHubChange = 0; + fhci->vroot_hub->port.wPortStatus = 0; + fhci->vroot_hub->port.wPortChange = 0; + + hcd->state = HC_STATE_RUNNING; + + /* + * From here on, khubd concurrently accesses the root + * hub; drivers will be talking to enumerated devices. + * (On restart paths, khubd already knows about the root + * hub and could find work as soon as we wrote FLAG_CF.) + * + * Before this point the HC was idle/ready. After, khubd + * and device drivers may start it running. + */ + fhci_usb_enable(fhci); + return 0; +err: + fhci_mem_free(fhci); + return ret; +} + +static void fhci_stop(struct usb_hcd *hcd) +{ + struct fhci_hcd *fhci = hcd_to_fhci(hcd); + + fhci_usb_disable_interrupt(fhci->usb_lld); + fhci_usb_disable(fhci); + + fhci_usb_free(fhci->usb_lld); + fhci->usb_lld = NULL; + fhci_mem_free(fhci); +} + +static int fhci_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, + gfp_t mem_flags) +{ + struct fhci_hcd *fhci = hcd_to_fhci(hcd); + u32 pipe = urb->pipe; + int ret; + int i; + int size = 0; + struct urb_priv *urb_priv; + unsigned long flags; + + switch (usb_pipetype(pipe)) { + case PIPE_CONTROL: + /* 1 td fro setup,1 for ack */ + size = 2; + case PIPE_BULK: + /* one td for every 4096 bytes(can be upto 8k) */ + size += urb->transfer_buffer_length / 4096; + /* ...add for any remaining bytes... */ + if ((urb->transfer_buffer_length % 4096) != 0) + size++; + /* ..and maybe a zero length packet to wrap it up */ + if (size == 0) + size++; + else if ((urb->transfer_flags & URB_ZERO_PACKET) != 0 + && (urb->transfer_buffer_length + % usb_maxpacket(urb->dev, pipe, + usb_pipeout(pipe))) != 0) + size++; + break; + case PIPE_ISOCHRONOUS: + size = urb->number_of_packets; + if (size <= 0) + return -EINVAL; + for (i = 0; i < urb->number_of_packets; i++) { + urb->iso_frame_desc[i].actual_length = 0; + urb->iso_frame_desc[i].status = (u32) (-EXDEV); + } + break; + case PIPE_INTERRUPT: + size = 1; + } + + /* allocate the private part of the URB */ + urb_priv = kzalloc(sizeof(*urb_priv), mem_flags); + if (!urb_priv) + return -ENOMEM; + + /* allocate the private part of the URB */ + urb_priv->tds = kzalloc(size * sizeof(struct td), mem_flags); + if (!urb_priv->tds) { + kfree(urb_priv); + return -ENOMEM; + } + + spin_lock_irqsave(&fhci->lock, flags); + + ret = usb_hcd_link_urb_to_ep(hcd, urb); + if (ret) + goto err; + + /* fill the private part of the URB */ + urb_priv->num_of_tds = size; + + urb->status = -EINPROGRESS; + urb->actual_length = 0; + urb->error_count = 0; + urb->hcpriv = urb_priv; + + fhci_queue_urb(fhci, urb); +err: + if (ret) { + kfree(urb_priv->tds); + kfree(urb_priv); + } + spin_unlock_irqrestore(&fhci->lock, flags); + return ret; +} + +/* dequeue FHCI URB */ +static int fhci_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status) +{ + struct fhci_hcd *fhci = hcd_to_fhci(hcd); + struct fhci_usb *usb = fhci->usb_lld; + int ret = -EINVAL; + unsigned long flags; + + if (!urb || !urb->dev || !urb->dev->bus) + goto out; + + spin_lock_irqsave(&fhci->lock, flags); + + ret = usb_hcd_check_unlink_urb(hcd, urb, status); + if (ret) + goto out2; + + if (usb->port_status != FHCI_PORT_DISABLED) { + struct urb_priv *urb_priv; + + /* + * flag the urb's data for deletion in some upcoming + * SF interrupt's delete list processing + */ + urb_priv = urb->hcpriv; + + if (!urb_priv || (urb_priv->state == URB_DEL)) + goto out2; + + urb_priv->state = URB_DEL; + + /* already pending? */ + urb_priv->ed->state = FHCI_ED_URB_DEL; + } else { + fhci_urb_complete_free(fhci, urb); + } + +out2: + spin_unlock_irqrestore(&fhci->lock, flags); +out: + return ret; +} + +static void fhci_endpoint_disable(struct usb_hcd *hcd, + struct usb_host_endpoint *ep) +{ + struct fhci_hcd *fhci; + struct ed *ed; + unsigned long flags; + + fhci = hcd_to_fhci(hcd); + spin_lock_irqsave(&fhci->lock, flags); + ed = ep->hcpriv; + if (ed) { + while (ed->td_head != NULL) { + struct td *td = fhci_remove_td_from_ed(ed); + fhci_urb_complete_free(fhci, td->urb); + } + fhci_recycle_empty_ed(fhci, ed); + ep->hcpriv = NULL; + } + spin_unlock_irqrestore(&fhci->lock, flags); +} + +static int fhci_get_frame_number(struct usb_hcd *hcd) +{ + struct fhci_hcd *fhci = hcd_to_fhci(hcd); + + return get_frame_num(fhci); +} + +static const struct hc_driver fhci_driver = { + .description = "fsl,usb-fhci", + .product_desc = "FHCI HOST Controller", + .hcd_priv_size = sizeof(struct fhci_hcd), + + /* generic hardware linkage */ + .irq = fhci_irq, + .flags = HCD_USB11 | HCD_MEMORY, + + /* basic lifecycle operation */ + .start = fhci_start, + .stop = fhci_stop, + + /* managing i/o requests and associated device resources */ + .urb_enqueue = fhci_urb_enqueue, + .urb_dequeue = fhci_urb_dequeue, + .endpoint_disable = fhci_endpoint_disable, + + /* scheduling support */ + .get_frame_number = fhci_get_frame_number, + + /* root hub support */ + .hub_status_data = fhci_hub_status_data, + .hub_control = fhci_hub_control, +}; + +static int __devinit of_fhci_probe(struct of_device *ofdev, + const struct of_device_id *ofid) +{ + struct device *dev = &ofdev->dev; + struct device_node *node = ofdev->node; + struct usb_hcd *hcd; + struct fhci_hcd *fhci; + struct resource usb_regs; + unsigned long pram_addr; + unsigned int usb_irq; + const char *sprop; + const u32 *iprop; + int size; + int ret; + int i; + int j; + + if (usb_disabled()) + return -ENODEV; + + sprop = of_get_property(node, "mode", NULL); + if (sprop && strcmp(sprop, "host")) + return -ENODEV; + + hcd = usb_create_hcd(&fhci_driver, dev, dev->bus_id); + if (!hcd) { + dev_err(dev, "could not create hcd\n"); + return -ENOMEM; + } + + fhci = hcd_to_fhci(hcd); + hcd->self.controller = dev; + dev_set_drvdata(dev, hcd); + + iprop = of_get_property(node, "hub-power-budget", &size); + if (iprop && size == sizeof(*iprop)) + hcd->power_budget = *iprop; + + /* FHCI registers. */ + ret = of_address_to_resource(node, 0, &usb_regs); + if (ret) { + dev_err(dev, "could not get regs\n"); + goto err_regs; + } + + hcd->regs = ioremap(usb_regs.start, usb_regs.end - usb_regs.start + 1); + if (!hcd->regs) { + dev_err(dev, "could not ioremap regs\n"); + ret = -ENOMEM; + goto err_regs; + } + fhci->regs = hcd->regs; + + /* Parameter RAM. */ + iprop = of_get_property(node, "reg", &size); + if (!iprop || size < sizeof(*iprop) * 4) { + dev_err(dev, "can't get pram offset\n"); + ret = -EINVAL; + goto err_pram; + } + + pram_addr = cpm_muram_alloc_fixed(iprop[2], FHCI_PRAM_SIZE); + if (IS_ERR_VALUE(pram_addr)) { + dev_err(dev, "failed to allocate usb pram\n"); + ret = -ENOMEM; + goto err_pram; + } + fhci->pram = cpm_muram_addr(pram_addr); + + /* GPIOs and pins */ + for (i = 0; i < NUM_GPIOS; i++) { + int gpio; + enum of_gpio_flags flags; + + gpio = of_get_gpio_flags(node, i, &flags); + fhci->gpios[i] = gpio; + fhci->alow_gpios[i] = flags & OF_GPIO_ACTIVE_LOW; + + if (!gpio_is_valid(gpio)) { + if (i < GPIO_SPEED) { + dev_err(dev, "incorrect GPIO%d: %d\n", + i, gpio); + goto err_gpios; + } else { + dev_info(dev, "assuming board doesn't have " + "%s gpio\n", i == GPIO_SPEED ? + "speed" : "power"); + continue; + } + } + + ret = gpio_request(gpio, dev->bus_id); + if (ret) { + dev_err(dev, "failed to request gpio %d", i); + goto err_gpios; + } + + if (i >= GPIO_SPEED) { + ret = gpio_direction_output(gpio, 0); + if (ret) { + dev_err(dev, "failed to set gpio %d as " + "an output\n", i); + i++; + goto err_gpios; + } + } + } + + for (j = 0; j < NUM_PINS; j++) { + fhci->pins[j] = qe_pin_request(ofdev->node, j); + if (IS_ERR(fhci->pins[j])) { + ret = PTR_ERR(fhci->pins[j]); + dev_err(dev, "can't get pin %d: %d\n", j, ret); + goto err_pins; + } + } + + /* Frame limit timer and its interrupt. */ + fhci->timer = gtm_get_timer16(); + if (IS_ERR(fhci->timer)) { + ret = PTR_ERR(fhci->timer); + dev_err(dev, "failed to request qe timer: %i", ret); + goto err_get_timer; + } + + ret = request_irq(fhci->timer->irq, fhci_frame_limit_timer_irq, + IRQF_DISABLED, "qe timer (usb)", hcd); + if (ret) { + dev_err(dev, "failed to request timer irq"); + goto err_timer_irq; + } + + /* USB Host interrupt. */ + usb_irq = irq_of_parse_and_map(node, 0); + if (usb_irq == NO_IRQ) { + dev_err(dev, "could not get usb irq\n"); + ret = -EINVAL; + goto err_usb_irq; + } + + /* Clocks. */ + sprop = of_get_property(node, "fsl,fullspeed-clock", NULL); + if (sprop) { + fhci->fullspeed_clk = qe_clock_source(sprop); + if (fhci->fullspeed_clk == QE_CLK_DUMMY) { + dev_err(dev, "wrong fullspeed-clock\n"); + ret = -EINVAL; + goto err_clocks; + } + } + + sprop = of_get_property(node, "fsl,lowspeed-clock", NULL); + if (sprop) { + fhci->lowspeed_clk = qe_clock_source(sprop); + if (fhci->lowspeed_clk == QE_CLK_DUMMY) { + dev_err(dev, "wrong lowspeed-clock\n"); + ret = -EINVAL; + goto err_clocks; + } + } + + if (fhci->fullspeed_clk == QE_CLK_NONE && + fhci->lowspeed_clk == QE_CLK_NONE) { + dev_err(dev, "no clocks specified\n"); + ret = -EINVAL; + goto err_clocks; + } + + dev_info(dev, "at 0x%p, irq %d\n", hcd->regs, usb_irq); + + fhci_config_transceiver(fhci, FHCI_PORT_POWER_OFF); + + /* Start with full-speed, if possible. */ + if (fhci->fullspeed_clk != QE_CLK_NONE) { + fhci_config_transceiver(fhci, FHCI_PORT_FULL); + qe_usb_clock_set(fhci->fullspeed_clk, USB_CLOCK); + } else { + fhci_config_transceiver(fhci, FHCI_PORT_LOW); + qe_usb_clock_set(fhci->lowspeed_clk, USB_CLOCK >> 3); + } + + /* Clear and disable any pending interrupts. */ + out_be16(&fhci->regs->usb_event, 0xffff); + out_be16(&fhci->regs->usb_mask, 0); + + ret = usb_add_hcd(hcd, usb_irq, IRQF_DISABLED); + if (ret < 0) + goto err_add_hcd; + + fhci_dfs_create(fhci); + + return 0; + +err_add_hcd: +err_clocks: + irq_dispose_mapping(usb_irq); +err_usb_irq: + free_irq(fhci->timer->irq, hcd); +err_timer_irq: + gtm_put_timer16(fhci->timer); +err_get_timer: +err_pins: + while (--j >= 0) + qe_pin_free(fhci->pins[j]); +err_gpios: + while (--i >= 0) { + if (gpio_is_valid(fhci->gpios[i])) + gpio_free(fhci->gpios[i]); + } + cpm_muram_free(pram_addr); +err_pram: + iounmap(hcd->regs); +err_regs: + usb_put_hcd(hcd); + return ret; +} + +static int __devexit fhci_remove(struct device *dev) +{ + struct usb_hcd *hcd = dev_get_drvdata(dev); + struct fhci_hcd *fhci = hcd_to_fhci(hcd); + int i; + int j; + + usb_remove_hcd(hcd); + free_irq(fhci->timer->irq, hcd); + gtm_put_timer16(fhci->timer); + cpm_muram_free(cpm_muram_offset(fhci->pram)); + for (i = 0; i < NUM_GPIOS; i++) { + if (!gpio_is_valid(fhci->gpios[i])) + continue; + gpio_free(fhci->gpios[i]); + } + for (j = 0; j < NUM_PINS; j++) + qe_pin_free(fhci->pins[j]); + fhci_dfs_destroy(fhci); + usb_put_hcd(hcd); + return 0; +} + +static int __devexit of_fhci_remove(struct of_device *ofdev) +{ + return fhci_remove(&ofdev->dev); +} + +static struct of_device_id of_fhci_match[] = { + { .compatible = "fsl,mpc8323-qe-usb", }, + {}, +}; +MODULE_DEVICE_TABLE(of, of_fhci_match); + +static struct of_platform_driver of_fhci_driver = { + .name = "fsl,usb-fhci", + .match_table = of_fhci_match, + .probe = of_fhci_probe, + .remove = __devexit_p(of_fhci_remove), +}; + +static int __init fhci_module_init(void) +{ + return of_register_platform_driver(&of_fhci_driver); +} +module_init(fhci_module_init); + +static void __exit fhci_module_exit(void) +{ + of_unregister_platform_driver(&of_fhci_driver); +} +module_exit(fhci_module_exit); + +MODULE_DESCRIPTION("USB Freescale Host Controller Interface Driver"); +MODULE_AUTHOR("Shlomi Gridish <gridish@freescale.com>, " + "Jerry Huang <Chang-Ming.Huang@freescale.com>, " + "Anton Vorontsov <avorontsov@ru.mvista.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/host/fhci-hub.c b/drivers/usb/host/fhci-hub.c new file mode 100644 index 000000000000..0cfaedc3e124 --- /dev/null +++ b/drivers/usb/host/fhci-hub.c @@ -0,0 +1,345 @@ +/* + * Freescale QUICC Engine USB Host Controller Driver + * + * Copyright (c) Freescale Semicondutor, Inc. 2006. + * Shlomi Gridish <gridish@freescale.com> + * Jerry Huang <Chang-Ming.Huang@freescale.com> + * Copyright (c) Logic Product Development, Inc. 2007 + * Peter Barada <peterb@logicpd.com> + * Copyright (c) MontaVista Software, Inc. 2008. + * Anton Vorontsov <avorontsov@ru.mvista.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; either version 2 of the License, or (at your + * option) any later version. + */ + +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/spinlock.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/io.h> +#include <linux/usb.h> +#include <linux/gpio.h> +#include <asm/qe.h> +#include "../core/hcd.h" +#include "fhci.h" + +/* virtual root hub specific descriptor */ +static u8 root_hub_des[] = { + 0x09, /* blength */ + 0x29, /* bDescriptorType;hub-descriptor */ + 0x01, /* bNbrPorts */ + 0x00, /* wHubCharacteristics */ + 0x00, + 0x01, /* bPwrOn2pwrGood;2ms */ + 0x00, /* bHubContrCurrent;0mA */ + 0x00, /* DeviceRemoveable */ + 0xff, /* PortPwrCtrlMask */ +}; + +static void fhci_gpio_set_value(struct fhci_hcd *fhci, int gpio_nr, bool on) +{ + int gpio = fhci->gpios[gpio_nr]; + bool alow = fhci->alow_gpios[gpio_nr]; + + if (!gpio_is_valid(gpio)) + return; + + gpio_set_value(gpio, on ^ alow); + mdelay(5); +} + +void fhci_config_transceiver(struct fhci_hcd *fhci, + enum fhci_port_status status) +{ + fhci_dbg(fhci, "-> %s: %d\n", __func__, status); + + switch (status) { + case FHCI_PORT_POWER_OFF: + fhci_gpio_set_value(fhci, GPIO_POWER, false); + break; + case FHCI_PORT_DISABLED: + case FHCI_PORT_WAITING: + fhci_gpio_set_value(fhci, GPIO_POWER, true); + break; + case FHCI_PORT_LOW: + fhci_gpio_set_value(fhci, GPIO_SPEED, false); + break; + case FHCI_PORT_FULL: + fhci_gpio_set_value(fhci, GPIO_SPEED, true); + break; + default: + WARN_ON(1); + break; + } + + fhci_dbg(fhci, "<- %s: %d\n", __func__, status); +} + +/* disable the USB port by clearing the EN bit in the USBMOD register */ +void fhci_port_disable(struct fhci_hcd *fhci) +{ + struct fhci_usb *usb = (struct fhci_usb *)fhci->usb_lld; + enum fhci_port_status port_status; + + fhci_dbg(fhci, "-> %s\n", __func__); + + fhci_stop_sof_timer(fhci); + + fhci_flush_all_transmissions(usb); + + fhci_usb_disable_interrupt((struct fhci_usb *)fhci->usb_lld); + port_status = usb->port_status; + usb->port_status = FHCI_PORT_DISABLED; + + /* Enable IDLE since we want to know if something comes along */ + usb->saved_msk |= USB_E_IDLE_MASK; + out_be16(&usb->fhci->regs->usb_mask, usb->saved_msk); + + /* check if during the disconnection process attached new device */ + if (port_status == FHCI_PORT_WAITING) + fhci_device_connected_interrupt(fhci); + usb->vroot_hub->port.wPortStatus &= ~USB_PORT_STAT_ENABLE; + usb->vroot_hub->port.wPortChange |= USB_PORT_STAT_C_ENABLE; + fhci_usb_enable_interrupt((struct fhci_usb *)fhci->usb_lld); + + fhci_dbg(fhci, "<- %s\n", __func__); +} + +/* enable the USB port by setting the EN bit in the USBMOD register */ +void fhci_port_enable(void *lld) +{ + struct fhci_usb *usb = (struct fhci_usb *)lld; + struct fhci_hcd *fhci = usb->fhci; + + fhci_dbg(fhci, "-> %s\n", __func__); + + fhci_config_transceiver(fhci, usb->port_status); + + if ((usb->port_status != FHCI_PORT_FULL) && + (usb->port_status != FHCI_PORT_LOW)) + fhci_start_sof_timer(fhci); + + usb->vroot_hub->port.wPortStatus |= USB_PORT_STAT_ENABLE; + usb->vroot_hub->port.wPortChange |= USB_PORT_STAT_C_ENABLE; + + fhci_dbg(fhci, "<- %s\n", __func__); +} + +void fhci_io_port_generate_reset(struct fhci_hcd *fhci) +{ + fhci_dbg(fhci, "-> %s\n", __func__); + + gpio_direction_output(fhci->gpios[GPIO_USBOE], 0); + gpio_direction_output(fhci->gpios[GPIO_USBTP], 0); + gpio_direction_output(fhci->gpios[GPIO_USBTN], 0); + + mdelay(5); + + qe_pin_set_dedicated(fhci->pins[PIN_USBOE]); + qe_pin_set_dedicated(fhci->pins[PIN_USBTP]); + qe_pin_set_dedicated(fhci->pins[PIN_USBTN]); + + fhci_dbg(fhci, "<- %s\n", __func__); +} + +/* generate the RESET condition on the bus */ +void fhci_port_reset(void *lld) +{ + struct fhci_usb *usb = (struct fhci_usb *)lld; + struct fhci_hcd *fhci = usb->fhci; + u8 mode; + u16 mask; + + fhci_dbg(fhci, "-> %s\n", __func__); + + fhci_stop_sof_timer(fhci); + /* disable the USB controller */ + mode = in_8(&fhci->regs->usb_mod); + out_8(&fhci->regs->usb_mod, mode & (~USB_MODE_EN)); + + /* disable idle interrupts */ + mask = in_be16(&fhci->regs->usb_mask); + out_be16(&fhci->regs->usb_mask, mask & (~USB_E_IDLE_MASK)); + + fhci_io_port_generate_reset(fhci); + + /* enable interrupt on this endpoint */ + out_be16(&fhci->regs->usb_mask, mask); + + /* enable the USB controller */ + mode = in_8(&fhci->regs->usb_mod); + out_8(&fhci->regs->usb_mod, mode | USB_MODE_EN); + fhci_start_sof_timer(fhci); + + fhci_dbg(fhci, "<- %s\n", __func__); +} + +int fhci_hub_status_data(struct usb_hcd *hcd, char *buf) +{ + struct fhci_hcd *fhci = hcd_to_fhci(hcd); + int ret = 0; + unsigned long flags; + + fhci_dbg(fhci, "-> %s\n", __func__); + + spin_lock_irqsave(&fhci->lock, flags); + + if (fhci->vroot_hub->port.wPortChange & (USB_PORT_STAT_C_CONNECTION | + USB_PORT_STAT_C_ENABLE | USB_PORT_STAT_C_SUSPEND | + USB_PORT_STAT_C_RESET | USB_PORT_STAT_C_OVERCURRENT)) { + *buf = 1 << 1; + ret = 1; + fhci_dbg(fhci, "-- %s\n", __func__); + } + + spin_unlock_irqrestore(&fhci->lock, flags); + + fhci_dbg(fhci, "<- %s\n", __func__); + + return ret; +} + +int fhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, + u16 wIndex, char *buf, u16 wLength) +{ + struct fhci_hcd *fhci = hcd_to_fhci(hcd); + int retval = 0; + int len = 0; + struct usb_hub_status *hub_status; + struct usb_port_status *port_status; + unsigned long flags; + + spin_lock_irqsave(&fhci->lock, flags); + + fhci_dbg(fhci, "-> %s\n", __func__); + + switch (typeReq) { + case ClearHubFeature: + switch (wValue) { + case C_HUB_LOCAL_POWER: + case C_HUB_OVER_CURRENT: + break; + default: + goto error; + } + break; + case ClearPortFeature: + fhci->vroot_hub->feature &= (1 << wValue); + + switch (wValue) { + case USB_PORT_FEAT_ENABLE: + fhci->vroot_hub->port.wPortStatus &= + ~USB_PORT_STAT_ENABLE; + fhci_port_disable(fhci); + break; + case USB_PORT_FEAT_C_ENABLE: + fhci->vroot_hub->port.wPortChange &= + ~USB_PORT_STAT_C_ENABLE; + break; + case USB_PORT_FEAT_SUSPEND: + fhci->vroot_hub->port.wPortStatus &= + ~USB_PORT_STAT_SUSPEND; + fhci_stop_sof_timer(fhci); + break; + case USB_PORT_FEAT_C_SUSPEND: + fhci->vroot_hub->port.wPortChange &= + ~USB_PORT_STAT_C_SUSPEND; + break; + case USB_PORT_FEAT_POWER: + fhci->vroot_hub->port.wPortStatus &= + ~USB_PORT_STAT_POWER; + fhci_config_transceiver(fhci, FHCI_PORT_POWER_OFF); + break; + case USB_PORT_FEAT_C_CONNECTION: + fhci->vroot_hub->port.wPortChange &= + ~USB_PORT_STAT_C_CONNECTION; + break; + case USB_PORT_FEAT_C_OVER_CURRENT: + fhci->vroot_hub->port.wPortChange &= + ~USB_PORT_STAT_C_OVERCURRENT; + break; + case USB_PORT_FEAT_C_RESET: + fhci->vroot_hub->port.wPortChange &= + ~USB_PORT_STAT_C_RESET; + break; + default: + goto error; + } + break; + case GetHubDescriptor: + memcpy(buf, root_hub_des, sizeof(root_hub_des)); + buf[3] = 0x11; /* per-port power, no ovrcrnt */ + len = (buf[0] < wLength) ? buf[0] : wLength; + break; + case GetHubStatus: + hub_status = (struct usb_hub_status *)buf; + hub_status->wHubStatus = + cpu_to_le16(fhci->vroot_hub->hub.wHubStatus); + hub_status->wHubChange = + cpu_to_le16(fhci->vroot_hub->hub.wHubChange); + len = 4; + break; + case GetPortStatus: + port_status = (struct usb_port_status *)buf; + port_status->wPortStatus = + cpu_to_le16(fhci->vroot_hub->port.wPortStatus); + port_status->wPortChange = + cpu_to_le16(fhci->vroot_hub->port.wPortChange); + len = 4; + break; + case SetHubFeature: + switch (wValue) { + case C_HUB_OVER_CURRENT: + case C_HUB_LOCAL_POWER: + break; + default: + goto error; + } + break; + case SetPortFeature: + fhci->vroot_hub->feature |= (1 << wValue); + + switch (wValue) { + case USB_PORT_FEAT_ENABLE: + fhci->vroot_hub->port.wPortStatus |= + USB_PORT_STAT_ENABLE; + fhci_port_enable(fhci->usb_lld); + break; + case USB_PORT_FEAT_SUSPEND: + fhci->vroot_hub->port.wPortStatus |= + USB_PORT_STAT_SUSPEND; + fhci_stop_sof_timer(fhci); + break; + case USB_PORT_FEAT_RESET: + fhci->vroot_hub->port.wPortStatus |= + USB_PORT_STAT_RESET; + fhci_port_reset(fhci->usb_lld); + fhci->vroot_hub->port.wPortStatus |= + USB_PORT_STAT_ENABLE; + fhci->vroot_hub->port.wPortStatus &= + ~USB_PORT_STAT_RESET; + break; + case USB_PORT_FEAT_POWER: + fhci->vroot_hub->port.wPortStatus |= + USB_PORT_STAT_POWER; + fhci_config_transceiver(fhci, FHCI_PORT_WAITING); + break; + default: + goto error; + } + break; + default: +error: + retval = -EPIPE; + } + + fhci_dbg(fhci, "<- %s\n", __func__); + + spin_unlock_irqrestore(&fhci->lock, flags); + + return retval; +} diff --git a/drivers/usb/host/fhci-mem.c b/drivers/usb/host/fhci-mem.c new file mode 100644 index 000000000000..2c0736c99712 --- /dev/null +++ b/drivers/usb/host/fhci-mem.c @@ -0,0 +1,113 @@ +/* + * Freescale QUICC Engine USB Host Controller Driver + * + * Copyright (c) Freescale Semicondutor, Inc. 2006. + * Shlomi Gridish <gridish@freescale.com> + * Jerry Huang <Chang-Ming.Huang@freescale.com> + * Copyright (c) Logic Product Development, Inc. 2007 + * Peter Barada <peterb@logicpd.com> + * Copyright (c) MontaVista Software, Inc. 2008. + * Anton Vorontsov <avorontsov@ru.mvista.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; either version 2 of the License, or (at your + * option) any later version. + */ + +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/delay.h> +#include <linux/list.h> +#include <linux/usb.h> +#include "../core/hcd.h" +#include "fhci.h" + +static void init_td(struct td *td) +{ + memset(td, 0, sizeof(*td)); + INIT_LIST_HEAD(&td->node); + INIT_LIST_HEAD(&td->frame_lh); +} + +static void init_ed(struct ed *ed) +{ + memset(ed, 0, sizeof(*ed)); + INIT_LIST_HEAD(&ed->td_list); + INIT_LIST_HEAD(&ed->node); +} + +static struct td *get_empty_td(struct fhci_hcd *fhci) +{ + struct td *td; + + if (!list_empty(&fhci->empty_tds)) { + td = list_entry(fhci->empty_tds.next, struct td, node); + list_del(fhci->empty_tds.next); + } else { + td = kmalloc(sizeof(*td), GFP_ATOMIC); + if (!td) + fhci_err(fhci, "No memory to allocate to TD\n"); + else + init_td(td); + } + + return td; +} + +void fhci_recycle_empty_td(struct fhci_hcd *fhci, struct td *td) +{ + init_td(td); + list_add(&td->node, &fhci->empty_tds); +} + +struct ed *fhci_get_empty_ed(struct fhci_hcd *fhci) +{ + struct ed *ed; + + if (!list_empty(&fhci->empty_eds)) { + ed = list_entry(fhci->empty_eds.next, struct ed, node); + list_del(fhci->empty_eds.next); + } else { + ed = kmalloc(sizeof(*ed), GFP_ATOMIC); + if (!ed) + fhci_err(fhci, "No memory to allocate to ED\n"); + else + init_ed(ed); + } + + return ed; +} + +void fhci_recycle_empty_ed(struct fhci_hcd *fhci, struct ed *ed) +{ + init_ed(ed); + list_add(&ed->node, &fhci->empty_eds); +} + +struct td *fhci_td_fill(struct fhci_hcd *fhci, struct urb *urb, + struct urb_priv *urb_priv, struct ed *ed, u16 index, + enum fhci_ta_type type, int toggle, u8 *data, u32 len, + u16 interval, u16 start_frame, bool ioc) +{ + struct td *td = get_empty_td(fhci); + + if (!td) + return NULL; + + td->urb = urb; + td->ed = ed; + td->type = type; + td->toggle = toggle; + td->data = data; + td->len = len; + td->iso_index = index; + td->interval = interval; + td->start_frame = start_frame; + td->ioc = ioc; + td->status = USB_TD_OK; + + urb_priv->tds[index] = td; + + return td; +} diff --git a/drivers/usb/host/fhci-q.c b/drivers/usb/host/fhci-q.c new file mode 100644 index 000000000000..b0a1446ba292 --- /dev/null +++ b/drivers/usb/host/fhci-q.c @@ -0,0 +1,284 @@ +/* + * Freescale QUICC Engine USB Host Controller Driver + * + * Copyright (c) Freescale Semicondutor, Inc. 2006. + * Shlomi Gridish <gridish@freescale.com> + * Jerry Huang <Chang-Ming.Huang@freescale.com> + * Copyright (c) Logic Product Development, Inc. 2007 + * Peter Barada <peterb@logicpd.com> + * Copyright (c) MontaVista Software, Inc. 2008. + * Anton Vorontsov <avorontsov@ru.mvista.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; either version 2 of the License, or (at your + * option) any later version. + */ + +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/spinlock.h> +#include <linux/errno.h> +#include <linux/list.h> +#include <linux/usb.h> +#include "../core/hcd.h" +#include "fhci.h" + +/* maps the hardware error code to the USB error code */ +static int status_to_error(u32 status) +{ + if (status == USB_TD_OK) + return 0; + else if (status & USB_TD_RX_ER_CRC) + return -EILSEQ; + else if (status & USB_TD_RX_ER_NONOCT) + return -EPROTO; + else if (status & USB_TD_RX_ER_OVERUN) + return -ECOMM; + else if (status & USB_TD_RX_ER_BITSTUFF) + return -EPROTO; + else if (status & USB_TD_RX_ER_PID) + return -EILSEQ; + else if (status & (USB_TD_TX_ER_NAK | USB_TD_TX_ER_TIMEOUT)) + return -ETIMEDOUT; + else if (status & USB_TD_TX_ER_STALL) + return -EPIPE; + else if (status & USB_TD_TX_ER_UNDERUN) + return -ENOSR; + else if (status & USB_TD_RX_DATA_UNDERUN) + return -EREMOTEIO; + else if (status & USB_TD_RX_DATA_OVERUN) + return -EOVERFLOW; + else + return -EINVAL; +} + +void fhci_add_td_to_frame(struct fhci_time_frame *frame, struct td *td) +{ + list_add_tail(&td->frame_lh, &frame->tds_list); +} + +void fhci_add_tds_to_ed(struct ed *ed, struct td **td_list, int number) +{ + int i; + + for (i = 0; i < number; i++) { + struct td *td = td_list[i]; + list_add_tail(&td->node, &ed->td_list); + } + if (ed->td_head == NULL) + ed->td_head = td_list[0]; +} + +static struct td *peek_td_from_ed(struct ed *ed) +{ + struct td *td; + + if (!list_empty(&ed->td_list)) + td = list_entry(ed->td_list.next, struct td, node); + else + td = NULL; + + return td; +} + +struct td *fhci_remove_td_from_frame(struct fhci_time_frame *frame) +{ + struct td *td; + + if (!list_empty(&frame->tds_list)) { + td = list_entry(frame->tds_list.next, struct td, frame_lh); + list_del_init(frame->tds_list.next); + } else + td = NULL; + + return td; +} + +struct td *fhci_peek_td_from_frame(struct fhci_time_frame *frame) +{ + struct td *td; + + if (!list_empty(&frame->tds_list)) + td = list_entry(frame->tds_list.next, struct td, frame_lh); + else + td = NULL; + + return td; +} + +struct td *fhci_remove_td_from_ed(struct ed *ed) +{ + struct td *td; + + if (!list_empty(&ed->td_list)) { + td = list_entry(ed->td_list.next, struct td, node); + list_del_init(ed->td_list.next); + + /* if this TD was the ED's head, find next TD */ + if (!list_empty(&ed->td_list)) + ed->td_head = list_entry(ed->td_list.next, struct td, + node); + else + ed->td_head = NULL; + } else + td = NULL; + + return td; +} + +struct td *fhci_remove_td_from_done_list(struct fhci_controller_list *p_list) +{ + struct td *td; + + if (!list_empty(&p_list->done_list)) { + td = list_entry(p_list->done_list.next, struct td, node); + list_del_init(p_list->done_list.next); + } else + td = NULL; + + return td; +} + +void fhci_move_td_from_ed_to_done_list(struct fhci_usb *usb, struct ed *ed) +{ + struct td *td; + + td = ed->td_head; + list_del_init(&td->node); + + /* If this TD was the ED's head,find next TD */ + if (!list_empty(&ed->td_list)) + ed->td_head = list_entry(ed->td_list.next, struct td, node); + else { + ed->td_head = NULL; + ed->state = FHCI_ED_SKIP; + } + ed->toggle_carry = td->toggle; + list_add_tail(&td->node, &usb->hc_list->done_list); + if (td->ioc) + usb->transfer_confirm(usb->fhci); +} + +/* free done FHCI URB resource such as ED and TD */ +static void free_urb_priv(struct fhci_hcd *fhci, struct urb *urb) +{ + int i; + struct urb_priv *urb_priv = urb->hcpriv; + struct ed *ed = urb_priv->ed; + + for (i = 0; i < urb_priv->num_of_tds; i++) { + list_del_init(&urb_priv->tds[i]->node); + fhci_recycle_empty_td(fhci, urb_priv->tds[i]); + } + + /* if this TD was the ED's head,find the next TD */ + if (!list_empty(&ed->td_list)) + ed->td_head = list_entry(ed->td_list.next, struct td, node); + else + ed->td_head = NULL; + + kfree(urb_priv->tds); + kfree(urb_priv); + urb->hcpriv = NULL; + + /* if this TD was the ED's head,find next TD */ + if (ed->td_head == NULL) + list_del_init(&ed->node); + fhci->active_urbs--; +} + +/* this routine called to complete and free done URB */ +void fhci_urb_complete_free(struct fhci_hcd *fhci, struct urb *urb) +{ + free_urb_priv(fhci, urb); + + if (urb->status == -EINPROGRESS) { + if (urb->actual_length != urb->transfer_buffer_length && + urb->transfer_flags & URB_SHORT_NOT_OK) + urb->status = -EREMOTEIO; + else + urb->status = 0; + } + + usb_hcd_unlink_urb_from_ep(fhci_to_hcd(fhci), urb); + + spin_unlock(&fhci->lock); + + usb_hcd_giveback_urb(fhci_to_hcd(fhci), urb, urb->status); + + spin_lock(&fhci->lock); +} + +/* + * caculate transfer length/stats and update the urb + * Precondition: irqsafe(only for urb-?status locking) + */ +void fhci_done_td(struct urb *urb, struct td *td) +{ + struct ed *ed = td->ed; + u32 cc = td->status; + + /* ISO...drivers see per-TD length/status */ + if (ed->mode == FHCI_TF_ISO) { + u32 len; + if (!(urb->transfer_flags & URB_SHORT_NOT_OK && + cc == USB_TD_RX_DATA_UNDERUN)) + cc = USB_TD_OK; + + if (usb_pipeout(urb->pipe)) + len = urb->iso_frame_desc[td->iso_index].length; + else + len = td->actual_len; + + urb->actual_length += len; + urb->iso_frame_desc[td->iso_index].actual_length = len; + urb->iso_frame_desc[td->iso_index].status = + status_to_error(cc); + } + + /* BULK,INT,CONTROL... drivers see aggregate length/status, + * except that "setup" bytes aren't counted and "short" transfers + * might not be reported as errors. + */ + else { + if (td->error_cnt >= 3) + urb->error_count = 3; + + /* control endpoint only have soft stalls */ + + /* update packet status if needed(short may be ok) */ + if (!(urb->transfer_flags & URB_SHORT_NOT_OK) && + cc == USB_TD_RX_DATA_UNDERUN) { + ed->state = FHCI_ED_OPER; + cc = USB_TD_OK; + } + if (cc != USB_TD_OK) { + if (urb->status == -EINPROGRESS) + urb->status = status_to_error(cc); + } + + /* count all non-empty packets except control SETUP packet */ + if (td->type != FHCI_TA_SETUP || td->iso_index != 0) + urb->actual_length += td->actual_len; + } +} + +/* there are some pedning request to unlink */ +void fhci_del_ed_list(struct fhci_hcd *fhci, struct ed *ed) +{ + struct td *td = peek_td_from_ed(ed); + struct urb *urb = td->urb; + struct urb_priv *urb_priv = urb->hcpriv; + + if (urb_priv->state == URB_DEL) { + td = fhci_remove_td_from_ed(ed); + /* HC may have partly processed this TD */ + if (td->status != USB_TD_INPROGRESS) + fhci_done_td(urb, td); + + /* URB is done;clean up */ + if (++(urb_priv->tds_cnt) == urb_priv->num_of_tds) + fhci_urb_complete_free(fhci, urb); + } +} diff --git a/drivers/usb/host/fhci-sched.c b/drivers/usb/host/fhci-sched.c new file mode 100644 index 000000000000..bb63b68ddb77 --- /dev/null +++ b/drivers/usb/host/fhci-sched.c @@ -0,0 +1,888 @@ +/* + * Freescale QUICC Engine USB Host Controller Driver + * + * Copyright (c) Freescale Semicondutor, Inc. 2006. + * Shlomi Gridish <gridish@freescale.com> + * Jerry Huang <Chang-Ming.Huang@freescale.com> + * Copyright (c) Logic Product Development, Inc. 2007 + * Peter Barada <peterb@logicpd.com> + * Copyright (c) MontaVista Software, Inc. 2008. + * Anton Vorontsov <avorontsov@ru.mvista.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; either version 2 of the License, or (at your + * option) any later version. + */ + +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/spinlock.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/list.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/usb.h> +#include <asm/qe.h> +#include <asm/fsl_gtm.h> +#include "../core/hcd.h" +#include "fhci.h" + +static void recycle_frame(struct fhci_usb *usb, struct packet *pkt) +{ + pkt->data = NULL; + pkt->len = 0; + pkt->status = USB_TD_OK; + pkt->info = 0; + pkt->priv_data = NULL; + + cq_put(usb->ep0->empty_frame_Q, pkt); +} + +/* confirm submitted packet */ +void fhci_transaction_confirm(struct fhci_usb *usb, struct packet *pkt) +{ + struct td *td; + struct packet *td_pkt; + struct ed *ed; + u32 trans_len; + bool td_done = false; + + td = fhci_remove_td_from_frame(usb->actual_frame); + td_pkt = td->pkt; + trans_len = pkt->len; + td->status = pkt->status; + if (td->type == FHCI_TA_IN && td_pkt->info & PKT_DUMMY_PACKET) { + if ((td->data + td->actual_len) && trans_len) + memcpy(td->data + td->actual_len, pkt->data, + trans_len); + cq_put(usb->ep0->dummy_packets_Q, pkt->data); + } + + recycle_frame(usb, pkt); + + ed = td->ed; + if (ed->mode == FHCI_TF_ISO) { + if (ed->td_list.next->next != &ed->td_list) { + struct td *td_next = + list_entry(ed->td_list.next->next, struct td, + node); + + td_next->start_frame = usb->actual_frame->frame_num; + } + td->actual_len = trans_len; + td_done = true; + } else if ((td->status & USB_TD_ERROR) && + !(td->status & USB_TD_TX_ER_NAK)) { + /* + * There was an error on the transaction (but not NAK). + * If it is fatal error (data underrun, stall, bad pid or 3 + * errors exceeded), mark this TD as done. + */ + if ((td->status & USB_TD_RX_DATA_UNDERUN) || + (td->status & USB_TD_TX_ER_STALL) || + (td->status & USB_TD_RX_ER_PID) || + (++td->error_cnt >= 3)) { + ed->state = FHCI_ED_HALTED; + td_done = true; + + if (td->status & USB_TD_RX_DATA_UNDERUN) { + fhci_dbg(usb->fhci, "td err fu\n"); + td->toggle = !td->toggle; + td->actual_len += trans_len; + } else { + fhci_dbg(usb->fhci, "td err f!u\n"); + } + } else { + fhci_dbg(usb->fhci, "td err !f\n"); + /* it is not a fatal error -retry this transaction */ + td->nak_cnt = 0; + td->error_cnt++; + td->status = USB_TD_OK; + } + } else if (td->status & USB_TD_TX_ER_NAK) { + /* there was a NAK response */ + fhci_vdbg(usb->fhci, "td nack\n"); + td->nak_cnt++; + td->error_cnt = 0; + td->status = USB_TD_OK; + } else { + /* there was no error on transaction */ + td->error_cnt = 0; + td->nak_cnt = 0; + td->toggle = !td->toggle; + td->actual_len += trans_len; + + if (td->len == td->actual_len) + td_done = true; + } + + if (td_done) + fhci_move_td_from_ed_to_done_list(usb, ed); +} + +/* + * Flush all transmitted packets from BDs + * This routine is called when disabling the USB port to flush all + * transmissions that are allready scheduled in the BDs + */ +void fhci_flush_all_transmissions(struct fhci_usb *usb) +{ + u8 mode; + struct td *td; + + mode = in_8(&usb->fhci->regs->usb_mod); + clrbits8(&usb->fhci->regs->usb_mod, USB_MODE_EN); + + fhci_flush_bds(usb); + + while ((td = fhci_peek_td_from_frame(usb->actual_frame)) != NULL) { + struct packet *pkt = td->pkt; + + pkt->status = USB_TD_TX_ER_TIMEOUT; + fhci_transaction_confirm(usb, pkt); + } + + usb->actual_frame->frame_status = FRAME_END_TRANSMISSION; + + /* reset the event register */ + out_be16(&usb->fhci->regs->usb_event, 0xffff); + /* enable the USB controller */ + out_8(&usb->fhci->regs->usb_mod, mode | USB_MODE_EN); +} + +/* + * This function forms the packet and transmit the packet. This function + * will handle all endpoint type:ISO,interrupt,control and bulk + */ +static int add_packet(struct fhci_usb *usb, struct ed *ed, struct td *td) +{ + u32 fw_transaction_time, len = 0; + struct packet *pkt; + u8 *data = NULL; + + /* calcalate data address,len and toggle and then add the transaction */ + if (td->toggle == USB_TD_TOGGLE_CARRY) + td->toggle = ed->toggle_carry; + + switch (ed->mode) { + case FHCI_TF_ISO: + len = td->len; + if (td->type != FHCI_TA_IN) + data = td->data; + break; + case FHCI_TF_CTRL: + case FHCI_TF_BULK: + len = min(td->len - td->actual_len, ed->max_pkt_size); + if (!((td->type == FHCI_TA_IN) && + ((len + td->actual_len) == td->len))) + data = td->data + td->actual_len; + break; + case FHCI_TF_INTR: + len = min(td->len, ed->max_pkt_size); + if (!((td->type == FHCI_TA_IN) && + ((td->len + CRC_SIZE) >= ed->max_pkt_size))) + data = td->data; + break; + default: + break; + } + + if (usb->port_status == FHCI_PORT_FULL) + fw_transaction_time = (((len + PROTOCOL_OVERHEAD) * 11) >> 4); + else + fw_transaction_time = ((len + PROTOCOL_OVERHEAD) * 6); + + /* check if there's enough space in this frame to submit this TD */ + if (usb->actual_frame->total_bytes + len + PROTOCOL_OVERHEAD >= + usb->max_bytes_per_frame) { + fhci_vdbg(usb->fhci, "not enough space in this frame: " + "%d %d %d\n", usb->actual_frame->total_bytes, len, + usb->max_bytes_per_frame); + return -1; + } + + /* check if there's enough time in this frame to submit this TD */ + if (usb->actual_frame->frame_status != FRAME_IS_PREPARED && + (usb->actual_frame->frame_status & FRAME_END_TRANSMISSION || + (fw_transaction_time + usb->sw_transaction_time >= + 1000 - fhci_get_sof_timer_count(usb)))) { + fhci_dbg(usb->fhci, "not enough time in this frame\n"); + return -1; + } + + /* update frame object fields before transmitting */ + pkt = cq_get(usb->ep0->empty_frame_Q); + if (!pkt) { + fhci_dbg(usb->fhci, "there is no empty frame\n"); + return -1; + } + td->pkt = pkt; + + pkt->info = 0; + if (data == NULL) { + data = cq_get(usb->ep0->dummy_packets_Q); + BUG_ON(!data); + pkt->info = PKT_DUMMY_PACKET; + } + pkt->data = data; + pkt->len = len; + pkt->status = USB_TD_OK; + /* update TD status field before transmitting */ + td->status = USB_TD_INPROGRESS; + /* update actual frame time object with the actual transmission */ + usb->actual_frame->total_bytes += (len + PROTOCOL_OVERHEAD); + fhci_add_td_to_frame(usb->actual_frame, td); + + if (usb->port_status != FHCI_PORT_FULL && + usb->port_status != FHCI_PORT_LOW) { + pkt->status = USB_TD_TX_ER_TIMEOUT; + pkt->len = 0; + fhci_transaction_confirm(usb, pkt); + } else if (fhci_host_transaction(usb, pkt, td->type, ed->dev_addr, + ed->ep_addr, ed->mode, ed->speed, td->toggle)) { + /* remove TD from actual frame */ + list_del_init(&td->frame_lh); + td->status = USB_TD_OK; + if (pkt->info & PKT_DUMMY_PACKET) + cq_put(usb->ep0->dummy_packets_Q, pkt->data); + recycle_frame(usb, pkt); + usb->actual_frame->total_bytes -= (len + PROTOCOL_OVERHEAD); + fhci_err(usb->fhci, "host transaction failed\n"); + return -1; + } + + return len; +} + +static void move_head_to_tail(struct list_head *list) +{ + struct list_head *node = list->next; + + if (!list_empty(list)) { + list_del(node); + list_add_tail(node, list); + } +} + +/* + * This function goes through the endpoint list and schedules the + * transactions within this list + */ +static int scan_ed_list(struct fhci_usb *usb, + struct list_head *list, enum fhci_tf_mode list_type) +{ + static const int frame_part[4] = { + [FHCI_TF_CTRL] = MAX_BYTES_PER_FRAME, + [FHCI_TF_ISO] = (MAX_BYTES_PER_FRAME * + MAX_PERIODIC_FRAME_USAGE) / 100, + [FHCI_TF_BULK] = MAX_BYTES_PER_FRAME, + [FHCI_TF_INTR] = (MAX_BYTES_PER_FRAME * + MAX_PERIODIC_FRAME_USAGE) / 100 + }; + struct ed *ed; + struct td *td; + int ans = 1; + u32 save_transaction_time = usb->sw_transaction_time; + + list_for_each_entry(ed, list, node) { + td = ed->td_head; + + if (!td || (td && td->status == USB_TD_INPROGRESS)) + continue; + + if (ed->state != FHCI_ED_OPER) { + if (ed->state == FHCI_ED_URB_DEL) { + td->status = USB_TD_OK; + fhci_move_td_from_ed_to_done_list(usb, ed); + ed->state = FHCI_ED_SKIP; + } + continue; + } + + /* + * if it isn't interrupt pipe or it is not iso pipe and the + * interval time passed + */ + if ((list_type == FHCI_TF_INTR || list_type == FHCI_TF_ISO) && + (((usb->actual_frame->frame_num - + td->start_frame) & 0x7ff) < td->interval)) + continue; + + if (add_packet(usb, ed, td) < 0) + continue; + + /* update time stamps in the TD */ + td->start_frame = usb->actual_frame->frame_num; + usb->sw_transaction_time += save_transaction_time; + + if (usb->actual_frame->total_bytes >= + usb->max_bytes_per_frame) { + usb->actual_frame->frame_status = + FRAME_DATA_END_TRANSMISSION; + fhci_push_dummy_bd(usb->ep0); + ans = 0; + break; + } + + if (usb->actual_frame->total_bytes >= frame_part[list_type]) + break; + } + + /* be fair to each ED(move list head around) */ + move_head_to_tail(list); + usb->sw_transaction_time = save_transaction_time; + + return ans; +} + +static u32 rotate_frames(struct fhci_usb *usb) +{ + struct fhci_hcd *fhci = usb->fhci; + + if (!list_empty(&usb->actual_frame->tds_list)) { + if ((((in_be16(&fhci->pram->frame_num) & 0x07ff) - + usb->actual_frame->frame_num) & 0x7ff) > 5) + fhci_flush_actual_frame(usb); + else + return -EINVAL; + } + + usb->actual_frame->frame_status = FRAME_IS_PREPARED; + usb->actual_frame->frame_num = in_be16(&fhci->pram->frame_num) & 0x7ff; + usb->actual_frame->total_bytes = 0; + + return 0; +} + +/* + * This function schedule the USB transaction and will process the + * endpoint in the following order: iso, interrupt, control and bulk. + */ +void fhci_schedule_transactions(struct fhci_usb *usb) +{ + int left = 1; + + if (usb->actual_frame->frame_status & FRAME_END_TRANSMISSION) + if (rotate_frames(usb) != 0) + return; + + if (usb->actual_frame->frame_status & FRAME_END_TRANSMISSION) + return; + + if (usb->actual_frame->total_bytes == 0) { + /* + * schedule the next available ISO transfer + *or next stage of the ISO transfer + */ + scan_ed_list(usb, &usb->hc_list->iso_list, FHCI_TF_ISO); + + /* + * schedule the next available interrupt transfer or + * the next stage of the interrupt transfer + */ + scan_ed_list(usb, &usb->hc_list->intr_list, FHCI_TF_INTR); + + /* + * schedule the next available control transfer + * or the next stage of the control transfer + */ + left = scan_ed_list(usb, &usb->hc_list->ctrl_list, + FHCI_TF_CTRL); + } + + /* + * schedule the next available bulk transfer or the next stage of the + * bulk transfer + */ + if (left > 0) + scan_ed_list(usb, &usb->hc_list->bulk_list, FHCI_TF_BULK); +} + +/* Handles SOF interrupt */ +static void sof_interrupt(struct fhci_hcd *fhci) +{ + struct fhci_usb *usb = fhci->usb_lld; + + if ((usb->port_status == FHCI_PORT_DISABLED) && + (usb->vroot_hub->port.wPortStatus & USB_PORT_STAT_CONNECTION) && + !(usb->vroot_hub->port.wPortChange & USB_PORT_STAT_C_CONNECTION)) { + if (usb->vroot_hub->port.wPortStatus & USB_PORT_STAT_LOW_SPEED) + usb->port_status = FHCI_PORT_LOW; + else + usb->port_status = FHCI_PORT_FULL; + /* Disable IDLE */ + usb->saved_msk &= ~USB_E_IDLE_MASK; + out_be16(&usb->fhci->regs->usb_mask, usb->saved_msk); + } + + gtm_set_exact_timer16(fhci->timer, usb->max_frame_usage, false); + + fhci_host_transmit_actual_frame(usb); + usb->actual_frame->frame_status = FRAME_IS_TRANSMITTED; + + fhci_schedule_transactions(usb); +} + +/* Handles device disconnected interrupt on port */ +void fhci_device_disconnected_interrupt(struct fhci_hcd *fhci) +{ + struct fhci_usb *usb = fhci->usb_lld; + + fhci_dbg(fhci, "-> %s\n", __func__); + + fhci_usb_disable_interrupt(usb); + clrbits8(&usb->fhci->regs->usb_mod, USB_MODE_LSS); + usb->port_status = FHCI_PORT_DISABLED; + + fhci_stop_sof_timer(fhci); + + /* Enable IDLE since we want to know if something comes along */ + usb->saved_msk |= USB_E_IDLE_MASK; + out_be16(&usb->fhci->regs->usb_mask, usb->saved_msk); + + usb->vroot_hub->port.wPortStatus &= ~USB_PORT_STAT_CONNECTION; + usb->vroot_hub->port.wPortChange |= USB_PORT_STAT_C_CONNECTION; + usb->max_bytes_per_frame = 0; + fhci_usb_enable_interrupt(usb); + + fhci_dbg(fhci, "<- %s\n", __func__); +} + +/* detect a new device connected on the USB port */ +void fhci_device_connected_interrupt(struct fhci_hcd *fhci) +{ + + struct fhci_usb *usb = fhci->usb_lld; + int state; + int ret; + + fhci_dbg(fhci, "-> %s\n", __func__); + + fhci_usb_disable_interrupt(usb); + state = fhci_ioports_check_bus_state(fhci); + + /* low-speed device was connected to the USB port */ + if (state == 1) { + ret = qe_usb_clock_set(fhci->lowspeed_clk, USB_CLOCK >> 3); + if (ret) { + fhci_warn(fhci, "Low-Speed device is not supported, " + "try use BRGx\n"); + goto out; + } + + usb->port_status = FHCI_PORT_LOW; + setbits8(&usb->fhci->regs->usb_mod, USB_MODE_LSS); + usb->vroot_hub->port.wPortStatus |= + (USB_PORT_STAT_LOW_SPEED | + USB_PORT_STAT_CONNECTION); + usb->vroot_hub->port.wPortChange |= + USB_PORT_STAT_C_CONNECTION; + usb->max_bytes_per_frame = + (MAX_BYTES_PER_FRAME >> 3) - 7; + fhci_port_enable(usb); + } else if (state == 2) { + ret = qe_usb_clock_set(fhci->fullspeed_clk, USB_CLOCK); + if (ret) { + fhci_warn(fhci, "Full-Speed device is not supported, " + "try use CLKx\n"); + goto out; + } + + usb->port_status = FHCI_PORT_FULL; + clrbits8(&usb->fhci->regs->usb_mod, USB_MODE_LSS); + usb->vroot_hub->port.wPortStatus &= + ~USB_PORT_STAT_LOW_SPEED; + usb->vroot_hub->port.wPortStatus |= + USB_PORT_STAT_CONNECTION; + usb->vroot_hub->port.wPortChange |= + USB_PORT_STAT_C_CONNECTION; + usb->max_bytes_per_frame = (MAX_BYTES_PER_FRAME - 15); + fhci_port_enable(usb); + } +out: + fhci_usb_enable_interrupt(usb); + fhci_dbg(fhci, "<- %s\n", __func__); +} + +irqreturn_t fhci_frame_limit_timer_irq(int irq, void *_hcd) +{ + struct usb_hcd *hcd = _hcd; + struct fhci_hcd *fhci = hcd_to_fhci(hcd); + struct fhci_usb *usb = fhci->usb_lld; + + spin_lock(&fhci->lock); + + gtm_set_exact_timer16(fhci->timer, 1000, false); + + if (usb->actual_frame->frame_status == FRAME_IS_TRANSMITTED) { + usb->actual_frame->frame_status = FRAME_TIMER_END_TRANSMISSION; + fhci_push_dummy_bd(usb->ep0); + } + + fhci_schedule_transactions(usb); + + spin_unlock(&fhci->lock); + + return IRQ_HANDLED; +} + +/* Cancel transmission on the USB endpoint */ +static void abort_transmission(struct fhci_usb *usb) +{ + fhci_dbg(usb->fhci, "-> %s\n", __func__); + /* issue stop Tx command */ + qe_issue_cmd(QE_USB_STOP_TX, QE_CR_SUBBLOCK_USB, EP_ZERO, 0); + /* flush Tx FIFOs */ + out_8(&usb->fhci->regs->usb_comm, USB_CMD_FLUSH_FIFO | EP_ZERO); + udelay(1000); + /* reset Tx BDs */ + fhci_flush_bds(usb); + /* issue restart Tx command */ + qe_issue_cmd(QE_USB_RESTART_TX, QE_CR_SUBBLOCK_USB, EP_ZERO, 0); + fhci_dbg(usb->fhci, "<- %s\n", __func__); +} + +irqreturn_t fhci_irq(struct usb_hcd *hcd) +{ + struct fhci_hcd *fhci = hcd_to_fhci(hcd); + struct fhci_usb *usb; + u16 usb_er = 0; + unsigned long flags; + + spin_lock_irqsave(&fhci->lock, flags); + + usb = fhci->usb_lld; + + usb_er |= in_be16(&usb->fhci->regs->usb_event) & + in_be16(&usb->fhci->regs->usb_mask); + + /* clear event bits for next time */ + out_be16(&usb->fhci->regs->usb_event, usb_er); + + fhci_dbg_isr(fhci, usb_er); + + if (usb_er & USB_E_RESET_MASK) { + if ((usb->port_status == FHCI_PORT_FULL) || + (usb->port_status == FHCI_PORT_LOW)) { + fhci_device_disconnected_interrupt(fhci); + usb_er &= ~USB_E_IDLE_MASK; + } else if (usb->port_status == FHCI_PORT_WAITING) { + usb->port_status = FHCI_PORT_DISCONNECTING; + + /* Turn on IDLE since we want to disconnect */ + usb->saved_msk |= USB_E_IDLE_MASK; + out_be16(&usb->fhci->regs->usb_event, + usb->saved_msk); + } else if (usb->port_status == FHCI_PORT_DISABLED) { + if (fhci_ioports_check_bus_state(fhci) == 1 && + usb->port_status != FHCI_PORT_LOW && + usb->port_status != FHCI_PORT_FULL) + fhci_device_connected_interrupt(fhci); + } + usb_er &= ~USB_E_RESET_MASK; + } + + if (usb_er & USB_E_MSF_MASK) { + abort_transmission(fhci->usb_lld); + usb_er &= ~USB_E_MSF_MASK; + } + + if (usb_er & (USB_E_SOF_MASK | USB_E_SFT_MASK)) { + sof_interrupt(fhci); + usb_er &= ~(USB_E_SOF_MASK | USB_E_SFT_MASK); + } + + if (usb_er & USB_E_TXB_MASK) { + fhci_tx_conf_interrupt(fhci->usb_lld); + usb_er &= ~USB_E_TXB_MASK; + } + + if (usb_er & USB_E_TXE1_MASK) { + fhci_tx_conf_interrupt(fhci->usb_lld); + usb_er &= ~USB_E_TXE1_MASK; + } + + if (usb_er & USB_E_IDLE_MASK) { + if (usb->port_status == FHCI_PORT_DISABLED && + usb->port_status != FHCI_PORT_LOW && + usb->port_status != FHCI_PORT_FULL) { + usb_er &= ~USB_E_RESET_MASK; + fhci_device_connected_interrupt(fhci); + } else if (usb->port_status == + FHCI_PORT_DISCONNECTING) { + /* XXX usb->port_status = FHCI_PORT_WAITING; */ + /* Disable IDLE */ + usb->saved_msk &= ~USB_E_IDLE_MASK; + out_be16(&usb->fhci->regs->usb_mask, + usb->saved_msk); + } else { + fhci_dbg_isr(fhci, -1); + } + + usb_er &= ~USB_E_IDLE_MASK; + } + + spin_unlock_irqrestore(&fhci->lock, flags); + + return IRQ_HANDLED; +} + + +/* + * Process normal completions(error or sucess) and clean the schedule. + * + * This is the main path for handing urbs back to drivers. The only other patth + * is process_del_list(),which unlinks URBs by scanning EDs,instead of scanning + * the (re-reversed) done list as this does. + */ +static void process_done_list(unsigned long data) +{ + struct urb *urb; + struct ed *ed; + struct td *td; + struct urb_priv *urb_priv; + struct fhci_hcd *fhci = (struct fhci_hcd *)data; + + disable_irq(fhci->timer->irq); + disable_irq(fhci_to_hcd(fhci)->irq); + spin_lock(&fhci->lock); + + td = fhci_remove_td_from_done_list(fhci->hc_list); + while (td != NULL) { + urb = td->urb; + urb_priv = urb->hcpriv; + ed = td->ed; + + /* update URB's length and status from TD */ + fhci_done_td(urb, td); + urb_priv->tds_cnt++; + + /* + * if all this urb's TDs are done, call complete() + * Interrupt transfers are the onley special case: + * they are reissued,until "deleted" by usb_unlink_urb + * (real work done in a SOF intr, by process_del_list) + */ + if (urb_priv->tds_cnt == urb_priv->num_of_tds) { + fhci_urb_complete_free(fhci, urb); + } else if (urb_priv->state == URB_DEL && + ed->state == FHCI_ED_SKIP) { + fhci_del_ed_list(fhci, ed); + ed->state = FHCI_ED_OPER; + } else if (ed->state == FHCI_ED_HALTED) { + urb_priv->state = URB_DEL; + ed->state = FHCI_ED_URB_DEL; + fhci_del_ed_list(fhci, ed); + ed->state = FHCI_ED_OPER; + } + + td = fhci_remove_td_from_done_list(fhci->hc_list); + } + + spin_unlock(&fhci->lock); + enable_irq(fhci->timer->irq); + enable_irq(fhci_to_hcd(fhci)->irq); +} + +DECLARE_TASKLET(fhci_tasklet, process_done_list, 0); + +/* transfer complted callback */ +u32 fhci_transfer_confirm_callback(struct fhci_hcd *fhci) +{ + if (!fhci->process_done_task->state) + tasklet_schedule(fhci->process_done_task); + return 0; +} + +/* + * adds urb to the endpoint descriptor list + * arguments: + * fhci data structure for the Low level host controller + * ep USB Host endpoint data structure + * urb USB request block data structure + */ +void fhci_queue_urb(struct fhci_hcd *fhci, struct urb *urb) +{ + struct ed *ed = urb->ep->hcpriv; + struct urb_priv *urb_priv = urb->hcpriv; + u32 data_len = urb->transfer_buffer_length; + int urb_state = 0; + int toggle = 0; + struct td *td; + u8 *data; + u16 cnt = 0; + + if (ed == NULL) { + ed = fhci_get_empty_ed(fhci); + ed->dev_addr = usb_pipedevice(urb->pipe); + ed->ep_addr = usb_pipeendpoint(urb->pipe); + switch (usb_pipetype(urb->pipe)) { + case PIPE_CONTROL: + ed->mode = FHCI_TF_CTRL; + break; + case PIPE_BULK: + ed->mode = FHCI_TF_BULK; + break; + case PIPE_INTERRUPT: + ed->mode = FHCI_TF_INTR; + break; + case PIPE_ISOCHRONOUS: + ed->mode = FHCI_TF_ISO; + break; + default: + break; + } + ed->speed = (urb->dev->speed == USB_SPEED_LOW) ? + FHCI_LOW_SPEED : FHCI_FULL_SPEED; + ed->max_pkt_size = usb_maxpacket(urb->dev, + urb->pipe, usb_pipeout(urb->pipe)); + urb->ep->hcpriv = ed; + fhci_dbg(fhci, "new ep speed=%d max_pkt_size=%d\n", + ed->speed, ed->max_pkt_size); + } + + /* for ISO transfer calculate start frame index */ + if (ed->mode == FHCI_TF_ISO && urb->transfer_flags & URB_ISO_ASAP) + urb->start_frame = ed->td_head ? ed->last_iso + 1 : + get_frame_num(fhci); + + /* + * OHCI handles the DATA toggle itself,we just use the USB + * toggle bits + */ + if (usb_gettoggle(urb->dev, usb_pipeendpoint(urb->pipe), + usb_pipeout(urb->pipe))) + toggle = USB_TD_TOGGLE_CARRY; + else { + toggle = USB_TD_TOGGLE_DATA0; + usb_settoggle(urb->dev, usb_pipeendpoint(urb->pipe), + usb_pipeout(urb->pipe), 1); + } + + urb_priv->tds_cnt = 0; + urb_priv->ed = ed; + if (data_len > 0) + data = urb->transfer_buffer; + else + data = NULL; + + switch (ed->mode) { + case FHCI_TF_BULK: + if (urb->transfer_flags & URB_ZERO_PACKET && + urb->transfer_buffer_length > 0 && + ((urb->transfer_buffer_length % + usb_maxpacket(urb->dev, urb->pipe, + usb_pipeout(urb->pipe))) == 0)) + urb_state = US_BULK0; + while (data_len > 4096) { + td = fhci_td_fill(fhci, urb, urb_priv, ed, cnt, + usb_pipeout(urb->pipe) ? FHCI_TA_OUT : + FHCI_TA_IN, + cnt ? USB_TD_TOGGLE_CARRY : + toggle, + data, 4096, 0, 0, true); + data += 4096; + data_len -= 4096; + cnt++; + } + + td = fhci_td_fill(fhci, urb, urb_priv, ed, cnt, + usb_pipeout(urb->pipe) ? FHCI_TA_OUT : FHCI_TA_IN, + cnt ? USB_TD_TOGGLE_CARRY : toggle, + data, data_len, 0, 0, true); + cnt++; + + if (urb->transfer_flags & URB_ZERO_PACKET && + cnt < urb_priv->num_of_tds) { + td = fhci_td_fill(fhci, urb, urb_priv, ed, cnt, + usb_pipeout(urb->pipe) ? FHCI_TA_OUT : + FHCI_TA_IN, + USB_TD_TOGGLE_CARRY, NULL, 0, 0, 0, true); + cnt++; + } + break; + case FHCI_TF_INTR: + urb->start_frame = get_frame_num(fhci) + 1; + td = fhci_td_fill(fhci, urb, urb_priv, ed, cnt++, + usb_pipeout(urb->pipe) ? FHCI_TA_OUT : FHCI_TA_IN, + USB_TD_TOGGLE_DATA0, data, data_len, + urb->interval, urb->start_frame, true); + break; + case FHCI_TF_CTRL: + ed->dev_addr = usb_pipedevice(urb->pipe); + ed->max_pkt_size = usb_maxpacket(urb->dev, urb->pipe, + usb_pipeout(urb->pipe)); + td = fhci_td_fill(fhci, urb, urb_priv, ed, cnt++, FHCI_TA_SETUP, + USB_TD_TOGGLE_DATA0, urb->setup_packet, 8, 0, 0, true); + + if (data_len > 0) { + td = fhci_td_fill(fhci, urb, urb_priv, ed, cnt++, + usb_pipeout(urb->pipe) ? FHCI_TA_OUT : + FHCI_TA_IN, + USB_TD_TOGGLE_DATA1, data, data_len, 0, 0, + true); + } + td = fhci_td_fill(fhci, urb, urb_priv, ed, cnt++, + usb_pipeout(urb->pipe) ? FHCI_TA_IN : FHCI_TA_OUT, + USB_TD_TOGGLE_DATA1, data, 0, 0, 0, true); + urb_state = US_CTRL_SETUP; + break; + case FHCI_TF_ISO: + for (cnt = 0; cnt < urb->number_of_packets; cnt++) { + u16 frame = urb->start_frame; + + /* + * FIXME scheduling should handle frame counter + * roll-around ... exotic case (and OHCI has + * a 2^16 iso range, vs other HCs max of 2^10) + */ + frame += cnt * urb->interval; + frame &= 0x07ff; + td = fhci_td_fill(fhci, urb, urb_priv, ed, cnt, + usb_pipeout(urb->pipe) ? FHCI_TA_OUT : + FHCI_TA_IN, + USB_TD_TOGGLE_DATA0, + data + urb->iso_frame_desc[cnt].offset, + urb->iso_frame_desc[cnt].length, + urb->interval, frame, true); + } + break; + default: + break; + } + + /* + * set the state of URB + * control pipe:3 states -- setup,data,status + * interrupt and bulk pipe:1 state -- data + */ + urb->pipe &= ~0x1f; + urb->pipe |= urb_state & 0x1f; + + urb_priv->state = URB_INPROGRESS; + + if (!ed->td_head) { + ed->state = FHCI_ED_OPER; + switch (ed->mode) { + case FHCI_TF_CTRL: + list_add(&ed->node, &fhci->hc_list->ctrl_list); + break; + case FHCI_TF_BULK: + list_add(&ed->node, &fhci->hc_list->bulk_list); + break; + case FHCI_TF_INTR: + list_add(&ed->node, &fhci->hc_list->intr_list); + break; + case FHCI_TF_ISO: + list_add(&ed->node, &fhci->hc_list->iso_list); + break; + default: + break; + } + } + + fhci_add_tds_to_ed(ed, urb_priv->tds, urb_priv->num_of_tds); + fhci->active_urbs++; +} diff --git a/drivers/usb/host/fhci-tds.c b/drivers/usb/host/fhci-tds.c new file mode 100644 index 000000000000..b40332290319 --- /dev/null +++ b/drivers/usb/host/fhci-tds.c @@ -0,0 +1,626 @@ +/* + * Freescale QUICC Engine USB Host Controller Driver + * + * Copyright (c) Freescale Semicondutor, Inc. 2006. + * Shlomi Gridish <gridish@freescale.com> + * Jerry Huang <Chang-Ming.Huang@freescale.com> + * Copyright (c) Logic Product Development, Inc. 2007 + * Peter Barada <peterb@logicpd.com> + * Copyright (c) MontaVista Software, Inc. 2008. + * Anton Vorontsov <avorontsov@ru.mvista.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; either version 2 of the License, or (at your + * option) any later version. + */ + +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/list.h> +#include <linux/io.h> +#include <linux/usb.h> +#include "../core/hcd.h" +#include "fhci.h" + +#define DUMMY_BD_BUFFER 0xdeadbeef +#define DUMMY2_BD_BUFFER 0xbaadf00d + +/* Transaction Descriptors bits */ +#define TD_R 0x8000 /* ready bit */ +#define TD_W 0x2000 /* wrap bit */ +#define TD_I 0x1000 /* interrupt on completion */ +#define TD_L 0x0800 /* last */ +#define TD_TC 0x0400 /* transmit CRC */ +#define TD_CNF 0x0200 /* CNF - Must be always 1 */ +#define TD_LSP 0x0100 /* Low-speed transaction */ +#define TD_PID 0x00c0 /* packet id */ +#define TD_RXER 0x0020 /* Rx error or not */ + +#define TD_NAK 0x0010 /* No ack. */ +#define TD_STAL 0x0008 /* Stall recieved */ +#define TD_TO 0x0004 /* time out */ +#define TD_UN 0x0002 /* underrun */ +#define TD_NO 0x0010 /* Rx Non Octet Aligned Packet */ +#define TD_AB 0x0008 /* Frame Aborted */ +#define TD_CR 0x0004 /* CRC Error */ +#define TD_OV 0x0002 /* Overrun */ +#define TD_BOV 0x0001 /* Buffer Overrun */ + +#define TD_ERRORS (TD_NAK | TD_STAL | TD_TO | TD_UN | \ + TD_NO | TD_AB | TD_CR | TD_OV | TD_BOV) + +#define TD_PID_DATA0 0x0080 /* Data 0 toggle */ +#define TD_PID_DATA1 0x00c0 /* Data 1 toggle */ +#define TD_PID_TOGGLE 0x00c0 /* Data 0/1 toggle mask */ + +#define TD_TOK_SETUP 0x0000 +#define TD_TOK_OUT 0x4000 +#define TD_TOK_IN 0x8000 +#define TD_ISO 0x1000 +#define TD_ENDP 0x0780 +#define TD_ADDR 0x007f + +#define TD_ENDP_SHIFT 7 + +struct usb_td { + __be16 status; + __be16 length; + __be32 buf_ptr; + __be16 extra; + __be16 reserved; +}; + +static struct usb_td __iomem *next_bd(struct usb_td __iomem *base, + struct usb_td __iomem *td, + u16 status) +{ + if (status & TD_W) + return base; + else + return ++td; +} + +void fhci_push_dummy_bd(struct endpoint *ep) +{ + if (ep->already_pushed_dummy_bd == false) { + u16 td_status = in_be16(&ep->empty_td->status); + + out_be32(&ep->empty_td->buf_ptr, DUMMY_BD_BUFFER); + /* get the next TD in the ring */ + ep->empty_td = next_bd(ep->td_base, ep->empty_td, td_status); + ep->already_pushed_dummy_bd = true; + } +} + +/* destroy an USB endpoint */ +void fhci_ep0_free(struct fhci_usb *usb) +{ + struct endpoint *ep; + int size; + + ep = usb->ep0; + if (ep) { + if (ep->td_base) + cpm_muram_free(cpm_muram_offset(ep->td_base)); + + if (ep->conf_frame_Q) { + size = cq_howmany(ep->conf_frame_Q); + for (; size; size--) { + struct packet *pkt = cq_get(ep->conf_frame_Q); + + kfree(pkt); + } + cq_delete(ep->conf_frame_Q); + } + + if (ep->empty_frame_Q) { + size = cq_howmany(ep->empty_frame_Q); + for (; size; size--) { + struct packet *pkt = cq_get(ep->empty_frame_Q); + + kfree(pkt); + } + cq_delete(ep->empty_frame_Q); + } + + if (ep->dummy_packets_Q) { + size = cq_howmany(ep->dummy_packets_Q); + for (; size; size--) { + u8 *buff = cq_get(ep->dummy_packets_Q); + + kfree(buff); + } + cq_delete(ep->dummy_packets_Q); + } + + kfree(ep); + usb->ep0 = NULL; + } +} + +/* + * create the endpoint structure + * + * arguments: + * usb A pointer to the data structure of the USB + * data_mem The data memory partition(BUS) + * ring_len TD ring length + */ +u32 fhci_create_ep(struct fhci_usb *usb, enum fhci_mem_alloc data_mem, + u32 ring_len) +{ + struct endpoint *ep; + struct usb_td __iomem *td; + unsigned long ep_offset; + char *err_for = "enpoint PRAM"; + int ep_mem_size; + u32 i; + + /* we need at least 3 TDs in the ring */ + if (!(ring_len > 2)) { + fhci_err(usb->fhci, "illegal TD ring length parameters\n"); + return -EINVAL; + } + + ep = kzalloc(sizeof(*ep), GFP_KERNEL); + if (!ep) + return -ENOMEM; + + ep_mem_size = ring_len * sizeof(*td) + sizeof(struct fhci_ep_pram); + ep_offset = cpm_muram_alloc(ep_mem_size, 32); + if (IS_ERR_VALUE(ep_offset)) + goto err; + ep->td_base = cpm_muram_addr(ep_offset); + + /* zero all queue pointers */ + ep->conf_frame_Q = cq_new(ring_len + 2); + ep->empty_frame_Q = cq_new(ring_len + 2); + ep->dummy_packets_Q = cq_new(ring_len + 2); + if (!ep->conf_frame_Q || !ep->empty_frame_Q || !ep->dummy_packets_Q) { + err_for = "frame_queues"; + goto err; + } + + for (i = 0; i < (ring_len + 1); i++) { + struct packet *pkt; + u8 *buff; + + pkt = kmalloc(sizeof(*pkt), GFP_KERNEL); + if (!pkt) { + err_for = "frame"; + goto err; + } + + buff = kmalloc(1028 * sizeof(*buff), GFP_KERNEL); + if (!buff) { + kfree(pkt); + err_for = "buffer"; + goto err; + } + cq_put(ep->empty_frame_Q, pkt); + cq_put(ep->dummy_packets_Q, buff); + } + + /* we put the endpoint parameter RAM right behind the TD ring */ + ep->ep_pram_ptr = (void __iomem *)ep->td_base + sizeof(*td) * ring_len; + + ep->conf_td = ep->td_base; + ep->empty_td = ep->td_base; + + ep->already_pushed_dummy_bd = false; + + /* initialize tds */ + td = ep->td_base; + for (i = 0; i < ring_len; i++) { + out_be32(&td->buf_ptr, 0); + out_be16(&td->status, 0); + out_be16(&td->length, 0); + out_be16(&td->extra, 0); + td++; + } + td--; + out_be16(&td->status, TD_W); /* for last TD set Wrap bit */ + out_be16(&td->length, 0); + + /* endpoint structure has been created */ + usb->ep0 = ep; + + return 0; +err: + fhci_ep0_free(usb); + kfree(ep); + fhci_err(usb->fhci, "no memory for the %s\n", err_for); + return -ENOMEM; +} + +/* + * initialize the endpoint register according to the given parameters + * + * artuments: + * usb A pointer to the data strucutre of the USB + * ep A pointer to the endpoint structre + * data_mem The data memory partition(BUS) + */ +void fhci_init_ep_registers(struct fhci_usb *usb, struct endpoint *ep, + enum fhci_mem_alloc data_mem) +{ + u8 rt; + + /* set the endpoint registers according to the endpoint */ + out_be16(&usb->fhci->regs->usb_ep[0], + USB_TRANS_CTR | USB_EP_MF | USB_EP_RTE); + out_be16(&usb->fhci->pram->ep_ptr[0], + cpm_muram_offset(ep->ep_pram_ptr)); + + rt = (BUS_MODE_BO_BE | BUS_MODE_GBL); +#ifdef MULTI_DATA_BUS + if (data_mem == MEM_SECONDARY) + rt |= BUS_MODE_DTB; +#endif + out_8(&ep->ep_pram_ptr->rx_func_code, rt); + out_8(&ep->ep_pram_ptr->tx_func_code, rt); + out_be16(&ep->ep_pram_ptr->rx_buff_len, 1028); + out_be16(&ep->ep_pram_ptr->rx_base, 0); + out_be16(&ep->ep_pram_ptr->tx_base, cpm_muram_offset(ep->td_base)); + out_be16(&ep->ep_pram_ptr->rx_bd_ptr, 0); + out_be16(&ep->ep_pram_ptr->tx_bd_ptr, cpm_muram_offset(ep->td_base)); + out_be32(&ep->ep_pram_ptr->tx_state, 0); +} + +/* + * Collect the submitted frames and inform the application about them + * It is also prepearing the TDs for new frames. If the Tx interrupts + * are diabled, the application should call that routine to get + * confirmation about the submitted frames. Otherwise, the routine is + * called frome the interrupt service routine during the Tx interrupt. + * In that case the application is informed by calling the application + * specific 'fhci_transaction_confirm' routine + */ +static void fhci_td_transaction_confirm(struct fhci_usb *usb) +{ + struct endpoint *ep = usb->ep0; + struct packet *pkt; + struct usb_td __iomem *td; + u16 extra_data; + u16 td_status; + u16 td_length; + u32 buf; + + /* + * collect transmitted BDs from the chip. The routine clears all BDs + * with R bit = 0 and the pointer to data buffer is not NULL, that is + * BDs which point to the transmitted data buffer + */ + while (1) { + td = ep->conf_td; + td_status = in_be16(&td->status); + td_length = in_be16(&td->length); + buf = in_be32(&td->buf_ptr); + extra_data = in_be16(&td->extra); + + /* check if the TD is empty */ + if (!(!(td_status & TD_R) && ((td_status & ~TD_W) || buf))) + break; + /* check if it is a dummy buffer */ + else if ((buf == DUMMY_BD_BUFFER) && !(td_status & ~TD_W)) + break; + + /* mark TD as empty */ + clrbits16(&td->status, ~TD_W); + out_be16(&td->length, 0); + out_be32(&td->buf_ptr, 0); + out_be16(&td->extra, 0); + /* advance the TD pointer */ + ep->conf_td = next_bd(ep->td_base, ep->conf_td, td_status); + + /* check if it is a dummy buffer(type2) */ + if ((buf == DUMMY2_BD_BUFFER) && !(td_status & ~TD_W)) + continue; + + pkt = cq_get(ep->conf_frame_Q); + if (!pkt) + fhci_err(usb->fhci, "no frame to confirm\n"); + + if (td_status & TD_ERRORS) { + if (td_status & TD_RXER) { + if (td_status & TD_CR) + pkt->status = USB_TD_RX_ER_CRC; + else if (td_status & TD_AB) + pkt->status = USB_TD_RX_ER_BITSTUFF; + else if (td_status & TD_OV) + pkt->status = USB_TD_RX_ER_OVERUN; + else if (td_status & TD_BOV) + pkt->status = USB_TD_RX_DATA_OVERUN; + else if (td_status & TD_NO) + pkt->status = USB_TD_RX_ER_NONOCT; + else + fhci_err(usb->fhci, "illegal error " + "occured\n"); + } else if (td_status & TD_NAK) + pkt->status = USB_TD_TX_ER_NAK; + else if (td_status & TD_TO) + pkt->status = USB_TD_TX_ER_TIMEOUT; + else if (td_status & TD_UN) + pkt->status = USB_TD_TX_ER_UNDERUN; + else if (td_status & TD_STAL) + pkt->status = USB_TD_TX_ER_STALL; + else + fhci_err(usb->fhci, "illegal error occured\n"); + } else if ((extra_data & TD_TOK_IN) && + pkt->len > td_length - CRC_SIZE) { + pkt->status = USB_TD_RX_DATA_UNDERUN; + } + + if (extra_data & TD_TOK_IN) + pkt->len = td_length - CRC_SIZE; + else if (pkt->info & PKT_ZLP) + pkt->len = 0; + else + pkt->len = td_length; + + fhci_transaction_confirm(usb, pkt); + } +} + +/* + * Submitting a data frame to a specified endpoint of a USB device + * The frame is put in the driver's transmit queue for this endpoint + * + * Arguments: + * usb A pointer to the USB structure + * pkt A pointer to the user frame structure + * trans_type Transaction tyep - IN,OUT or SETUP + * dest_addr Device address - 0~127 + * dest_ep Endpoint number of the device - 0~16 + * trans_mode Pipe type - ISO,Interrupt,bulk or control + * dest_speed USB speed - Low speed or FULL speed + * data_toggle Data sequence toggle - 0 or 1 + */ +u32 fhci_host_transaction(struct fhci_usb *usb, + struct packet *pkt, + enum fhci_ta_type trans_type, + u8 dest_addr, + u8 dest_ep, + enum fhci_tf_mode trans_mode, + enum fhci_speed dest_speed, u8 data_toggle) +{ + struct endpoint *ep = usb->ep0; + struct usb_td __iomem *td; + u16 extra_data; + u16 td_status; + + fhci_usb_disable_interrupt(usb); + /* start from the next BD that should be filled */ + td = ep->empty_td; + td_status = in_be16(&td->status); + + if (td_status & TD_R && in_be16(&td->length)) { + /* if the TD is not free */ + fhci_usb_enable_interrupt(usb); + return -1; + } + + /* get the next TD in the ring */ + ep->empty_td = next_bd(ep->td_base, ep->empty_td, td_status); + fhci_usb_enable_interrupt(usb); + pkt->priv_data = td; + out_be32(&td->buf_ptr, virt_to_phys(pkt->data)); + /* sets up transaction parameters - addr,endp,dir,and type */ + extra_data = (dest_ep << TD_ENDP_SHIFT) | dest_addr; + switch (trans_type) { + case FHCI_TA_IN: + extra_data |= TD_TOK_IN; + break; + case FHCI_TA_OUT: + extra_data |= TD_TOK_OUT; + break; + case FHCI_TA_SETUP: + extra_data |= TD_TOK_SETUP; + break; + } + if (trans_mode == FHCI_TF_ISO) + extra_data |= TD_ISO; + out_be16(&td->extra, extra_data); + + /* sets up the buffer descriptor */ + td_status = ((td_status & TD_W) | TD_R | TD_L | TD_I | TD_CNF); + if (!(pkt->info & PKT_NO_CRC)) + td_status |= TD_TC; + + switch (trans_type) { + case FHCI_TA_IN: + if (data_toggle) + pkt->info |= PKT_PID_DATA1; + else + pkt->info |= PKT_PID_DATA0; + break; + default: + if (data_toggle) { + td_status |= TD_PID_DATA1; + pkt->info |= PKT_PID_DATA1; + } else { + td_status |= TD_PID_DATA0; + pkt->info |= PKT_PID_DATA0; + } + break; + } + + if ((dest_speed == FHCI_LOW_SPEED) && + (usb->port_status == FHCI_PORT_FULL)) + td_status |= TD_LSP; + + out_be16(&td->status, td_status); + + /* set up buffer length */ + if (trans_type == FHCI_TA_IN) + out_be16(&td->length, pkt->len + CRC_SIZE); + else + out_be16(&td->length, pkt->len); + + /* put the frame to the confirmation queue */ + cq_put(ep->conf_frame_Q, pkt); + + if (cq_howmany(ep->conf_frame_Q) == 1) + out_8(&usb->fhci->regs->usb_comm, USB_CMD_STR_FIFO); + + return 0; +} + +/* Reset the Tx BD ring */ +void fhci_flush_bds(struct fhci_usb *usb) +{ + u16 extra_data; + u16 td_status; + u32 buf; + struct usb_td __iomem *td; + struct endpoint *ep = usb->ep0; + + td = ep->td_base; + while (1) { + td_status = in_be16(&td->status); + buf = in_be32(&td->buf_ptr); + extra_data = in_be16(&td->extra); + + /* if the TD is not empty - we'll confirm it as Timeout */ + if (td_status & TD_R) + out_be16(&td->status, (td_status & ~TD_R) | TD_TO); + /* if this TD is dummy - let's skip this TD */ + else if (in_be32(&td->buf_ptr) == DUMMY_BD_BUFFER) + out_be32(&td->buf_ptr, DUMMY2_BD_BUFFER); + /* if this is the last TD - break */ + if (td_status & TD_W) + break; + + td++; + } + + fhci_td_transaction_confirm(usb); + + td = ep->td_base; + do { + out_be16(&td->status, 0); + out_be16(&td->length, 0); + out_be32(&td->buf_ptr, 0); + out_be16(&td->extra, 0); + td++; + } while (!(in_be16(&td->status) & TD_W)); + out_be16(&td->status, TD_W); /* for last TD set Wrap bit */ + out_be16(&td->length, 0); + out_be32(&td->buf_ptr, 0); + out_be16(&td->extra, 0); + + out_be16(&ep->ep_pram_ptr->tx_bd_ptr, + in_be16(&ep->ep_pram_ptr->tx_base)); + out_be32(&ep->ep_pram_ptr->tx_state, 0); + out_be16(&ep->ep_pram_ptr->tx_cnt, 0); + ep->empty_td = ep->td_base; + ep->conf_td = ep->td_base; +} + +/* + * Flush all transmitted packets from TDs in the actual frame. + * This routine is called when something wrong with the controller and + * we want to get rid of the actual frame and start again next frame + */ +void fhci_flush_actual_frame(struct fhci_usb *usb) +{ + u8 mode; + u16 tb_ptr; + u16 extra_data; + u16 td_status; + u32 buf_ptr; + struct usb_td __iomem *td; + struct endpoint *ep = usb->ep0; + + /* disable the USB controller */ + mode = in_8(&usb->fhci->regs->usb_mod); + out_8(&usb->fhci->regs->usb_mod, mode & ~USB_MODE_EN); + + tb_ptr = in_be16(&ep->ep_pram_ptr->tx_bd_ptr); + td = cpm_muram_addr(tb_ptr); + td_status = in_be16(&td->status); + buf_ptr = in_be32(&td->buf_ptr); + extra_data = in_be16(&td->extra); + do { + if (td_status & TD_R) { + out_be16(&td->status, (td_status & ~TD_R) | TD_TO); + } else { + out_be32(&td->buf_ptr, 0); + ep->already_pushed_dummy_bd = false; + break; + } + + /* advance the TD pointer */ + td = next_bd(ep->td_base, td, td_status); + td_status = in_be16(&td->status); + buf_ptr = in_be32(&td->buf_ptr); + extra_data = in_be16(&td->extra); + } while ((td_status & TD_R) || buf_ptr); + + fhci_td_transaction_confirm(usb); + + out_be16(&ep->ep_pram_ptr->tx_bd_ptr, + in_be16(&ep->ep_pram_ptr->tx_base)); + out_be32(&ep->ep_pram_ptr->tx_state, 0); + out_be16(&ep->ep_pram_ptr->tx_cnt, 0); + ep->empty_td = ep->td_base; + ep->conf_td = ep->td_base; + + usb->actual_frame->frame_status = FRAME_TIMER_END_TRANSMISSION; + + /* reset the event register */ + out_be16(&usb->fhci->regs->usb_event, 0xffff); + /* enable the USB controller */ + out_8(&usb->fhci->regs->usb_mod, mode | USB_MODE_EN); +} + +/* handles Tx confirm and Tx error interrupt */ +void fhci_tx_conf_interrupt(struct fhci_usb *usb) +{ + fhci_td_transaction_confirm(usb); + + /* + * Schedule another transaction to this frame only if we have + * already confirmed all transaction in the frame. + */ + if (((fhci_get_sof_timer_count(usb) < usb->max_frame_usage) || + (usb->actual_frame->frame_status & FRAME_END_TRANSMISSION)) && + (list_empty(&usb->actual_frame->tds_list))) + fhci_schedule_transactions(usb); +} + +void fhci_host_transmit_actual_frame(struct fhci_usb *usb) +{ + u16 tb_ptr; + u16 td_status; + struct usb_td __iomem *td; + struct endpoint *ep = usb->ep0; + + tb_ptr = in_be16(&ep->ep_pram_ptr->tx_bd_ptr); + td = cpm_muram_addr(tb_ptr); + + if (in_be32(&td->buf_ptr) == DUMMY_BD_BUFFER) { + struct usb_td __iomem *old_td = td; + + ep->already_pushed_dummy_bd = false; + td_status = in_be16(&td->status); + /* gets the next TD in the ring */ + td = next_bd(ep->td_base, td, td_status); + tb_ptr = cpm_muram_offset(td); + out_be16(&ep->ep_pram_ptr->tx_bd_ptr, tb_ptr); + + /* start transmit only if we have something in the TDs */ + if (in_be16(&td->status) & TD_R) + out_8(&usb->fhci->regs->usb_comm, USB_CMD_STR_FIFO); + + if (in_be32(&ep->conf_td->buf_ptr) == DUMMY_BD_BUFFER) { + out_be32(&old_td->buf_ptr, 0); + ep->conf_td = next_bd(ep->td_base, ep->conf_td, + td_status); + } else { + out_be32(&old_td->buf_ptr, DUMMY2_BD_BUFFER); + } + } +} diff --git a/drivers/usb/host/fhci.h b/drivers/usb/host/fhci.h new file mode 100644 index 000000000000..7116284ed21a --- /dev/null +++ b/drivers/usb/host/fhci.h @@ -0,0 +1,607 @@ +/* + * Freescale QUICC Engine USB Host Controller Driver + * + * Copyright (c) Freescale Semicondutor, Inc. 2006. + * Shlomi Gridish <gridish@freescale.com> + * Jerry Huang <Chang-Ming.Huang@freescale.com> + * Copyright (c) Logic Product Development, Inc. 2007 + * Peter Barada <peterb@logicpd.com> + * Copyright (c) MontaVista Software, Inc. 2008. + * Anton Vorontsov <avorontsov@ru.mvista.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; either version 2 of the License, or (at your + * option) any later version. + */ + +#ifndef __FHCI_H +#define __FHCI_H + +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/spinlock.h> +#include <linux/interrupt.h> +#include <linux/kfifo.h> +#include <linux/io.h> +#include <linux/usb.h> +#include <asm/qe.h> +#include "../core/hcd.h" + +#define USB_CLOCK 48000000 + +#define FHCI_PRAM_SIZE 0x100 + +#define MAX_EDS 32 +#define MAX_TDS 32 + + +/* CRC16 field size */ +#define CRC_SIZE 2 + +/* USB protocol overhead for each frame transmitted from the host */ +#define PROTOCOL_OVERHEAD 7 + +/* Packet structure, info field */ +#define PKT_PID_DATA0 0x80000000 /* PID - Data toggle zero */ +#define PKT_PID_DATA1 0x40000000 /* PID - Data toggle one */ +#define PKT_PID_SETUP 0x20000000 /* PID - Setup bit */ +#define PKT_SETUP_STATUS 0x10000000 /* Setup status bit */ +#define PKT_SETADDR_STATUS 0x08000000 /* Set address status bit */ +#define PKT_SET_HOST_LAST 0x04000000 /* Last data packet */ +#define PKT_HOST_DATA 0x02000000 /* Data packet */ +#define PKT_FIRST_IN_FRAME 0x01000000 /* First packet in the frame */ +#define PKT_TOKEN_FRAME 0x00800000 /* Token packet */ +#define PKT_ZLP 0x00400000 /* Zero length packet */ +#define PKT_IN_TOKEN_FRAME 0x00200000 /* IN token packet */ +#define PKT_OUT_TOKEN_FRAME 0x00100000 /* OUT token packet */ +#define PKT_SETUP_TOKEN_FRAME 0x00080000 /* SETUP token packet */ +#define PKT_STALL_FRAME 0x00040000 /* STALL packet */ +#define PKT_NACK_FRAME 0x00020000 /* NACK packet */ +#define PKT_NO_PID 0x00010000 /* No PID */ +#define PKT_NO_CRC 0x00008000 /* don't append CRC */ +#define PKT_HOST_COMMAND 0x00004000 /* Host command packet */ +#define PKT_DUMMY_PACKET 0x00002000 /* Dummy packet, used for mmm */ +#define PKT_LOW_SPEED_PACKET 0x00001000 /* Low-Speed packet */ + +#define TRANS_OK (0) +#define TRANS_INPROGRESS (-1) +#define TRANS_DISCARD (-2) +#define TRANS_FAIL (-3) + +#define PS_INT 0 +#define PS_DISCONNECTED 1 +#define PS_CONNECTED 2 +#define PS_READY 3 +#define PS_MISSING 4 + +/* Transfer Descriptor status field */ +#define USB_TD_OK 0x00000000 /* TD transmited or received ok */ +#define USB_TD_INPROGRESS 0x80000000 /* TD is being transmitted */ +#define USB_TD_RX_ER_NONOCT 0x40000000 /* Tx Non Octet Aligned Packet */ +#define USB_TD_RX_ER_BITSTUFF 0x20000000 /* Frame Aborted-Received pkt */ +#define USB_TD_RX_ER_CRC 0x10000000 /* CRC error */ +#define USB_TD_RX_ER_OVERUN 0x08000000 /* Over - run occured */ +#define USB_TD_RX_ER_PID 0x04000000 /* wrong PID received */ +#define USB_TD_RX_DATA_UNDERUN 0x02000000 /* shorter than expected */ +#define USB_TD_RX_DATA_OVERUN 0x01000000 /* longer than expected */ +#define USB_TD_TX_ER_NAK 0x00800000 /* NAK handshake */ +#define USB_TD_TX_ER_STALL 0x00400000 /* STALL handshake */ +#define USB_TD_TX_ER_TIMEOUT 0x00200000 /* transmit time out */ +#define USB_TD_TX_ER_UNDERUN 0x00100000 /* transmit underrun */ + +#define USB_TD_ERROR (USB_TD_RX_ER_NONOCT | USB_TD_RX_ER_BITSTUFF | \ + USB_TD_RX_ER_CRC | USB_TD_RX_ER_OVERUN | USB_TD_RX_ER_PID | \ + USB_TD_RX_DATA_UNDERUN | USB_TD_RX_DATA_OVERUN | \ + USB_TD_TX_ER_NAK | USB_TD_TX_ER_STALL | \ + USB_TD_TX_ER_TIMEOUT | USB_TD_TX_ER_UNDERUN) + +/* Transfer Descriptor toggle field */ +#define USB_TD_TOGGLE_DATA0 0 +#define USB_TD_TOGGLE_DATA1 1 +#define USB_TD_TOGGLE_CARRY 2 + +/* #define MULTI_DATA_BUS */ + +/* Bus mode register RBMR/TBMR */ +#define BUS_MODE_GBL 0x20 /* Global snooping */ +#define BUS_MODE_BO 0x18 /* Byte ordering */ +#define BUS_MODE_BO_BE 0x10 /* Byte ordering - Big-endian */ +#define BUS_MODE_DTB 0x02 /* Data bus */ + +/* FHCI QE USB Register Description */ + +/* USB Mode Register bit define */ +#define USB_MODE_EN 0x01 +#define USB_MODE_HOST 0x02 +#define USB_MODE_TEST 0x04 +#define USB_MODE_SFTE 0x08 +#define USB_MODE_RESUME 0x40 +#define USB_MODE_LSS 0x80 + +/* USB Slave Address Register Mask */ +#define USB_SLVADDR_MASK 0x7F + +/* USB Endpoint register define */ +#define USB_EPNUM_MASK 0xF000 +#define USB_EPNUM_SHIFT 12 + +#define USB_TRANS_MODE_SHIFT 8 +#define USB_TRANS_CTR 0x0000 +#define USB_TRANS_INT 0x0100 +#define USB_TRANS_BULK 0x0200 +#define USB_TRANS_ISO 0x0300 + +#define USB_EP_MF 0x0020 +#define USB_EP_RTE 0x0010 + +#define USB_THS_SHIFT 2 +#define USB_THS_MASK 0x000c +#define USB_THS_NORMAL 0x0 +#define USB_THS_IGNORE_IN 0x0004 +#define USB_THS_NACK 0x0008 +#define USB_THS_STALL 0x000c + +#define USB_RHS_SHIFT 0 +#define USB_RHS_MASK 0x0003 +#define USB_RHS_NORMAL 0x0 +#define USB_RHS_IGNORE_OUT 0x0001 +#define USB_RHS_NACK 0x0002 +#define USB_RHS_STALL 0x0003 + +#define USB_RTHS_MASK 0x000f + +/* USB Command Register define */ +#define USB_CMD_STR_FIFO 0x80 +#define USB_CMD_FLUSH_FIFO 0x40 +#define USB_CMD_ISFT 0x20 +#define USB_CMD_DSFT 0x10 +#define USB_CMD_EP_MASK 0x03 + +/* USB Event and Mask Register define */ +#define USB_E_MSF_MASK 0x0800 +#define USB_E_SFT_MASK 0x0400 +#define USB_E_RESET_MASK 0x0200 +#define USB_E_IDLE_MASK 0x0100 +#define USB_E_TXE4_MASK 0x0080 +#define USB_E_TXE3_MASK 0x0040 +#define USB_E_TXE2_MASK 0x0020 +#define USB_E_TXE1_MASK 0x0010 +#define USB_E_SOF_MASK 0x0008 +#define USB_E_BSY_MASK 0x0004 +#define USB_E_TXB_MASK 0x0002 +#define USB_E_RXB_MASK 0x0001 + +/* Freescale USB Host controller registers */ +struct fhci_regs { + u8 usb_mod; /* mode register */ + u8 usb_addr; /* address register */ + u8 usb_comm; /* command register */ + u8 reserved1[1]; + __be16 usb_ep[4]; /* endpoint register */ + u8 reserved2[4]; + __be16 usb_event; /* event register */ + u8 reserved3[2]; + __be16 usb_mask; /* mask register */ + u8 reserved4[1]; + u8 usb_status; /* status register */ + __be16 usb_sof_tmr; /* Start Of Frame timer */ + u8 reserved5[2]; + __be16 usb_frame_num; /* frame number register */ + u8 reserved6[1]; +}; + +/* Freescale USB HOST */ +struct fhci_pram { + __be16 ep_ptr[4]; /* Endpoint porter reg */ + __be32 rx_state; /* Rx internal state */ + __be32 rx_ptr; /* Rx internal data pointer */ + __be16 frame_num; /* Frame number */ + __be16 rx_cnt; /* Rx byte count */ + __be32 rx_temp; /* Rx temp */ + __be32 rx_data_temp; /* Rx data temp */ + __be16 rx_u_ptr; /* Rx microcode return address temp */ + u8 reserved1[2]; /* reserved area */ + __be32 sof_tbl; /* SOF lookup table pointer */ + u8 sof_u_crc_temp; /* SOF micorcode CRC5 temp reg */ + u8 reserved2[0xdb]; +}; + +/* Freescale USB Endpoint*/ +struct fhci_ep_pram { + __be16 rx_base; /* Rx BD base address */ + __be16 tx_base; /* Tx BD base address */ + u8 rx_func_code; /* Rx function code */ + u8 tx_func_code; /* Tx function code */ + __be16 rx_buff_len; /* Rx buffer length */ + __be16 rx_bd_ptr; /* Rx BD pointer */ + __be16 tx_bd_ptr; /* Tx BD pointer */ + __be32 tx_state; /* Tx internal state */ + __be32 tx_ptr; /* Tx internal data pointer */ + __be16 tx_crc; /* temp transmit CRC */ + __be16 tx_cnt; /* Tx byte count */ + __be32 tx_temp; /* Tx temp */ + __be16 tx_u_ptr; /* Tx microcode return address temp */ + __be16 reserved; +}; + +struct fhci_controller_list { + struct list_head ctrl_list; /* control endpoints */ + struct list_head bulk_list; /* bulk endpoints */ + struct list_head iso_list; /* isochronous endpoints */ + struct list_head intr_list; /* interruput endpoints */ + struct list_head done_list; /* done transfers */ +}; + +struct virtual_root_hub { + int dev_num; /* USB address of the root hub */ + u32 feature; /* indicates what feature has been set */ + struct usb_hub_status hub; + struct usb_port_status port; +}; + +enum fhci_gpios { + GPIO_USBOE = 0, + GPIO_USBTP, + GPIO_USBTN, + GPIO_USBRP, + GPIO_USBRN, + /* these are optional */ + GPIO_SPEED, + GPIO_POWER, + NUM_GPIOS, +}; + +enum fhci_pins { + PIN_USBOE = 0, + PIN_USBTP, + PIN_USBTN, + NUM_PINS, +}; + +struct fhci_hcd { + enum qe_clock fullspeed_clk; + enum qe_clock lowspeed_clk; + struct qe_pin *pins[NUM_PINS]; + int gpios[NUM_GPIOS]; + bool alow_gpios[NUM_GPIOS]; + + struct fhci_regs __iomem *regs; /* I/O memory used to communicate */ + struct fhci_pram __iomem *pram; /* Parameter RAM */ + struct gtm_timer *timer; + + spinlock_t lock; + struct fhci_usb *usb_lld; /* Low-level driver */ + struct virtual_root_hub *vroot_hub; /* the virtual root hub */ + int active_urbs; + struct fhci_controller_list *hc_list; + struct tasklet_struct *process_done_task; /* tasklet for done list */ + + struct list_head empty_eds; + struct list_head empty_tds; + +#ifdef CONFIG_FHCI_DEBUG + int usb_irq_stat[13]; + struct dentry *dfs_root; + struct dentry *dfs_regs; + struct dentry *dfs_irq_stat; +#endif +}; + +#define USB_FRAME_USAGE 90 +#define FRAME_TIME_USAGE (USB_FRAME_USAGE*10) /* frame time usage */ +#define SW_FIX_TIME_BETWEEN_TRANSACTION 150 /* SW */ +#define MAX_BYTES_PER_FRAME (USB_FRAME_USAGE*15) +#define MAX_PERIODIC_FRAME_USAGE 90 + +/* transaction type */ +enum fhci_ta_type { + FHCI_TA_IN = 0, /* input transaction */ + FHCI_TA_OUT, /* output transaction */ + FHCI_TA_SETUP, /* setup transaction */ +}; + +/* transfer mode */ +enum fhci_tf_mode { + FHCI_TF_CTRL = 0, + FHCI_TF_ISO, + FHCI_TF_BULK, + FHCI_TF_INTR, +}; + +enum fhci_speed { + FHCI_FULL_SPEED, + FHCI_LOW_SPEED, +}; + +/* endpoint state */ +enum fhci_ed_state { + FHCI_ED_NEW = 0, /* pipe is new */ + FHCI_ED_OPER, /* pipe is operating */ + FHCI_ED_URB_DEL, /* pipe is in hold because urb is being deleted */ + FHCI_ED_SKIP, /* skip this pipe */ + FHCI_ED_HALTED, /* pipe is halted */ +}; + +enum fhci_port_status { + FHCI_PORT_POWER_OFF = 0, + FHCI_PORT_DISABLED, + FHCI_PORT_DISCONNECTING, + FHCI_PORT_WAITING, /* waiting for connection */ + FHCI_PORT_FULL, /* full speed connected */ + FHCI_PORT_LOW, /* low speed connected */ +}; + +enum fhci_mem_alloc { + MEM_CACHABLE_SYS = 0x00000001, /* primary DDR,cachable */ + MEM_NOCACHE_SYS = 0x00000004, /* primary DDR,non-cachable */ + MEM_SECONDARY = 0x00000002, /* either secondary DDR or SDRAM */ + MEM_PRAM = 0x00000008, /* multi-user RAM identifier */ +}; + +/* USB default parameters*/ +#define DEFAULT_RING_LEN 8 +#define DEFAULT_DATA_MEM MEM_CACHABLE_SYS + +struct ed { + u8 dev_addr; /* device address */ + u8 ep_addr; /* endpoint address */ + enum fhci_tf_mode mode; /* USB transfer mode */ + enum fhci_speed speed; + unsigned int max_pkt_size; + enum fhci_ed_state state; + struct list_head td_list; /* a list of all queued TD to this pipe */ + struct list_head node; + + /* read only parameters, should be cleared upon initialization */ + u8 toggle_carry; /* toggle carry from the last TD submitted */ + u32 last_iso; /* time stamp of last queued ISO transfer */ + struct td *td_head; /* a pointer to the current TD handled */ +}; + +struct td { + void *data; /* a pointer to the data buffer */ + unsigned int len; /* length of the data to be submitted */ + unsigned int actual_len; /* actual bytes transfered on this td */ + enum fhci_ta_type type; /* transaction type */ + u8 toggle; /* toggle for next trans. within this TD */ + u16 iso_index; /* ISO transaction index */ + u16 start_frame; /* start frame time stamp */ + u16 interval; /* interval between trans. (for ISO/Intr) */ + u32 status; /* status of the TD */ + struct ed *ed; /* a handle to the corresponding ED */ + struct urb *urb; /* a handle to the corresponding URB */ + bool ioc; /* Inform On Completion */ + struct list_head node; + + /* read only parameters should be cleared upon initialization */ + struct packet *pkt; + int nak_cnt; + int error_cnt; + struct list_head frame_lh; +}; + +struct packet { + u8 *data; /* packet data */ + u32 len; /* packet length */ + u32 status; /* status of the packet - equivalent to the status + * field for the corresponding structure td */ + u32 info; /* packet information */ + void __iomem *priv_data; /* private data of the driver (TDs or BDs) */ +}; + +/* struct for each URB */ +#define URB_INPROGRESS 0 +#define URB_DEL 1 + +/* URB states (state field) */ +#define US_BULK 0 +#define US_BULK0 1 + +/* three setup states */ +#define US_CTRL_SETUP 2 +#define US_CTRL_DATA 1 +#define US_CTRL_ACK 0 + +#define EP_ZERO 0 + +struct urb_priv { + int num_of_tds; + int tds_cnt; + int state; + + struct td **tds; + struct ed *ed; + struct timer_list time_out; +}; + +struct endpoint { + /* Pointer to ep parameter RAM */ + struct fhci_ep_pram __iomem *ep_pram_ptr; + + /* Host transactions */ + struct usb_td __iomem *td_base; /* first TD in the ring */ + struct usb_td __iomem *conf_td; /* next TD for confirm after transac */ + struct usb_td __iomem *empty_td;/* next TD for new transaction req. */ + struct kfifo *empty_frame_Q; /* Empty frames list to use */ + struct kfifo *conf_frame_Q; /* frames passed to TDs,waiting for tx */ + struct kfifo *dummy_packets_Q;/* dummy packets for the CRC overun */ + + bool already_pushed_dummy_bd; +}; + +/* struct for each 1mSec frame time */ +#define FRAME_IS_TRANSMITTED 0x00 +#define FRAME_TIMER_END_TRANSMISSION 0x01 +#define FRAME_DATA_END_TRANSMISSION 0x02 +#define FRAME_END_TRANSMISSION 0x03 +#define FRAME_IS_PREPARED 0x04 + +struct fhci_time_frame { + u16 frame_num; /* frame number */ + u16 total_bytes; /* total bytes submitted within this frame */ + u8 frame_status; /* flag that indicates to stop fill this frame */ + struct list_head tds_list; /* all tds of this frame */ +}; + +/* internal driver structure*/ +struct fhci_usb { + u16 saved_msk; /* saving of the USB mask register */ + struct endpoint *ep0; /* pointer for endpoint0 structure */ + int intr_nesting_cnt; /* interrupt nesting counter */ + u16 max_frame_usage; /* max frame time usage,in micro-sec */ + u16 max_bytes_per_frame; /* max byte can be tx in one time frame */ + u32 sw_transaction_time; /* sw complete trans time,in micro-sec */ + struct fhci_time_frame *actual_frame; + struct fhci_controller_list *hc_list; /* main structure for hc */ + struct virtual_root_hub *vroot_hub; + enum fhci_port_status port_status; /* v_rh port status */ + + u32 (*transfer_confirm)(struct fhci_hcd *fhci); + + struct fhci_hcd *fhci; +}; + +/* + * Various helpers and prototypes below. + */ + +static inline u16 get_frame_num(struct fhci_hcd *fhci) +{ + return in_be16(&fhci->pram->frame_num) & 0x07ff; +} + +#define fhci_dbg(fhci, fmt, args...) \ + dev_dbg(fhci_to_hcd(fhci)->self.controller, fmt, ##args) +#define fhci_vdbg(fhci, fmt, args...) \ + dev_vdbg(fhci_to_hcd(fhci)->self.controller, fmt, ##args) +#define fhci_err(fhci, fmt, args...) \ + dev_err(fhci_to_hcd(fhci)->self.controller, fmt, ##args) +#define fhci_info(fhci, fmt, args...) \ + dev_info(fhci_to_hcd(fhci)->self.controller, fmt, ##args) +#define fhci_warn(fhci, fmt, args...) \ + dev_warn(fhci_to_hcd(fhci)->self.controller, fmt, ##args) + +static inline struct fhci_hcd *hcd_to_fhci(struct usb_hcd *hcd) +{ + return (struct fhci_hcd *)hcd->hcd_priv; +} + +static inline struct usb_hcd *fhci_to_hcd(struct fhci_hcd *fhci) +{ + return container_of((void *)fhci, struct usb_hcd, hcd_priv); +} + +/* fifo of pointers */ +static inline struct kfifo *cq_new(int size) +{ + return kfifo_alloc(size * sizeof(void *), GFP_KERNEL, NULL); +} + +static inline void cq_delete(struct kfifo *kfifo) +{ + kfifo_free(kfifo); +} + +static inline unsigned int cq_howmany(struct kfifo *kfifo) +{ + return __kfifo_len(kfifo) / sizeof(void *); +} + +static inline int cq_put(struct kfifo *kfifo, void *p) +{ + return __kfifo_put(kfifo, (void *)&p, sizeof(p)); +} + +static inline void *cq_get(struct kfifo *kfifo) +{ + void *p = NULL; + + __kfifo_get(kfifo, (void *)&p, sizeof(p)); + return p; +} + +/* fhci-hcd.c */ +void fhci_start_sof_timer(struct fhci_hcd *fhci); +void fhci_stop_sof_timer(struct fhci_hcd *fhci); +u16 fhci_get_sof_timer_count(struct fhci_usb *usb); +void fhci_usb_enable_interrupt(struct fhci_usb *usb); +void fhci_usb_disable_interrupt(struct fhci_usb *usb); +int fhci_ioports_check_bus_state(struct fhci_hcd *fhci); + +/* fhci-mem.c */ +void fhci_recycle_empty_td(struct fhci_hcd *fhci, struct td *td); +void fhci_recycle_empty_ed(struct fhci_hcd *fhci, struct ed *ed); +struct ed *fhci_get_empty_ed(struct fhci_hcd *fhci); +struct td *fhci_td_fill(struct fhci_hcd *fhci, struct urb *urb, + struct urb_priv *urb_priv, struct ed *ed, u16 index, + enum fhci_ta_type type, int toggle, u8 *data, u32 len, + u16 interval, u16 start_frame, bool ioc); +void fhci_add_tds_to_ed(struct ed *ed, struct td **td_list, int number); + +/* fhci-hub.c */ +void fhci_config_transceiver(struct fhci_hcd *fhci, + enum fhci_port_status status); +void fhci_port_disable(struct fhci_hcd *fhci); +void fhci_port_enable(void *lld); +void fhci_io_port_generate_reset(struct fhci_hcd *fhci); +void fhci_port_reset(void *lld); +int fhci_hub_status_data(struct usb_hcd *hcd, char *buf); +int fhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, + u16 wIndex, char *buf, u16 wLength); + +/* fhci-tds.c */ +void fhci_flush_bds(struct fhci_usb *usb); +void fhci_flush_actual_frame(struct fhci_usb *usb); +u32 fhci_host_transaction(struct fhci_usb *usb, struct packet *pkt, + enum fhci_ta_type trans_type, u8 dest_addr, + u8 dest_ep, enum fhci_tf_mode trans_mode, + enum fhci_speed dest_speed, u8 data_toggle); +void fhci_host_transmit_actual_frame(struct fhci_usb *usb); +void fhci_tx_conf_interrupt(struct fhci_usb *usb); +void fhci_push_dummy_bd(struct endpoint *ep); +u32 fhci_create_ep(struct fhci_usb *usb, enum fhci_mem_alloc data_mem, + u32 ring_len); +void fhci_init_ep_registers(struct fhci_usb *usb, + struct endpoint *ep, + enum fhci_mem_alloc data_mem); +void fhci_ep0_free(struct fhci_usb *usb); + +/* fhci-sched.c */ +extern struct tasklet_struct fhci_tasklet; +void fhci_transaction_confirm(struct fhci_usb *usb, struct packet *pkt); +void fhci_flush_all_transmissions(struct fhci_usb *usb); +void fhci_schedule_transactions(struct fhci_usb *usb); +void fhci_device_connected_interrupt(struct fhci_hcd *fhci); +void fhci_device_disconnected_interrupt(struct fhci_hcd *fhci); +void fhci_queue_urb(struct fhci_hcd *fhci, struct urb *urb); +u32 fhci_transfer_confirm_callback(struct fhci_hcd *fhci); +irqreturn_t fhci_irq(struct usb_hcd *hcd); +irqreturn_t fhci_frame_limit_timer_irq(int irq, void *_hcd); + +/* fhci-q.h */ +void fhci_urb_complete_free(struct fhci_hcd *fhci, struct urb *urb); +struct td *fhci_remove_td_from_ed(struct ed *ed); +struct td *fhci_remove_td_from_frame(struct fhci_time_frame *frame); +void fhci_move_td_from_ed_to_done_list(struct fhci_usb *usb, struct ed *ed); +struct td *fhci_peek_td_from_frame(struct fhci_time_frame *frame); +void fhci_add_td_to_frame(struct fhci_time_frame *frame, struct td *td); +struct td *fhci_remove_td_from_done_list(struct fhci_controller_list *p_list); +void fhci_done_td(struct urb *urb, struct td *td); +void fhci_del_ed_list(struct fhci_hcd *fhci, struct ed *ed); + +#ifdef CONFIG_FHCI_DEBUG + +void fhci_dbg_isr(struct fhci_hcd *fhci, int usb_er); +void fhci_dfs_destroy(struct fhci_hcd *fhci); +void fhci_dfs_create(struct fhci_hcd *fhci); + +#else + +static inline void fhci_dbg_isr(struct fhci_hcd *fhci, int usb_er) {} +static inline void fhci_dfs_destroy(struct fhci_hcd *fhci) {} +static inline void fhci_dfs_create(struct fhci_hcd *fhci) {} + +#endif /* CONFIG_FHCI_DEBUG */ + +#endif /* __FHCI_H */ -- cgit v1.2.2 From a3c1239eb59c0a907f8be5587d42e950f44543f8 Mon Sep 17 00:00:00 2001 From: David Vrabel <david.vrabel@csr.com> Date: Mon, 16 Feb 2009 14:37:12 +0000 Subject: wusb: whci-hcd: always lock whc->lock with interrupts disabled Always lock whc->lock with spin_lock_irq() or spin_lock_irqsave(). Signed-off-by: David Vrabel <david.vrabel@csr.com> --- drivers/usb/host/whci/asl.c | 4 ++-- drivers/usb/host/whci/pzl.c | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'drivers/usb/host') diff --git a/drivers/usb/host/whci/asl.c b/drivers/usb/host/whci/asl.c index 2291c5f5af51..958751ccea43 100644 --- a/drivers/usb/host/whci/asl.c +++ b/drivers/usb/host/whci/asl.c @@ -227,13 +227,13 @@ void scan_async_work(struct work_struct *work) * Now that the ASL is updated, complete the removal of any * removed qsets. */ - spin_lock(&whc->lock); + spin_lock_irq(&whc->lock); list_for_each_entry_safe(qset, t, &whc->async_removed_list, list_node) { qset_remove_complete(whc, qset); } - spin_unlock(&whc->lock); + spin_unlock_irq(&whc->lock); } /** diff --git a/drivers/usb/host/whci/pzl.c b/drivers/usb/host/whci/pzl.c index 7dc85a0bee7c..df8b85f07092 100644 --- a/drivers/usb/host/whci/pzl.c +++ b/drivers/usb/host/whci/pzl.c @@ -255,13 +255,13 @@ void scan_periodic_work(struct work_struct *work) * Now that the PZL is updated, complete the removal of any * removed qsets. */ - spin_lock(&whc->lock); + spin_lock_irq(&whc->lock); list_for_each_entry_safe(qset, t, &whc->periodic_removed_list, list_node) { qset_remove_complete(whc, qset); } - spin_unlock(&whc->lock); + spin_unlock_irq(&whc->lock); } /** -- cgit v1.2.2 From 3494252d5644993f407a45f01c3e8ad5ae38f93c Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" <rjw@sisk.pl> Date: Fri, 13 Feb 2009 23:41:12 +0100 Subject: USB/PCI: Fix resume breakage of controllers behind cardbus bridges If a USB PCI controller is behind a cardbus bridge, we are trying to restore its configuration registers too early, before the cardbus bridge is operational. To fix this, call pci_restore_state() from usb_hcd_pci_resume() and remove usb_hcd_pci_resume_early() which is no longer necessary (the configuration spaces of USB controllers that are not behind cardbus bridges will be restored by the PCI PM core with interrupts disabled anyway). This patch fixes the regression from 2.6.28 tracked as http://bugzilla.kernel.org/show_bug.cgi?id=12659 [ Side note: the proper long-term fix is probably to just force the unplug event at suspend time instead of doing a plug/unplug at resume time, but this patch is fine regardless - Linus ] Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl> Reported-by: Miles Lane <miles.lane@gmail.com> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org> --- drivers/usb/host/ehci-pci.c | 1 - drivers/usb/host/ohci-pci.c | 1 - drivers/usb/host/uhci-hcd.c | 1 - 3 files changed, 3 deletions(-) (limited to 'drivers/usb/host') diff --git a/drivers/usb/host/ehci-pci.c b/drivers/usb/host/ehci-pci.c index bb21fb0a4969..abb9a7706ec7 100644 --- a/drivers/usb/host/ehci-pci.c +++ b/drivers/usb/host/ehci-pci.c @@ -432,7 +432,6 @@ static struct pci_driver ehci_pci_driver = { #ifdef CONFIG_PM .suspend = usb_hcd_pci_suspend, - .resume_early = usb_hcd_pci_resume_early, .resume = usb_hcd_pci_resume, #endif .shutdown = usb_hcd_pci_shutdown, diff --git a/drivers/usb/host/ohci-pci.c b/drivers/usb/host/ohci-pci.c index 5d625c3fd423..f9961b4c0da3 100644 --- a/drivers/usb/host/ohci-pci.c +++ b/drivers/usb/host/ohci-pci.c @@ -487,7 +487,6 @@ static struct pci_driver ohci_pci_driver = { #ifdef CONFIG_PM .suspend = usb_hcd_pci_suspend, - .resume_early = usb_hcd_pci_resume_early, .resume = usb_hcd_pci_resume, #endif diff --git a/drivers/usb/host/uhci-hcd.c b/drivers/usb/host/uhci-hcd.c index 944f7e0ca4df..cf5e4cf7ea42 100644 --- a/drivers/usb/host/uhci-hcd.c +++ b/drivers/usb/host/uhci-hcd.c @@ -942,7 +942,6 @@ static struct pci_driver uhci_pci_driver = { #ifdef CONFIG_PM .suspend = usb_hcd_pci_suspend, - .resume_early = usb_hcd_pci_resume_early, .resume = usb_hcd_pci_resume, #endif /* PM */ }; -- cgit v1.2.2 From 9aa09d2f8f4bc440d6db1c3414d4009642875240 Mon Sep 17 00:00:00 2001 From: Karsten Wiese <fzu@wemgehoertderstaat.de> Date: Sun, 8 Feb 2009 16:07:58 -0800 Subject: USB: EHCI: slow down ITD reuse Currently ITDs are immediately recycled whenever their URB completes. However, EHCI hardware can sometimes remember some ITD state. This means that when the ITD is reused before end-of-frame it may sometimes cause the hardware to reference bogus state. This patch defers reusing such ITDs by moving them into a new ehci member cached_itd_list. ITDs resting in cached_itd_list are moved back into their stream's free_list once scan_periodic() detects that the active frame has elapsed. This makes the snd_usb_us122l driver (in kernel since .28) work right when it's hooked up through EHCI. [ dbrownell@users.sourceforge.net: comment fixups ] Signed-off-by: Karsten Wiese <fzu@wemgehoertderstaat.de> Tested-by: Philippe Carriere <philippe-f.carriere@wanadoo.fr> Tested-by: Federico Briata <federicobriata@gmail.com> Cc: stable <stable@kernel.org> Signed-off-by: David Brownell <dbrownell@users.sourceforge.net> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/host/ehci-hcd.c | 2 ++ drivers/usb/host/ehci-mem.c | 1 + drivers/usb/host/ehci-sched.c | 56 ++++++++++++++++++++++++++++++++++++------- drivers/usb/host/ehci.h | 6 +++++ 4 files changed, 57 insertions(+), 8 deletions(-) (limited to 'drivers/usb/host') diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c index 4725d15d096f..e551bb38852b 100644 --- a/drivers/usb/host/ehci-hcd.c +++ b/drivers/usb/host/ehci-hcd.c @@ -485,6 +485,7 @@ static int ehci_init(struct usb_hcd *hcd) * periodic_size can shrink by USBCMD update if hcc_params allows. */ ehci->periodic_size = DEFAULT_I_TDPS; + INIT_LIST_HEAD(&ehci->cached_itd_list); if ((retval = ehci_mem_init(ehci, GFP_KERNEL)) < 0) return retval; @@ -497,6 +498,7 @@ static int ehci_init(struct usb_hcd *hcd) ehci->reclaim = NULL; ehci->next_uframe = -1; + ehci->clock_frame = -1; /* * dedicate a qh for the async ring head, since we couldn't unlink diff --git a/drivers/usb/host/ehci-mem.c b/drivers/usb/host/ehci-mem.c index 0431397836f6..10d52919abbb 100644 --- a/drivers/usb/host/ehci-mem.c +++ b/drivers/usb/host/ehci-mem.c @@ -128,6 +128,7 @@ static inline void qh_put (struct ehci_qh *qh) static void ehci_mem_cleanup (struct ehci_hcd *ehci) { + free_cached_itd_list(ehci); if (ehci->async) qh_put (ehci->async); ehci->async = NULL; diff --git a/drivers/usb/host/ehci-sched.c b/drivers/usb/host/ehci-sched.c index a081ee65bde6..07bcb931021b 100644 --- a/drivers/usb/host/ehci-sched.c +++ b/drivers/usb/host/ehci-sched.c @@ -1004,7 +1004,8 @@ iso_stream_put(struct ehci_hcd *ehci, struct ehci_iso_stream *stream) is_in = (stream->bEndpointAddress & USB_DIR_IN) ? 0x10 : 0; stream->bEndpointAddress &= 0x0f; - stream->ep->hcpriv = NULL; + if (stream->ep) + stream->ep->hcpriv = NULL; if (stream->rescheduled) { ehci_info (ehci, "ep%d%s-iso rescheduled " @@ -1653,14 +1654,28 @@ itd_complete ( (stream->bEndpointAddress & USB_DIR_IN) ? "in" : "out"); } iso_stream_put (ehci, stream); - /* OK to recycle this ITD now that its completion callback ran. */ + done: usb_put_urb(urb); itd->urb = NULL; - itd->stream = NULL; - list_move(&itd->itd_list, &stream->free_list); - iso_stream_put(ehci, stream); - + if (ehci->clock_frame != itd->frame || itd->index[7] != -1) { + /* OK to recycle this ITD now. */ + itd->stream = NULL; + list_move(&itd->itd_list, &stream->free_list); + iso_stream_put(ehci, stream); + } else { + /* HW might remember this ITD, so we can't recycle it yet. + * Move it to a safe place until a new frame starts. + */ + list_move(&itd->itd_list, &ehci->cached_itd_list); + if (stream->refcount == 2) { + /* If iso_stream_put() were called here, stream + * would be freed. Instead, just prevent reuse. + */ + stream->ep->hcpriv = NULL; + stream->ep = NULL; + } + } return retval; } @@ -2101,6 +2116,20 @@ done: /*-------------------------------------------------------------------------*/ +static void free_cached_itd_list(struct ehci_hcd *ehci) +{ + struct ehci_itd *itd, *n; + + list_for_each_entry_safe(itd, n, &ehci->cached_itd_list, itd_list) { + struct ehci_iso_stream *stream = itd->stream; + itd->stream = NULL; + list_move(&itd->itd_list, &stream->free_list); + iso_stream_put(ehci, stream); + } +} + +/*-------------------------------------------------------------------------*/ + static void scan_periodic (struct ehci_hcd *ehci) { @@ -2115,10 +2144,17 @@ scan_periodic (struct ehci_hcd *ehci) * Touches as few pages as possible: cache-friendly. */ now_uframe = ehci->next_uframe; - if (HC_IS_RUNNING (ehci_to_hcd(ehci)->state)) + if (HC_IS_RUNNING(ehci_to_hcd(ehci)->state)) { clock = ehci_readl(ehci, &ehci->regs->frame_index); - else + clock_frame = (clock >> 3) % ehci->periodic_size; + } else { clock = now_uframe + mod - 1; + clock_frame = -1; + } + if (ehci->clock_frame != clock_frame) { + free_cached_itd_list(ehci); + ehci->clock_frame = clock_frame; + } clock %= mod; clock_frame = clock >> 3; @@ -2277,6 +2313,10 @@ restart: /* rescan the rest of this frame, then ... */ clock = now; clock_frame = clock >> 3; + if (ehci->clock_frame != clock_frame) { + free_cached_itd_list(ehci); + ehci->clock_frame = clock_frame; + } } else { now_uframe++; now_uframe %= mod; diff --git a/drivers/usb/host/ehci.h b/drivers/usb/host/ehci.h index fb7054ccf4fc..262b00c9b334 100644 --- a/drivers/usb/host/ehci.h +++ b/drivers/usb/host/ehci.h @@ -87,6 +87,10 @@ struct ehci_hcd { /* one per controller */ int next_uframe; /* scan periodic, start here */ unsigned periodic_sched; /* periodic activity count */ + /* list of itds completed while clock_frame was still active */ + struct list_head cached_itd_list; + unsigned clock_frame; + /* per root hub port */ unsigned long reset_done [EHCI_MAX_ROOT_PORTS]; @@ -220,6 +224,8 @@ timer_action (struct ehci_hcd *ehci, enum ehci_timer_action action) } } +static void free_cached_itd_list(struct ehci_hcd *ehci); + /*-------------------------------------------------------------------------*/ #include <linux/usb/ehci_def.h> -- cgit v1.2.2 From 4c3f450ba4e4c00df91f98664b58f9a98dc049fd Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto <morimoto.kuninori@renesas.com> Date: Thu, 12 Mar 2009 08:40:15 +0000 Subject: sh: Add OHCI USB support for SH7786 Signed-off-by: Kuninori Morimoto <morimoto.kuninori@renesas.com> Signed-off-by: Paul Mundt <lethal@linux-sh.org> --- drivers/usb/host/ohci-hcd.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'drivers/usb/host') diff --git a/drivers/usb/host/ohci-hcd.c b/drivers/usb/host/ohci-hcd.c index 5cf5f1eca4f4..7658589edb1c 100644 --- a/drivers/usb/host/ohci-hcd.c +++ b/drivers/usb/host/ohci-hcd.c @@ -1049,7 +1049,8 @@ MODULE_LICENSE ("GPL"); #if defined(CONFIG_CPU_SUBTYPE_SH7720) || \ defined(CONFIG_CPU_SUBTYPE_SH7721) || \ - defined(CONFIG_CPU_SUBTYPE_SH7763) + defined(CONFIG_CPU_SUBTYPE_SH7763) || \ + defined(CONFIG_CPU_SUBTYPE_SH7786) #include "ohci-sh.c" #define PLATFORM_DRIVER ohci_hcd_sh_driver #endif -- cgit v1.2.2 From 508db8c954d55ed30f870d2c24d741ba6269d13c Mon Sep 17 00:00:00 2001 From: Karsten Wiese <fzuuzf@googlemail.com> Date: Thu, 26 Feb 2009 01:47:48 +0100 Subject: USB: EHCI: Fix isochronous URB leak ehci-hcd uses usb_get_urb() and usb_put_urb() in an unbalanced way causing isochronous URB's kref.counts incrementing once per usb_submit_urb() call. The culprit is *usb being set to NULL when usb_put_urb() is called after URB is given back. Due to other fixes there is no need for ehci-hcd to deal with usb_get_urb() nor usb_put_urb() anymore, so patch removes their usages in ehci-hcd. Patch also makes ehci_to_hcd(ehci)->self.bandwidth_allocated adjust, if a stream finishes. Signed-off-by: Karsten Wiese <fzu@wemgehoertderstaat.de> Cc: David Brownell <david-b@pacbell.net> Cc: stable <stable@kernel.org> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/host/ehci-sched.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) (limited to 'drivers/usb/host') diff --git a/drivers/usb/host/ehci-sched.c b/drivers/usb/host/ehci-sched.c index 07bcb931021b..1d0b49e3f192 100644 --- a/drivers/usb/host/ehci-sched.c +++ b/drivers/usb/host/ehci-sched.c @@ -1536,7 +1536,7 @@ itd_link_urb ( struct ehci_itd, itd_list); list_move_tail (&itd->itd_list, &stream->td_list); itd->stream = iso_stream_get (stream); - itd->urb = usb_get_urb (urb); + itd->urb = urb; itd_init (ehci, stream, itd); } @@ -1645,7 +1645,7 @@ itd_complete ( (void) disable_periodic(ehci); ehci_to_hcd(ehci)->self.bandwidth_isoc_reqs--; - if (unlikely (list_empty (&stream->td_list))) { + if (unlikely(list_is_singular(&stream->td_list))) { ehci_to_hcd(ehci)->self.bandwidth_allocated -= stream->bandwidth; ehci_vdbg (ehci, @@ -1656,7 +1656,6 @@ itd_complete ( iso_stream_put (ehci, stream); done: - usb_put_urb(urb); itd->urb = NULL; if (ehci->clock_frame != itd->frame || itd->index[7] != -1) { /* OK to recycle this ITD now. */ @@ -1949,7 +1948,7 @@ sitd_link_urb ( struct ehci_sitd, sitd_list); list_move_tail (&sitd->sitd_list, &stream->td_list); sitd->stream = iso_stream_get (stream); - sitd->urb = usb_get_urb (urb); + sitd->urb = urb; sitd_patch(ehci, stream, sitd, sched, packet); sitd_link (ehci, (next_uframe >> 3) % ehci->periodic_size, @@ -2034,7 +2033,7 @@ sitd_complete ( (void) disable_periodic(ehci); ehci_to_hcd(ehci)->self.bandwidth_isoc_reqs--; - if (list_empty (&stream->td_list)) { + if (list_is_singular(&stream->td_list)) { ehci_to_hcd(ehci)->self.bandwidth_allocated -= stream->bandwidth; ehci_vdbg (ehci, @@ -2045,7 +2044,6 @@ sitd_complete ( iso_stream_put (ehci, stream); /* OK to recycle this SITD now that its completion callback ran. */ done: - usb_put_urb(urb); sitd->urb = NULL; sitd->stream = NULL; list_move(&sitd->sitd_list, &stream->free_list); -- cgit v1.2.2 From 391016f6e2fe3b9979b4c6880a76e5e434d6947c Mon Sep 17 00:00:00 2001 From: Alan Stern <stern@rowland.harvard.edu> Date: Mon, 16 Mar 2009 14:21:56 -0400 Subject: USB: EHCI: expedite unlinks when the root hub is suspended This patch (as1225) fixes a bug in ehci-hcd. The condition for whether unlinked QHs can become IDLE should not be that the controller is halted, but rather that the controller isn't running. In other words when the root hub is suspended, the hardware doesn't own any QHs. This fixes a problem that can show up during hibernation: If a QH is only partially unlinked when the root hub is frozen, then when the root hub is thawed the QH won't be in the IDLE state. As a result it can't be used properly for new URB submissions. Signed-off-by: Alan Stern <stern@rowland.harvard.edu> Reported-by: Brandon Philips <brandon@ifup.org> Tested-by: Brandon Philips <brandon@ifup.org> Acked-by: David Brownell <dbrownell@users.sourceforge.net> Cc: Stable <stable@kernel.org> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/host/ehci-q.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'drivers/usb/host') diff --git a/drivers/usb/host/ehci-q.c b/drivers/usb/host/ehci-q.c index 3712b925b315..ecc9b66c03cd 100644 --- a/drivers/usb/host/ehci-q.c +++ b/drivers/usb/host/ehci-q.c @@ -1095,7 +1095,8 @@ static void start_unlink_async (struct ehci_hcd *ehci, struct ehci_qh *qh) prev->qh_next = qh->qh_next; wmb (); - if (unlikely (ehci_to_hcd(ehci)->state == HC_STATE_HALT)) { + /* If the controller isn't running, we don't have to wait for it */ + if (unlikely(!HC_IS_RUNNING(ehci_to_hcd(ehci)->state))) { /* if (unlikely (qh->reclaim != 0)) * this will recurse, probably not much */ -- cgit v1.2.2 From 2e0fe709687470637a0709b930ccc9e993d2dad5 Mon Sep 17 00:00:00 2001 From: Julia Lawall <julia@diku.dk> Date: Mon, 29 Dec 2008 11:22:14 +0100 Subject: USB: drivers: use USB API functions rather than constants This set of patches introduces calls to the following set of functions: usb_endpoint_dir_in(epd) usb_endpoint_dir_out(epd) usb_endpoint_is_bulk_in(epd) usb_endpoint_is_bulk_out(epd) usb_endpoint_is_int_in(epd) usb_endpoint_is_int_out(epd) usb_endpoint_num(epd) usb_endpoint_type(epd) usb_endpoint_xfer_bulk(epd) usb_endpoint_xfer_control(epd) usb_endpoint_xfer_int(epd) usb_endpoint_xfer_isoc(epd) In some cases, introducing one of these functions is not possible, and it just replaces an explicit integer value by one of the following constants: USB_ENDPOINT_XFER_BULK USB_ENDPOINT_XFER_CONTROL USB_ENDPOINT_XFER_INT USB_ENDPOINT_XFER_ISOC An extract of the semantic patch that makes these changes is as follows: (http://www.emn.fr/x-info/coccinelle/) // <smpl> @r1@ struct usb_endpoint_descriptor *epd; @@ - ((epd->bmAttributes & \(USB_ENDPOINT_XFERTYPE_MASK\|3\)) == - \(USB_ENDPOINT_XFER_CONTROL\|0\)) + usb_endpoint_xfer_control(epd) @r5@ struct usb_endpoint_descriptor *epd; @@ - ((epd->bEndpointAddress & \(USB_ENDPOINT_DIR_MASK\|0x80\)) == - \(USB_DIR_IN\|0x80\)) + usb_endpoint_dir_in(epd) @inc@ @@ #include <linux/usb.h> @depends on !inc && (r1||r5)@ @@ + #include <linux/usb.h> #include <linux/usb/...> // </smpl> Signed-off-by: Julia Lawall <julia@diku.dk> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/host/r8a66597-hcd.c | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) (limited to 'drivers/usb/host') diff --git a/drivers/usb/host/r8a66597-hcd.c b/drivers/usb/host/r8a66597-hcd.c index 319041205b57..5e942d94aebe 100644 --- a/drivers/usb/host/r8a66597-hcd.c +++ b/drivers/usb/host/r8a66597-hcd.c @@ -660,9 +660,9 @@ static u16 get_empty_pipenum(struct r8a66597 *r8a66597, u16 array[R8A66597_MAX_NUM_PIPE], i = 0, min; memset(array, 0, sizeof(array)); - switch (ep->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) { + switch (usb_endpoint_type(ep)) { case USB_ENDPOINT_XFER_BULK: - if (ep->bEndpointAddress & USB_ENDPOINT_DIR_MASK) + if (usb_endpoint_dir_in(ep)) array[i++] = 4; else { array[i++] = 3; @@ -670,7 +670,7 @@ static u16 get_empty_pipenum(struct r8a66597 *r8a66597, } break; case USB_ENDPOINT_XFER_INT: - if (ep->bEndpointAddress & USB_ENDPOINT_DIR_MASK) { + if (usb_endpoint_dir_in(ep)) { array[i++] = 6; array[i++] = 7; array[i++] = 8; @@ -678,7 +678,7 @@ static u16 get_empty_pipenum(struct r8a66597 *r8a66597, array[i++] = 9; break; case USB_ENDPOINT_XFER_ISOC: - if (ep->bEndpointAddress & USB_ENDPOINT_DIR_MASK) + if (usb_endpoint_dir_in(ep)) array[i++] = 2; else array[i++] = 1; @@ -928,10 +928,9 @@ static void init_pipe_info(struct r8a66597 *r8a66597, struct urb *urb, info.pipenum = get_empty_pipenum(r8a66597, ep); info.address = get_urb_to_r8a66597_addr(r8a66597, urb); - info.epnum = ep->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK; + info.epnum = usb_endpoint_num(ep); info.maxpacket = le16_to_cpu(ep->wMaxPacketSize); - info.type = get_r8a66597_type(ep->bmAttributes - & USB_ENDPOINT_XFERTYPE_MASK); + info.type = get_r8a66597_type(usb_endpoint_type(ep)); info.bufnum = get_bufnum(info.pipenum); info.buf_bsize = get_buf_bsize(info.pipenum); if (info.type == R8A66597_BULK) { @@ -941,7 +940,7 @@ static void init_pipe_info(struct r8a66597 *r8a66597, struct urb *urb, info.interval = get_interval(urb, ep->bInterval); info.timer_interval = get_timer_interval(urb, ep->bInterval); } - if (ep->bEndpointAddress & USB_ENDPOINT_DIR_MASK) + if (usb_endpoint_dir_in(ep)) info.dir_in = 1; else info.dir_in = 0; -- cgit v1.2.2 From 6e14bda1b18b2e3c16258427fc43ceb43e1bc1d5 Mon Sep 17 00:00:00 2001 From: Roel Kluin <roel.kluin@gmail.com> Date: Sat, 31 Jan 2009 12:37:04 +0100 Subject: USB: count reaches -1, tested 0 With a postfix decrement count will reach -1 rather than 0, so the warning will not be issued. Signed-off-by: Roel Kluin <roel.kluin@gmail.com> Acked-by: David Brownell <dbrownell@users.sourceforge.net> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/host/pci-quirks.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/usb/host') diff --git a/drivers/usb/host/pci-quirks.c b/drivers/usb/host/pci-quirks.c index 75b69847918e..033c2846ce59 100644 --- a/drivers/usb/host/pci-quirks.c +++ b/drivers/usb/host/pci-quirks.c @@ -234,7 +234,7 @@ static void __devinit quirk_usb_disable_ehci(struct pci_dev *pdev) */ hcc_params = readl(base + EHCI_HCC_PARAMS); offset = (hcc_params >> 8) & 0xff; - while (offset && count--) { + while (offset && --count) { u32 cap; int msec; -- cgit v1.2.2 From 6f8aa65b52037123beab573432e371c0f70b7b9a Mon Sep 17 00:00:00 2001 From: Frank Seidel <frank@f-seidel.de> Date: Thu, 5 Feb 2009 16:16:24 +0100 Subject: USB: add missing KERN_* constants to printks According to kerneljanitors todo list all printk calls (beginning a new line) should have an according KERN_* constant. Those are the missing peaces here for the usb subsystem. Signed-off-by: Frank Seidel <frank@f-seidel.de> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/host/isp116x.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'drivers/usb/host') diff --git a/drivers/usb/host/isp116x.h b/drivers/usb/host/isp116x.h index aa211bafcff9..12db961acdfb 100644 --- a/drivers/usb/host/isp116x.h +++ b/drivers/usb/host/isp116x.h @@ -563,7 +563,7 @@ static void urb_dbg(struct urb *urb, char *msg) */ static inline void dump_ptd(struct ptd *ptd) { - printk("td: %x %d%c%d %d,%d,%d %x %x%x%x\n", + printk(KERN_WARNING "td: %x %d%c%d %d,%d,%d %x %x%x%x\n", PTD_GET_CC(ptd), PTD_GET_FA(ptd), PTD_DIR_STR(ptd), PTD_GET_EP(ptd), PTD_GET_COUNT(ptd), PTD_GET_LEN(ptd), PTD_GET_MPS(ptd), @@ -576,7 +576,7 @@ static inline void dump_ptd_out_data(struct ptd *ptd, u8 * buf) int k; if (PTD_GET_DIR(ptd) != PTD_DIR_IN && PTD_GET_LEN(ptd)) { - printk("-> "); + printk(KERN_WARNING "-> "); for (k = 0; k < PTD_GET_LEN(ptd); ++k) printk("%02x ", ((u8 *) buf)[k]); printk("\n"); @@ -588,13 +588,13 @@ static inline void dump_ptd_in_data(struct ptd *ptd, u8 * buf) int k; if (PTD_GET_DIR(ptd) == PTD_DIR_IN && PTD_GET_COUNT(ptd)) { - printk("<- "); + printk(KERN_WARNING "<- "); for (k = 0; k < PTD_GET_COUNT(ptd); ++k) printk("%02x ", ((u8 *) buf)[k]); printk("\n"); } if (PTD_GET_LAST(ptd)) - printk("-\n"); + printk(KERN_WARNING "-\n"); } #else -- cgit v1.2.2 From b967c88ed1b48bc353ea83e0cacb2249a3bb1a51 Mon Sep 17 00:00:00 2001 From: Thierry Vignaud <tvignaud@mandriva.com> Date: Wed, 11 Feb 2009 13:31:05 -0800 Subject: usb: kill prehistorical comments about USB_EHCI_HCD Remove old comments about USB_EHCI_HCD. Cc: Alan Stern <stern@rowland.harvard.edu> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Acked-by: David Brownell <dbrownell@users.sourceforge.net> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/host/Kconfig | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) (limited to 'drivers/usb/host') diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig index 2c63bfb1f8d9..c1cfed7eefb5 100644 --- a/drivers/usb/host/Kconfig +++ b/drivers/usb/host/Kconfig @@ -24,10 +24,7 @@ config USB_EHCI_HCD The Enhanced Host Controller Interface (EHCI) is standard for USB 2.0 "high speed" (480 Mbit/sec, 60 Mbyte/sec) host controller hardware. If your USB host controller supports USB 2.0, you will likely want to - configure this Host Controller Driver. At the time of this writing, - the primary implementation of EHCI is a chip from NEC, widely available - in add-on PCI cards, but implementations are in the works from other - vendors including Intel and Philips. Motherboard support is appearing. + configure this Host Controller Driver. EHCI controllers are packaged with "companion" host controllers (OHCI or UHCI) to handle USB 1.1 devices connected to root hub ports. Ports -- cgit v1.2.2 From bc29847e16cb6b571157220ec9b20a7d86e58046 Mon Sep 17 00:00:00 2001 From: Alan Stern <stern@rowland.harvard.edu> Date: Wed, 11 Feb 2009 14:26:38 -0500 Subject: USB: EHCI: Make timer_action out-of-line This patch (as1205) moves timer_action() from ehci.h to ehci-hcd.c and makes it out-of-line. Over the years it has grown too big to be inline any more. Signed-off-by: Alan Stern <stern@rowland.harvard.edu> Acked-by: David Brownell <dbrownell@users.sourceforge.net> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/host/ehci-hcd.c | 36 ++++++++++++++++++++++++++++++++++++ drivers/usb/host/ehci.h | 34 ---------------------------------- 2 files changed, 36 insertions(+), 34 deletions(-) (limited to 'drivers/usb/host') diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c index e551bb38852b..f2618d17710d 100644 --- a/drivers/usb/host/ehci-hcd.c +++ b/drivers/usb/host/ehci-hcd.c @@ -110,6 +110,42 @@ MODULE_PARM_DESC (ignore_oc, "ignore bogus hardware overcurrent indications"); /*-------------------------------------------------------------------------*/ +static void +timer_action(struct ehci_hcd *ehci, enum ehci_timer_action action) +{ + /* Don't override timeouts which shrink or (later) disable + * the async ring; just the I/O watchdog. Note that if a + * SHRINK were pending, OFF would never be requested. + */ + if (timer_pending(&ehci->watchdog) + && ((BIT(TIMER_ASYNC_SHRINK) | BIT(TIMER_ASYNC_OFF)) + & ehci->actions)) + return; + + if (!test_and_set_bit(action, &ehci->actions)) { + unsigned long t; + + switch (action) { + case TIMER_IO_WATCHDOG: + t = EHCI_IO_JIFFIES; + break; + case TIMER_ASYNC_OFF: + t = EHCI_ASYNC_JIFFIES; + break; + /* case TIMER_ASYNC_SHRINK: */ + default: + /* add a jiffie since we synch against the + * 8 KHz uframe counter. + */ + t = DIV_ROUND_UP(EHCI_SHRINK_FRAMES * HZ, 1000) + 1; + break; + } + mod_timer(&ehci->watchdog, t + jiffies); + } +} + +/*-------------------------------------------------------------------------*/ + /* * handshake - spin reading hc until handshake completes or fails * @ptr: address of hc register to be read diff --git a/drivers/usb/host/ehci.h b/drivers/usb/host/ehci.h index 262b00c9b334..0042deb671dd 100644 --- a/drivers/usb/host/ehci.h +++ b/drivers/usb/host/ehci.h @@ -190,40 +190,6 @@ timer_action_done (struct ehci_hcd *ehci, enum ehci_timer_action action) clear_bit (action, &ehci->actions); } -static inline void -timer_action (struct ehci_hcd *ehci, enum ehci_timer_action action) -{ - /* Don't override timeouts which shrink or (later) disable - * the async ring; just the I/O watchdog. Note that if a - * SHRINK were pending, OFF would never be requested. - */ - if (timer_pending(&ehci->watchdog) - && ((BIT(TIMER_ASYNC_SHRINK) | BIT(TIMER_ASYNC_OFF)) - & ehci->actions)) - return; - - if (!test_and_set_bit (action, &ehci->actions)) { - unsigned long t; - - switch (action) { - case TIMER_IO_WATCHDOG: - t = EHCI_IO_JIFFIES; - break; - case TIMER_ASYNC_OFF: - t = EHCI_ASYNC_JIFFIES; - break; - // case TIMER_ASYNC_SHRINK: - default: - /* add a jiffie since we synch against the - * 8 KHz uframe counter. - */ - t = DIV_ROUND_UP(EHCI_SHRINK_FRAMES * HZ, 1000) + 1; - break; - } - mod_timer(&ehci->watchdog, t + jiffies); - } -} - static void free_cached_itd_list(struct ehci_hcd *ehci); /*-------------------------------------------------------------------------*/ -- cgit v1.2.2 From a2c2706e1043c17139c2dafd171c4a5cf008ef7e Mon Sep 17 00:00:00 2001 From: Alan Stern <stern@rowland.harvard.edu> Date: Tue, 10 Feb 2009 10:16:58 -0500 Subject: USB: EHCI: add software retry for transaction errors This patch (as1204) adds a software retry mechanism to ehci-hcd. It gets invoked when the driver encounters transaction errors on an asynchronous endpoint. On many systems, hardware deficiencies cause such errors to occur if one device is unplugged while the host is communicating with another device. With the patch, the failed transactions are retried and generally succeed the second or third time through. This is based on code originally written by Koichiro Saito. Signed-off-by: Alan Stern <stern@rowland.harvard.edu> Tested by: Koichiro Saito <Saito.Koichiro@adniss.jp> CC: David Brownell <david-b@pacbell.net> Cc: stable <stable@kernel.org> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/host/ehci-q.c | 32 ++++++++++++++++++++++++++++++++ drivers/usb/host/ehci.h | 3 +++ 2 files changed, 35 insertions(+) (limited to 'drivers/usb/host') diff --git a/drivers/usb/host/ehci-q.c b/drivers/usb/host/ehci-q.c index ecc9b66c03cd..01132ac74eb8 100644 --- a/drivers/usb/host/ehci-q.c +++ b/drivers/usb/host/ehci-q.c @@ -333,12 +333,40 @@ qh_completions (struct ehci_hcd *ehci, struct ehci_qh *qh) token = hc32_to_cpu(ehci, qtd->hw_token); /* always clean up qtds the hc de-activated */ + retry_xacterr: if ((token & QTD_STS_ACTIVE) == 0) { /* on STALL, error, and short reads this urb must * complete and all its qtds must be recycled. */ if ((token & QTD_STS_HALT) != 0) { + + /* retry transaction errors until we + * reach the software xacterr limit + */ + if ((token & QTD_STS_XACT) && + QTD_CERR(token) == 0 && + --qh->xacterrs > 0 && + !urb->unlinked) { + ehci_dbg(ehci, + "detected XactErr len %d/%d retry %d\n", + qtd->length - QTD_LENGTH(token), qtd->length, + QH_XACTERR_MAX - qh->xacterrs); + + /* reset the token in the qtd and the + * qh overlay (which still contains + * the qtd) so that we pick up from + * where we left off + */ + token &= ~QTD_STS_HALT; + token |= QTD_STS_ACTIVE | + (EHCI_TUNE_CERR << 10); + qtd->hw_token = cpu_to_hc32(ehci, + token); + wmb(); + qh->hw_token = cpu_to_hc32(ehci, token); + goto retry_xacterr; + } stopped = 1; /* magic dummy for some short reads; qh won't advance. @@ -421,6 +449,9 @@ halt: /* remove qtd; it's recycled after possible urb completion */ list_del (&qtd->qtd_list); last = qtd; + + /* reinit the xacterr counter for the next qtd */ + qh->xacterrs = QH_XACTERR_MAX; } /* last urb's completion might still need calling */ @@ -862,6 +893,7 @@ static void qh_link_async (struct ehci_hcd *ehci, struct ehci_qh *qh) head->qh_next.qh = qh; head->hw_next = dma; + qh->xacterrs = QH_XACTERR_MAX; qh->qh_state = QH_STATE_LINKED; /* qtd completions reported later by interrupt */ } diff --git a/drivers/usb/host/ehci.h b/drivers/usb/host/ehci.h index 0042deb671dd..9aba560fd569 100644 --- a/drivers/usb/host/ehci.h +++ b/drivers/usb/host/ehci.h @@ -342,6 +342,9 @@ struct ehci_qh { #define QH_STATE_UNLINK_WAIT 4 /* LINKED and on reclaim q */ #define QH_STATE_COMPLETING 5 /* don't touch token.HALT */ + u8 xacterrs; /* XactErr retry counter */ +#define QH_XACTERR_MAX 32 /* XactErr retry limit */ + /* periodic schedule info */ u8 usecs; /* intr bandwidth */ u8 gap_uf; /* uframes split/csplit gap */ -- cgit v1.2.2 From f9031f2c4237abfe75d9ad33f5c0f0dde96f7d09 Mon Sep 17 00:00:00 2001 From: Catalin Marinas <catalin.marinas@arm.com> Date: Tue, 10 Feb 2009 16:55:45 +0000 Subject: USB: Make the isp1760_register function prototype more generic The patch changes the prototype of the isp1760_register() function to use predefined types like phys_addr_t and resource_size_t rather than u64 Signed-off-by: Catalin Marinas <catalin.marinas@arm.com> Cc: Sebastian Siewior <bigeasy@linutronix.de> Cc: Russell King <rmk@arm.linux.org.uk> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/host/isp1760-hcd.c | 7 ++++--- drivers/usb/host/isp1760-hcd.h | 7 ++++--- 2 files changed, 8 insertions(+), 6 deletions(-) (limited to 'drivers/usb/host') diff --git a/drivers/usb/host/isp1760-hcd.c b/drivers/usb/host/isp1760-hcd.c index b899f1a59c26..8ee2f4159845 100644 --- a/drivers/usb/host/isp1760-hcd.c +++ b/drivers/usb/host/isp1760-hcd.c @@ -2235,9 +2235,10 @@ void deinit_kmem_cache(void) kmem_cache_destroy(qh_cachep); } -struct usb_hcd *isp1760_register(u64 res_start, u64 res_len, int irq, - u64 irqflags, struct device *dev, const char *busname, - unsigned int devflags) +struct usb_hcd *isp1760_register(phys_addr_t res_start, resource_size_t res_len, + int irq, unsigned long irqflags, + struct device *dev, const char *busname, + unsigned int devflags) { struct usb_hcd *hcd; struct isp1760_hcd *priv; diff --git a/drivers/usb/host/isp1760-hcd.h b/drivers/usb/host/isp1760-hcd.h index a9daea587962..462f4943cb1b 100644 --- a/drivers/usb/host/isp1760-hcd.h +++ b/drivers/usb/host/isp1760-hcd.h @@ -2,9 +2,10 @@ #define _ISP1760_HCD_H_ /* exports for if */ -struct usb_hcd *isp1760_register(u64 res_start, u64 res_len, int irq, - u64 irqflags, struct device *dev, const char *busname, - unsigned int devflags); +struct usb_hcd *isp1760_register(phys_addr_t res_start, resource_size_t res_len, + int irq, unsigned long irqflags, + struct device *dev, const char *busname, + unsigned int devflags); int init_kmem_once(void); void deinit_kmem_cache(void); -- cgit v1.2.2 From f7e7aa5850839faa5eb7c7c177da5fd6bca8949b Mon Sep 17 00:00:00 2001 From: Catalin Marinas <catalin.marinas@arm.com> Date: Tue, 10 Feb 2009 16:55:51 +0000 Subject: USB: Add platform device support for the ISP1760 USB chip Currently, the driver only supports PCI and PPC_OF but there are boards like ARM RealView where this is a platform device. The patch adds the necessary functions and registration to the isp1760-if.c file and modifies the corresponding Makefile and Kconfig to be able to use this driver even if PCI and PPC_OF are not enabled. Signed-off-by: Catalin Marinas <catalin.marinas@arm.com> Cc: Sebastian Siewior <bigeasy@linutronix.de> Cc: Russell King <rmk@arm.linux.org.uk> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/host/Kconfig | 2 +- drivers/usb/host/isp1760-if.c | 95 +++++++++++++++++++++++++++++++++++-------- 2 files changed, 80 insertions(+), 17 deletions(-) (limited to 'drivers/usb/host') diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig index c1cfed7eefb5..845479f7c707 100644 --- a/drivers/usb/host/Kconfig +++ b/drivers/usb/host/Kconfig @@ -120,7 +120,7 @@ config USB_ISP116X_HCD config USB_ISP1760_HCD tristate "ISP 1760 HCD support" - depends on USB && EXPERIMENTAL && (PCI || PPC_OF) + depends on USB && EXPERIMENTAL ---help--- The ISP1760 chip is a USB 2.0 host controller. diff --git a/drivers/usb/host/isp1760-if.c b/drivers/usb/host/isp1760-if.c index 4cf7ca428b33..3fa3a1702796 100644 --- a/drivers/usb/host/isp1760-if.c +++ b/drivers/usb/host/isp1760-if.c @@ -10,6 +10,7 @@ #include <linux/usb.h> #include <linux/io.h> +#include <linux/platform_device.h> #include "../core/hcd.h" #include "isp1760-hcd.h" @@ -300,39 +301,101 @@ static struct pci_driver isp1761_pci_driver = { }; #endif +static int __devinit isp1760_plat_probe(struct platform_device *pdev) +{ + int ret = 0; + struct usb_hcd *hcd; + struct resource *mem_res; + struct resource *irq_res; + resource_size_t mem_size; + unsigned long irqflags = IRQF_SHARED | IRQF_DISABLED; + + mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!mem_res) { + pr_warning("isp1760: Memory resource not available\n"); + ret = -ENODEV; + goto out; + } + mem_size = resource_size(mem_res); + if (!request_mem_region(mem_res->start, mem_size, "isp1760")) { + pr_warning("isp1760: Cannot reserve the memory resource\n"); + ret = -EBUSY; + goto out; + } + + irq_res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (!irq_res) { + pr_warning("isp1760: IRQ resource not available\n"); + return -ENODEV; + } + irqflags |= irq_res->flags & IRQF_TRIGGER_MASK; + + hcd = isp1760_register(mem_res->start, mem_size, irq_res->start, + irqflags, &pdev->dev, dev_name(&pdev->dev), 0); + if (IS_ERR(hcd)) { + pr_warning("isp1760: Failed to register the HCD device\n"); + ret = -ENODEV; + goto cleanup; + } + + pr_info("ISP1760 USB device initialised\n"); + return ret; + +cleanup: + release_mem_region(mem_res->start, mem_size); +out: + return ret; +} + +static int __devexit isp1760_plat_remove(struct platform_device *pdev) +{ + struct resource *mem_res; + resource_size_t mem_size; + + mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + mem_size = resource_size(mem_res); + release_mem_region(mem_res->start, mem_size); + + return 0; +} + +static struct platform_driver isp1760_plat_driver = { + .probe = isp1760_plat_probe, + .remove = isp1760_plat_remove, + .driver = { + .name = "isp1760", + }, +}; + static int __init isp1760_init(void) { - int ret; + int ret, any_ret = -ENODEV; init_kmem_once(); + ret = platform_driver_register(&isp1760_plat_driver); + if (!ret) + any_ret = 0; #ifdef CONFIG_PPC_OF ret = of_register_platform_driver(&isp1760_of_driver); - if (ret) { - deinit_kmem_cache(); - return ret; - } + if (!ret) + any_ret = 0; #endif #ifdef CONFIG_PCI ret = pci_register_driver(&isp1761_pci_driver); - if (ret) - goto unreg_of; + if (!ret) + any_ret = 0; #endif - return ret; -#ifdef CONFIG_PCI -unreg_of: -#endif -#ifdef CONFIG_PPC_OF - of_unregister_platform_driver(&isp1760_of_driver); -#endif - deinit_kmem_cache(); - return ret; + if (any_ret) + deinit_kmem_cache(); + return any_ret; } module_init(isp1760_init); static void __exit isp1760_exit(void) { + platform_driver_unregister(&isp1760_plat_driver); #ifdef CONFIG_PPC_OF of_unregister_platform_driver(&isp1760_of_driver); #endif -- cgit v1.2.2 From 551509d267905705f6d723e51ec706916f06b859 Mon Sep 17 00:00:00 2001 From: Harvey Harrison <harvey.harrison@gmail.com> Date: Wed, 11 Feb 2009 14:11:36 -0800 Subject: USB: replace uses of __constant_{endian} The base versions handle constant folding now. Signed-off-by: Harvey Harrison <harvey.harrison@gmail.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/host/ehci-sched.c | 2 +- drivers/usb/host/ehci.h | 2 +- drivers/usb/host/isp1760-hcd.c | 4 ++-- drivers/usb/host/oxu210hp-hcd.c | 22 +++++++++++----------- drivers/usb/host/oxu210hp.h | 8 ++++---- drivers/usb/host/uhci-hcd.h | 10 +++++----- drivers/usb/host/uhci-q.c | 10 +++++----- 7 files changed, 29 insertions(+), 29 deletions(-) (limited to 'drivers/usb/host') diff --git a/drivers/usb/host/ehci-sched.c b/drivers/usb/host/ehci-sched.c index 1d0b49e3f192..ada5d2ba297b 100644 --- a/drivers/usb/host/ehci-sched.c +++ b/drivers/usb/host/ehci-sched.c @@ -563,7 +563,7 @@ static int qh_unlink_periodic(struct ehci_hcd *ehci, struct ehci_qh *qh) // and this qh is active in the current uframe // (and overlay token SplitXstate is false?) // THEN - // qh->hw_info1 |= __constant_cpu_to_hc32(1 << 7 /* "ignore" */); + // qh->hw_info1 |= cpu_to_hc32(1 << 7 /* "ignore" */); /* high bandwidth, or otherwise part of every microframe */ if ((period = qh->period) == 0) diff --git a/drivers/usb/host/ehci.h b/drivers/usb/host/ehci.h index 9aba560fd569..6cff195e1a36 100644 --- a/drivers/usb/host/ehci.h +++ b/drivers/usb/host/ehci.h @@ -253,7 +253,7 @@ struct ehci_qtd { /* * Now the following defines are not converted using the - * __constant_cpu_to_le32() macro anymore, since we have to support + * cpu_to_le32() macro anymore, since we have to support * "dynamic" switching between be and le support, so that the driver * can be used on one system with SoC EHCI controller using big-endian * descriptors as well as a normal little-endian PCI EHCI controller. diff --git a/drivers/usb/host/isp1760-hcd.c b/drivers/usb/host/isp1760-hcd.c index 8ee2f4159845..3172c0fd2a6d 100644 --- a/drivers/usb/host/isp1760-hcd.c +++ b/drivers/usb/host/isp1760-hcd.c @@ -644,7 +644,7 @@ static void transform_add_int(struct isp1760_hcd *priv, struct isp1760_qh *qh, if (urb->dev->speed != USB_SPEED_HIGH) { /* split */ - ptd->dw5 = __constant_cpu_to_le32(0x1c); + ptd->dw5 = cpu_to_le32(0x1c); if (qh->period >= 32) period = qh->period / 2; @@ -1054,7 +1054,7 @@ static void do_atl_int(struct usb_hcd *usb_hcd) priv_write_copy(priv, (u32 *)&ptd, usb_hcd->regs + atl_regs, sizeof(ptd)); - ptd.dw0 |= __constant_cpu_to_le32(PTD_VALID); + ptd.dw0 |= cpu_to_le32(PTD_VALID); priv_write_copy(priv, (u32 *)&ptd, usb_hcd->regs + atl_regs, sizeof(ptd)); diff --git a/drivers/usb/host/oxu210hp-hcd.c b/drivers/usb/host/oxu210hp-hcd.c index 75548f7c716b..2947c69b3476 100644 --- a/drivers/usb/host/oxu210hp-hcd.c +++ b/drivers/usb/host/oxu210hp-hcd.c @@ -845,14 +845,14 @@ static inline void qh_update(struct oxu_hcd *oxu, is_out = !(qtd->hw_token & cpu_to_le32(1 << 8)); epnum = (le32_to_cpup(&qh->hw_info1) >> 8) & 0x0f; if (unlikely(!usb_gettoggle(qh->dev, epnum, is_out))) { - qh->hw_token &= ~__constant_cpu_to_le32(QTD_TOGGLE); + qh->hw_token &= ~cpu_to_le32(QTD_TOGGLE); usb_settoggle(qh->dev, epnum, is_out, 1); } } /* HC must see latest qtd and qh data before we clear ACTIVE+HALT */ wmb(); - qh->hw_token &= __constant_cpu_to_le32(QTD_TOGGLE | QTD_STS_PING); + qh->hw_token &= cpu_to_le32(QTD_TOGGLE | QTD_STS_PING); } /* If it weren't for a common silicon quirk (writing the dummy into the qh @@ -937,7 +937,7 @@ __acquires(oxu->lock) struct ehci_qh *qh = (struct ehci_qh *) urb->hcpriv; /* S-mask in a QH means it's an interrupt urb */ - if ((qh->hw_info2 & __constant_cpu_to_le32(QH_SMASK)) != 0) { + if ((qh->hw_info2 & cpu_to_le32(QH_SMASK)) != 0) { /* ... update hc-wide periodic stats (for usbfs) */ oxu_to_hcd(oxu)->self.bandwidth_int_reqs--; @@ -981,7 +981,7 @@ static void unlink_async(struct oxu_hcd *oxu, struct ehci_qh *qh); static void intr_deschedule(struct oxu_hcd *oxu, struct ehci_qh *qh); static int qh_schedule(struct oxu_hcd *oxu, struct ehci_qh *qh); -#define HALT_BIT __constant_cpu_to_le32(QTD_STS_HALT) +#define HALT_BIT cpu_to_le32(QTD_STS_HALT) /* Process and free completed qtds for a qh, returning URBs to drivers. * Chases up to qh->hw_current. Returns number of completions called, @@ -1160,7 +1160,7 @@ halt: /* should be rare for periodic transfers, * except maybe high bandwidth ... */ - if ((__constant_cpu_to_le32(QH_SMASK) + if ((cpu_to_le32(QH_SMASK) & qh->hw_info2) != 0) { intr_deschedule(oxu, qh); (void) qh_schedule(oxu, qh); @@ -1350,7 +1350,7 @@ static struct list_head *qh_urb_transaction(struct oxu_hcd *oxu, } /* by default, enable interrupt on urb completion */ - qtd->hw_token |= __constant_cpu_to_le32(QTD_IOC); + qtd->hw_token |= cpu_to_le32(QTD_IOC); return head; cleanup: @@ -1539,7 +1539,7 @@ static void qh_link_async(struct oxu_hcd *oxu, struct ehci_qh *qh) /* qtd completions reported later by interrupt */ } -#define QH_ADDR_MASK __constant_cpu_to_le32(0x7f) +#define QH_ADDR_MASK cpu_to_le32(0x7f) /* * For control/bulk/interrupt, return QH with these TDs appended. @@ -2012,7 +2012,7 @@ static void qh_unlink_periodic(struct oxu_hcd *oxu, struct ehci_qh *qh) * and this qh is active in the current uframe * (and overlay token SplitXstate is false?) * THEN - * qh->hw_info1 |= __constant_cpu_to_le32(1 << 7 "ignore"); + * qh->hw_info1 |= cpu_to_le32(1 << 7 "ignore"); */ /* high bandwidth, or otherwise part of every microframe */ @@ -2057,7 +2057,7 @@ static void intr_deschedule(struct oxu_hcd *oxu, struct ehci_qh *qh) * active high speed queues may need bigger delays... */ if (list_empty(&qh->qtd_list) - || (__constant_cpu_to_le32(QH_CMASK) & qh->hw_info2) != 0) + || (cpu_to_le32(QH_CMASK) & qh->hw_info2) != 0) wait = 2; else wait = 55; /* worst case: 3 * 1024 */ @@ -2183,10 +2183,10 @@ static int qh_schedule(struct oxu_hcd *oxu, struct ehci_qh *qh) qh->start = frame; /* reset S-frame and (maybe) C-frame masks */ - qh->hw_info2 &= __constant_cpu_to_le32(~(QH_CMASK | QH_SMASK)); + qh->hw_info2 &= cpu_to_le32(~(QH_CMASK | QH_SMASK)); qh->hw_info2 |= qh->period ? cpu_to_le32(1 << uframe) - : __constant_cpu_to_le32(QH_SMASK); + : cpu_to_le32(QH_SMASK); qh->hw_info2 |= c_mask; } else oxu_dbg(oxu, "reused qh %p schedule\n", qh); diff --git a/drivers/usb/host/oxu210hp.h b/drivers/usb/host/oxu210hp.h index 8910e271cc7d..1c216ad9aad2 100644 --- a/drivers/usb/host/oxu210hp.h +++ b/drivers/usb/host/oxu210hp.h @@ -235,21 +235,21 @@ struct ehci_qtd { } __attribute__ ((aligned(32))); /* mask NakCnt+T in qh->hw_alt_next */ -#define QTD_MASK __constant_cpu_to_le32 (~0x1f) +#define QTD_MASK cpu_to_le32 (~0x1f) #define IS_SHORT_READ(token) (QTD_LENGTH(token) != 0 && QTD_PID(token) == 1) /* Type tag from {qh, itd, sitd, fstn}->hw_next */ -#define Q_NEXT_TYPE(dma) ((dma) & __constant_cpu_to_le32 (3 << 1)) +#define Q_NEXT_TYPE(dma) ((dma) & cpu_to_le32 (3 << 1)) /* values for that type tag */ -#define Q_TYPE_QH __constant_cpu_to_le32 (1 << 1) +#define Q_TYPE_QH cpu_to_le32 (1 << 1) /* next async queue entry, or pointer to interrupt/periodic QH */ #define QH_NEXT(dma) (cpu_to_le32(((u32)dma)&~0x01f)|Q_TYPE_QH) /* for periodic/async schedules and qtd lists, mark end of list */ -#define EHCI_LIST_END __constant_cpu_to_le32(1) /* "null pointer" to hw */ +#define EHCI_LIST_END cpu_to_le32(1) /* "null pointer" to hw */ /* * Entries in periodic shadow table are pointers to one of four kinds diff --git a/drivers/usb/host/uhci-hcd.h b/drivers/usb/host/uhci-hcd.h index 7d01c5677f92..26bd1b2bcbfc 100644 --- a/drivers/usb/host/uhci-hcd.h +++ b/drivers/usb/host/uhci-hcd.h @@ -73,11 +73,11 @@ #define USBLEGSUP_RWC 0x8f00 /* the R/WC bits */ #define USBLEGSUP_RO 0x5040 /* R/O and reserved bits */ -#define UHCI_PTR_BITS __constant_cpu_to_le32(0x000F) -#define UHCI_PTR_TERM __constant_cpu_to_le32(0x0001) -#define UHCI_PTR_QH __constant_cpu_to_le32(0x0002) -#define UHCI_PTR_DEPTH __constant_cpu_to_le32(0x0004) -#define UHCI_PTR_BREADTH __constant_cpu_to_le32(0x0000) +#define UHCI_PTR_BITS cpu_to_le32(0x000F) +#define UHCI_PTR_TERM cpu_to_le32(0x0001) +#define UHCI_PTR_QH cpu_to_le32(0x0002) +#define UHCI_PTR_DEPTH cpu_to_le32(0x0004) +#define UHCI_PTR_BREADTH cpu_to_le32(0x0000) #define UHCI_NUMFRAMES 1024 /* in the frame list [array] */ #define UHCI_MAX_SOF_NUMBER 2047 /* in an SOF packet */ diff --git a/drivers/usb/host/uhci-q.c b/drivers/usb/host/uhci-q.c index 5631d89c8730..58f873679145 100644 --- a/drivers/usb/host/uhci-q.c +++ b/drivers/usb/host/uhci-q.c @@ -402,7 +402,7 @@ static void uhci_fixup_toggles(struct uhci_qh *qh, int skip_first) /* Otherwise all the toggles in the URB have to be switched */ } else { list_for_each_entry(td, &urbp->td_list, list) { - td->token ^= __constant_cpu_to_le32( + td->token ^= cpu_to_le32( TD_TOKEN_TOGGLE); toggle ^= 1; } @@ -883,7 +883,7 @@ static int uhci_submit_control(struct uhci_hcd *uhci, struct urb *urb, uhci_fill_td(td, 0, USB_PID_OUT | uhci_explen(0), 0); wmb(); - qh->dummy_td->status |= __constant_cpu_to_le32(TD_CTRL_ACTIVE); + qh->dummy_td->status |= cpu_to_le32(TD_CTRL_ACTIVE); qh->dummy_td = td; /* Low-speed transfers get a different queue, and won't hog the bus. @@ -1003,7 +1003,7 @@ static int uhci_submit_common(struct uhci_hcd *uhci, struct urb *urb, * fast side but not enough to justify delaying an interrupt * more than 2 or 3 URBs, so we will ignore the URB_NO_INTERRUPT * flag setting. */ - td->status |= __constant_cpu_to_le32(TD_CTRL_IOC); + td->status |= cpu_to_le32(TD_CTRL_IOC); /* * Build the new dummy TD and activate the old one @@ -1015,7 +1015,7 @@ static int uhci_submit_common(struct uhci_hcd *uhci, struct urb *urb, uhci_fill_td(td, 0, USB_PID_OUT | uhci_explen(0), 0); wmb(); - qh->dummy_td->status |= __constant_cpu_to_le32(TD_CTRL_ACTIVE); + qh->dummy_td->status |= cpu_to_le32(TD_CTRL_ACTIVE); qh->dummy_td = td; usb_settoggle(urb->dev, usb_pipeendpoint(urb->pipe), @@ -1317,7 +1317,7 @@ static int uhci_submit_isochronous(struct uhci_hcd *uhci, struct urb *urb, } /* Set the interrupt-on-completion flag on the last packet. */ - td->status |= __constant_cpu_to_le32(TD_CTRL_IOC); + td->status |= cpu_to_le32(TD_CTRL_IOC); /* Add the TDs to the frame list */ frame = urb->start_frame; -- cgit v1.2.2 From d0626808f7a6181c1c750d261da9a7a845c29e13 Mon Sep 17 00:00:00 2001 From: Randy Dunlap <randy.dunlap@oracle.com> Date: Fri, 13 Feb 2009 11:22:06 -0800 Subject: USB: fix ehci printk formats Fix ehci printk formats: drivers/usb/host/ehci-q.c:351: warning: format '%d' expects type 'int', but argument 4 has type 'size_t' drivers/usb/host/ehci-q.c:351: warning: format '%d' expects type 'int', but argument 5 has type 'size_t' Signed-off-by: Randy Dunlap <randy.dunlap@oracle.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/host/ehci-q.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/usb/host') diff --git a/drivers/usb/host/ehci-q.c b/drivers/usb/host/ehci-q.c index 01132ac74eb8..1976b1b3778c 100644 --- a/drivers/usb/host/ehci-q.c +++ b/drivers/usb/host/ehci-q.c @@ -349,7 +349,7 @@ qh_completions (struct ehci_hcd *ehci, struct ehci_qh *qh) --qh->xacterrs > 0 && !urb->unlinked) { ehci_dbg(ehci, - "detected XactErr len %d/%d retry %d\n", + "detected XactErr len %zu/%zu retry %d\n", qtd->length - QTD_LENGTH(token), qtd->length, QH_XACTERR_MAX - qh->xacterrs); -- cgit v1.2.2 From 7ea0a2bcfe40b1c525e63e931b7142ab22b64269 Mon Sep 17 00:00:00 2001 From: Alan Stern <stern@rowland.harvard.edu> Date: Thu, 5 Mar 2009 11:01:11 -0500 Subject: USB: uhci: don't use pseudo negative values The code in uhci-q.c doesn't have to use pseudo-negative values. I did it that way because it was easy and because it would give the expected output during debugging. But it doesn't have to work that way. Here's another approach. Signed-off-by: Alan Stern <stern@rowland.harvard.edu> Cc: Roel Kluin <roel.kluin@gmail.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/host/uhci-debug.c | 4 +++- drivers/usb/host/uhci-q.c | 11 ++++------- 2 files changed, 7 insertions(+), 8 deletions(-) (limited to 'drivers/usb/host') diff --git a/drivers/usb/host/uhci-debug.c b/drivers/usb/host/uhci-debug.c index 20cc58b97807..e52b954dda47 100644 --- a/drivers/usb/host/uhci-debug.c +++ b/drivers/usb/host/uhci-debug.c @@ -118,7 +118,9 @@ static int uhci_show_urbp(struct urb_priv *urbp, char *buf, int len, int space) } out += sprintf(out, "%s%s", ptype, (urbp->fsbr ? " FSBR" : "")); - out += sprintf(out, " Actlen=%d", urbp->urb->actual_length); + out += sprintf(out, " Actlen=%d%s", urbp->urb->actual_length, + (urbp->qh->type == USB_ENDPOINT_XFER_CONTROL ? + "-8" : "")); if (urbp->urb->unlinked) out += sprintf(out, " Unlinked=%d", urbp->urb->unlinked); diff --git a/drivers/usb/host/uhci-q.c b/drivers/usb/host/uhci-q.c index 58f873679145..3e5807d14ffb 100644 --- a/drivers/usb/host/uhci-q.c +++ b/drivers/usb/host/uhci-q.c @@ -899,8 +899,6 @@ static int uhci_submit_control(struct uhci_hcd *uhci, struct urb *urb, } if (qh->state != QH_STATE_ACTIVE) qh->skel = skel; - - urb->actual_length = -8; /* Account for the SETUP packet */ return 0; nomem: @@ -1494,11 +1492,10 @@ __acquires(uhci->lock) if (qh->type == USB_ENDPOINT_XFER_CONTROL) { - /* urb->actual_length < 0 means the setup transaction didn't - * complete successfully. Either it failed or the URB was - * unlinked first. Regardless, don't confuse people with a - * negative length. */ - urb->actual_length = max(urb->actual_length, 0); + /* Subtract off the length of the SETUP packet from + * urb->actual_length. + */ + urb->actual_length -= min_t(u32, 8, urb->actual_length); } /* When giving back the first URB in an Isochronous queue, -- cgit v1.2.2 From 16e2e5f634f86ccda18366967c4e592eb61bc9cc Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman <gregkh@suse.de> Date: Tue, 3 Mar 2009 16:44:13 -0800 Subject: USB: make transfer_buffer_lengths in struct urb field u32 Roel Kluin pointed out that transfer_buffer_lengths in struct urb was declared as an 'int'. This patch changes this field to be 'u32' to prevent any potential negative conversion and comparison errors. This triggered a few compiler warning messages when these fields were being used with the min macro, so they have also been fixed up in this patch. Cc: Roel Kluin <roel.kluin@gmail.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/host/isp116x-hcd.c | 2 +- drivers/usb/host/r8a66597-hcd.c | 2 +- drivers/usb/host/sl811-hcd.c | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) (limited to 'drivers/usb/host') diff --git a/drivers/usb/host/isp116x-hcd.c b/drivers/usb/host/isp116x-hcd.c index 4dda31b26892..a2b305477afe 100644 --- a/drivers/usb/host/isp116x-hcd.c +++ b/drivers/usb/host/isp116x-hcd.c @@ -772,7 +772,7 @@ static int isp116x_urb_enqueue(struct usb_hcd *hcd, break; case PIPE_INTERRUPT: urb->interval = ep->period; - ep->length = min((int)ep->maxpacket, + ep->length = min_t(u32, ep->maxpacket, urb->transfer_buffer_length); /* urb submitted for already existing endpoint */ diff --git a/drivers/usb/host/r8a66597-hcd.c b/drivers/usb/host/r8a66597-hcd.c index 5e942d94aebe..713f4cf0b0dd 100644 --- a/drivers/usb/host/r8a66597-hcd.c +++ b/drivers/usb/host/r8a66597-hcd.c @@ -1394,7 +1394,7 @@ static void packet_write(struct r8a66597 *r8a66597, u16 pipenum) (int)urb->iso_frame_desc[td->iso_cnt].length); } else { buf = (u16 *)(urb->transfer_buffer + urb->actual_length); - size = min((int)bufsize, + size = min_t(u32, bufsize, urb->transfer_buffer_length - urb->actual_length); } diff --git a/drivers/usb/host/sl811-hcd.c b/drivers/usb/host/sl811-hcd.c index e106e9d48d4a..a949259f18b9 100644 --- a/drivers/usb/host/sl811-hcd.c +++ b/drivers/usb/host/sl811-hcd.c @@ -230,7 +230,7 @@ static void in_packet( writeb(usb_pipedevice(urb->pipe), data_reg); sl811_write(sl811, bank + SL11H_HOSTCTLREG, control); - ep->length = min((int)len, + ep->length = min_t(u32, len, urb->transfer_buffer_length - urb->actual_length); PACKET("IN%s/%d qh%p len%d\n", ep->nak_count ? "/retry" : "", !!usb_gettoggle(urb->dev, ep->epnum, 0), ep, len); @@ -255,7 +255,7 @@ static void out_packet( buf = urb->transfer_buffer + urb->actual_length; prefetch(buf); - len = min((int)ep->maxpacket, + len = min_t(u32, ep->maxpacket, urb->transfer_buffer_length - urb->actual_length); if (!(control & SL11H_HCTLMASK_ISOCH) -- cgit v1.2.2 From 31dbb803464d75b96212cce9052dfeaeac0819de Mon Sep 17 00:00:00 2001 From: Johannes Weiner <hannes@cmpxchg.org> Date: Wed, 4 Mar 2009 12:06:15 -0800 Subject: USB: use kzfree() Use kzfree() instead of memset() + kfree(). Signed-off-by: Johannes Weiner <hannes@cmpxchg.org> Reviewed-by: Pekka Enberg <penberg@cs.helsinki.fi> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/host/hwa-hc.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'drivers/usb/host') diff --git a/drivers/usb/host/hwa-hc.c b/drivers/usb/host/hwa-hc.c index 8582236e4cad..cbf30e515f29 100644 --- a/drivers/usb/host/hwa-hc.c +++ b/drivers/usb/host/hwa-hc.c @@ -464,8 +464,7 @@ static int __hwahc_dev_set_key(struct wusbhc *wusbhc, u8 port_idx, u32 tkid, port_idx << 8 | iface_no, keyd, keyd_len, 1000 /* FIXME: arbitrary */); - memset(keyd, 0, sizeof(*keyd)); /* clear keys etc. */ - kfree(keyd); + kzfree(keyd); /* clear keys etc. */ return result; } -- cgit v1.2.2 From 3edb8a208b5be90c829f7b19058cb63e947b1d18 Mon Sep 17 00:00:00 2001 From: Ben Dooks <ben@simtec.co.uk> Date: Thu, 26 Feb 2009 23:02:19 +0000 Subject: USB: ohci-s3c2410: remove <mach/hardware.h> include Remove the include of <mach/hardware.h>, as no definitions from it are used by the OHCI driver. Signed-off-by: Ben Dooks <ben-linux@fluff.org> Acked-by: David Brownell <dbrownell@users.sourceforge.net> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/host/ohci-s3c2410.c | 1 - 1 file changed, 1 deletion(-) (limited to 'drivers/usb/host') diff --git a/drivers/usb/host/ohci-s3c2410.c b/drivers/usb/host/ohci-s3c2410.c index f46af7a718d4..0e62d2044f7e 100644 --- a/drivers/usb/host/ohci-s3c2410.c +++ b/drivers/usb/host/ohci-s3c2410.c @@ -22,7 +22,6 @@ #include <linux/platform_device.h> #include <linux/clk.h> -#include <mach/hardware.h> #include <mach/usb-control.h> #define valid_port(idx) ((idx) == 1 || (idx) == 2) -- cgit v1.2.2 From 2dfa319a649b9de029777994b6af201c1fe81d53 Mon Sep 17 00:00:00 2001 From: Ben Dooks <ben@simtec.co.uk> Date: Thu, 26 Feb 2009 23:03:15 +0000 Subject: USB: ohci-s3c2410: fix name of bus clock The USB bus clock is usb-bus-host, so print the correct name in the dev_err() statement if we cannot find it. Signed-off-by: Ben Dooks <ben@simtec.co.uk> Acked-by: David Brownell <dbrownell@users.sourceforge.net> --- drivers/usb/host/ohci-s3c2410.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/usb/host') diff --git a/drivers/usb/host/ohci-s3c2410.c b/drivers/usb/host/ohci-s3c2410.c index 0e62d2044f7e..a7ddd5d14a51 100644 --- a/drivers/usb/host/ohci-s3c2410.c +++ b/drivers/usb/host/ohci-s3c2410.c @@ -371,7 +371,7 @@ static int usb_hcd_s3c2410_probe (const struct hc_driver *driver, usb_clk = clk_get(&dev->dev, "usb-bus-host"); if (IS_ERR(usb_clk)) { - dev_err(&dev->dev, "cannot get usb-host clock\n"); + dev_err(&dev->dev, "cannot get usb-bus-host clock\n"); retval = -ENOENT; goto err_clk; } -- cgit v1.2.2 From a9f8ec4db1d308643e13ec7638ccb5ace4d34982 Mon Sep 17 00:00:00 2001 From: Hannes Eder <hannes@hanneseder.net> Date: Fri, 27 Feb 2009 02:04:31 +0100 Subject: USB: host: fix sparse warning: Using plain integer as NULL pointer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix this sparse warning: drivers/usb/host/oxu210hp-hcd.c:2687:42: warning: Using plain integer as NULL pointer Signed-off-by: Hannes Eder <hannes@hanneseder.net> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/host/oxu210hp-hcd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/usb/host') diff --git a/drivers/usb/host/oxu210hp-hcd.c b/drivers/usb/host/oxu210hp-hcd.c index 2947c69b3476..5ac489ee3dab 100644 --- a/drivers/usb/host/oxu210hp-hcd.c +++ b/drivers/usb/host/oxu210hp-hcd.c @@ -2684,7 +2684,7 @@ static int oxu_reset(struct usb_hcd *hcd) oxu->urb_len = 0; /* FIMXE */ - hcd->self.controller->dma_mask = 0UL; + hcd->self.controller->dma_mask = NULL; if (oxu->is_otg) { oxu->caps = hcd->regs + OXU_OTG_CAP_OFFSET; -- cgit v1.2.2 From 3ba5f38f3d5143a879de132a9df71814d1f3cff0 Mon Sep 17 00:00:00 2001 From: Ben Dooks <ben@simtec.co.uk> Date: Sat, 7 Mar 2009 11:44:10 +0000 Subject: USB: ohci-hcd: Add ARCH_S3C24XX to the ohci-s3c2410.c glue The ohci-s3c2410.c glue supports both CONFIG_ARCH_S3C2410 and CONFIG_ARCH_S3C64XX so add it to the build of ohci-s3c2410.c Signed-off-by: Ben Dooks <ben@simtec.co.uk> Cc: David Brownell <dbrownell@users.sourceforge.net> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/host/ohci-hcd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/usb/host') diff --git a/drivers/usb/host/ohci-hcd.c b/drivers/usb/host/ohci-hcd.c index 5cf5f1eca4f4..d052955439c3 100644 --- a/drivers/usb/host/ohci-hcd.c +++ b/drivers/usb/host/ohci-hcd.c @@ -997,7 +997,7 @@ MODULE_LICENSE ("GPL"); #define SA1111_DRIVER ohci_hcd_sa1111_driver #endif -#ifdef CONFIG_ARCH_S3C2410 +#if defined(CONFIG_ARCH_S3C2410) || defined(CONFIG_ARCH_S3C64XX) #include "ohci-s3c2410.c" #define PLATFORM_DRIVER ohci_hcd_s3c2410_driver #endif -- cgit v1.2.2 From 49121aa14c2a372a5fd01982df900257784be63d Mon Sep 17 00:00:00 2001 From: Ben Dooks <ben@simtec.co.uk> Date: Sat, 7 Mar 2009 11:44:21 +0000 Subject: USB: S3C: Move usb-control.h to platform include The usb-control.h is needed by ohci-s3c2410.c for both S3C24XX and S3C64XX architectures, so move it to <plat/usb-control.h> Signed-off-by: Ben Dooks <ben@simtec.co.uk> Cc: David Brownell <dbrownell@users.sourceforge.net> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/host/ohci-s3c2410.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'drivers/usb/host') diff --git a/drivers/usb/host/ohci-s3c2410.c b/drivers/usb/host/ohci-s3c2410.c index a7ddd5d14a51..a68af2dd55ca 100644 --- a/drivers/usb/host/ohci-s3c2410.c +++ b/drivers/usb/host/ohci-s3c2410.c @@ -21,8 +21,7 @@ #include <linux/platform_device.h> #include <linux/clk.h> - -#include <mach/usb-control.h> +#include <plat/usb-control.h> #define valid_port(idx) ((idx) == 1 || (idx) == 2) -- cgit v1.2.2 From e1e609be49c9d345e8b67a122a7cdae48ad27c7e Mon Sep 17 00:00:00 2001 From: Yoshihiro Shimoda <shimoda.yoshihiro@renesas.com> Date: Thu, 19 Mar 2009 14:18:15 +0900 Subject: USB: r8a66597-hcd: suspend/resume support Fix the problem that system cannot suspend. Signed-off-by: Yoshihiro Shimoda <shimoda.yoshihiro@renesas.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/host/r8a66597-hcd.c | 101 +++++++++++++++++++++++++++++++++++++++- drivers/usb/host/r8a66597.h | 2 + 2 files changed, 102 insertions(+), 1 deletion(-) (limited to 'drivers/usb/host') diff --git a/drivers/usb/host/r8a66597-hcd.c b/drivers/usb/host/r8a66597-hcd.c index 713f4cf0b0dd..f1626e58c141 100644 --- a/drivers/usb/host/r8a66597-hcd.c +++ b/drivers/usb/host/r8a66597-hcd.c @@ -1013,6 +1013,9 @@ static void r8a66597_check_syssts(struct r8a66597 *r8a66597, int port, r8a66597_write(r8a66597, ~DTCH, get_intsts_reg(port)); r8a66597_bset(r8a66597, DTCHE, get_intenb_reg(port)); + + if (r8a66597->bus_suspended) + usb_hcd_resume_root_hub(r8a66597_to_hcd(r8a66597)); } /* this function must be called with interrupt disabled */ @@ -1614,6 +1617,11 @@ static irqreturn_t r8a66597_irq(struct usb_hcd *hcd) r8a66597_bclr(r8a66597, DTCHE, INTENB2); r8a66597_usb_disconnect(r8a66597, 1); } + if (mask2 & BCHG) { + r8a66597_write(r8a66597, ~BCHG, INTSTS2); + r8a66597_bclr(r8a66597, BCHGE, INTENB2); + usb_hcd_resume_root_hub(r8a66597_to_hcd(r8a66597)); + } } if (mask1) { @@ -1629,6 +1637,12 @@ static irqreturn_t r8a66597_irq(struct usb_hcd *hcd) r8a66597_bclr(r8a66597, DTCHE, INTENB1); r8a66597_usb_disconnect(r8a66597, 0); } + if (mask1 & BCHG) { + r8a66597_write(r8a66597, ~BCHG, INTSTS1); + r8a66597_bclr(r8a66597, BCHGE, INTENB1); + usb_hcd_resume_root_hub(r8a66597_to_hcd(r8a66597)); + } + if (mask1 & SIGN) { r8a66597_write(r8a66597, ~SIGN, INTSTS1); status = get_urb_error(r8a66597, 0); @@ -2140,7 +2154,7 @@ static int r8a66597_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, switch (wValue) { case USB_PORT_FEAT_ENABLE: - rh->port &= (1 << USB_PORT_FEAT_POWER); + rh->port &= ~(1 << USB_PORT_FEAT_POWER); break; case USB_PORT_FEAT_SUSPEND: break; @@ -2212,6 +2226,68 @@ error: return ret; } +#if defined(CONFIG_PM) +static int r8a66597_bus_suspend(struct usb_hcd *hcd) +{ + struct r8a66597 *r8a66597 = hcd_to_r8a66597(hcd); + int port; + + dbg("%s", __func__); + + for (port = 0; port < R8A66597_MAX_ROOT_HUB; port++) { + struct r8a66597_root_hub *rh = &r8a66597->root_hub[port]; + unsigned long dvstctr_reg = get_dvstctr_reg(port); + + if (!(rh->port & (1 << USB_PORT_FEAT_ENABLE))) + continue; + + dbg("suspend port = %d", port); + r8a66597_bclr(r8a66597, UACT, dvstctr_reg); /* suspend */ + rh->port |= 1 << USB_PORT_FEAT_SUSPEND; + + if (rh->dev->udev->do_remote_wakeup) { + msleep(3); /* waiting last SOF */ + r8a66597_bset(r8a66597, RWUPE, dvstctr_reg); + r8a66597_write(r8a66597, ~BCHG, get_intsts_reg(port)); + r8a66597_bset(r8a66597, BCHGE, get_intenb_reg(port)); + } + } + + r8a66597->bus_suspended = 1; + + return 0; +} + +static int r8a66597_bus_resume(struct usb_hcd *hcd) +{ + struct r8a66597 *r8a66597 = hcd_to_r8a66597(hcd); + int port; + + dbg("%s", __func__); + + for (port = 0; port < R8A66597_MAX_ROOT_HUB; port++) { + struct r8a66597_root_hub *rh = &r8a66597->root_hub[port]; + unsigned long dvstctr_reg = get_dvstctr_reg(port); + + if (!(rh->port & (1 << USB_PORT_FEAT_SUSPEND))) + continue; + + dbg("resume port = %d", port); + rh->port &= ~(1 << USB_PORT_FEAT_SUSPEND); + rh->port |= 1 << USB_PORT_FEAT_C_SUSPEND; + r8a66597_mdfy(r8a66597, RESUME, RESUME | UACT, dvstctr_reg); + msleep(50); + r8a66597_mdfy(r8a66597, UACT, RESUME | UACT, dvstctr_reg); + } + + return 0; + +} +#else +#define r8a66597_bus_suspend NULL +#define r8a66597_bus_resume NULL +#endif + static struct hc_driver r8a66597_hc_driver = { .description = hcd_name, .hcd_priv_size = sizeof(struct r8a66597), @@ -2242,16 +2318,39 @@ static struct hc_driver r8a66597_hc_driver = { */ .hub_status_data = r8a66597_hub_status_data, .hub_control = r8a66597_hub_control, + .bus_suspend = r8a66597_bus_suspend, + .bus_resume = r8a66597_bus_resume, }; #if defined(CONFIG_PM) static int r8a66597_suspend(struct platform_device *pdev, pm_message_t state) { + struct r8a66597 *r8a66597 = dev_get_drvdata(&pdev->dev); + int port; + + dbg("%s", __func__); + + disable_controller(r8a66597); + + for (port = 0; port < R8A66597_MAX_ROOT_HUB; port++) { + struct r8a66597_root_hub *rh = &r8a66597->root_hub[port]; + + rh->port = 0x00000000; + } + return 0; } static int r8a66597_resume(struct platform_device *pdev) { + struct r8a66597 *r8a66597 = dev_get_drvdata(&pdev->dev); + struct usb_hcd *hcd = r8a66597_to_hcd(r8a66597); + + dbg("%s", __func__); + + enable_controller(r8a66597); + usb_root_hub_lost_power(hcd->self.root_hub); + return 0; } #else /* if defined(CONFIG_PM) */ diff --git a/drivers/usb/host/r8a66597.h b/drivers/usb/host/r8a66597.h index ecacde4d69b0..f49208f1bb74 100644 --- a/drivers/usb/host/r8a66597.h +++ b/drivers/usb/host/r8a66597.h @@ -504,6 +504,8 @@ struct r8a66597 { struct list_head child_device; unsigned long child_connect_map[4]; + + unsigned bus_suspended:1; }; static inline struct r8a66597 *hcd_to_r8a66597(struct usb_hcd *hcd) -- cgit v1.2.2 From e6bdfe36e52f0e552b50acf49a82851eeb122fde Mon Sep 17 00:00:00 2001 From: Catalin Marinas <catalin.marinas@arm.com> Date: Mon, 23 Mar 2009 12:38:16 +0000 Subject: USB: isp1760: Add a delay before reading the SKIPMAP registers in isp1760-hcd.c The data read from the SKIPMAP registers is not immediately available after writing and the driver panics when a packet is enqueued from the interrupt handler. This patch adds an ndelay(195) before these registers are read (delay value mentioned in section 15.1.1.3 of the ISP1760 data sheet). Signed-off-by: Catalin Marinas <catalin.marinas@arm.com> Acked-by: Sebastian Andrzej Siewior <bigeasy@linutronix.de> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/host/isp1760-hcd.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) (limited to 'drivers/usb/host') diff --git a/drivers/usb/host/isp1760-hcd.c b/drivers/usb/host/isp1760-hcd.c index 3172c0fd2a6d..cd07ea3f0c63 100644 --- a/drivers/usb/host/isp1760-hcd.c +++ b/drivers/usb/host/isp1760-hcd.c @@ -819,6 +819,13 @@ static void enqueue_an_ATL_packet(struct usb_hcd *hcd, struct isp1760_qh *qh, u32 atl_regs, payload; u32 buffstatus; + /* + * When this function is called from the interrupt handler to enqueue + * a follow-up packet, the SKIP register gets written and read back + * almost immediately. With ISP1761, this register requires a delay of + * 195ns between a write and subsequent read (see section 15.1.1.3). + */ + ndelay(195); skip_map = isp1760_readl(hcd->regs + HC_ATL_PTD_SKIPMAP_REG); BUG_ON(!skip_map); @@ -853,6 +860,13 @@ static void enqueue_an_INT_packet(struct usb_hcd *hcd, struct isp1760_qh *qh, u32 int_regs, payload; u32 buffstatus; + /* + * When this function is called from the interrupt handler to enqueue + * a follow-up packet, the SKIP register gets written and read back + * almost immediately. With ISP1761, this register requires a delay of + * 195ns between a write and subsequent read (see section 15.1.1.3). + */ + ndelay(195); skip_map = isp1760_readl(hcd->regs + HC_INT_PTD_SKIPMAP_REG); BUG_ON(!skip_map); -- cgit v1.2.2 From 6866ac9db02ac1ec71f2aa720a1019581f69a725 Mon Sep 17 00:00:00 2001 From: Sachin Sant <sachinp@in.ibm.com> Date: Mon, 16 Feb 2009 19:10:11 +0530 Subject: USB: FHCI: use dev_name() in place of bus_id. Replace references to bus_id with dev_name() to fix fhci driver build break. drivers/usb/host/fhci-hcd.c:586: error: struct device has no member named bus_id drivers/usb/host/fhci-hcd.c:653: error: struct device has no member named bus_id drivers/usb/host/fhci-dbg.c:111: error: struct device has no member named bus_id Signed-off-by: Sachin Sant <sachinp@in.ibm.com> Acked-by: Anton Vorontsov <avorontsov@ru.mvista.com> Cc: Kay Sievers <kay.sievers@vrfy.org> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de> --- drivers/usb/host/fhci-dbg.c | 2 +- drivers/usb/host/fhci-hcd.c | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'drivers/usb/host') diff --git a/drivers/usb/host/fhci-dbg.c b/drivers/usb/host/fhci-dbg.c index 34e14edf390b..ea8a4255c5da 100644 --- a/drivers/usb/host/fhci-dbg.c +++ b/drivers/usb/host/fhci-dbg.c @@ -108,7 +108,7 @@ void fhci_dfs_create(struct fhci_hcd *fhci) { struct device *dev = fhci_to_hcd(fhci)->self.controller; - fhci->dfs_root = debugfs_create_dir(dev->bus_id, NULL); + fhci->dfs_root = debugfs_create_dir(dev_name(dev), NULL); if (!fhci->dfs_root) { WARN_ON(1); return; diff --git a/drivers/usb/host/fhci-hcd.c b/drivers/usb/host/fhci-hcd.c index ba622cc8a9ba..0951818ef93b 100644 --- a/drivers/usb/host/fhci-hcd.c +++ b/drivers/usb/host/fhci-hcd.c @@ -583,7 +583,7 @@ static int __devinit of_fhci_probe(struct of_device *ofdev, if (sprop && strcmp(sprop, "host")) return -ENODEV; - hcd = usb_create_hcd(&fhci_driver, dev, dev->bus_id); + hcd = usb_create_hcd(&fhci_driver, dev, dev_name(dev)); if (!hcd) { dev_err(dev, "could not create hcd\n"); return -ENOMEM; @@ -650,7 +650,7 @@ static int __devinit of_fhci_probe(struct of_device *ofdev, } } - ret = gpio_request(gpio, dev->bus_id); + ret = gpio_request(gpio, dev_name(dev)); if (ret) { dev_err(dev, "failed to request gpio %d", i); goto err_gpios; -- cgit v1.2.2