diff options
Diffstat (limited to 'drivers/usb/host')
41 files changed, 8915 insertions, 391 deletions
diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig index f3a75a929e0..2c63bfb1f8d 100644 --- a/drivers/usb/host/Kconfig +++ b/drivers/usb/host/Kconfig @@ -96,6 +96,19 @@ config USB_EHCI_HCD_PPC_OF Enables support for the USB controller present on the PowerPC OpenFirmware platform bus. +config USB_OXU210HP_HCD + tristate "OXU210HP HCD support" + depends on USB + ---help--- + The OXU210HP is an USB host/OTG/device controller. Enable this + option if your board has this chip. If unsure, say N. + + This driver does not support isochronous transfers and doesn't + implement OTG nor USB device controllers. + + To compile this driver as a module, choose M here: the + module will be called oxu210hp-hcd. + config USB_ISP116X_HCD tristate "ISP116X HCD support" depends on USB @@ -127,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 @@ -225,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 23be2222404..f163571e33d 100644 --- a/drivers/usb/host/Makefile +++ b/drivers/usb/host/Makefile @@ -7,15 +7,22 @@ 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/ obj-$(CONFIG_PCI) += pci-quirks.o obj-$(CONFIG_USB_EHCI_HCD) += ehci-hcd.o +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/ehci-dbg.c b/drivers/usb/host/ehci-dbg.c index 0cb53ca8d34..7f4ace73d44 100644 --- a/drivers/usb/host/ehci-dbg.c +++ b/drivers/usb/host/ehci-dbg.c @@ -455,9 +455,7 @@ static void qh_lines ( (scratch >> 16) & 0x7fff, scratch, td->urb); - if (temp < 0) - temp = 0; - else if (size < temp) + if (size < temp) temp = size; size -= temp; next += temp; @@ -466,9 +464,7 @@ static void qh_lines ( } temp = snprintf (next, size, "\n"); - if (temp < 0) - temp = 0; - else if (size < temp) + if (size < temp) temp = size; size -= temp; next += temp; diff --git a/drivers/usb/host/ehci-hub.c b/drivers/usb/host/ehci-hub.c index 218f9660d7e..97a53a48a3d 100644 --- a/drivers/usb/host/ehci-hub.c +++ b/drivers/usb/host/ehci-hub.c @@ -194,6 +194,7 @@ static int ehci_bus_resume (struct usb_hcd *hcd) u32 temp; u32 power_okay; int i; + u8 resume_needed = 0; if (time_before (jiffies, ehci->next_statechange)) msleep(5); @@ -228,7 +229,9 @@ static int ehci_bus_resume (struct usb_hcd *hcd) /* Some controller/firmware combinations need a delay during which * they set up the port statuses. See Bugzilla #8190. */ - mdelay(8); + spin_unlock_irq(&ehci->lock); + msleep(8); + spin_lock_irq(&ehci->lock); /* manually resume the ports we suspended during bus_suspend() */ i = HCS_N_PORTS (ehci->hcs_params); @@ -236,12 +239,21 @@ static int ehci_bus_resume (struct usb_hcd *hcd) temp = ehci_readl(ehci, &ehci->regs->port_status [i]); temp &= ~(PORT_RWC_BITS | PORT_WAKE_BITS); if (test_bit(i, &ehci->bus_suspended) && - (temp & PORT_SUSPEND)) + (temp & PORT_SUSPEND)) { temp |= PORT_RESUME; + resume_needed = 1; + } ehci_writel(ehci, temp, &ehci->regs->port_status [i]); } + + /* msleep for 20ms only if code is trying to resume port */ + if (resume_needed) { + spin_unlock_irq(&ehci->lock); + msleep(20); + spin_lock_irq(&ehci->lock); + } + i = HCS_N_PORTS (ehci->hcs_params); - mdelay (20); while (i--) { temp = ehci_readl(ehci, &ehci->regs->port_status [i]); if (test_bit(i, &ehci->bus_suspended) && @@ -422,8 +434,15 @@ static int check_reset_complete ( port_status &= ~PORT_RWC_BITS; ehci_writel(ehci, port_status, status_reg); - } else + /* ensure 440EPX ohci controller state is operational */ + if (ehci->has_amcc_usb23) + set_ohci_hcfs(ehci, 1); + } else { ehci_dbg (ehci, "port %d high speed\n", index + 1); + /* ensure 440EPx ohci controller state is suspended */ + if (ehci->has_amcc_usb23) + set_ohci_hcfs(ehci, 0); + } return port_status; } diff --git a/drivers/usb/host/ehci-pci.c b/drivers/usb/host/ehci-pci.c index 36864f95844..bb21fb0a496 100644 --- a/drivers/usb/host/ehci-pci.c +++ b/drivers/usb/host/ehci-pci.c @@ -219,15 +219,19 @@ static int ehci_pci_setup(struct usb_hcd *hcd) /* Serial Bus Release Number is at PCI 0x60 offset */ pci_read_config_byte(pdev, 0x60, &ehci->sbrn); - /* Workaround current PCI init glitch: wakeup bits aren't - * being set from PCI PM capability. + /* Keep this around for a while just in case some EHCI + * implementation uses legacy PCI PM support. This test + * can be removed on 17 Dec 2009 if the dev_warn() hasn't + * been triggered by then. */ if (!device_can_wakeup(&pdev->dev)) { u16 port_wake; pci_read_config_word(pdev, 0x62, &port_wake); - if (port_wake & 0x0001) - device_init_wakeup(&pdev->dev, 1); + if (port_wake & 0x0001) { + dev_warn(&pdev->dev, "Enabling legacy PCI PM\n"); + device_set_wakeup_capable(&pdev->dev, 1); + } } #ifdef CONFIG_USB_SUSPEND @@ -428,6 +432,7 @@ 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/ehci-ppc-of.c b/drivers/usb/host/ehci-ppc-of.c index b018deed2e8..ef732b704f5 100644 --- a/drivers/usb/host/ehci-ppc-of.c +++ b/drivers/usb/host/ehci-ppc-of.c @@ -107,11 +107,13 @@ ehci_hcd_ppc_of_probe(struct of_device *op, const struct of_device_id *match) { struct device_node *dn = op->node; struct usb_hcd *hcd; - struct ehci_hcd *ehci; + struct ehci_hcd *ehci = NULL; struct resource res; int irq; int rv; + struct device_node *np; + if (usb_disabled()) return -ENODEV; @@ -149,6 +151,20 @@ ehci_hcd_ppc_of_probe(struct of_device *op, const struct of_device_id *match) } ehci = hcd_to_ehci(hcd); + np = of_find_compatible_node(NULL, NULL, "ibm,usb-ohci-440epx"); + if (np != NULL) { + /* claim we really affected by usb23 erratum */ + if (!of_address_to_resource(np, 0, &res)) + ehci->ohci_hcctrl_reg = ioremap(res.start + + OHCI_HCCTRL_OFFSET, OHCI_HCCTRL_LEN); + else + pr_debug(__FILE__ ": no ohci offset in fdt\n"); + if (!ehci->ohci_hcctrl_reg) { + pr_debug(__FILE__ ": ioremap for ohci hcctrl failed\n"); + } else { + ehci->has_amcc_usb23 = 1; + } + } if (of_get_property(dn, "big-endian", NULL)) { ehci->big_endian_mmio = 1; @@ -181,6 +197,9 @@ err_ioremap: irq_dispose_mapping(irq); err_irq: release_mem_region(hcd->rsrc_start, hcd->rsrc_len); + + if (ehci->has_amcc_usb23) + iounmap(ehci->ohci_hcctrl_reg); err_rmr: usb_put_hcd(hcd); @@ -191,6 +210,11 @@ err_rmr: static int ehci_hcd_ppc_of_remove(struct of_device *op) { struct usb_hcd *hcd = dev_get_drvdata(&op->dev); + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + + struct device_node *np; + struct resource res; + dev_set_drvdata(&op->dev, NULL); dev_dbg(&op->dev, "stopping PPC-OF USB Controller\n"); @@ -201,6 +225,25 @@ static int ehci_hcd_ppc_of_remove(struct of_device *op) irq_dispose_mapping(hcd->irq); release_mem_region(hcd->rsrc_start, hcd->rsrc_len); + /* use request_mem_region to test if the ohci driver is loaded. if so + * ensure the ohci core is operational. + */ + if (ehci->has_amcc_usb23) { + np = of_find_compatible_node(NULL, NULL, "ibm,usb-ohci-440epx"); + if (np != NULL) { + if (!of_address_to_resource(np, 0, &res)) + if (!request_mem_region(res.start, + 0x4, hcd_name)) + set_ohci_hcfs(ehci, 1); + else + release_mem_region(res.start, 0x4); + else + pr_debug(__FILE__ ": no ohci offset in fdt\n"); + of_node_put(np); + } + + iounmap(ehci->ohci_hcctrl_reg); + } usb_put_hcd(hcd); return 0; diff --git a/drivers/usb/host/ehci.h b/drivers/usb/host/ehci.h index c7d4b5a06bd..fb7054ccf4f 100644 --- a/drivers/usb/host/ehci.h +++ b/drivers/usb/host/ehci.h @@ -120,6 +120,16 @@ struct ehci_hcd { /* one per controller */ unsigned has_fsl_port_bug:1; /* FreeScale */ unsigned big_endian_mmio:1; unsigned big_endian_desc:1; + unsigned has_amcc_usb23:1; + + /* required for usb32 quirk */ + #define OHCI_CTRL_HCFS (3 << 6) + #define OHCI_USB_OPER (2 << 6) + #define OHCI_USB_SUSPEND (3 << 6) + + #define OHCI_HCCTRL_OFFSET 0x4 + #define OHCI_HCCTRL_LEN 0x4 + __hc32 *ohci_hcctrl_reg; u8 sbrn; /* packed release number */ @@ -636,6 +646,30 @@ static inline void ehci_writel(const struct ehci_hcd *ehci, #endif } +/* + * On certain ppc-44x SoC there is a HW issue, that could only worked around with + * explicit suspend/operate of OHCI. This function hereby makes sense only on that arch. + * Other common bits are dependant on has_amcc_usb23 quirk flag. + */ +#ifdef CONFIG_44x +static inline void set_ohci_hcfs(struct ehci_hcd *ehci, int operational) +{ + u32 hc_control; + + hc_control = (readl_be(ehci->ohci_hcctrl_reg) & ~OHCI_CTRL_HCFS); + if (operational) + hc_control |= OHCI_USB_OPER; + else + hc_control |= OHCI_USB_SUSPEND; + + writel_be(hc_control, ehci->ohci_hcctrl_reg); + (void) readl_be(ehci->ohci_hcctrl_reg); +} +#else +static inline void set_ohci_hcfs(struct ehci_hcd *ehci, int operational) +{ } +#endif + /*-------------------------------------------------------------------------*/ /* diff --git a/drivers/usb/host/fhci-dbg.c b/drivers/usb/host/fhci-dbg.c new file mode 100644 index 00000000000..34e14edf390 --- /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 00000000000..ba622cc8a9b --- /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 00000000000..0cfaedc3e12 --- /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 00000000000..2c0736c9971 --- /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 00000000000..b0a1446ba29 --- /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 00000000000..bb63b68ddb7 --- /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 00000000000..b4033229031 --- /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 00000000000..7116284ed21 --- /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 */ diff --git a/drivers/usb/host/hwa-hc.c b/drivers/usb/host/hwa-hc.c index 64be4d88df1..8582236e4ca 100644 --- a/drivers/usb/host/hwa-hc.c +++ b/drivers/usb/host/hwa-hc.c @@ -54,7 +54,6 @@ * DWA). */ #include <linux/kernel.h> -#include <linux/version.h> #include <linux/init.h> #include <linux/module.h> #include <linux/workqueue.h> @@ -63,16 +62,12 @@ #include "../wusbcore/wa-hc.h" #include "../wusbcore/wusbhc.h" -#define D_LOCAL 0 -#include <linux/uwb/debug.h> - struct hwahc { struct wusbhc wusbhc; /* has to be 1st */ struct wahc wa; - u8 buffer[16]; /* for misc usb transactions */ }; -/** +/* * FIXME should be wusbhc * * NOTE: we need to cache the Cluster ID because later...there is no @@ -126,7 +121,6 @@ static int hwahc_op_reset(struct usb_hcd *usb_hcd) struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc); struct device *dev = &hwahc->wa.usb_iface->dev; - d_fnstart(4, dev, "(hwahc %p)\n", hwahc); mutex_lock(&wusbhc->mutex); wa_nep_disarm(&hwahc->wa); result = __wa_set_feature(&hwahc->wa, WA_RESET); @@ -134,7 +128,6 @@ static int hwahc_op_reset(struct usb_hcd *usb_hcd) dev_err(dev, "error commanding HC to reset: %d\n", result); goto error_unlock; } - d_printf(3, dev, "reset: waiting for device to change state\n"); result = __wa_wait_status(&hwahc->wa, WA_STATUS_RESETTING, 0); if (result < 0) { dev_err(dev, "error waiting for HC to reset: %d\n", result); @@ -142,7 +135,6 @@ static int hwahc_op_reset(struct usb_hcd *usb_hcd) } error_unlock: mutex_unlock(&wusbhc->mutex); - d_fnend(4, dev, "(hwahc %p) = %d\n", hwahc, result); return result; } @@ -155,15 +147,9 @@ static int hwahc_op_start(struct usb_hcd *usb_hcd) int result; struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd); struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc); - struct device *dev = &hwahc->wa.usb_iface->dev; - /* Set up a Host Info WUSB Information Element */ - d_fnstart(4, dev, "(hwahc %p)\n", hwahc); result = -ENOSPC; mutex_lock(&wusbhc->mutex); - /* Start the numbering from the top so that the bottom - * range of the unauth addr space is used for devices, - * the top for HCs; use 0xfe - RC# */ addr = wusb_cluster_id_get(); if (addr == 0) goto error_cluster_id_get; @@ -171,22 +157,14 @@ static int hwahc_op_start(struct usb_hcd *usb_hcd) if (result < 0) goto error_set_cluster_id; - result = wa_nep_arm(&hwahc->wa, GFP_KERNEL); - if (result < 0) { - dev_err(dev, "cannot listen to notifications: %d\n", result); - goto error_stop; - } usb_hcd->uses_new_polling = 1; usb_hcd->poll_rh = 1; usb_hcd->state = HC_STATE_RUNNING; result = 0; out: mutex_unlock(&wusbhc->mutex); - d_fnend(4, dev, "(hwahc %p) = %d\n", hwahc, result); return result; -error_stop: - __wa_stop(&hwahc->wa); error_set_cluster_id: wusb_cluster_id_put(wusbhc->cluster_id); error_cluster_id_get: @@ -194,39 +172,6 @@ error_cluster_id_get: } -/* - * FIXME: break this function up - */ -static int __hwahc_op_wusbhc_start(struct wusbhc *wusbhc) -{ - int result; - struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc); - struct device *dev = &hwahc->wa.usb_iface->dev; - - /* Set up a Host Info WUSB Information Element */ - d_fnstart(4, dev, "(hwahc %p)\n", hwahc); - result = -ENOSPC; - - result = __wa_set_feature(&hwahc->wa, WA_ENABLE); - if (result < 0) { - dev_err(dev, "error commanding HC to start: %d\n", result); - goto error_stop; - } - result = __wa_wait_status(&hwahc->wa, WA_ENABLE, WA_ENABLE); - if (result < 0) { - dev_err(dev, "error waiting for HC to start: %d\n", result); - goto error_stop; - } - result = 0; -out: - d_fnend(4, dev, "(hwahc %p) = %d\n", hwahc, result); - return result; - -error_stop: - result = __wa_clear_feature(&hwahc->wa, WA_ENABLE); - goto out; -} - static int hwahc_op_suspend(struct usb_hcd *usb_hcd, pm_message_t msg) { struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd); @@ -246,18 +191,6 @@ static int hwahc_op_resume(struct usb_hcd *usb_hcd) return -ENOSYS; } -static void __hwahc_op_wusbhc_stop(struct wusbhc *wusbhc) -{ - int result; - struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc); - struct device *dev = &hwahc->wa.usb_iface->dev; - - d_fnstart(4, dev, "(hwahc %p)\n", hwahc); - /* Nothing for now */ - d_fnend(4, dev, "(hwahc %p) = %d\n", hwahc, result); - return; -} - /* * No need to abort pipes, as when this is called, all the children * has been disconnected and that has done it [through @@ -266,21 +199,11 @@ static void __hwahc_op_wusbhc_stop(struct wusbhc *wusbhc) */ static void hwahc_op_stop(struct usb_hcd *usb_hcd) { - int result; struct wusbhc *wusbhc = usb_hcd_to_wusbhc(usb_hcd); - struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc); - struct wahc *wa = &hwahc->wa; - struct device *dev = &wa->usb_iface->dev; - d_fnstart(4, dev, "(hwahc %p)\n", hwahc); mutex_lock(&wusbhc->mutex); - wusbhc_stop(wusbhc); - wa_nep_disarm(&hwahc->wa); - result = __wa_stop(&hwahc->wa); wusb_cluster_id_put(wusbhc->cluster_id); mutex_unlock(&wusbhc->mutex); - d_fnend(4, dev, "(hwahc %p) = %d\n", hwahc, result); - return; } static int hwahc_op_get_frame_number(struct usb_hcd *usb_hcd) @@ -325,6 +248,54 @@ static void hwahc_op_endpoint_disable(struct usb_hcd *usb_hcd, rpipe_ep_disable(&hwahc->wa, ep); } +static int __hwahc_op_wusbhc_start(struct wusbhc *wusbhc) +{ + int result; + struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc); + struct device *dev = &hwahc->wa.usb_iface->dev; + + result = __wa_set_feature(&hwahc->wa, WA_ENABLE); + if (result < 0) { + dev_err(dev, "error commanding HC to start: %d\n", result); + goto error_stop; + } + result = __wa_wait_status(&hwahc->wa, WA_ENABLE, WA_ENABLE); + if (result < 0) { + dev_err(dev, "error waiting for HC to start: %d\n", result); + goto error_stop; + } + result = wa_nep_arm(&hwahc->wa, GFP_KERNEL); + if (result < 0) { + dev_err(dev, "cannot listen to notifications: %d\n", result); + goto error_stop; + } + return result; + +error_stop: + __wa_clear_feature(&hwahc->wa, WA_ENABLE); + return result; +} + +static void __hwahc_op_wusbhc_stop(struct wusbhc *wusbhc, int delay) +{ + struct hwahc *hwahc = container_of(wusbhc, struct hwahc, wusbhc); + struct wahc *wa = &hwahc->wa; + u8 iface_no = wa->usb_iface->cur_altsetting->desc.bInterfaceNumber; + int ret; + + ret = usb_control_msg(wa->usb_dev, usb_sndctrlpipe(wa->usb_dev, 0), + WUSB_REQ_CHAN_STOP, + USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE, + delay * 1000, + iface_no, + NULL, 0, 1000 /* FIXME: arbitrary */); + if (ret == 0) + msleep(delay); + + wa_nep_disarm(&hwahc->wa); + __wa_stop(&hwahc->wa); +} + /* * Set the UWB MAS allocation for the WUSB cluster * @@ -581,11 +552,11 @@ static int wa_fill_descr(struct wahc *wa) itr_size = le16_to_cpu(usb_dev->actconfig->desc.wTotalLength); while (itr_size >= sizeof(*hdr)) { hdr = (struct usb_descriptor_header *) itr; - d_printf(3, dev, "Extra device descriptor: " - "type %02x/%u bytes @ %zu (%zu left)\n", - hdr->bDescriptorType, hdr->bLength, - (itr - usb_dev->rawdescriptors[actconfig_idx]), - itr_size); + dev_dbg(dev, "Extra device descriptor: " + "type %02x/%u bytes @ %zu (%zu left)\n", + hdr->bDescriptorType, hdr->bLength, + (itr - usb_dev->rawdescriptors[actconfig_idx]), + itr_size); if (hdr->bDescriptorType == USB_DT_WIRE_ADAPTER) goto found; itr += hdr->bLength; @@ -794,7 +765,6 @@ static void hwahc_destroy(struct hwahc *hwahc) { struct wusbhc *wusbhc = &hwahc->wusbhc; - d_fnstart(1, NULL, "(hwahc %p)\n", hwahc); mutex_lock(&wusbhc->mutex); __wa_destroy(&hwahc->wa); wusbhc_destroy(&hwahc->wusbhc); @@ -804,7 +774,6 @@ static void hwahc_destroy(struct hwahc *hwahc) usb_put_intf(hwahc->wa.usb_iface); usb_put_dev(hwahc->wa.usb_dev); mutex_unlock(&wusbhc->mutex); - d_fnend(1, NULL, "(hwahc %p) = void\n", hwahc); } static void hwahc_init(struct hwahc *hwahc) @@ -821,7 +790,6 @@ static int hwahc_probe(struct usb_interface *usb_iface, struct hwahc *hwahc; struct device *dev = &usb_iface->dev; - d_fnstart(4, dev, "(%p, %p)\n", usb_iface, id); result = -ENOMEM; usb_hcd = usb_create_hcd(&hwahc_hc_driver, &usb_iface->dev, "wusb-hwa"); if (usb_hcd == NULL) { @@ -848,7 +816,6 @@ static int hwahc_probe(struct usb_interface *usb_iface, dev_err(dev, "Cannot setup phase B of WUSBHC: %d\n", result); goto error_wusbhc_b_create; } - d_fnend(4, dev, "(%p, %p) = 0\n", usb_iface, id); return 0; error_wusbhc_b_create: @@ -858,7 +825,6 @@ error_add_hcd: error_hwahc_create: usb_put_hcd(usb_hcd); error_alloc: - d_fnend(4, dev, "(%p, %p) = %d\n", usb_iface, id, result); return result; } @@ -872,16 +838,12 @@ static void hwahc_disconnect(struct usb_interface *usb_iface) wusbhc = usb_hcd_to_wusbhc(usb_hcd); hwahc = container_of(wusbhc, struct hwahc, wusbhc); - d_fnstart(1, NULL, "(hwahc %p [usb_iface %p])\n", hwahc, usb_iface); wusbhc_b_destroy(&hwahc->wusbhc); usb_remove_hcd(usb_hcd); hwahc_destroy(hwahc); usb_put_hcd(usb_hcd); - d_fnend(1, NULL, "(hwahc %p [usb_iface %p]) = void\n", hwahc, - usb_iface); } -/** USB device ID's that we handle */ static struct usb_device_id hwahc_id_table[] = { /* FIXME: use class labels for this */ { USB_INTERFACE_INFO(0xe0, 0x02, 0x01), }, @@ -898,18 +860,7 @@ static struct usb_driver hwahc_driver = { static int __init hwahc_driver_init(void) { - int result; - result = usb_register(&hwahc_driver); - if (result < 0) { - printk(KERN_ERR "WA-CDS: Cannot register USB driver: %d\n", - result); - goto error_usb_register; - } - return 0; - -error_usb_register: - return result; - + return usb_register(&hwahc_driver); } module_init(hwahc_driver_init); diff --git a/drivers/usb/host/isp1760-hcd.c b/drivers/usb/host/isp1760-hcd.c index 8017f1cf78e..b899f1a59c2 100644 --- a/drivers/usb/host/isp1760-hcd.c +++ b/drivers/usb/host/isp1760-hcd.c @@ -435,14 +435,13 @@ static int isp1760_hc_setup(struct usb_hcd *hcd) /* * PORT 1 Control register of the ISP1760 is the OTG control - * register on ISP1761. + * register on ISP1761. Since there is no OTG or device controller + * support in this driver, we use port 1 as a "normal" USB host port on + * both chips. */ - if (!(priv->devflags & ISP1760_FLAG_ISP1761) && - !(priv->devflags & ISP1760_FLAG_PORT1_DIS)) { - isp1760_writel(PORT1_POWER | PORT1_INIT2, - hcd->regs + HC_PORT1_CTRL); - mdelay(10); - } + isp1760_writel(PORT1_POWER | PORT1_INIT2, + hcd->regs + HC_PORT1_CTRL); + mdelay(10); priv->hcs_params = isp1760_readl(hcd->regs + HC_HCSPARAMS); diff --git a/drivers/usb/host/isp1760-hcd.h b/drivers/usb/host/isp1760-hcd.h index 4377277667d..a9daea58796 100644 --- a/drivers/usb/host/isp1760-hcd.h +++ b/drivers/usb/host/isp1760-hcd.h @@ -135,7 +135,6 @@ typedef void (packet_enqueue)(struct usb_hcd *hcd, struct isp1760_qh *qh, * indicate the most "atypical" case, so that a devflags of 0 is * a sane default configuration. */ -#define ISP1760_FLAG_PORT1_DIS 0x00000001 /* Port 1 disabled */ #define ISP1760_FLAG_BUS_WIDTH_16 0x00000002 /* 16-bit data bus width */ #define ISP1760_FLAG_OTG_EN 0x00000004 /* Port 1 supports OTG */ #define ISP1760_FLAG_ANALOG_OC 0x00000008 /* Analog overcurrent */ diff --git a/drivers/usb/host/isp1760-if.c b/drivers/usb/host/isp1760-if.c index b87ca7cf4b3..4cf7ca428b3 100644 --- a/drivers/usb/host/isp1760-if.c +++ b/drivers/usb/host/isp1760-if.c @@ -60,9 +60,6 @@ static int of_isp1760_probe(struct of_device *dev, if (of_device_is_compatible(dp, "nxp,usb-isp1761")) devflags |= ISP1760_FLAG_ISP1761; - if (of_get_property(dp, "port1-disable", NULL) != NULL) - devflags |= ISP1760_FLAG_PORT1_DIS; - /* Some systems wire up only 16 of the 32 data lines */ prop = of_get_property(dp, "bus-width", NULL); if (prop && *prop == 16) @@ -129,23 +126,23 @@ static struct of_platform_driver isp1760_of_driver = { #endif #ifdef CONFIG_PCI -static u32 nxp_pci_io_base; -static u32 iolength; -static u32 pci_mem_phy0; -static u32 length; -static u8 __iomem *chip_addr; -static u8 __iomem *iobase; - static int __devinit isp1761_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) { u8 latency, limit; __u32 reg_data; int retry_count; - int length; - int status = 1; struct usb_hcd *hcd; unsigned int devflags = 0; + int ret_status = 0; + + resource_size_t pci_mem_phy0; + resource_size_t memlength; + + u8 __iomem *chip_addr; + u8 __iomem *iobase; + resource_size_t nxp_pci_io_base; + resource_size_t iolength; if (usb_disabled()) return -ENODEV; @@ -168,26 +165,30 @@ static int __devinit isp1761_pci_probe(struct pci_dev *dev, iobase = ioremap_nocache(nxp_pci_io_base, iolength); if (!iobase) { printk(KERN_ERR "ioremap #1\n"); - release_mem_region(nxp_pci_io_base, iolength); - return -ENOMEM; + ret_status = -ENOMEM; + goto cleanup1; } /* Grab the PLX PCI shared memory of the ISP 1761 we need */ pci_mem_phy0 = pci_resource_start(dev, 3); - length = pci_resource_len(dev, 3); - - if (length < 0xffff) { - printk(KERN_ERR "memory length for this resource is less than " - "required\n"); - release_mem_region(nxp_pci_io_base, iolength); - iounmap(iobase); - return -ENOMEM; + memlength = pci_resource_len(dev, 3); + if (memlength < 0xffff) { + printk(KERN_ERR "memory length for this resource is wrong\n"); + ret_status = -ENOMEM; + goto cleanup2; } - if (!request_mem_region(pci_mem_phy0, length, "ISP-PCI")) { + if (!request_mem_region(pci_mem_phy0, memlength, "ISP-PCI")) { printk(KERN_ERR "host controller already in use\n"); - release_mem_region(nxp_pci_io_base, iolength); - iounmap(iobase); - return -EBUSY; + ret_status = -EBUSY; + goto cleanup2; + } + + /* map available memory */ + chip_addr = ioremap_nocache(pci_mem_phy0,memlength); + if (!chip_addr) { + printk(KERN_ERR "Error ioremap failed\n"); + ret_status = -ENOMEM; + goto cleanup3; } /* bad pci latencies can contribute to overruns */ @@ -210,39 +211,54 @@ static int __devinit isp1761_pci_probe(struct pci_dev *dev, * */ writel(0xface, chip_addr + HC_SCRATCH_REG); udelay(100); - reg_data = readl(chip_addr + HC_SCRATCH_REG); + reg_data = readl(chip_addr + HC_SCRATCH_REG) & 0x0000ffff; retry_count--; } + iounmap(chip_addr); + /* Host Controller presence is detected by writing to scratch register * and reading back and checking the contents are same or not */ if (reg_data != 0xFACE) { dev_err(&dev->dev, "scratch register mismatch %x\n", reg_data); - goto clean; + ret_status = -ENOMEM; + goto cleanup3; } pci_set_master(dev); - status = readl(iobase + 0x68); - status |= 0x900; - writel(status, iobase + 0x68); + /* configure PLX PCI chip to pass interrupts */ +#define PLX_INT_CSR_REG 0x68 + reg_data = readl(iobase + PLX_INT_CSR_REG); + reg_data |= 0x900; + writel(reg_data, iobase + PLX_INT_CSR_REG); dev->dev.dma_mask = NULL; - hcd = isp1760_register(pci_mem_phy0, length, dev->irq, + hcd = isp1760_register(pci_mem_phy0, memlength, dev->irq, IRQF_SHARED | IRQF_DISABLED, &dev->dev, dev_name(&dev->dev), devflags); - if (!IS_ERR(hcd)) { - pci_set_drvdata(dev, hcd); - return 0; + if (IS_ERR(hcd)) { + ret_status = -ENODEV; + goto cleanup3; } -clean: - status = -ENODEV; + + /* done with PLX IO access */ + iounmap(iobase); + release_mem_region(nxp_pci_io_base, iolength); + + pci_set_drvdata(dev, hcd); + return 0; + +cleanup3: + release_mem_region(pci_mem_phy0, memlength); +cleanup2: iounmap(iobase); - release_mem_region(pci_mem_phy0, length); +cleanup1: release_mem_region(nxp_pci_io_base, iolength); - return status; + return ret_status; } + static void isp1761_pci_remove(struct pci_dev *dev) { struct usb_hcd *hcd; @@ -255,12 +271,6 @@ static void isp1761_pci_remove(struct pci_dev *dev) usb_put_hcd(hcd); pci_disable_device(dev); - - iounmap(iobase); - iounmap(chip_addr); - - release_mem_region(nxp_pci_io_base, iolength); - release_mem_region(pci_mem_phy0, length); } static void isp1761_pci_shutdown(struct pci_dev *dev) @@ -268,12 +278,16 @@ static void isp1761_pci_shutdown(struct pci_dev *dev) printk(KERN_ERR "ips1761_pci_shutdown\n"); } -static const struct pci_device_id isp1760_plx [] = { { - /* handle any USB 2.0 EHCI controller */ - PCI_DEVICE_CLASS(((PCI_CLASS_BRIDGE_OTHER << 8) | (0x06 << 16)), ~0), - .driver_data = 0, -}, -{ /* end: all zeroes */ } +static const struct pci_device_id isp1760_plx [] = { + { + .class = PCI_CLASS_BRIDGE_OTHER << 8, + .class_mask = ~0, + .vendor = PCI_VENDOR_ID_PLX, + .device = 0x5406, + .subvendor = PCI_VENDOR_ID_PLX, + .subdevice = 0x9054, + }, + { } }; MODULE_DEVICE_TABLE(pci, isp1760_plx); diff --git a/drivers/usb/host/ohci-hcd.c b/drivers/usb/host/ohci-hcd.c index 8aa3f4556a3..5cf5f1eca4f 100644 --- a/drivers/usb/host/ohci-hcd.c +++ b/drivers/usb/host/ohci-hcd.c @@ -589,14 +589,14 @@ static int ohci_run (struct ohci_hcd *ohci) /* also: power/overcurrent flags in roothub.a */ } - /* Reset USB nearly "by the book". RemoteWakeupConnected was - * saved if boot firmware (BIOS/SMM/...) told us it's connected, - * or if bus glue did the same (e.g. for PCI add-in cards with - * PCI PM support). + /* Reset USB nearly "by the book". RemoteWakeupConnected has + * 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; if so we'll just enable it again. */ - if ((ohci->hc_control & OHCI_CTRL_RWC) != 0 - && !device_may_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: diff --git a/drivers/usb/host/ohci-omap.c b/drivers/usb/host/ohci-omap.c index 4bbddb73abd..f3aaba35e91 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); diff --git a/drivers/usb/host/ohci-pci.c b/drivers/usb/host/ohci-pci.c index a9c2ae36c7a..5d625c3fd42 100644 --- a/drivers/usb/host/ohci-pci.c +++ b/drivers/usb/host/ohci-pci.c @@ -355,9 +355,9 @@ static int __devinit ohci_pci_start (struct usb_hcd *hcd) /* RWC may not be set for add-in PCI cards, since boot * firmware probably ignored them. This transfers PCI - * PM wakeup capabilities (once the PCI layer is fixed). + * PM wakeup capabilities. */ - if (device_may_wakeup(&pdev->dev)) + if (device_can_wakeup(&pdev->dev)) ohci->hc_control |= OHCI_CTRL_RWC; } #endif /* CONFIG_PM */ @@ -487,6 +487,7 @@ 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/ohci-pnx4008.c b/drivers/usb/host/ohci-pnx4008.c index e306ca6aef3..100bf3d8437 100644 --- a/drivers/usb/host/ohci-pnx4008.c +++ b/drivers/usb/host/ohci-pnx4008.c @@ -106,65 +106,34 @@ extern int ocpi_enable(void); static struct clk *usb_clk; -static int isp1301_probe(struct i2c_adapter *adap); -static int isp1301_detach(struct i2c_client *client); - static const unsigned short normal_i2c[] = { ISP1301_I2C_ADDR, ISP1301_I2C_ADDR + 1, I2C_CLIENT_END }; -static const unsigned short dummy_i2c_addrlist[] = { I2C_CLIENT_END }; - -static struct i2c_client_address_data addr_data = { - .normal_i2c = normal_i2c, - .probe = dummy_i2c_addrlist, - .ignore = dummy_i2c_addrlist, -}; - -struct i2c_driver isp1301_driver = { - .driver = { - .name = "isp1301_pnx", - }, - .attach_adapter = isp1301_probe, - .detach_client = isp1301_detach, -}; -static int isp1301_attach(struct i2c_adapter *adap, int addr, int kind) +static int isp1301_probe(struct i2c_client *client, + const struct i2c_device_id *id) { - struct i2c_client *c; - int err; - - c = kzalloc(sizeof(*c), GFP_KERNEL); - if (!c) - return -ENOMEM; - - strlcpy(c->name, "isp1301_pnx", I2C_NAME_SIZE); - c->flags = 0; - c->addr = addr; - c->adapter = adap; - c->driver = &isp1301_driver; - - err = i2c_attach_client(c); - if (err) { - kfree(c); - return err; - } - - isp1301_i2c_client = c; - return 0; } -static int isp1301_probe(struct i2c_adapter *adap) +static int isp1301_remove(struct i2c_client *client) { - return i2c_probe(adap, &addr_data, isp1301_attach); -} - -static int isp1301_detach(struct i2c_client *client) -{ - i2c_detach_client(client); - kfree(isp1301_i2c_client); return 0; } +const struct i2c_device_id isp1301_id[] = { + { "isp1301_pnx", 0 }, + { } +}; + +struct i2c_driver isp1301_driver = { + .driver = { + .name = "isp1301_pnx", + }, + .probe = isp1301_probe, + .remove = isp1301_remove, + .id_table = isp1301_id, +}; + static void i2c_write(u8 buf, u8 subaddr) { char tmpbuf[2]; @@ -328,6 +297,8 @@ static int __devinit usb_hcd_pnx4008_probe(struct platform_device *pdev) struct usb_hcd *hcd = 0; struct ohci_hcd *ohci; const struct hc_driver *driver = &ohci_pnx4008_hc_driver; + struct i2c_adapter *i2c_adap; + struct i2c_board_info i2c_info; int ret = 0, irq; @@ -351,9 +322,20 @@ static int __devinit usb_hcd_pnx4008_probe(struct platform_device *pdev) ret = i2c_add_driver(&isp1301_driver); if (ret < 0) { - err("failed to connect I2C to ISP1301 USB Transceiver"); + err("failed to add ISP1301 driver"); goto out; } + i2c_adap = i2c_get_adapter(2); + memset(&i2c_info, 0, sizeof(struct i2c_board_info)); + strlcpy(i2c_info.name, "isp1301_pnx", I2C_NAME_SIZE); + isp1301_i2c_client = i2c_new_probed_device(i2c_adap, &i2c_info, + normal_i2c); + i2c_put_adapter(i2c_adap); + if (!isp1301_i2c_client) { + err("failed to connect I2C to ISP1301 USB Transceiver"); + ret = -ENODEV; + goto out_i2c_driver; + } isp1301_configure(); @@ -429,6 +411,9 @@ out3: out2: clk_put(usb_clk); out1: + i2c_unregister_client(isp1301_i2c_client); + isp1301_i2c_client = NULL; +out_i2c_driver: i2c_del_driver(&isp1301_driver); out: return ret; @@ -445,6 +430,8 @@ static int usb_hcd_pnx4008_remove(struct platform_device *pdev) pnx4008_unset_usb_bits(); clk_disable(usb_clk); clk_put(usb_clk); + i2c_unregister_client(isp1301_i2c_client); + isp1301_i2c_client = NULL; i2c_del_driver(&isp1301_driver); platform_set_drvdata(pdev, NULL); diff --git a/drivers/usb/host/ohci-ppc-of.c b/drivers/usb/host/ohci-ppc-of.c index 7ac53264ead..68a30171029 100644 --- a/drivers/usb/host/ohci-ppc-of.c +++ b/drivers/usb/host/ohci-ppc-of.c @@ -91,6 +91,7 @@ ohci_hcd_ppc_of_probe(struct of_device *op, const struct of_device_id *match) int rv; int is_bigendian; + struct device_node *np; if (usb_disabled()) return -ENODEV; @@ -147,6 +148,30 @@ ohci_hcd_ppc_of_probe(struct of_device *op, const struct of_device_id *match) if (rv == 0) return 0; + /* by now, 440epx is known to show usb_23 erratum */ + np = of_find_compatible_node(NULL, NULL, "ibm,usb-ehci-440epx"); + + /* Work around - At this point ohci_run has executed, the + * controller is running, everything, the root ports, etc., is + * set up. If the ehci driver is loaded, put the ohci core in + * the suspended state. The ehci driver will bring it out of + * suspended state when / if a non-high speed USB device is + * attached to the USB Host port. If the ehci driver is not + * loaded, do nothing. request_mem_region is used to test if + * the ehci driver is loaded. + */ + if (np != NULL) { + if (!of_address_to_resource(np, 0, &res)) { + if (!request_mem_region(res.start, 0x4, hcd_name)) { + writel_be((readl_be(&ohci->regs->control) | + OHCI_USB_SUSPEND), &ohci->regs->control); + (void) readl_be(&ohci->regs->control); + } else + release_mem_region(res.start, 0x4); + } else + pr_debug(__FILE__ ": cannot get ehci offset from fdt\n"); + } + iounmap(hcd->regs); err_ioremap: irq_dispose_mapping(irq); diff --git a/drivers/usb/host/ohci-tmio.c b/drivers/usb/host/ohci-tmio.c index f9f134af0bd..8dabe8e31d8 100644 --- a/drivers/usb/host/ohci-tmio.c +++ b/drivers/usb/host/ohci-tmio.c @@ -201,7 +201,7 @@ static int __devinit ohci_hcd_tmio_drv_probe(struct platform_device *dev) if (!cell) return -EINVAL; - hcd = usb_create_hcd(&ohci_tmio_hc_driver, &dev->dev, dev->dev.bus_id); + hcd = usb_create_hcd(&ohci_tmio_hc_driver, &dev->dev, dev_name(&dev->dev)); if (!hcd) { ret = -ENOMEM; goto err_usb_create_hcd; diff --git a/drivers/usb/host/oxu210hp-hcd.c b/drivers/usb/host/oxu210hp-hcd.c new file mode 100644 index 00000000000..75548f7c716 --- /dev/null +++ b/drivers/usb/host/oxu210hp-hcd.c @@ -0,0 +1,3985 @@ +/* + * Copyright (c) 2008 Rodolfo Giometti <giometti@linux.it> + * Copyright (c) 2008 Eurotech S.p.A. <info@eurtech.it> + * + * This code is *strongly* based on EHCI-HCD code by David Brownell since + * the chip is a quasi-EHCI compatible. + * + * 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. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/dmapool.h> +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/ioport.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/timer.h> +#include <linux/list.h> +#include <linux/interrupt.h> +#include <linux/reboot.h> +#include <linux/usb.h> +#include <linux/moduleparam.h> +#include <linux/dma-mapping.h> +#include <linux/io.h> + +#include "../core/hcd.h" + +#include <asm/irq.h> +#include <asm/system.h> +#include <asm/unaligned.h> + +#include <linux/irq.h> +#include <linux/platform_device.h> + +#include "oxu210hp.h" + +#define DRIVER_VERSION "0.0.50" + +/* + * Main defines + */ + +#define oxu_dbg(oxu, fmt, args...) \ + dev_dbg(oxu_to_hcd(oxu)->self.controller , fmt , ## args) +#define oxu_err(oxu, fmt, args...) \ + dev_err(oxu_to_hcd(oxu)->self.controller , fmt , ## args) +#define oxu_info(oxu, fmt, args...) \ + dev_info(oxu_to_hcd(oxu)->self.controller , fmt , ## args) + +static inline struct usb_hcd *oxu_to_hcd(struct oxu_hcd *oxu) +{ + return container_of((void *) oxu, struct usb_hcd, hcd_priv); +} + +static inline struct oxu_hcd *hcd_to_oxu(struct usb_hcd *hcd) +{ + return (struct oxu_hcd *) (hcd->hcd_priv); +} + +/* + * Debug stuff + */ + +#undef OXU_URB_TRACE +#undef OXU_VERBOSE_DEBUG + +#ifdef OXU_VERBOSE_DEBUG +#define oxu_vdbg oxu_dbg +#else +#define oxu_vdbg(oxu, fmt, args...) /* Nop */ +#endif + +#ifdef DEBUG + +static int __attribute__((__unused__)) +dbg_status_buf(char *buf, unsigned len, const char *label, u32 status) +{ + return scnprintf(buf, len, "%s%sstatus %04x%s%s%s%s%s%s%s%s%s%s", + label, label[0] ? " " : "", status, + (status & STS_ASS) ? " Async" : "", + (status & STS_PSS) ? " Periodic" : "", + (status & STS_RECL) ? " Recl" : "", + (status & STS_HALT) ? " Halt" : "", + (status & STS_IAA) ? " IAA" : "", + (status & STS_FATAL) ? " FATAL" : "", + (status & STS_FLR) ? " FLR" : "", + (status & STS_PCD) ? " PCD" : "", + (status & STS_ERR) ? " ERR" : "", + (status & STS_INT) ? " INT" : "" + ); +} + +static int __attribute__((__unused__)) +dbg_intr_buf(char *buf, unsigned len, const char *label, u32 enable) +{ + return scnprintf(buf, len, "%s%sintrenable %02x%s%s%s%s%s%s", + label, label[0] ? " " : "", enable, + (enable & STS_IAA) ? " IAA" : "", + (enable & STS_FATAL) ? " FATAL" : "", + (enable & STS_FLR) ? " FLR" : "", + (enable & STS_PCD) ? " PCD" : "", + (enable & STS_ERR) ? " ERR" : "", + (enable & STS_INT) ? " INT" : "" + ); +} + +static const char *const fls_strings[] = + { "1024", "512", "256", "??" }; + +static int dbg_command_buf(char *buf, unsigned len, + const char *label, u32 command) +{ + return scnprintf(buf, len, + "%s%scommand %06x %s=%d ithresh=%d%s%s%s%s period=%s%s %s", + label, label[0] ? " " : "", command, + (command & CMD_PARK) ? "park" : "(park)", + CMD_PARK_CNT(command), + (command >> 16) & 0x3f, + (command & CMD_LRESET) ? " LReset" : "", + (command & CMD_IAAD) ? " IAAD" : "", + (command & CMD_ASE) ? " Async" : "", + (command & CMD_PSE) ? " Periodic" : "", + fls_strings[(command >> 2) & 0x3], + (command & CMD_RESET) ? " Reset" : "", + (command & CMD_RUN) ? "RUN" : "HALT" + ); +} + +static int dbg_port_buf(char *buf, unsigned len, const char *label, + int port, u32 status) +{ + char *sig; + + /* signaling state */ + switch (status & (3 << 10)) { + case 0 << 10: + sig = "se0"; + break; + case 1 << 10: + sig = "k"; /* low speed */ + break; + case 2 << 10: + sig = "j"; + break; + default: + sig = "?"; + break; + } + + return scnprintf(buf, len, + "%s%sport %d status %06x%s%s sig=%s%s%s%s%s%s%s%s%s%s", + label, label[0] ? " " : "", port, status, + (status & PORT_POWER) ? " POWER" : "", + (status & PORT_OWNER) ? " OWNER" : "", + sig, + (status & PORT_RESET) ? " RESET" : "", + (status & PORT_SUSPEND) ? " SUSPEND" : "", + (status & PORT_RESUME) ? " RESUME" : "", + (status & PORT_OCC) ? " OCC" : "", + (status & PORT_OC) ? " OC" : "", + (status & PORT_PEC) ? " PEC" : "", + (status & PORT_PE) ? " PE" : "", + (status & PORT_CSC) ? " CSC" : "", + (status & PORT_CONNECT) ? " CONNECT" : "" + ); +} + +#else + +static inline int __attribute__((__unused__)) +dbg_status_buf(char *buf, unsigned len, const char *label, u32 status) +{ return 0; } + +static inline int __attribute__((__unused__)) +dbg_command_buf(char *buf, unsigned len, const char *label, u32 command) +{ return 0; } + +static inline int __attribute__((__unused__)) +dbg_intr_buf(char *buf, unsigned len, const char *label, u32 enable) +{ return 0; } + +static inline int __attribute__((__unused__)) +dbg_port_buf(char *buf, unsigned len, const char *label, int port, u32 status) +{ return 0; } + +#endif /* DEBUG */ + +/* functions have the "wrong" filename when they're output... */ +#define dbg_status(oxu, label, status) { \ + char _buf[80]; \ + dbg_status_buf(_buf, sizeof _buf, label, status); \ + oxu_dbg(oxu, "%s\n", _buf); \ +} + +#define dbg_cmd(oxu, label, command) { \ + char _buf[80]; \ + dbg_command_buf(_buf, sizeof _buf, label, command); \ + oxu_dbg(oxu, "%s\n", _buf); \ +} + +#define dbg_port(oxu, label, port, status) { \ + char _buf[80]; \ + dbg_port_buf(_buf, sizeof _buf, label, port, status); \ + oxu_dbg(oxu, "%s\n", _buf); \ +} + +/* + * Module parameters + */ + +/* Initial IRQ latency: faster than hw default */ +static int log2_irq_thresh; /* 0 to 6 */ +module_param(log2_irq_thresh, int, S_IRUGO); +MODULE_PARM_DESC(log2_irq_thresh, "log2 IRQ latency, 1-64 microframes"); + +/* Initial park setting: slower than hw default */ +static unsigned park; +module_param(park, uint, S_IRUGO); +MODULE_PARM_DESC(park, "park setting; 1-3 back-to-back async packets"); + +/* For flakey hardware, ignore overcurrent indicators */ +static int ignore_oc; +module_param(ignore_oc, bool, S_IRUGO); +MODULE_PARM_DESC(ignore_oc, "ignore bogus hardware overcurrent indications"); + + +static void ehci_work(struct oxu_hcd *oxu); +static int oxu_hub_control(struct usb_hcd *hcd, + u16 typeReq, u16 wValue, u16 wIndex, + char *buf, u16 wLength); + +/* + * Local functions + */ + +/* Low level read/write registers functions */ +static inline u32 oxu_readl(void *base, u32 reg) +{ + return readl(base + reg); +} + +static inline void oxu_writel(void *base, u32 reg, u32 val) +{ + writel(val, base + reg); +} + +static inline void timer_action_done(struct oxu_hcd *oxu, + enum ehci_timer_action action) +{ + clear_bit(action, &oxu->actions); +} + +static inline void timer_action(struct oxu_hcd *oxu, + enum ehci_timer_action action) +{ + if (!test_and_set_bit(action, &oxu->actions)) { + unsigned long t; + + switch (action) { + case TIMER_IAA_WATCHDOG: + t = EHCI_IAA_JIFFIES; + break; + case TIMER_IO_WATCHDOG: + t = EHCI_IO_JIFFIES; + break; + case TIMER_ASYNC_OFF: + t = EHCI_ASYNC_JIFFIES; + break; + case TIMER_ASYNC_SHRINK: + default: + t = EHCI_SHRINK_JIFFIES; + break; + } + t += jiffies; + /* all timings except IAA watchdog can be overridden. + * async queue SHRINK often precedes IAA. while it's ready + * to go OFF neither can matter, and afterwards the IO + * watchdog stops unless there's still periodic traffic. + */ + if (action != TIMER_IAA_WATCHDOG + && t > oxu->watchdog.expires + && timer_pending(&oxu->watchdog)) + return; + mod_timer(&oxu->watchdog, t); + } +} + +/* + * handshake - spin reading hc until handshake completes or fails + * @ptr: address of hc register to be read + * @mask: bits to look at in result of read + * @done: value of those bits when handshake succeeds + * @usec: timeout in microseconds + * + * Returns negative errno, or zero on success + * + * Success happens when the "mask" bits have the specified value (hardware + * handshake done). There are two failure modes: "usec" have passed (major + * hardware flakeout), or the register reads as all-ones (hardware removed). + * + * That last failure should_only happen in cases like physical cardbus eject + * before driver shutdown. But it also seems to be caused by bugs in cardbus + * bridge shutdown: shutting down the bridge before the devices using it. + */ +static int handshake(struct oxu_hcd *oxu, void __iomem *ptr, + u32 mask, u32 done, int usec) +{ + u32 result; + + do { + result = readl(ptr); + if (result == ~(u32)0) /* card removed */ + return -ENODEV; + result &= mask; + if (result == done) + return 0; + udelay(1); + usec--; + } while (usec > 0); + return -ETIMEDOUT; +} + +/* Force HC to halt state from unknown (EHCI spec section 2.3) */ +static int ehci_halt(struct oxu_hcd *oxu) +{ + u32 temp = readl(&oxu->regs->status); + + /* disable any irqs left enabled by previous code */ + writel(0, &oxu->regs->intr_enable); + + if ((temp & STS_HALT) != 0) + return 0; + + temp = readl(&oxu->regs->command); + temp &= ~CMD_RUN; + writel(temp, &oxu->regs->command); + return handshake(oxu, &oxu->regs->status, + STS_HALT, STS_HALT, 16 * 125); +} + +/* Put TDI/ARC silicon into EHCI mode */ +static void tdi_reset(struct oxu_hcd *oxu) +{ + u32 __iomem *reg_ptr; + u32 tmp; + + reg_ptr = (u32 __iomem *)(((u8 __iomem *)oxu->regs) + 0x68); + tmp = readl(reg_ptr); + tmp |= 0x3; + writel(tmp, reg_ptr); +} + +/* Reset a non-running (STS_HALT == 1) controller */ +static int ehci_reset(struct oxu_hcd *oxu) +{ + int retval; + u32 command = readl(&oxu->regs->command); + + command |= CMD_RESET; + dbg_cmd(oxu, "reset", command); + writel(command, &oxu->regs->command); + oxu_to_hcd(oxu)->state = HC_STATE_HALT; + oxu->next_statechange = jiffies; + retval = handshake(oxu, &oxu->regs->command, + CMD_RESET, 0, 250 * 1000); + + if (retval) + return retval; + + tdi_reset(oxu); + + return retval; +} + +/* Idle the controller (from running) */ +static void ehci_quiesce(struct oxu_hcd *oxu) +{ + u32 temp; + +#ifdef DEBUG + if (!HC_IS_RUNNING(oxu_to_hcd(oxu)->state)) + BUG(); +#endif + + /* wait for any schedule enables/disables to take effect */ + temp = readl(&oxu->regs->command) << 10; + temp &= STS_ASS | STS_PSS; + if (handshake(oxu, &oxu->regs->status, STS_ASS | STS_PSS, + temp, 16 * 125) != 0) { + oxu_to_hcd(oxu)->state = HC_STATE_HALT; + return; + } + + /* then disable anything that's still active */ + temp = readl(&oxu->regs->command); + temp &= ~(CMD_ASE | CMD_IAAD | CMD_PSE); + writel(temp, &oxu->regs->command); + + /* hardware can take 16 microframes to turn off ... */ + if (handshake(oxu, &oxu->regs->status, STS_ASS | STS_PSS, + 0, 16 * 125) != 0) { + oxu_to_hcd(oxu)->state = HC_STATE_HALT; + return; + } +} + +static int check_reset_complete(struct oxu_hcd *oxu, int index, + u32 __iomem *status_reg, int port_status) +{ + if (!(port_status & PORT_CONNECT)) { + oxu->reset_done[index] = 0; + return port_status; + } + + /* if reset finished and it's still not enabled -- handoff */ + if (!(port_status & PORT_PE)) { + oxu_dbg(oxu, "Failed to enable port %d on root hub TT\n", + index+1); + return port_status; + } else + oxu_dbg(oxu, "port %d high speed\n", index + 1); + + return port_status; +} + +static void ehci_hub_descriptor(struct oxu_hcd *oxu, + struct usb_hub_descriptor *desc) +{ + int ports = HCS_N_PORTS(oxu->hcs_params); + u16 temp; + + desc->bDescriptorType = 0x29; + desc->bPwrOn2PwrGood = 10; /* oxu 1.0, 2.3.9 says 20ms max */ + desc->bHubContrCurrent = 0; + + desc->bNbrPorts = ports; + temp = 1 + (ports / 8); + desc->bDescLength = 7 + 2 * temp; + + /* two bitmaps: ports removable, and usb 1.0 legacy PortPwrCtrlMask */ + memset(&desc->bitmap[0], 0, temp); + memset(&desc->bitmap[temp], 0xff, temp); + + temp = 0x0008; /* per-port overcurrent reporting */ + if (HCS_PPC(oxu->hcs_params)) + temp |= 0x0001; /* per-port power control */ + else + temp |= 0x0002; /* no power switching */ + desc->wHubCharacteristics = (__force __u16)cpu_to_le16(temp); +} + + +/* Allocate an OXU210HP on-chip memory data buffer + * + * An on-chip memory data buffer is required for each OXU210HP USB transfer. + * Each transfer descriptor has one or more on-chip memory data buffers. + * + * Data buffers are allocated from a fix sized pool of data blocks. + * To minimise fragmentation and give reasonable memory utlisation, + * data buffers are allocated with sizes the power of 2 multiples of + * the block size, starting on an address a multiple of the allocated size. + * + * FIXME: callers of this function require a buffer to be allocated for + * len=0. This is a waste of on-chip memory and should be fix. Then this + * function should be changed to not allocate a buffer for len=0. + */ +static int oxu_buf_alloc(struct oxu_hcd *oxu, struct ehci_qtd *qtd, int len) +{ + int n_blocks; /* minium blocks needed to hold len */ + int a_blocks; /* blocks allocated */ + int i, j; + + /* Don't allocte bigger than supported */ + if (len > BUFFER_SIZE * BUFFER_NUM) { + oxu_err(oxu, "buffer too big (%d)\n", len); + return -ENOMEM; + } + + spin_lock(&oxu->mem_lock); + + /* Number of blocks needed to hold len */ + n_blocks = (len + BUFFER_SIZE - 1) / BUFFER_SIZE; + + /* Round the number of blocks up to the power of 2 */ + for (a_blocks = 1; a_blocks < n_blocks; a_blocks <<= 1) + ; + + /* Find a suitable available data buffer */ + for (i = 0; i < BUFFER_NUM; + i += max(a_blocks, (int)oxu->db_used[i])) { + + /* Check all the required blocks are available */ + for (j = 0; j < a_blocks; j++) + if (oxu->db_used[i + j]) + break; + + if (j != a_blocks) + continue; + + /* Allocate blocks found! */ + qtd->buffer = (void *) &oxu->mem->db_pool[i]; + qtd->buffer_dma = virt_to_phys(qtd->buffer); + + qtd->qtd_buffer_len = BUFFER_SIZE * a_blocks; + oxu->db_used[i] = a_blocks; + + spin_unlock(&oxu->mem_lock); + + return 0; + } + + /* Failed */ + + spin_unlock(&oxu->mem_lock); + + return -ENOMEM; +} + +static void oxu_buf_free(struct oxu_hcd *oxu, struct ehci_qtd *qtd) +{ + int index; + + spin_lock(&oxu->mem_lock); + + index = (qtd->buffer - (void *) &oxu->mem->db_pool[0]) + / BUFFER_SIZE; + oxu->db_used[index] = 0; + qtd->qtd_buffer_len = 0; + qtd->buffer_dma = 0; + qtd->buffer = NULL; + + spin_unlock(&oxu->mem_lock); + + return; +} + +static inline void ehci_qtd_init(struct ehci_qtd *qtd, dma_addr_t dma) +{ + memset(qtd, 0, sizeof *qtd); + qtd->qtd_dma = dma; + qtd->hw_token = cpu_to_le32(QTD_STS_HALT); + qtd->hw_next = EHCI_LIST_END; + qtd->hw_alt_next = EHCI_LIST_END; + INIT_LIST_HEAD(&qtd->qtd_list); +} + +static inline void oxu_qtd_free(struct oxu_hcd *oxu, struct ehci_qtd *qtd) +{ + int index; + + if (qtd->buffer) + oxu_buf_free(oxu, qtd); + + spin_lock(&oxu->mem_lock); + + index = qtd - &oxu->mem->qtd_pool[0]; + oxu->qtd_used[index] = 0; + + spin_unlock(&oxu->mem_lock); + + return; +} + +static struct ehci_qtd *ehci_qtd_alloc(struct oxu_hcd *oxu) +{ + int i; + struct ehci_qtd *qtd = NULL; + + spin_lock(&oxu->mem_lock); + + for (i = 0; i < QTD_NUM; i++) + if (!oxu->qtd_used[i]) + break; + + if (i < QTD_NUM) { + qtd = (struct ehci_qtd *) &oxu->mem->qtd_pool[i]; + memset(qtd, 0, sizeof *qtd); + + qtd->hw_token = cpu_to_le32(QTD_STS_HALT); + qtd->hw_next = EHCI_LIST_END; + qtd->hw_alt_next = EHCI_LIST_END; + INIT_LIST_HEAD(&qtd->qtd_list); + + qtd->qtd_dma = virt_to_phys(qtd); + + oxu->qtd_used[i] = 1; + } + + spin_unlock(&oxu->mem_lock); + + return qtd; +} + +static void oxu_qh_free(struct oxu_hcd *oxu, struct ehci_qh *qh) +{ + int index; + + spin_lock(&oxu->mem_lock); + + index = qh - &oxu->mem->qh_pool[0]; + oxu->qh_used[index] = 0; + + spin_unlock(&oxu->mem_lock); + + return; +} + +static void qh_destroy(struct kref *kref) +{ + struct ehci_qh *qh = container_of(kref, struct ehci_qh, kref); + struct oxu_hcd *oxu = qh->oxu; + + /* clean qtds first, and know this is not linked */ + if (!list_empty(&qh->qtd_list) || qh->qh_next.ptr) { + oxu_dbg(oxu, "unused qh not empty!\n"); + BUG(); + } + if (qh->dummy) + oxu_qtd_free(oxu, qh->dummy); + oxu_qh_free(oxu, qh); +} + +static struct ehci_qh *oxu_qh_alloc(struct oxu_hcd *oxu) +{ + int i; + struct ehci_qh *qh = NULL; + + spin_lock(&oxu->mem_lock); + + for (i = 0; i < QHEAD_NUM; i++) + if (!oxu->qh_used[i]) + break; + + if (i < QHEAD_NUM) { + qh = (struct ehci_qh *) &oxu->mem->qh_pool[i]; + memset(qh, 0, sizeof *qh); + + kref_init(&qh->kref); + qh->oxu = oxu; + qh->qh_dma = virt_to_phys(qh); + INIT_LIST_HEAD(&qh->qtd_list); + + /* dummy td enables safe urb queuing */ + qh->dummy = ehci_qtd_alloc(oxu); + if (qh->dummy == NULL) { + oxu_dbg(oxu, "no dummy td\n"); + oxu->qh_used[i] = 0; + + return NULL; + } + + oxu->qh_used[i] = 1; + } + + spin_unlock(&oxu->mem_lock); + + return qh; +} + +/* to share a qh (cpu threads, or hc) */ +static inline struct ehci_qh *qh_get(struct ehci_qh *qh) +{ + kref_get(&qh->kref); + return qh; +} + +static inline void qh_put(struct ehci_qh *qh) +{ + kref_put(&qh->kref, qh_destroy); +} + +static void oxu_murb_free(struct oxu_hcd *oxu, struct oxu_murb *murb) +{ + int index; + + spin_lock(&oxu->mem_lock); + + index = murb - &oxu->murb_pool[0]; + oxu->murb_used[index] = 0; + + spin_unlock(&oxu->mem_lock); + + return; +} + +static struct oxu_murb *oxu_murb_alloc(struct oxu_hcd *oxu) + +{ + int i; + struct oxu_murb *murb = NULL; + + spin_lock(&oxu->mem_lock); + + for (i = 0; i < MURB_NUM; i++) + if (!oxu->murb_used[i]) + break; + + if (i < MURB_NUM) { + murb = &(oxu->murb_pool)[i]; + + oxu->murb_used[i] = 1; + } + + spin_unlock(&oxu->mem_lock); + + return murb; +} + +/* The queue heads and transfer descriptors are managed from pools tied + * to each of the "per device" structures. + * This is the initialisation and cleanup code. + */ +static void ehci_mem_cleanup(struct oxu_hcd *oxu) +{ + kfree(oxu->murb_pool); + oxu->murb_pool = NULL; + + if (oxu->async) + qh_put(oxu->async); + oxu->async = NULL; + + del_timer(&oxu->urb_timer); + + oxu->periodic = NULL; + + /* shadow periodic table */ + kfree(oxu->pshadow); + oxu->pshadow = NULL; +} + +/* Remember to add cleanup code (above) if you add anything here. + */ +static int ehci_mem_init(struct oxu_hcd *oxu, gfp_t flags) +{ + int i; + + for (i = 0; i < oxu->periodic_size; i++) + oxu->mem->frame_list[i] = EHCI_LIST_END; + for (i = 0; i < QHEAD_NUM; i++) + oxu->qh_used[i] = 0; + for (i = 0; i < QTD_NUM; i++) + oxu->qtd_used[i] = 0; + + oxu->murb_pool = kcalloc(MURB_NUM, sizeof(struct oxu_murb), flags); + if (!oxu->murb_pool) + goto fail; + + for (i = 0; i < MURB_NUM; i++) + oxu->murb_used[i] = 0; + + oxu->async = oxu_qh_alloc(oxu); + if (!oxu->async) + goto fail; + + oxu->periodic = (__le32 *) &oxu->mem->frame_list; + oxu->periodic_dma = virt_to_phys(oxu->periodic); + + for (i = 0; i < oxu->periodic_size; i++) + oxu->periodic[i] = EHCI_LIST_END; + + /* software shadow of hardware table */ + oxu->pshadow = kcalloc(oxu->periodic_size, sizeof(void *), flags); + if (oxu->pshadow != NULL) + return 0; + +fail: + oxu_dbg(oxu, "couldn't init memory\n"); + ehci_mem_cleanup(oxu); + return -ENOMEM; +} + +/* Fill a qtd, returning how much of the buffer we were able to queue up. + */ +static int qtd_fill(struct ehci_qtd *qtd, dma_addr_t buf, size_t len, + int token, int maxpacket) +{ + int i, count; + u64 addr = buf; + + /* one buffer entry per 4K ... first might be short or unaligned */ + qtd->hw_buf[0] = cpu_to_le32((u32)addr); + qtd->hw_buf_hi[0] = cpu_to_le32((u32)(addr >> 32)); + count = 0x1000 - (buf & 0x0fff); /* rest of that page */ + if (likely(len < count)) /* ... iff needed */ + count = len; + else { + buf += 0x1000; + buf &= ~0x0fff; + + /* per-qtd limit: from 16K to 20K (best alignment) */ + for (i = 1; count < len && i < 5; i++) { + addr = buf; + qtd->hw_buf[i] = cpu_to_le32((u32)addr); + qtd->hw_buf_hi[i] = cpu_to_le32((u32)(addr >> 32)); + buf += 0x1000; + if ((count + 0x1000) < len) + count += 0x1000; + else + count = len; + } + + /* short packets may only terminate transfers */ + if (count != len) + count -= (count % maxpacket); + } + qtd->hw_token = cpu_to_le32((count << 16) | token); + qtd->length = count; + + return count; +} + +static inline void qh_update(struct oxu_hcd *oxu, + struct ehci_qh *qh, struct ehci_qtd *qtd) +{ + /* writes to an active overlay are unsafe */ + BUG_ON(qh->qh_state != QH_STATE_IDLE); + + qh->hw_qtd_next = QTD_NEXT(qtd->qtd_dma); + qh->hw_alt_next = EHCI_LIST_END; + + /* Except for control endpoints, we make hardware maintain data + * toggle (like OHCI) ... here (re)initialize the toggle in the QH, + * and set the pseudo-toggle in udev. Only usb_clear_halt() will + * ever clear it. + */ + if (!(qh->hw_info1 & cpu_to_le32(1 << 14))) { + unsigned is_out, epnum; + + 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); + 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); +} + +/* If it weren't for a common silicon quirk (writing the dummy into the qh + * overlay, so qh->hw_token wrongly becomes inactive/halted), only fault + * recovery (including urb dequeue) would need software changes to a QH... + */ +static void qh_refresh(struct oxu_hcd *oxu, struct ehci_qh *qh) +{ + struct ehci_qtd *qtd; + + if (list_empty(&qh->qtd_list)) + qtd = qh->dummy; + else { + qtd = list_entry(qh->qtd_list.next, + struct ehci_qtd, qtd_list); + /* first qtd may already be partially processed */ + if (cpu_to_le32(qtd->qtd_dma) == qh->hw_current) + qtd = NULL; + } + + if (qtd) + qh_update(oxu, qh, qtd); +} + +static void qtd_copy_status(struct oxu_hcd *oxu, struct urb *urb, + size_t length, u32 token) +{ + /* count IN/OUT bytes, not SETUP (even short packets) */ + if (likely(QTD_PID(token) != 2)) + urb->actual_length += length - QTD_LENGTH(token); + + /* don't modify error codes */ + if (unlikely(urb->status != -EINPROGRESS)) + return; + + /* force cleanup after short read; not always an error */ + if (unlikely(IS_SHORT_READ(token))) + urb->status = -EREMOTEIO; + + /* serious "can't proceed" faults reported by the hardware */ + if (token & QTD_STS_HALT) { + if (token & QTD_STS_BABBLE) { + /* FIXME "must" disable babbling device's port too */ + urb->status = -EOVERFLOW; + } else if (token & QTD_STS_MMF) { + /* fs/ls interrupt xfer missed the complete-split */ + urb->status = -EPROTO; + } else if (token & QTD_STS_DBE) { + urb->status = (QTD_PID(token) == 1) /* IN ? */ + ? -ENOSR /* hc couldn't read data */ + : -ECOMM; /* hc couldn't write data */ + } else if (token & QTD_STS_XACT) { + /* timeout, bad crc, wrong PID, etc; retried */ + if (QTD_CERR(token)) + urb->status = -EPIPE; + else { + oxu_dbg(oxu, "devpath %s ep%d%s 3strikes\n", + urb->dev->devpath, + usb_pipeendpoint(urb->pipe), + usb_pipein(urb->pipe) ? "in" : "out"); + urb->status = -EPROTO; + } + /* CERR nonzero + no errors + halt --> stall */ + } else if (QTD_CERR(token)) + urb->status = -EPIPE; + else /* unknown */ + urb->status = -EPROTO; + + oxu_vdbg(oxu, "dev%d ep%d%s qtd token %08x --> status %d\n", + usb_pipedevice(urb->pipe), + usb_pipeendpoint(urb->pipe), + usb_pipein(urb->pipe) ? "in" : "out", + token, urb->status); + } +} + +static void ehci_urb_done(struct oxu_hcd *oxu, struct urb *urb) +__releases(oxu->lock) +__acquires(oxu->lock) +{ + if (likely(urb->hcpriv != NULL)) { + 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) { + + /* ... update hc-wide periodic stats (for usbfs) */ + oxu_to_hcd(oxu)->self.bandwidth_int_reqs--; + } + qh_put(qh); + } + + urb->hcpriv = NULL; + switch (urb->status) { + case -EINPROGRESS: /* success */ + urb->status = 0; + default: /* fault */ + break; + case -EREMOTEIO: /* fault or normal */ + if (!(urb->transfer_flags & URB_SHORT_NOT_OK)) + urb->status = 0; + break; + case -ECONNRESET: /* canceled */ + case -ENOENT: + break; + } + +#ifdef OXU_URB_TRACE + oxu_dbg(oxu, "%s %s urb %p ep%d%s status %d len %d/%d\n", + __func__, urb->dev->devpath, urb, + usb_pipeendpoint(urb->pipe), + usb_pipein(urb->pipe) ? "in" : "out", + urb->status, + urb->actual_length, urb->transfer_buffer_length); +#endif + + /* complete() can reenter this HCD */ + spin_unlock(&oxu->lock); + usb_hcd_giveback_urb(oxu_to_hcd(oxu), urb, urb->status); + spin_lock(&oxu->lock); +} + +static void start_unlink_async(struct oxu_hcd *oxu, struct ehci_qh *qh); +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) + +/* Process and free completed qtds for a qh, returning URBs to drivers. + * Chases up to qh->hw_current. Returns number of completions called, + * indicating how much "real" work we did. + */ +static unsigned qh_completions(struct oxu_hcd *oxu, struct ehci_qh *qh) +{ + struct ehci_qtd *last = NULL, *end = qh->dummy; + struct list_head *entry, *tmp; + int stopped; + unsigned count = 0; + int do_status = 0; + u8 state; + struct oxu_murb *murb = NULL; + + if (unlikely(list_empty(&qh->qtd_list))) + return count; + + /* completions (or tasks on other cpus) must never clobber HALT + * till we've gone through and cleaned everything up, even when + * they add urbs to this qh's queue or mark them for unlinking. + * + * NOTE: unlinking expects to be done in queue order. + */ + state = qh->qh_state; + qh->qh_state = QH_STATE_COMPLETING; + stopped = (state == QH_STATE_IDLE); + + /* remove de-activated QTDs from front of queue. + * after faults (including short reads), cleanup this urb + * then let the queue advance. + * if queue is stopped, handles unlinks. + */ + list_for_each_safe(entry, tmp, &qh->qtd_list) { + struct ehci_qtd *qtd; + struct urb *urb; + u32 token = 0; + + qtd = list_entry(entry, struct ehci_qtd, qtd_list); + urb = qtd->urb; + + /* Clean up any state from previous QTD ...*/ + if (last) { + if (likely(last->urb != urb)) { + if (last->urb->complete == NULL) { + murb = (struct oxu_murb *) last->urb; + last->urb = murb->main; + if (murb->last) { + ehci_urb_done(oxu, last->urb); + count++; + } + oxu_murb_free(oxu, murb); + } else { + ehci_urb_done(oxu, last->urb); + count++; + } + } + oxu_qtd_free(oxu, last); + last = NULL; + } + + /* ignore urbs submitted during completions we reported */ + if (qtd == end) + break; + + /* hardware copies qtd out of qh overlay */ + rmb(); + token = le32_to_cpu(qtd->hw_token); + + /* always clean up qtds the hc de-activated */ + if ((token & QTD_STS_ACTIVE) == 0) { + + if ((token & QTD_STS_HALT) != 0) { + stopped = 1; + + /* magic dummy for some short reads; qh won't advance. + * that silicon quirk can kick in with this dummy too. + */ + } else if (IS_SHORT_READ(token) && + !(qtd->hw_alt_next & EHCI_LIST_END)) { + stopped = 1; + goto halt; + } + + /* stop scanning when we reach qtds the hc is using */ + } else if (likely(!stopped && + HC_IS_RUNNING(oxu_to_hcd(oxu)->state))) { + break; + + } else { + stopped = 1; + + if (unlikely(!HC_IS_RUNNING(oxu_to_hcd(oxu)->state))) + urb->status = -ESHUTDOWN; + + /* ignore active urbs unless some previous qtd + * for the urb faulted (including short read) or + * its urb was canceled. we may patch qh or qtds. + */ + if (likely(urb->status == -EINPROGRESS)) + continue; + + /* issue status after short control reads */ + if (unlikely(do_status != 0) + && QTD_PID(token) == 0 /* OUT */) { + do_status = 0; + continue; + } + + /* token in overlay may be most current */ + if (state == QH_STATE_IDLE + && cpu_to_le32(qtd->qtd_dma) + == qh->hw_current) + token = le32_to_cpu(qh->hw_token); + + /* force halt for unlinked or blocked qh, so we'll + * patch the qh later and so that completions can't + * activate it while we "know" it's stopped. + */ + if ((HALT_BIT & qh->hw_token) == 0) { +halt: + qh->hw_token |= HALT_BIT; + wmb(); + } + } + + /* Remove it from the queue */ + qtd_copy_status(oxu, urb->complete ? + urb : ((struct oxu_murb *) urb)->main, + qtd->length, token); + if ((usb_pipein(qtd->urb->pipe)) && + (NULL != qtd->transfer_buffer)) + memcpy(qtd->transfer_buffer, qtd->buffer, qtd->length); + do_status = (urb->status == -EREMOTEIO) + && usb_pipecontrol(urb->pipe); + + if (stopped && qtd->qtd_list.prev != &qh->qtd_list) { + last = list_entry(qtd->qtd_list.prev, + struct ehci_qtd, qtd_list); + last->hw_next = qtd->hw_next; + } + list_del(&qtd->qtd_list); + last = qtd; + } + + /* last urb's completion might still need calling */ + if (likely(last != NULL)) { + if (last->urb->complete == NULL) { + murb = (struct oxu_murb *) last->urb; + last->urb = murb->main; + if (murb->last) { + ehci_urb_done(oxu, last->urb); + count++; + } + oxu_murb_free(oxu, murb); + } else { + ehci_urb_done(oxu, last->urb); + count++; + } + oxu_qtd_free(oxu, last); + } + + /* restore original state; caller must unlink or relink */ + qh->qh_state = state; + + /* be sure the hardware's done with the qh before refreshing + * it after fault cleanup, or recovering from silicon wrongly + * overlaying the dummy qtd (which reduces DMA chatter). + */ + if (stopped != 0 || qh->hw_qtd_next == EHCI_LIST_END) { + switch (state) { + case QH_STATE_IDLE: + qh_refresh(oxu, qh); + break; + case QH_STATE_LINKED: + /* should be rare for periodic transfers, + * except maybe high bandwidth ... + */ + if ((__constant_cpu_to_le32(QH_SMASK) + & qh->hw_info2) != 0) { + intr_deschedule(oxu, qh); + (void) qh_schedule(oxu, qh); + } else + unlink_async(oxu, qh); + break; + /* otherwise, unlink already started */ + } + } + + return count; +} + +/* High bandwidth multiplier, as encoded in highspeed endpoint descriptors */ +#define hb_mult(wMaxPacketSize) (1 + (((wMaxPacketSize) >> 11) & 0x03)) +/* ... and packet size, for any kind of endpoint descriptor */ +#define max_packet(wMaxPacketSize) ((wMaxPacketSize) & 0x07ff) + +/* Reverse of qh_urb_transaction: free a list of TDs. + * used for cleanup after errors, before HC sees an URB's TDs. + */ +static void qtd_list_free(struct oxu_hcd *oxu, + struct urb *urb, struct list_head *qtd_list) +{ + struct list_head *entry, *temp; + + list_for_each_safe(entry, temp, qtd_list) { + struct ehci_qtd *qtd; + + qtd = list_entry(entry, struct ehci_qtd, qtd_list); + list_del(&qtd->qtd_list); + oxu_qtd_free(oxu, qtd); + } +} + +/* Create a list of filled qtds for this URB; won't link into qh. + */ +static struct list_head *qh_urb_transaction(struct oxu_hcd *oxu, + struct urb *urb, + struct list_head *head, + gfp_t flags) +{ + struct ehci_qtd *qtd, *qtd_prev; + dma_addr_t buf; + int len, maxpacket; + int is_input; + u32 token; + void *transfer_buf = NULL; + int ret; + + /* + * URBs map to sequences of QTDs: one logical transaction + */ + qtd = ehci_qtd_alloc(oxu); + if (unlikely(!qtd)) + return NULL; + list_add_tail(&qtd->qtd_list, head); + qtd->urb = urb; + + token = QTD_STS_ACTIVE; + token |= (EHCI_TUNE_CERR << 10); + /* for split transactions, SplitXState initialized to zero */ + + len = urb->transfer_buffer_length; + is_input = usb_pipein(urb->pipe); + if (!urb->transfer_buffer && urb->transfer_buffer_length && is_input) + urb->transfer_buffer = phys_to_virt(urb->transfer_dma); + + if (usb_pipecontrol(urb->pipe)) { + /* SETUP pid */ + ret = oxu_buf_alloc(oxu, qtd, sizeof(struct usb_ctrlrequest)); + if (ret) + goto cleanup; + + qtd_fill(qtd, qtd->buffer_dma, sizeof(struct usb_ctrlrequest), + token | (2 /* "setup" */ << 8), 8); + memcpy(qtd->buffer, qtd->urb->setup_packet, + sizeof(struct usb_ctrlrequest)); + + /* ... and always at least one more pid */ + token ^= QTD_TOGGLE; + qtd_prev = qtd; + qtd = ehci_qtd_alloc(oxu); + if (unlikely(!qtd)) + goto cleanup; + qtd->urb = urb; + qtd_prev->hw_next = QTD_NEXT(qtd->qtd_dma); + list_add_tail(&qtd->qtd_list, head); + + /* for zero length DATA stages, STATUS is always IN */ + if (len == 0) + token |= (1 /* "in" */ << 8); + } + + /* + * Data transfer stage: buffer setup + */ + + ret = oxu_buf_alloc(oxu, qtd, len); + if (ret) + goto cleanup; + + buf = qtd->buffer_dma; + transfer_buf = urb->transfer_buffer; + + if (!is_input) + memcpy(qtd->buffer, qtd->urb->transfer_buffer, len); + + if (is_input) + token |= (1 /* "in" */ << 8); + /* else it's already initted to "out" pid (0 << 8) */ + + maxpacket = max_packet(usb_maxpacket(urb->dev, urb->pipe, !is_input)); + + /* + * buffer gets wrapped in one or more qtds; + * last one may be "short" (including zero len) + * and may serve as a control status ack + */ + for (;;) { + int this_qtd_len; + + this_qtd_len = qtd_fill(qtd, buf, len, token, maxpacket); + qtd->transfer_buffer = transfer_buf; + len -= this_qtd_len; + buf += this_qtd_len; + transfer_buf += this_qtd_len; + if (is_input) + qtd->hw_alt_next = oxu->async->hw_alt_next; + + /* qh makes control packets use qtd toggle; maybe switch it */ + if ((maxpacket & (this_qtd_len + (maxpacket - 1))) == 0) + token ^= QTD_TOGGLE; + + if (likely(len <= 0)) + break; + + qtd_prev = qtd; + qtd = ehci_qtd_alloc(oxu); + if (unlikely(!qtd)) + goto cleanup; + if (likely(len > 0)) { + ret = oxu_buf_alloc(oxu, qtd, len); + if (ret) + goto cleanup; + } + qtd->urb = urb; + qtd_prev->hw_next = QTD_NEXT(qtd->qtd_dma); + list_add_tail(&qtd->qtd_list, head); + } + + /* unless the bulk/interrupt caller wants a chance to clean + * up after short reads, hc should advance qh past this urb + */ + if (likely((urb->transfer_flags & URB_SHORT_NOT_OK) == 0 + || usb_pipecontrol(urb->pipe))) + qtd->hw_alt_next = EHCI_LIST_END; + + /* + * control requests may need a terminating data "status" ack; + * bulk ones may need a terminating short packet (zero length). + */ + if (likely(urb->transfer_buffer_length != 0)) { + int one_more = 0; + + if (usb_pipecontrol(urb->pipe)) { + one_more = 1; + token ^= 0x0100; /* "in" <--> "out" */ + token |= QTD_TOGGLE; /* force DATA1 */ + } else if (usb_pipebulk(urb->pipe) + && (urb->transfer_flags & URB_ZERO_PACKET) + && !(urb->transfer_buffer_length % maxpacket)) { + one_more = 1; + } + if (one_more) { + qtd_prev = qtd; + qtd = ehci_qtd_alloc(oxu); + if (unlikely(!qtd)) + goto cleanup; + qtd->urb = urb; + qtd_prev->hw_next = QTD_NEXT(qtd->qtd_dma); + list_add_tail(&qtd->qtd_list, head); + + /* never any data in such packets */ + qtd_fill(qtd, 0, 0, token, 0); + } + } + + /* by default, enable interrupt on urb completion */ + qtd->hw_token |= __constant_cpu_to_le32(QTD_IOC); + return head; + +cleanup: + qtd_list_free(oxu, urb, head); + return NULL; +} + +/* Each QH holds a qtd list; a QH is used for everything except iso. + * + * For interrupt urbs, the scheduler must set the microframe scheduling + * mask(s) each time the QH gets scheduled. For highspeed, that's + * just one microframe in the s-mask. For split interrupt transactions + * there are additional complications: c-mask, maybe FSTNs. + */ +static struct ehci_qh *qh_make(struct oxu_hcd *oxu, + struct urb *urb, gfp_t flags) +{ + struct ehci_qh *qh = oxu_qh_alloc(oxu); + u32 info1 = 0, info2 = 0; + int is_input, type; + int maxp = 0; + + if (!qh) + return qh; + + /* + * init endpoint/device data for this QH + */ + info1 |= usb_pipeendpoint(urb->pipe) << 8; + info1 |= usb_pipedevice(urb->pipe) << 0; + + is_input = usb_pipein(urb->pipe); + type = usb_pipetype(urb->pipe); + maxp = usb_maxpacket(urb->dev, urb->pipe, !is_input); + + /* Compute interrupt scheduling parameters just once, and save. + * - allowing for high bandwidth, how many nsec/uframe are used? + * - split transactions need a second CSPLIT uframe; same question + * - splits also need a schedule gap (for full/low speed I/O) + * - qh has a polling interval + * + * For control/bulk requests, the HC or TT handles these. + */ + if (type == PIPE_INTERRUPT) { + qh->usecs = NS_TO_US(usb_calc_bus_time(USB_SPEED_HIGH, + is_input, 0, + hb_mult(maxp) * max_packet(maxp))); + qh->start = NO_FRAME; + + if (urb->dev->speed == USB_SPEED_HIGH) { + qh->c_usecs = 0; + qh->gap_uf = 0; + + qh->period = urb->interval >> 3; + if (qh->period == 0 && urb->interval != 1) { + /* NOTE interval 2 or 4 uframes could work. + * But interval 1 scheduling is simpler, and + * includes high bandwidth. + */ + dbg("intr period %d uframes, NYET!", + urb->interval); + goto done; + } + } else { + struct usb_tt *tt = urb->dev->tt; + int think_time; + + /* gap is f(FS/LS transfer times) */ + qh->gap_uf = 1 + usb_calc_bus_time(urb->dev->speed, + is_input, 0, maxp) / (125 * 1000); + + /* FIXME this just approximates SPLIT/CSPLIT times */ + if (is_input) { /* SPLIT, gap, CSPLIT+DATA */ + qh->c_usecs = qh->usecs + HS_USECS(0); + qh->usecs = HS_USECS(1); + } else { /* SPLIT+DATA, gap, CSPLIT */ + qh->usecs += HS_USECS(1); + qh->c_usecs = HS_USECS(0); + } + + think_time = tt ? tt->think_time : 0; + qh->tt_usecs = NS_TO_US(think_time + + usb_calc_bus_time(urb->dev->speed, + is_input, 0, max_packet(maxp))); + qh->period = urb->interval; + } + } + + /* support for tt scheduling, and access to toggles */ + qh->dev = urb->dev; + + /* using TT? */ + switch (urb->dev->speed) { + case USB_SPEED_LOW: + info1 |= (1 << 12); /* EPS "low" */ + /* FALL THROUGH */ + + case USB_SPEED_FULL: + /* EPS 0 means "full" */ + if (type != PIPE_INTERRUPT) + info1 |= (EHCI_TUNE_RL_TT << 28); + if (type == PIPE_CONTROL) { + info1 |= (1 << 27); /* for TT */ + info1 |= 1 << 14; /* toggle from qtd */ + } + info1 |= maxp << 16; + + info2 |= (EHCI_TUNE_MULT_TT << 30); + info2 |= urb->dev->ttport << 23; + + /* NOTE: if (PIPE_INTERRUPT) { scheduler sets c-mask } */ + + break; + + case USB_SPEED_HIGH: /* no TT involved */ + info1 |= (2 << 12); /* EPS "high" */ + if (type == PIPE_CONTROL) { + info1 |= (EHCI_TUNE_RL_HS << 28); + info1 |= 64 << 16; /* usb2 fixed maxpacket */ + info1 |= 1 << 14; /* toggle from qtd */ + info2 |= (EHCI_TUNE_MULT_HS << 30); + } else if (type == PIPE_BULK) { + info1 |= (EHCI_TUNE_RL_HS << 28); + info1 |= 512 << 16; /* usb2 fixed maxpacket */ + info2 |= (EHCI_TUNE_MULT_HS << 30); + } else { /* PIPE_INTERRUPT */ + info1 |= max_packet(maxp) << 16; + info2 |= hb_mult(maxp) << 30; + } + break; + default: + dbg("bogus dev %p speed %d", urb->dev, urb->dev->speed); +done: + qh_put(qh); + return NULL; + } + + /* NOTE: if (PIPE_INTERRUPT) { scheduler sets s-mask } */ + + /* init as live, toggle clear, advance to dummy */ + qh->qh_state = QH_STATE_IDLE; + qh->hw_info1 = cpu_to_le32(info1); + qh->hw_info2 = cpu_to_le32(info2); + usb_settoggle(urb->dev, usb_pipeendpoint(urb->pipe), !is_input, 1); + qh_refresh(oxu, qh); + return qh; +} + +/* Move qh (and its qtds) onto async queue; maybe enable queue. + */ +static void qh_link_async(struct oxu_hcd *oxu, struct ehci_qh *qh) +{ + __le32 dma = QH_NEXT(qh->qh_dma); + struct ehci_qh *head; + + /* (re)start the async schedule? */ + head = oxu->async; + timer_action_done(oxu, TIMER_ASYNC_OFF); + if (!head->qh_next.qh) { + u32 cmd = readl(&oxu->regs->command); + + if (!(cmd & CMD_ASE)) { + /* in case a clear of CMD_ASE didn't take yet */ + (void)handshake(oxu, &oxu->regs->status, + STS_ASS, 0, 150); + cmd |= CMD_ASE | CMD_RUN; + writel(cmd, &oxu->regs->command); + oxu_to_hcd(oxu)->state = HC_STATE_RUNNING; + /* posted write need not be known to HC yet ... */ + } + } + + /* clear halt and/or toggle; and maybe recover from silicon quirk */ + if (qh->qh_state == QH_STATE_IDLE) + qh_refresh(oxu, qh); + + /* splice right after start */ + qh->qh_next = head->qh_next; + qh->hw_next = head->hw_next; + wmb(); + + head->qh_next.qh = qh; + head->hw_next = dma; + + qh->qh_state = QH_STATE_LINKED; + /* qtd completions reported later by interrupt */ +} + +#define QH_ADDR_MASK __constant_cpu_to_le32(0x7f) + +/* + * For control/bulk/interrupt, return QH with these TDs appended. + * Allocates and initializes the QH if necessary. + * Returns null if it can't allocate a QH it needs to. + * If the QH has TDs (urbs) already, that's great. + */ +static struct ehci_qh *qh_append_tds(struct oxu_hcd *oxu, + struct urb *urb, struct list_head *qtd_list, + int epnum, void **ptr) +{ + struct ehci_qh *qh = NULL; + + qh = (struct ehci_qh *) *ptr; + if (unlikely(qh == NULL)) { + /* can't sleep here, we have oxu->lock... */ + qh = qh_make(oxu, urb, GFP_ATOMIC); + *ptr = qh; + } + if (likely(qh != NULL)) { + struct ehci_qtd *qtd; + + if (unlikely(list_empty(qtd_list))) + qtd = NULL; + else + qtd = list_entry(qtd_list->next, struct ehci_qtd, + qtd_list); + + /* control qh may need patching ... */ + if (unlikely(epnum == 0)) { + + /* usb_reset_device() briefly reverts to address 0 */ + if (usb_pipedevice(urb->pipe) == 0) + qh->hw_info1 &= ~QH_ADDR_MASK; + } + + /* just one way to queue requests: swap with the dummy qtd. + * only hc or qh_refresh() ever modify the overlay. + */ + if (likely(qtd != NULL)) { + struct ehci_qtd *dummy; + dma_addr_t dma; + __le32 token; + + /* to avoid racing the HC, use the dummy td instead of + * the first td of our list (becomes new dummy). both + * tds stay deactivated until we're done, when the + * HC is allowed to fetch the old dummy (4.10.2). + */ + token = qtd->hw_token; + qtd->hw_token = HALT_BIT; + wmb(); + dummy = qh->dummy; + + dma = dummy->qtd_dma; + *dummy = *qtd; + dummy->qtd_dma = dma; + + list_del(&qtd->qtd_list); + list_add(&dummy->qtd_list, qtd_list); + list_splice(qtd_list, qh->qtd_list.prev); + + ehci_qtd_init(qtd, qtd->qtd_dma); + qh->dummy = qtd; + + /* hc must see the new dummy at list end */ + dma = qtd->qtd_dma; + qtd = list_entry(qh->qtd_list.prev, + struct ehci_qtd, qtd_list); + qtd->hw_next = QTD_NEXT(dma); + + /* let the hc process these next qtds */ + dummy->hw_token = (token & ~(0x80)); + wmb(); + dummy->hw_token = token; + + urb->hcpriv = qh_get(qh); + } + } + return qh; +} + +static int submit_async(struct oxu_hcd *oxu, struct urb *urb, + struct list_head *qtd_list, gfp_t mem_flags) +{ + struct ehci_qtd *qtd; + int epnum; + unsigned long flags; + struct ehci_qh *qh = NULL; + int rc = 0; + + qtd = list_entry(qtd_list->next, struct ehci_qtd, qtd_list); + epnum = urb->ep->desc.bEndpointAddress; + +#ifdef OXU_URB_TRACE + oxu_dbg(oxu, "%s %s urb %p ep%d%s len %d, qtd %p [qh %p]\n", + __func__, urb->dev->devpath, urb, + epnum & 0x0f, (epnum & USB_DIR_IN) ? "in" : "out", + urb->transfer_buffer_length, + qtd, urb->ep->hcpriv); +#endif + + spin_lock_irqsave(&oxu->lock, flags); + if (unlikely(!test_bit(HCD_FLAG_HW_ACCESSIBLE, + &oxu_to_hcd(oxu)->flags))) { + rc = -ESHUTDOWN; + goto done; + } + + qh = qh_append_tds(oxu, urb, qtd_list, epnum, &urb->ep->hcpriv); + if (unlikely(qh == NULL)) { + rc = -ENOMEM; + goto done; + } + + /* Control/bulk operations through TTs don't need scheduling, + * the HC and TT handle it when the TT has a buffer ready. + */ + if (likely(qh->qh_state == QH_STATE_IDLE)) + qh_link_async(oxu, qh_get(qh)); +done: + spin_unlock_irqrestore(&oxu->lock, flags); + if (unlikely(qh == NULL)) + qtd_list_free(oxu, urb, qtd_list); + return rc; +} + +/* The async qh for the qtds being reclaimed are now unlinked from the HC */ + +static void end_unlink_async(struct oxu_hcd *oxu) +{ + struct ehci_qh *qh = oxu->reclaim; + struct ehci_qh *next; + + timer_action_done(oxu, TIMER_IAA_WATCHDOG); + + qh->qh_state = QH_STATE_IDLE; + qh->qh_next.qh = NULL; + qh_put(qh); /* refcount from reclaim */ + + /* other unlink(s) may be pending (in QH_STATE_UNLINK_WAIT) */ + next = qh->reclaim; + oxu->reclaim = next; + oxu->reclaim_ready = 0; + qh->reclaim = NULL; + + qh_completions(oxu, qh); + + if (!list_empty(&qh->qtd_list) + && HC_IS_RUNNING(oxu_to_hcd(oxu)->state)) + qh_link_async(oxu, qh); + else { + qh_put(qh); /* refcount from async list */ + + /* it's not free to turn the async schedule on/off; leave it + * active but idle for a while once it empties. + */ + if (HC_IS_RUNNING(oxu_to_hcd(oxu)->state) + && oxu->async->qh_next.qh == NULL) + timer_action(oxu, TIMER_ASYNC_OFF); + } + + if (next) { + oxu->reclaim = NULL; + start_unlink_async(oxu, next); + } +} + +/* makes sure the async qh will become idle */ +/* caller must own oxu->lock */ + +static void start_unlink_async(struct oxu_hcd *oxu, struct ehci_qh *qh) +{ + int cmd = readl(&oxu->regs->command); + struct ehci_qh *prev; + +#ifdef DEBUG + assert_spin_locked(&oxu->lock); + if (oxu->reclaim || (qh->qh_state != QH_STATE_LINKED + && qh->qh_state != QH_STATE_UNLINK_WAIT)) + BUG(); +#endif + + /* stop async schedule right now? */ + if (unlikely(qh == oxu->async)) { + /* can't get here without STS_ASS set */ + if (oxu_to_hcd(oxu)->state != HC_STATE_HALT + && !oxu->reclaim) { + /* ... and CMD_IAAD clear */ + writel(cmd & ~CMD_ASE, &oxu->regs->command); + wmb(); + /* handshake later, if we need to */ + timer_action_done(oxu, TIMER_ASYNC_OFF); + } + return; + } + + qh->qh_state = QH_STATE_UNLINK; + oxu->reclaim = qh = qh_get(qh); + + prev = oxu->async; + while (prev->qh_next.qh != qh) + prev = prev->qh_next.qh; + + prev->hw_next = qh->hw_next; + prev->qh_next = qh->qh_next; + wmb(); + + if (unlikely(oxu_to_hcd(oxu)->state == HC_STATE_HALT)) { + /* if (unlikely(qh->reclaim != 0)) + * this will recurse, probably not much + */ + end_unlink_async(oxu); + return; + } + + oxu->reclaim_ready = 0; + cmd |= CMD_IAAD; + writel(cmd, &oxu->regs->command); + (void) readl(&oxu->regs->command); + timer_action(oxu, TIMER_IAA_WATCHDOG); +} + +static void scan_async(struct oxu_hcd *oxu) +{ + struct ehci_qh *qh; + enum ehci_timer_action action = TIMER_IO_WATCHDOG; + + if (!++(oxu->stamp)) + oxu->stamp++; + timer_action_done(oxu, TIMER_ASYNC_SHRINK); +rescan: + qh = oxu->async->qh_next.qh; + if (likely(qh != NULL)) { + do { + /* clean any finished work for this qh */ + if (!list_empty(&qh->qtd_list) + && qh->stamp != oxu->stamp) { + int temp; + + /* unlinks could happen here; completion + * reporting drops the lock. rescan using + * the latest schedule, but don't rescan + * qhs we already finished (no looping). + */ + qh = qh_get(qh); + qh->stamp = oxu->stamp; + temp = qh_completions(oxu, qh); + qh_put(qh); + if (temp != 0) + goto rescan; + } + + /* unlink idle entries, reducing HC PCI usage as well + * as HCD schedule-scanning costs. delay for any qh + * we just scanned, there's a not-unusual case that it + * doesn't stay idle for long. + * (plus, avoids some kind of re-activation race.) + */ + if (list_empty(&qh->qtd_list)) { + if (qh->stamp == oxu->stamp) + action = TIMER_ASYNC_SHRINK; + else if (!oxu->reclaim + && qh->qh_state == QH_STATE_LINKED) + start_unlink_async(oxu, qh); + } + + qh = qh->qh_next.qh; + } while (qh); + } + if (action == TIMER_ASYNC_SHRINK) + timer_action(oxu, TIMER_ASYNC_SHRINK); +} + +/* + * periodic_next_shadow - return "next" pointer on shadow list + * @periodic: host pointer to qh/itd/sitd + * @tag: hardware tag for type of this record + */ +static union ehci_shadow *periodic_next_shadow(union ehci_shadow *periodic, + __le32 tag) +{ + switch (tag) { + default: + case Q_TYPE_QH: + return &periodic->qh->qh_next; + } +} + +/* caller must hold oxu->lock */ +static void periodic_unlink(struct oxu_hcd *oxu, unsigned frame, void *ptr) +{ + union ehci_shadow *prev_p = &oxu->pshadow[frame]; + __le32 *hw_p = &oxu->periodic[frame]; + union ehci_shadow here = *prev_p; + + /* find predecessor of "ptr"; hw and shadow lists are in sync */ + while (here.ptr && here.ptr != ptr) { + prev_p = periodic_next_shadow(prev_p, Q_NEXT_TYPE(*hw_p)); + hw_p = here.hw_next; + here = *prev_p; + } + /* an interrupt entry (at list end) could have been shared */ + if (!here.ptr) + return; + + /* update shadow and hardware lists ... the old "next" pointers + * from ptr may still be in use, the caller updates them. + */ + *prev_p = *periodic_next_shadow(&here, Q_NEXT_TYPE(*hw_p)); + *hw_p = *here.hw_next; +} + +/* how many of the uframe's 125 usecs are allocated? */ +static unsigned short periodic_usecs(struct oxu_hcd *oxu, + unsigned frame, unsigned uframe) +{ + __le32 *hw_p = &oxu->periodic[frame]; + union ehci_shadow *q = &oxu->pshadow[frame]; + unsigned usecs = 0; + + while (q->ptr) { + switch (Q_NEXT_TYPE(*hw_p)) { + case Q_TYPE_QH: + default: + /* is it in the S-mask? */ + if (q->qh->hw_info2 & cpu_to_le32(1 << uframe)) + usecs += q->qh->usecs; + /* ... or C-mask? */ + if (q->qh->hw_info2 & cpu_to_le32(1 << (8 + uframe))) + usecs += q->qh->c_usecs; + hw_p = &q->qh->hw_next; + q = &q->qh->qh_next; + break; + } + } +#ifdef DEBUG + if (usecs > 100) + oxu_err(oxu, "uframe %d sched overrun: %d usecs\n", + frame * 8 + uframe, usecs); +#endif + return usecs; +} + +static int enable_periodic(struct oxu_hcd *oxu) +{ + u32 cmd; + int status; + + /* did clearing PSE did take effect yet? + * takes effect only at frame boundaries... + */ + status = handshake(oxu, &oxu->regs->status, STS_PSS, 0, 9 * 125); + if (status != 0) { + oxu_to_hcd(oxu)->state = HC_STATE_HALT; + return status; + } + + cmd = readl(&oxu->regs->command) | CMD_PSE; + writel(cmd, &oxu->regs->command); + /* posted write ... PSS happens later */ + oxu_to_hcd(oxu)->state = HC_STATE_RUNNING; + + /* make sure ehci_work scans these */ + oxu->next_uframe = readl(&oxu->regs->frame_index) + % (oxu->periodic_size << 3); + return 0; +} + +static int disable_periodic(struct oxu_hcd *oxu) +{ + u32 cmd; + int status; + + /* did setting PSE not take effect yet? + * takes effect only at frame boundaries... + */ + status = handshake(oxu, &oxu->regs->status, STS_PSS, STS_PSS, 9 * 125); + if (status != 0) { + oxu_to_hcd(oxu)->state = HC_STATE_HALT; + return status; + } + + cmd = readl(&oxu->regs->command) & ~CMD_PSE; + writel(cmd, &oxu->regs->command); + /* posted write ... */ + + oxu->next_uframe = -1; + return 0; +} + +/* periodic schedule slots have iso tds (normal or split) first, then a + * sparse tree for active interrupt transfers. + * + * this just links in a qh; caller guarantees uframe masks are set right. + * no FSTN support (yet; oxu 0.96+) + */ +static int qh_link_periodic(struct oxu_hcd *oxu, struct ehci_qh *qh) +{ + unsigned i; + unsigned period = qh->period; + + dev_dbg(&qh->dev->dev, + "link qh%d-%04x/%p start %d [%d/%d us]\n", + period, le32_to_cpup(&qh->hw_info2) & (QH_CMASK | QH_SMASK), + qh, qh->start, qh->usecs, qh->c_usecs); + + /* high bandwidth, or otherwise every microframe */ + if (period == 0) + period = 1; + + for (i = qh->start; i < oxu->periodic_size; i += period) { + union ehci_shadow *prev = &oxu->pshadow[i]; + __le32 *hw_p = &oxu->periodic[i]; + union ehci_shadow here = *prev; + __le32 type = 0; + + /* skip the iso nodes at list head */ + while (here.ptr) { + type = Q_NEXT_TYPE(*hw_p); + if (type == Q_TYPE_QH) + break; + prev = periodic_next_shadow(prev, type); + hw_p = &here.qh->hw_next; + here = *prev; + } + + /* sorting each branch by period (slow-->fast) + * enables sharing interior tree nodes + */ + while (here.ptr && qh != here.qh) { + if (qh->period > here.qh->period) + break; + prev = &here.qh->qh_next; + hw_p = &here.qh->hw_next; + here = *prev; + } + /* link in this qh, unless some earlier pass did that */ + if (qh != here.qh) { + qh->qh_next = here; + if (here.qh) + qh->hw_next = *hw_p; + wmb(); + prev->qh = qh; + *hw_p = QH_NEXT(qh->qh_dma); + } + } + qh->qh_state = QH_STATE_LINKED; + qh_get(qh); + + /* update per-qh bandwidth for usbfs */ + oxu_to_hcd(oxu)->self.bandwidth_allocated += qh->period + ? ((qh->usecs + qh->c_usecs) / qh->period) + : (qh->usecs * 8); + + /* maybe enable periodic schedule processing */ + if (!oxu->periodic_sched++) + return enable_periodic(oxu); + + return 0; +} + +static void qh_unlink_periodic(struct oxu_hcd *oxu, struct ehci_qh *qh) +{ + unsigned i; + unsigned period; + + /* FIXME: + * IF this isn't high speed + * 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"); + */ + + /* high bandwidth, or otherwise part of every microframe */ + period = qh->period; + if (period == 0) + period = 1; + + for (i = qh->start; i < oxu->periodic_size; i += period) + periodic_unlink(oxu, i, qh); + + /* update per-qh bandwidth for usbfs */ + oxu_to_hcd(oxu)->self.bandwidth_allocated -= qh->period + ? ((qh->usecs + qh->c_usecs) / qh->period) + : (qh->usecs * 8); + + dev_dbg(&qh->dev->dev, + "unlink qh%d-%04x/%p start %d [%d/%d us]\n", + qh->period, + le32_to_cpup(&qh->hw_info2) & (QH_CMASK | QH_SMASK), + qh, qh->start, qh->usecs, qh->c_usecs); + + /* qh->qh_next still "live" to HC */ + qh->qh_state = QH_STATE_UNLINK; + qh->qh_next.ptr = NULL; + qh_put(qh); + + /* maybe turn off periodic schedule */ + oxu->periodic_sched--; + if (!oxu->periodic_sched) + (void) disable_periodic(oxu); +} + +static void intr_deschedule(struct oxu_hcd *oxu, struct ehci_qh *qh) +{ + unsigned wait; + + qh_unlink_periodic(oxu, qh); + + /* simple/paranoid: always delay, expecting the HC needs to read + * qh->hw_next or finish a writeback after SPLIT/CSPLIT ... and + * expect khubd to clean up after any CSPLITs we won't issue. + * active high speed queues may need bigger delays... + */ + if (list_empty(&qh->qtd_list) + || (__constant_cpu_to_le32(QH_CMASK) & qh->hw_info2) != 0) + wait = 2; + else + wait = 55; /* worst case: 3 * 1024 */ + + udelay(wait); + qh->qh_state = QH_STATE_IDLE; + qh->hw_next = EHCI_LIST_END; + wmb(); +} + +static int check_period(struct oxu_hcd *oxu, + unsigned frame, unsigned uframe, + unsigned period, unsigned usecs) +{ + int claimed; + + /* complete split running into next frame? + * given FSTN support, we could sometimes check... + */ + if (uframe >= 8) + return 0; + + /* + * 80% periodic == 100 usec/uframe available + * convert "usecs we need" to "max already claimed" + */ + usecs = 100 - usecs; + + /* we "know" 2 and 4 uframe intervals were rejected; so + * for period 0, check _every_ microframe in the schedule. + */ + if (unlikely(period == 0)) { + do { + for (uframe = 0; uframe < 7; uframe++) { + claimed = periodic_usecs(oxu, frame, uframe); + if (claimed > usecs) + return 0; + } + } while ((frame += 1) < oxu->periodic_size); + + /* just check the specified uframe, at that period */ + } else { + do { + claimed = periodic_usecs(oxu, frame, uframe); + if (claimed > usecs) + return 0; + } while ((frame += period) < oxu->periodic_size); + } + + return 1; +} + +static int check_intr_schedule(struct oxu_hcd *oxu, + unsigned frame, unsigned uframe, + const struct ehci_qh *qh, __le32 *c_maskp) +{ + int retval = -ENOSPC; + + if (qh->c_usecs && uframe >= 6) /* FSTN territory? */ + goto done; + + if (!check_period(oxu, frame, uframe, qh->period, qh->usecs)) + goto done; + if (!qh->c_usecs) { + retval = 0; + *c_maskp = 0; + goto done; + } + +done: + return retval; +} + +/* "first fit" scheduling policy used the first time through, + * or when the previous schedule slot can't be re-used. + */ +static int qh_schedule(struct oxu_hcd *oxu, struct ehci_qh *qh) +{ + int status; + unsigned uframe; + __le32 c_mask; + unsigned frame; /* 0..(qh->period - 1), or NO_FRAME */ + + qh_refresh(oxu, qh); + qh->hw_next = EHCI_LIST_END; + frame = qh->start; + + /* reuse the previous schedule slots, if we can */ + if (frame < qh->period) { + uframe = ffs(le32_to_cpup(&qh->hw_info2) & QH_SMASK); + status = check_intr_schedule(oxu, frame, --uframe, + qh, &c_mask); + } else { + uframe = 0; + c_mask = 0; + status = -ENOSPC; + } + + /* else scan the schedule to find a group of slots such that all + * uframes have enough periodic bandwidth available. + */ + if (status) { + /* "normal" case, uframing flexible except with splits */ + if (qh->period) { + frame = qh->period - 1; + do { + for (uframe = 0; uframe < 8; uframe++) { + status = check_intr_schedule(oxu, + frame, uframe, qh, + &c_mask); + if (status == 0) + break; + } + } while (status && frame--); + + /* qh->period == 0 means every uframe */ + } else { + frame = 0; + status = check_intr_schedule(oxu, 0, 0, qh, &c_mask); + } + if (status) + goto done; + 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 |= qh->period + ? cpu_to_le32(1 << uframe) + : __constant_cpu_to_le32(QH_SMASK); + qh->hw_info2 |= c_mask; + } else + oxu_dbg(oxu, "reused qh %p schedule\n", qh); + + /* stuff into the periodic schedule */ + status = qh_link_periodic(oxu, qh); +done: + return status; +} + +static int intr_submit(struct oxu_hcd *oxu, struct urb *urb, + struct list_head *qtd_list, gfp_t mem_flags) +{ + unsigned epnum; + unsigned long flags; + struct ehci_qh *qh; + int status = 0; + struct list_head empty; + + /* get endpoint and transfer/schedule data */ + epnum = urb->ep->desc.bEndpointAddress; + + spin_lock_irqsave(&oxu->lock, flags); + + if (unlikely(!test_bit(HCD_FLAG_HW_ACCESSIBLE, + &oxu_to_hcd(oxu)->flags))) { + status = -ESHUTDOWN; + goto done; + } + + /* get qh and force any scheduling errors */ + INIT_LIST_HEAD(&empty); + qh = qh_append_tds(oxu, urb, &empty, epnum, &urb->ep->hcpriv); + if (qh == NULL) { + status = -ENOMEM; + goto done; + } + if (qh->qh_state == QH_STATE_IDLE) { + status = qh_schedule(oxu, qh); + if (status != 0) + goto done; + } + + /* then queue the urb's tds to the qh */ + qh = qh_append_tds(oxu, urb, qtd_list, epnum, &urb->ep->hcpriv); + BUG_ON(qh == NULL); + + /* ... update usbfs periodic stats */ + oxu_to_hcd(oxu)->self.bandwidth_int_reqs++; + +done: + spin_unlock_irqrestore(&oxu->lock, flags); + if (status) + qtd_list_free(oxu, urb, qtd_list); + + return status; +} + +static inline int itd_submit(struct oxu_hcd *oxu, struct urb *urb, + gfp_t mem_flags) +{ + oxu_dbg(oxu, "iso support is missing!\n"); + return -ENOSYS; +} + +static inline int sitd_submit(struct oxu_hcd *oxu, struct urb *urb, + gfp_t mem_flags) +{ + oxu_dbg(oxu, "split iso support is missing!\n"); + return -ENOSYS; +} + +static void scan_periodic(struct oxu_hcd *oxu) +{ + unsigned frame, clock, now_uframe, mod; + unsigned modified; + + mod = oxu->periodic_size << 3; + + /* + * When running, scan from last scan point up to "now" + * else clean up by scanning everything that's left. + * Touches as few pages as possible: cache-friendly. + */ + now_uframe = oxu->next_uframe; + if (HC_IS_RUNNING(oxu_to_hcd(oxu)->state)) + clock = readl(&oxu->regs->frame_index); + else + clock = now_uframe + mod - 1; + clock %= mod; + + for (;;) { + union ehci_shadow q, *q_p; + __le32 type, *hw_p; + unsigned uframes; + + /* don't scan past the live uframe */ + frame = now_uframe >> 3; + if (frame == (clock >> 3)) + uframes = now_uframe & 0x07; + else { + /* safe to scan the whole frame at once */ + now_uframe |= 0x07; + uframes = 8; + } + +restart: + /* scan each element in frame's queue for completions */ + q_p = &oxu->pshadow[frame]; + hw_p = &oxu->periodic[frame]; + q.ptr = q_p->ptr; + type = Q_NEXT_TYPE(*hw_p); + modified = 0; + + while (q.ptr != NULL) { + union ehci_shadow temp; + int live; + + live = HC_IS_RUNNING(oxu_to_hcd(oxu)->state); + switch (type) { + case Q_TYPE_QH: + /* handle any completions */ + temp.qh = qh_get(q.qh); + type = Q_NEXT_TYPE(q.qh->hw_next); + q = q.qh->qh_next; + modified = qh_completions(oxu, temp.qh); + if (unlikely(list_empty(&temp.qh->qtd_list))) + intr_deschedule(oxu, temp.qh); + qh_put(temp.qh); + break; + default: + dbg("corrupt type %d frame %d shadow %p", + type, frame, q.ptr); + q.ptr = NULL; + } + + /* assume completion callbacks modify the queue */ + if (unlikely(modified)) + goto restart; + } + + /* Stop when we catch up to the HC */ + + /* FIXME: this assumes we won't get lapped when + * latencies climb; that should be rare, but... + * detect it, and just go all the way around. + * FLR might help detect this case, so long as latencies + * don't exceed periodic_size msec (default 1.024 sec). + */ + + /* FIXME: likewise assumes HC doesn't halt mid-scan */ + + if (now_uframe == clock) { + unsigned now; + + if (!HC_IS_RUNNING(oxu_to_hcd(oxu)->state)) + break; + oxu->next_uframe = now_uframe; + now = readl(&oxu->regs->frame_index) % mod; + if (now_uframe == now) + break; + + /* rescan the rest of this frame, then ... */ + clock = now; + } else { + now_uframe++; + now_uframe %= mod; + } + } +} + +/* On some systems, leaving remote wakeup enabled prevents system shutdown. + * The firmware seems to think that powering off is a wakeup event! + * This routine turns off remote wakeup and everything else, on all ports. + */ +static void ehci_turn_off_all_ports(struct oxu_hcd *oxu) +{ + int port = HCS_N_PORTS(oxu->hcs_params); + + while (port--) + writel(PORT_RWC_BITS, &oxu->regs->port_status[port]); +} + +static void ehci_port_power(struct oxu_hcd *oxu, int is_on) +{ + unsigned port; + + if (!HCS_PPC(oxu->hcs_params)) + return; + + oxu_dbg(oxu, "...power%s ports...\n", is_on ? "up" : "down"); + for (port = HCS_N_PORTS(oxu->hcs_params); port > 0; ) + (void) oxu_hub_control(oxu_to_hcd(oxu), + is_on ? SetPortFeature : ClearPortFeature, + USB_PORT_FEAT_POWER, + port--, NULL, 0); + msleep(20); +} + +/* Called from some interrupts, timers, and so on. + * It calls driver completion functions, after dropping oxu->lock. + */ +static void ehci_work(struct oxu_hcd *oxu) +{ + timer_action_done(oxu, TIMER_IO_WATCHDOG); + if (oxu->reclaim_ready) + end_unlink_async(oxu); + + /* another CPU may drop oxu->lock during a schedule scan while + * it reports urb completions. this flag guards against bogus + * attempts at re-entrant schedule scanning. + */ + if (oxu->scanning) + return; + oxu->scanning = 1; + scan_async(oxu); + if (oxu->next_uframe != -1) + scan_periodic(oxu); + oxu->scanning = 0; + + /* the IO watchdog guards against hardware or driver bugs that + * misplace IRQs, and should let us run completely without IRQs. + * such lossage has been observed on both VT6202 and VT8235. + */ + if (HC_IS_RUNNING(oxu_to_hcd(oxu)->state) && + (oxu->async->qh_next.ptr != NULL || + oxu->periodic_sched != 0)) + timer_action(oxu, TIMER_IO_WATCHDOG); +} + +static void unlink_async(struct oxu_hcd *oxu, struct ehci_qh *qh) +{ + /* if we need to use IAA and it's busy, defer */ + if (qh->qh_state == QH_STATE_LINKED + && oxu->reclaim + && HC_IS_RUNNING(oxu_to_hcd(oxu)->state)) { + struct ehci_qh *last; + + for (last = oxu->reclaim; + last->reclaim; + last = last->reclaim) + continue; + qh->qh_state = QH_STATE_UNLINK_WAIT; + last->reclaim = qh; + + /* bypass IAA if the hc can't care */ + } else if (!HC_IS_RUNNING(oxu_to_hcd(oxu)->state) && oxu->reclaim) + end_unlink_async(oxu); + + /* something else might have unlinked the qh by now */ + if (qh->qh_state == QH_STATE_LINKED) + start_unlink_async(oxu, qh); +} + +/* + * USB host controller methods + */ + +static irqreturn_t oxu210_hcd_irq(struct usb_hcd *hcd) +{ + struct oxu_hcd *oxu = hcd_to_oxu(hcd); + u32 status, pcd_status = 0; + int bh; + + spin_lock(&oxu->lock); + + status = readl(&oxu->regs->status); + + /* e.g. cardbus physical eject */ + if (status == ~(u32) 0) { + oxu_dbg(oxu, "device removed\n"); + goto dead; + } + + status &= INTR_MASK; + if (!status) { /* irq sharing? */ + spin_unlock(&oxu->lock); + return IRQ_NONE; + } + + /* clear (just) interrupts */ + writel(status, &oxu->regs->status); + readl(&oxu->regs->command); /* unblock posted write */ + bh = 0; + +#ifdef OXU_VERBOSE_DEBUG + /* unrequested/ignored: Frame List Rollover */ + dbg_status(oxu, "irq", status); +#endif + + /* INT, ERR, and IAA interrupt rates can be throttled */ + + /* normal [4.15.1.2] or error [4.15.1.1] completion */ + if (likely((status & (STS_INT|STS_ERR)) != 0)) + bh = 1; + + /* complete the unlinking of some qh [4.15.2.3] */ + if (status & STS_IAA) { + oxu->reclaim_ready = 1; + bh = 1; + } + + /* remote wakeup [4.3.1] */ + if (status & STS_PCD) { + unsigned i = HCS_N_PORTS(oxu->hcs_params); + pcd_status = status; + + /* resume root hub? */ + if (!(readl(&oxu->regs->command) & CMD_RUN)) + usb_hcd_resume_root_hub(hcd); + + while (i--) { + int pstatus = readl(&oxu->regs->port_status[i]); + + if (pstatus & PORT_OWNER) + continue; + if (!(pstatus & PORT_RESUME) + || oxu->reset_done[i] != 0) + continue; + + /* start 20 msec resume signaling from this port, + * and make khubd collect PORT_STAT_C_SUSPEND to + * stop that signaling. + */ + oxu->reset_done[i] = jiffies + msecs_to_jiffies(20); + oxu_dbg(oxu, "port %d remote wakeup\n", i + 1); + mod_timer(&hcd->rh_timer, oxu->reset_done[i]); + } + } + + /* PCI errors [4.15.2.4] */ + if (unlikely((status & STS_FATAL) != 0)) { + /* bogus "fatal" IRQs appear on some chips... why? */ + status = readl(&oxu->regs->status); + dbg_cmd(oxu, "fatal", readl(&oxu->regs->command)); + dbg_status(oxu, "fatal", status); + if (status & STS_HALT) { + oxu_err(oxu, "fatal error\n"); +dead: + ehci_reset(oxu); + writel(0, &oxu->regs->configured_flag); + /* generic layer kills/unlinks all urbs, then + * uses oxu_stop to clean up the rest + */ + bh = 1; + } + } + + if (bh) + ehci_work(oxu); + spin_unlock(&oxu->lock); + if (pcd_status & STS_PCD) + usb_hcd_poll_rh_status(hcd); + return IRQ_HANDLED; +} + +static irqreturn_t oxu_irq(struct usb_hcd *hcd) +{ + struct oxu_hcd *oxu = hcd_to_oxu(hcd); + int ret = IRQ_HANDLED; + + u32 status = oxu_readl(hcd->regs, OXU_CHIPIRQSTATUS); + u32 enable = oxu_readl(hcd->regs, OXU_CHIPIRQEN_SET); + + /* Disable all interrupt */ + oxu_writel(hcd->regs, OXU_CHIPIRQEN_CLR, enable); + + if ((oxu->is_otg && (status & OXU_USBOTGI)) || + (!oxu->is_otg && (status & OXU_USBSPHI))) + oxu210_hcd_irq(hcd); + else + ret = IRQ_NONE; + + /* Enable all interrupt back */ + oxu_writel(hcd->regs, OXU_CHIPIRQEN_SET, enable); + + return ret; +} + +static void oxu_watchdog(unsigned long param) +{ + struct oxu_hcd *oxu = (struct oxu_hcd *) param; + unsigned long flags; + + spin_lock_irqsave(&oxu->lock, flags); + + /* lost IAA irqs wedge things badly; seen with a vt8235 */ + if (oxu->reclaim) { + u32 status = readl(&oxu->regs->status); + if (status & STS_IAA) { + oxu_vdbg(oxu, "lost IAA\n"); + writel(STS_IAA, &oxu->regs->status); + oxu->reclaim_ready = 1; + } + } + + /* stop async processing after it's idled a bit */ + if (test_bit(TIMER_ASYNC_OFF, &oxu->actions)) + start_unlink_async(oxu, oxu->async); + + /* oxu could run by timer, without IRQs ... */ + ehci_work(oxu); + + spin_unlock_irqrestore(&oxu->lock, flags); +} + +/* One-time init, only for memory state. + */ +static int oxu_hcd_init(struct usb_hcd *hcd) +{ + struct oxu_hcd *oxu = hcd_to_oxu(hcd); + u32 temp; + int retval; + u32 hcc_params; + + spin_lock_init(&oxu->lock); + + init_timer(&oxu->watchdog); + oxu->watchdog.function = oxu_watchdog; + oxu->watchdog.data = (unsigned long) oxu; + + /* + * hw default: 1K periodic list heads, one per frame. + * periodic_size can shrink by USBCMD update if hcc_params allows. + */ + oxu->periodic_size = DEFAULT_I_TDPS; + retval = ehci_mem_init(oxu, GFP_KERNEL); + if (retval < 0) + return retval; + + /* controllers may cache some of the periodic schedule ... */ + hcc_params = readl(&oxu->caps->hcc_params); + if (HCC_ISOC_CACHE(hcc_params)) /* full frame cache */ + oxu->i_thresh = 8; + else /* N microframes cached */ + oxu->i_thresh = 2 + HCC_ISOC_THRES(hcc_params); + + oxu->reclaim = NULL; + oxu->reclaim_ready = 0; + oxu->next_uframe = -1; + + /* + * dedicate a qh for the async ring head, since we couldn't unlink + * a 'real' qh without stopping the async schedule [4.8]. use it + * as the 'reclamation list head' too. + * its dummy is used in hw_alt_next of many tds, to prevent the qh + * from automatically advancing to the next td after short reads. + */ + oxu->async->qh_next.qh = NULL; + oxu->async->hw_next = QH_NEXT(oxu->async->qh_dma); + oxu->async->hw_info1 = cpu_to_le32(QH_HEAD); + oxu->async->hw_token = cpu_to_le32(QTD_STS_HALT); + oxu->async->hw_qtd_next = EHCI_LIST_END; + oxu->async->qh_state = QH_STATE_LINKED; + oxu->async->hw_alt_next = QTD_NEXT(oxu->async->dummy->qtd_dma); + + /* clear interrupt enables, set irq latency */ + if (log2_irq_thresh < 0 || log2_irq_thresh > 6) + log2_irq_thresh = 0; + temp = 1 << (16 + log2_irq_thresh); + if (HCC_CANPARK(hcc_params)) { + /* HW default park == 3, on hardware that supports it (like + * NVidia and ALI silicon), maximizes throughput on the async + * schedule by avoiding QH fetches between transfers. + * + * With fast usb storage devices and NForce2, "park" seems to + * make problems: throughput reduction (!), data errors... + */ + if (park) { + park = min(park, (unsigned) 3); + temp |= CMD_PARK; + temp |= park << 8; + } + oxu_dbg(oxu, "park %d\n", park); + } + if (HCC_PGM_FRAMELISTLEN(hcc_params)) { + /* periodic schedule size can be smaller than default */ + temp &= ~(3 << 2); + temp |= (EHCI_TUNE_FLS << 2); + } + oxu->command = temp; + + return 0; +} + +/* Called during probe() after chip reset completes. + */ +static int oxu_reset(struct usb_hcd *hcd) +{ + struct oxu_hcd *oxu = hcd_to_oxu(hcd); + int ret; + + spin_lock_init(&oxu->mem_lock); + INIT_LIST_HEAD(&oxu->urb_list); + oxu->urb_len = 0; + + /* FIMXE */ + hcd->self.controller->dma_mask = 0UL; + + if (oxu->is_otg) { + oxu->caps = hcd->regs + OXU_OTG_CAP_OFFSET; + oxu->regs = hcd->regs + OXU_OTG_CAP_OFFSET + \ + HC_LENGTH(readl(&oxu->caps->hc_capbase)); + + oxu->mem = hcd->regs + OXU_SPH_MEM; + } else { + oxu->caps = hcd->regs + OXU_SPH_CAP_OFFSET; + oxu->regs = hcd->regs + OXU_SPH_CAP_OFFSET + \ + HC_LENGTH(readl(&oxu->caps->hc_capbase)); + + oxu->mem = hcd->regs + OXU_OTG_MEM; + } + + oxu->hcs_params = readl(&oxu->caps->hcs_params); + oxu->sbrn = 0x20; + + ret = oxu_hcd_init(hcd); + if (ret) + return ret; + + return 0; +} + +static int oxu_run(struct usb_hcd *hcd) +{ + struct oxu_hcd *oxu = hcd_to_oxu(hcd); + int retval; + u32 temp, hcc_params; + + hcd->uses_new_polling = 1; + hcd->poll_rh = 0; + + /* EHCI spec section 4.1 */ + retval = ehci_reset(oxu); + if (retval != 0) { + ehci_mem_cleanup(oxu); + return retval; + } + writel(oxu->periodic_dma, &oxu->regs->frame_list); + writel((u32) oxu->async->qh_dma, &oxu->regs->async_next); + + /* hcc_params controls whether oxu->regs->segment must (!!!) + * be used; it constrains QH/ITD/SITD and QTD locations. + * pci_pool consistent memory always uses segment zero. + * streaming mappings for I/O buffers, like pci_map_single(), + * can return segments above 4GB, if the device allows. + * + * NOTE: the dma mask is visible through dma_supported(), so + * drivers can pass this info along ... like NETIF_F_HIGHDMA, + * Scsi_Host.highmem_io, and so forth. It's readonly to all + * host side drivers though. + */ + hcc_params = readl(&oxu->caps->hcc_params); + if (HCC_64BIT_ADDR(hcc_params)) + writel(0, &oxu->regs->segment); + + oxu->command &= ~(CMD_LRESET | CMD_IAAD | CMD_PSE | + CMD_ASE | CMD_RESET); + oxu->command |= CMD_RUN; + writel(oxu->command, &oxu->regs->command); + dbg_cmd(oxu, "init", oxu->command); + + /* + * Start, enabling full USB 2.0 functionality ... usb 1.1 devices + * are explicitly handed to companion controller(s), so no TT is + * involved with the root hub. (Except where one is integrated, + * and there's no companion controller unless maybe for USB OTG.) + */ + hcd->state = HC_STATE_RUNNING; + writel(FLAG_CF, &oxu->regs->configured_flag); + readl(&oxu->regs->command); /* unblock posted writes */ + + temp = HC_VERSION(readl(&oxu->caps->hc_capbase)); + oxu_info(oxu, "USB %x.%x started, quasi-EHCI %x.%02x, driver %s%s\n", + ((oxu->sbrn & 0xf0)>>4), (oxu->sbrn & 0x0f), + temp >> 8, temp & 0xff, DRIVER_VERSION, + ignore_oc ? ", overcurrent ignored" : ""); + + writel(INTR_MASK, &oxu->regs->intr_enable); /* Turn On Interrupts */ + + return 0; +} + +static void oxu_stop(struct usb_hcd *hcd) +{ + struct oxu_hcd *oxu = hcd_to_oxu(hcd); + + /* Turn off port power on all root hub ports. */ + ehci_port_power(oxu, 0); + + /* no more interrupts ... */ + del_timer_sync(&oxu->watchdog); + + spin_lock_irq(&oxu->lock); + if (HC_IS_RUNNING(hcd->state)) + ehci_quiesce(oxu); + + ehci_reset(oxu); + writel(0, &oxu->regs->intr_enable); + spin_unlock_irq(&oxu->lock); + + /* let companion controllers work when we aren't */ + writel(0, &oxu->regs->configured_flag); + + /* root hub is shut down separately (first, when possible) */ + spin_lock_irq(&oxu->lock); + if (oxu->async) + ehci_work(oxu); + spin_unlock_irq(&oxu->lock); + ehci_mem_cleanup(oxu); + + dbg_status(oxu, "oxu_stop completed", readl(&oxu->regs->status)); +} + +/* Kick in for silicon on any bus (not just pci, etc). + * This forcibly disables dma and IRQs, helping kexec and other cases + * where the next system software may expect clean state. + */ +static void oxu_shutdown(struct usb_hcd *hcd) +{ + struct oxu_hcd *oxu = hcd_to_oxu(hcd); + + (void) ehci_halt(oxu); + ehci_turn_off_all_ports(oxu); + + /* make BIOS/etc use companion controller during reboot */ + writel(0, &oxu->regs->configured_flag); + + /* unblock posted writes */ + readl(&oxu->regs->configured_flag); +} + +/* Non-error returns are a promise to giveback() the urb later + * we drop ownership so next owner (or urb unlink) can get it + * + * urb + dev is in hcd.self.controller.urb_list + * we're queueing TDs onto software and hardware lists + * + * hcd-specific init for hcpriv hasn't been done yet + * + * NOTE: control, bulk, and interrupt share the same code to append TDs + * to a (possibly active) QH, and the same QH scanning code. + */ +static int __oxu_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, + gfp_t mem_flags) +{ + struct oxu_hcd *oxu = hcd_to_oxu(hcd); + struct list_head qtd_list; + + INIT_LIST_HEAD(&qtd_list); + + switch (usb_pipetype(urb->pipe)) { + case PIPE_CONTROL: + case PIPE_BULK: + default: + if (!qh_urb_transaction(oxu, urb, &qtd_list, mem_flags)) + return -ENOMEM; + return submit_async(oxu, urb, &qtd_list, mem_flags); + + case PIPE_INTERRUPT: + if (!qh_urb_transaction(oxu, urb, &qtd_list, mem_flags)) + return -ENOMEM; + return intr_submit(oxu, urb, &qtd_list, mem_flags); + + case PIPE_ISOCHRONOUS: + if (urb->dev->speed == USB_SPEED_HIGH) + return itd_submit(oxu, urb, mem_flags); + else + return sitd_submit(oxu, urb, mem_flags); + } +} + +/* This function is responsible for breaking URBs with big data size + * into smaller size and processing small urbs in sequence. + */ +static int oxu_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, + gfp_t mem_flags) +{ + struct oxu_hcd *oxu = hcd_to_oxu(hcd); + int num, rem; + int transfer_buffer_length; + void *transfer_buffer; + struct urb *murb; + int i, ret; + + /* If not bulk pipe just enqueue the URB */ + if (!usb_pipebulk(urb->pipe)) + return __oxu_urb_enqueue(hcd, urb, mem_flags); + + /* Otherwise we should verify the USB transfer buffer size! */ + transfer_buffer = urb->transfer_buffer; + transfer_buffer_length = urb->transfer_buffer_length; + + num = urb->transfer_buffer_length / 4096; + rem = urb->transfer_buffer_length % 4096; + if (rem != 0) + num++; + + /* If URB is smaller than 4096 bytes just enqueue it! */ + if (num == 1) + return __oxu_urb_enqueue(hcd, urb, mem_flags); + + /* Ok, we have more job to do! :) */ + + for (i = 0; i < num - 1; i++) { + /* Get free micro URB poll till a free urb is recieved */ + + do { + murb = (struct urb *) oxu_murb_alloc(oxu); + if (!murb) + schedule(); + } while (!murb); + + /* Coping the urb */ + memcpy(murb, urb, sizeof(struct urb)); + + murb->transfer_buffer_length = 4096; + murb->transfer_buffer = transfer_buffer + i * 4096; + + /* Null pointer for the encodes that this is a micro urb */ + murb->complete = NULL; + + ((struct oxu_murb *) murb)->main = urb; + ((struct oxu_murb *) murb)->last = 0; + + /* This loop is to guarantee urb to be processed when there's + * not enough resources at a particular time by retrying. + */ + do { + ret = __oxu_urb_enqueue(hcd, murb, mem_flags); + if (ret) + schedule(); + } while (ret); + } + + /* Last urb requires special handling */ + + /* Get free micro URB poll till a free urb is recieved */ + do { + murb = (struct urb *) oxu_murb_alloc(oxu); + if (!murb) + schedule(); + } while (!murb); + + /* Coping the urb */ + memcpy(murb, urb, sizeof(struct urb)); + + murb->transfer_buffer_length = rem > 0 ? rem : 4096; + murb->transfer_buffer = transfer_buffer + (num - 1) * 4096; + + /* Null pointer for the encodes that this is a micro urb */ + murb->complete = NULL; + + ((struct oxu_murb *) murb)->main = urb; + ((struct oxu_murb *) murb)->last = 1; + + do { + ret = __oxu_urb_enqueue(hcd, murb, mem_flags); + if (ret) + schedule(); + } while (ret); + + return ret; +} + +/* Remove from hardware lists. + * Completions normally happen asynchronously + */ +static int oxu_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status) +{ + struct oxu_hcd *oxu = hcd_to_oxu(hcd); + struct ehci_qh *qh; + unsigned long flags; + + spin_lock_irqsave(&oxu->lock, flags); + switch (usb_pipetype(urb->pipe)) { + case PIPE_CONTROL: + case PIPE_BULK: + default: + qh = (struct ehci_qh *) urb->hcpriv; + if (!qh) + break; + unlink_async(oxu, qh); + break; + + case PIPE_INTERRUPT: + qh = (struct ehci_qh *) urb->hcpriv; + if (!qh) + break; + switch (qh->qh_state) { + case QH_STATE_LINKED: + intr_deschedule(oxu, qh); + /* FALL THROUGH */ + case QH_STATE_IDLE: + qh_completions(oxu, qh); + break; + default: + oxu_dbg(oxu, "bogus qh %p state %d\n", + qh, qh->qh_state); + goto done; + } + + /* reschedule QH iff another request is queued */ + if (!list_empty(&qh->qtd_list) + && HC_IS_RUNNING(hcd->state)) { + int status; + + status = qh_schedule(oxu, qh); + spin_unlock_irqrestore(&oxu->lock, flags); + + if (status != 0) { + /* shouldn't happen often, but ... + * FIXME kill those tds' urbs + */ + err("can't reschedule qh %p, err %d", + qh, status); + } + return status; + } + break; + } +done: + spin_unlock_irqrestore(&oxu->lock, flags); + return 0; +} + +/* Bulk qh holds the data toggle */ +static void oxu_endpoint_disable(struct usb_hcd *hcd, + struct usb_host_endpoint *ep) +{ + struct oxu_hcd *oxu = hcd_to_oxu(hcd); + unsigned long flags; + struct ehci_qh *qh, *tmp; + + /* ASSERT: any requests/urbs are being unlinked */ + /* ASSERT: nobody can be submitting urbs for this any more */ + +rescan: + spin_lock_irqsave(&oxu->lock, flags); + qh = ep->hcpriv; + if (!qh) + goto done; + + /* endpoints can be iso streams. for now, we don't + * accelerate iso completions ... so spin a while. + */ + if (qh->hw_info1 == 0) { + oxu_vdbg(oxu, "iso delay\n"); + goto idle_timeout; + } + + if (!HC_IS_RUNNING(hcd->state)) + qh->qh_state = QH_STATE_IDLE; + switch (qh->qh_state) { + case QH_STATE_LINKED: + for (tmp = oxu->async->qh_next.qh; + tmp && tmp != qh; + tmp = tmp->qh_next.qh) + continue; + /* periodic qh self-unlinks on empty */ + if (!tmp) + goto nogood; + unlink_async(oxu, qh); + /* FALL THROUGH */ + case QH_STATE_UNLINK: /* wait for hw to finish? */ +idle_timeout: + spin_unlock_irqrestore(&oxu->lock, flags); + schedule_timeout_uninterruptible(1); + goto rescan; + case QH_STATE_IDLE: /* fully unlinked */ + if (list_empty(&qh->qtd_list)) { + qh_put(qh); + break; + } + /* else FALL THROUGH */ + default: +nogood: + /* caller was supposed to have unlinked any requests; + * that's not our job. just leak this memory. + */ + oxu_err(oxu, "qh %p (#%02x) state %d%s\n", + qh, ep->desc.bEndpointAddress, qh->qh_state, + list_empty(&qh->qtd_list) ? "" : "(has tds)"); + break; + } + ep->hcpriv = NULL; +done: + spin_unlock_irqrestore(&oxu->lock, flags); + return; +} + +static int oxu_get_frame(struct usb_hcd *hcd) +{ + struct oxu_hcd *oxu = hcd_to_oxu(hcd); + + return (readl(&oxu->regs->frame_index) >> 3) % + oxu->periodic_size; +} + +/* Build "status change" packet (one or two bytes) from HC registers */ +static int oxu_hub_status_data(struct usb_hcd *hcd, char *buf) +{ + struct oxu_hcd *oxu = hcd_to_oxu(hcd); + u32 temp, mask, status = 0; + int ports, i, retval = 1; + unsigned long flags; + + /* if !USB_SUSPEND, root hub timers won't get shut down ... */ + if (!HC_IS_RUNNING(hcd->state)) + return 0; + + /* init status to no-changes */ + buf[0] = 0; + ports = HCS_N_PORTS(oxu->hcs_params); + if (ports > 7) { + buf[1] = 0; + retval++; + } + + /* Some boards (mostly VIA?) report bogus overcurrent indications, + * causing massive log spam unless we completely ignore them. It + * may be relevant that VIA VT8235 controlers, where PORT_POWER is + * always set, seem to clear PORT_OCC and PORT_CSC when writing to + * PORT_POWER; that's surprising, but maybe within-spec. + */ + if (!ignore_oc) + mask = PORT_CSC | PORT_PEC | PORT_OCC; + else + mask = PORT_CSC | PORT_PEC; + + /* no hub change reports (bit 0) for now (power, ...) */ + + /* port N changes (bit N)? */ + spin_lock_irqsave(&oxu->lock, flags); + for (i = 0; i < ports; i++) { + temp = readl(&oxu->regs->port_status[i]); + + /* + * Return status information even for ports with OWNER set. + * Otherwise khubd wouldn't see the disconnect event when a + * high-speed device is switched over to the companion + * controller by the user. + */ + + if (!(temp & PORT_CONNECT)) + oxu->reset_done[i] = 0; + if ((temp & mask) != 0 || ((temp & PORT_RESUME) != 0 && + time_after_eq(jiffies, oxu->reset_done[i]))) { + if (i < 7) + buf[0] |= 1 << (i + 1); + else + buf[1] |= 1 << (i - 7); + status = STS_PCD; + } + } + /* FIXME autosuspend idle root hubs */ + spin_unlock_irqrestore(&oxu->lock, flags); + return status ? retval : 0; +} + +/* Returns the speed of a device attached to a port on the root hub. */ +static inline unsigned int oxu_port_speed(struct oxu_hcd *oxu, + unsigned int portsc) +{ + switch ((portsc >> 26) & 3) { + case 0: + return 0; + case 1: + return 1 << USB_PORT_FEAT_LOWSPEED; + case 2: + default: + return 1 << USB_PORT_FEAT_HIGHSPEED; + } +} + +#define PORT_WAKE_BITS (PORT_WKOC_E|PORT_WKDISC_E|PORT_WKCONN_E) +static int oxu_hub_control(struct usb_hcd *hcd, u16 typeReq, + u16 wValue, u16 wIndex, char *buf, u16 wLength) +{ + struct oxu_hcd *oxu = hcd_to_oxu(hcd); + int ports = HCS_N_PORTS(oxu->hcs_params); + u32 __iomem *status_reg = &oxu->regs->port_status[wIndex - 1]; + u32 temp, status; + unsigned long flags; + int retval = 0; + unsigned selector; + + /* + * FIXME: support SetPortFeatures USB_PORT_FEAT_INDICATOR. + * HCS_INDICATOR may say we can change LEDs to off/amber/green. + * (track current state ourselves) ... blink for diagnostics, + * power, "this is the one", etc. EHCI spec supports this. + */ + + spin_lock_irqsave(&oxu->lock, flags); + switch (typeReq) { + case ClearHubFeature: + switch (wValue) { + case C_HUB_LOCAL_POWER: + case C_HUB_OVER_CURRENT: + /* no hub-wide feature/status flags */ + break; + default: + goto error; + } + break; + case ClearPortFeature: + if (!wIndex || wIndex > ports) + goto error; + wIndex--; + temp = readl(status_reg); + + /* + * Even if OWNER is set, so the port is owned by the + * companion controller, khubd needs to be able to clear + * the port-change status bits (especially + * USB_PORT_FEAT_C_CONNECTION). + */ + + switch (wValue) { + case USB_PORT_FEAT_ENABLE: + writel(temp & ~PORT_PE, status_reg); + break; + case USB_PORT_FEAT_C_ENABLE: + writel((temp & ~PORT_RWC_BITS) | PORT_PEC, status_reg); + break; + case USB_PORT_FEAT_SUSPEND: + if (temp & PORT_RESET) + goto error; + if (temp & PORT_SUSPEND) { + if ((temp & PORT_PE) == 0) + goto error; + /* resume signaling for 20 msec */ + temp &= ~(PORT_RWC_BITS | PORT_WAKE_BITS); + writel(temp | PORT_RESUME, status_reg); + oxu->reset_done[wIndex] = jiffies + + msecs_to_jiffies(20); + } + break; + case USB_PORT_FEAT_C_SUSPEND: + /* we auto-clear this feature */ + break; + case USB_PORT_FEAT_POWER: + if (HCS_PPC(oxu->hcs_params)) + writel(temp & ~(PORT_RWC_BITS | PORT_POWER), + status_reg); + break; + case USB_PORT_FEAT_C_CONNECTION: + writel((temp & ~PORT_RWC_BITS) | PORT_CSC, status_reg); + break; + case USB_PORT_FEAT_C_OVER_CURRENT: + writel((temp & ~PORT_RWC_BITS) | PORT_OCC, status_reg); + break; + case USB_PORT_FEAT_C_RESET: + /* GetPortStatus clears reset */ + break; + default: + goto error; + } + readl(&oxu->regs->command); /* unblock posted write */ + break; + case GetHubDescriptor: + ehci_hub_descriptor(oxu, (struct usb_hub_descriptor *) + buf); + break; + case GetHubStatus: + /* no hub-wide feature/status flags */ + memset(buf, 0, 4); + break; + case GetPortStatus: + if (!wIndex || wIndex > ports) + goto error; + wIndex--; + status = 0; + temp = readl(status_reg); + + /* wPortChange bits */ + if (temp & PORT_CSC) + status |= 1 << USB_PORT_FEAT_C_CONNECTION; + if (temp & PORT_PEC) + status |= 1 << USB_PORT_FEAT_C_ENABLE; + if ((temp & PORT_OCC) && !ignore_oc) + status |= 1 << USB_PORT_FEAT_C_OVER_CURRENT; + + /* whoever resumes must GetPortStatus to complete it!! */ + if (temp & PORT_RESUME) { + + /* Remote Wakeup received? */ + if (!oxu->reset_done[wIndex]) { + /* resume signaling for 20 msec */ + oxu->reset_done[wIndex] = jiffies + + msecs_to_jiffies(20); + /* check the port again */ + mod_timer(&oxu_to_hcd(oxu)->rh_timer, + oxu->reset_done[wIndex]); + } + + /* resume completed? */ + else if (time_after_eq(jiffies, + oxu->reset_done[wIndex])) { + status |= 1 << USB_PORT_FEAT_C_SUSPEND; + oxu->reset_done[wIndex] = 0; + + /* stop resume signaling */ + temp = readl(status_reg); + writel(temp & ~(PORT_RWC_BITS | PORT_RESUME), + status_reg); + retval = handshake(oxu, status_reg, + PORT_RESUME, 0, 2000 /* 2msec */); + if (retval != 0) { + oxu_err(oxu, + "port %d resume error %d\n", + wIndex + 1, retval); + goto error; + } + temp &= ~(PORT_SUSPEND|PORT_RESUME|(3<<10)); + } + } + + /* whoever resets must GetPortStatus to complete it!! */ + if ((temp & PORT_RESET) + && time_after_eq(jiffies, + oxu->reset_done[wIndex])) { + status |= 1 << USB_PORT_FEAT_C_RESET; + oxu->reset_done[wIndex] = 0; + + /* force reset to complete */ + writel(temp & ~(PORT_RWC_BITS | PORT_RESET), + status_reg); + /* REVISIT: some hardware needs 550+ usec to clear + * this bit; seems too long to spin routinely... + */ + retval = handshake(oxu, status_reg, + PORT_RESET, 0, 750); + if (retval != 0) { + oxu_err(oxu, "port %d reset error %d\n", + wIndex + 1, retval); + goto error; + } + + /* see what we found out */ + temp = check_reset_complete(oxu, wIndex, status_reg, + readl(status_reg)); + } + + /* transfer dedicated ports to the companion hc */ + if ((temp & PORT_CONNECT) && + test_bit(wIndex, &oxu->companion_ports)) { + temp &= ~PORT_RWC_BITS; + temp |= PORT_OWNER; + writel(temp, status_reg); + oxu_dbg(oxu, "port %d --> companion\n", wIndex + 1); + temp = readl(status_reg); + } + + /* + * Even if OWNER is set, there's no harm letting khubd + * see the wPortStatus values (they should all be 0 except + * for PORT_POWER anyway). + */ + + if (temp & PORT_CONNECT) { + status |= 1 << USB_PORT_FEAT_CONNECTION; + /* status may be from integrated TT */ + status |= oxu_port_speed(oxu, temp); + } + if (temp & PORT_PE) + status |= 1 << USB_PORT_FEAT_ENABLE; + if (temp & (PORT_SUSPEND|PORT_RESUME)) + status |= 1 << USB_PORT_FEAT_SUSPEND; + if (temp & PORT_OC) + status |= 1 << USB_PORT_FEAT_OVER_CURRENT; + if (temp & PORT_RESET) + status |= 1 << USB_PORT_FEAT_RESET; + if (temp & PORT_POWER) + status |= 1 << USB_PORT_FEAT_POWER; + +#ifndef OXU_VERBOSE_DEBUG + if (status & ~0xffff) /* only if wPortChange is interesting */ +#endif + dbg_port(oxu, "GetStatus", wIndex + 1, temp); + put_unaligned(cpu_to_le32(status), (__le32 *) buf); + break; + case SetHubFeature: + switch (wValue) { + case C_HUB_LOCAL_POWER: + case C_HUB_OVER_CURRENT: + /* no hub-wide feature/status flags */ + break; + default: + goto error; + } + break; + case SetPortFeature: + selector = wIndex >> 8; + wIndex &= 0xff; + if (!wIndex || wIndex > ports) + goto error; + wIndex--; + temp = readl(status_reg); + if (temp & PORT_OWNER) + break; + + temp &= ~PORT_RWC_BITS; + switch (wValue) { + case USB_PORT_FEAT_SUSPEND: + if ((temp & PORT_PE) == 0 + || (temp & PORT_RESET) != 0) + goto error; + if (device_may_wakeup(&hcd->self.root_hub->dev)) + temp |= PORT_WAKE_BITS; + writel(temp | PORT_SUSPEND, status_reg); + break; + case USB_PORT_FEAT_POWER: + if (HCS_PPC(oxu->hcs_params)) + writel(temp | PORT_POWER, status_reg); + break; + case USB_PORT_FEAT_RESET: + if (temp & PORT_RESUME) + goto error; + /* line status bits may report this as low speed, + * which can be fine if this root hub has a + * transaction translator built in. + */ + oxu_vdbg(oxu, "port %d reset\n", wIndex + 1); + temp |= PORT_RESET; + temp &= ~PORT_PE; + + /* + * caller must wait, then call GetPortStatus + * usb 2.0 spec says 50 ms resets on root + */ + oxu->reset_done[wIndex] = jiffies + + msecs_to_jiffies(50); + writel(temp, status_reg); + break; + + /* For downstream facing ports (these): one hub port is put + * into test mode according to USB2 11.24.2.13, then the hub + * must be reset (which for root hub now means rmmod+modprobe, + * or else system reboot). See EHCI 2.3.9 and 4.14 for info + * about the EHCI-specific stuff. + */ + case USB_PORT_FEAT_TEST: + if (!selector || selector > 5) + goto error; + ehci_quiesce(oxu); + ehci_halt(oxu); + temp |= selector << 16; + writel(temp, status_reg); + break; + + default: + goto error; + } + readl(&oxu->regs->command); /* unblock posted writes */ + break; + + default: +error: + /* "stall" on error */ + retval = -EPIPE; + } + spin_unlock_irqrestore(&oxu->lock, flags); + return retval; +} + +#ifdef CONFIG_PM + +static int oxu_bus_suspend(struct usb_hcd *hcd) +{ + struct oxu_hcd *oxu = hcd_to_oxu(hcd); + int port; + int mask; + + oxu_dbg(oxu, "suspend root hub\n"); + + if (time_before(jiffies, oxu->next_statechange)) + msleep(5); + + port = HCS_N_PORTS(oxu->hcs_params); + spin_lock_irq(&oxu->lock); + + /* stop schedules, clean any completed work */ + if (HC_IS_RUNNING(hcd->state)) { + ehci_quiesce(oxu); + hcd->state = HC_STATE_QUIESCING; + } + oxu->command = readl(&oxu->regs->command); + if (oxu->reclaim) + oxu->reclaim_ready = 1; + ehci_work(oxu); + + /* Unlike other USB host controller types, EHCI doesn't have + * any notion of "global" or bus-wide suspend. The driver has + * to manually suspend all the active unsuspended ports, and + * then manually resume them in the bus_resume() routine. + */ + oxu->bus_suspended = 0; + while (port--) { + u32 __iomem *reg = &oxu->regs->port_status[port]; + u32 t1 = readl(reg) & ~PORT_RWC_BITS; + u32 t2 = t1; + + /* keep track of which ports we suspend */ + if ((t1 & PORT_PE) && !(t1 & PORT_OWNER) && + !(t1 & PORT_SUSPEND)) { + t2 |= PORT_SUSPEND; + set_bit(port, &oxu->bus_suspended); + } + + /* enable remote wakeup on all ports */ + if (device_may_wakeup(&hcd->self.root_hub->dev)) + t2 |= PORT_WKOC_E|PORT_WKDISC_E|PORT_WKCONN_E; + else + t2 &= ~(PORT_WKOC_E|PORT_WKDISC_E|PORT_WKCONN_E); + + if (t1 != t2) { + oxu_vdbg(oxu, "port %d, %08x -> %08x\n", + port + 1, t1, t2); + writel(t2, reg); + } + } + + /* turn off now-idle HC */ + del_timer_sync(&oxu->watchdog); + ehci_halt(oxu); + hcd->state = HC_STATE_SUSPENDED; + + /* allow remote wakeup */ + mask = INTR_MASK; + if (!device_may_wakeup(&hcd->self.root_hub->dev)) + mask &= ~STS_PCD; + writel(mask, &oxu->regs->intr_enable); + readl(&oxu->regs->intr_enable); + + oxu->next_statechange = jiffies + msecs_to_jiffies(10); + spin_unlock_irq(&oxu->lock); + return 0; +} + +/* Caller has locked the root hub, and should reset/reinit on error */ +static int oxu_bus_resume(struct usb_hcd *hcd) +{ + struct oxu_hcd *oxu = hcd_to_oxu(hcd); + u32 temp; + int i; + + if (time_before(jiffies, oxu->next_statechange)) + msleep(5); + spin_lock_irq(&oxu->lock); + + /* Ideally and we've got a real resume here, and no port's power + * was lost. (For PCI, that means Vaux was maintained.) But we + * could instead be restoring a swsusp snapshot -- so that BIOS was + * the last user of the controller, not reset/pm hardware keeping + * state we gave to it. + */ + temp = readl(&oxu->regs->intr_enable); + oxu_dbg(oxu, "resume root hub%s\n", temp ? "" : " after power loss"); + + /* at least some APM implementations will try to deliver + * IRQs right away, so delay them until we're ready. + */ + writel(0, &oxu->regs->intr_enable); + + /* re-init operational registers */ + writel(0, &oxu->regs->segment); + writel(oxu->periodic_dma, &oxu->regs->frame_list); + writel((u32) oxu->async->qh_dma, &oxu->regs->async_next); + + /* restore CMD_RUN, framelist size, and irq threshold */ + writel(oxu->command, &oxu->regs->command); + + /* Some controller/firmware combinations need a delay during which + * they set up the port statuses. See Bugzilla #8190. */ + mdelay(8); + + /* manually resume the ports we suspended during bus_suspend() */ + i = HCS_N_PORTS(oxu->hcs_params); + while (i--) { + temp = readl(&oxu->regs->port_status[i]); + temp &= ~(PORT_RWC_BITS + | PORT_WKOC_E | PORT_WKDISC_E | PORT_WKCONN_E); + if (test_bit(i, &oxu->bus_suspended) && (temp & PORT_SUSPEND)) { + oxu->reset_done[i] = jiffies + msecs_to_jiffies(20); + temp |= PORT_RESUME; + } + writel(temp, &oxu->regs->port_status[i]); + } + i = HCS_N_PORTS(oxu->hcs_params); + mdelay(20); + while (i--) { + temp = readl(&oxu->regs->port_status[i]); + if (test_bit(i, &oxu->bus_suspended) && (temp & PORT_SUSPEND)) { + temp &= ~(PORT_RWC_BITS | PORT_RESUME); + writel(temp, &oxu->regs->port_status[i]); + oxu_vdbg(oxu, "resumed port %d\n", i + 1); + } + } + (void) readl(&oxu->regs->command); + + /* maybe re-activate the schedule(s) */ + temp = 0; + if (oxu->async->qh_next.qh) + temp |= CMD_ASE; + if (oxu->periodic_sched) + temp |= CMD_PSE; + if (temp) { + oxu->command |= temp; + writel(oxu->command, &oxu->regs->command); + } + + oxu->next_statechange = jiffies + msecs_to_jiffies(5); + hcd->state = HC_STATE_RUNNING; + + /* Now we can safely re-enable irqs */ + writel(INTR_MASK, &oxu->regs->intr_enable); + + spin_unlock_irq(&oxu->lock); + return 0; +} + +#else + +static int oxu_bus_suspend(struct usb_hcd *hcd) +{ + return 0; +} + +static int oxu_bus_resume(struct usb_hcd *hcd) +{ + return 0; +} + +#endif /* CONFIG_PM */ + +static const struct hc_driver oxu_hc_driver = { + .description = "oxu210hp_hcd", + .product_desc = "oxu210hp HCD", + .hcd_priv_size = sizeof(struct oxu_hcd), + + /* + * Generic hardware linkage + */ + .irq = oxu_irq, + .flags = HCD_MEMORY | HCD_USB2, + + /* + * Basic lifecycle operations + */ + .reset = oxu_reset, + .start = oxu_run, + .stop = oxu_stop, + .shutdown = oxu_shutdown, + + /* + * Managing i/o requests and associated device resources + */ + .urb_enqueue = oxu_urb_enqueue, + .urb_dequeue = oxu_urb_dequeue, + .endpoint_disable = oxu_endpoint_disable, + + /* + * Scheduling support + */ + .get_frame_number = oxu_get_frame, + + /* + * Root hub support + */ + .hub_status_data = oxu_hub_status_data, + .hub_control = oxu_hub_control, + .bus_suspend = oxu_bus_suspend, + .bus_resume = oxu_bus_resume, +}; + +/* + * Module stuff + */ + +static void oxu_configuration(struct platform_device *pdev, void *base) +{ + u32 tmp; + + /* Initialize top level registers. + * First write ever + */ + oxu_writel(base, OXU_HOSTIFCONFIG, 0x0000037D); + oxu_writel(base, OXU_SOFTRESET, OXU_SRESET); + oxu_writel(base, OXU_HOSTIFCONFIG, 0x0000037D); + + tmp = oxu_readl(base, OXU_PIOBURSTREADCTRL); + oxu_writel(base, OXU_PIOBURSTREADCTRL, tmp | 0x0040); + + oxu_writel(base, OXU_ASO, OXU_SPHPOEN | OXU_OVRCCURPUPDEN | + OXU_COMPARATOR | OXU_ASO_OP); + + tmp = oxu_readl(base, OXU_CLKCTRL_SET); + oxu_writel(base, OXU_CLKCTRL_SET, tmp | OXU_SYSCLKEN | OXU_USBOTGCLKEN); + + /* Clear all top interrupt enable */ + oxu_writel(base, OXU_CHIPIRQEN_CLR, 0xff); + + /* Clear all top interrupt status */ + oxu_writel(base, OXU_CHIPIRQSTATUS, 0xff); + + /* Enable all needed top interrupt except OTG SPH core */ + oxu_writel(base, OXU_CHIPIRQEN_SET, OXU_USBSPHLPWUI | OXU_USBOTGLPWUI); +} + +static int oxu_verify_id(struct platform_device *pdev, void *base) +{ + u32 id; + char *bo[] = { + "reserved", + "128-pin LQFP", + "84-pin TFBGA", + "reserved", + }; + + /* Read controller signature register to find a match */ + id = oxu_readl(base, OXU_DEVICEID); + dev_info(&pdev->dev, "device ID %x\n", id); + if ((id & OXU_REV_MASK) != (OXU_REV_2100 << OXU_REV_SHIFT)) + return -1; + + dev_info(&pdev->dev, "found device %x %s (%04x:%04x)\n", + id >> OXU_REV_SHIFT, + bo[(id & OXU_BO_MASK) >> OXU_BO_SHIFT], + (id & OXU_MAJ_REV_MASK) >> OXU_MAJ_REV_SHIFT, + (id & OXU_MIN_REV_MASK) >> OXU_MIN_REV_SHIFT); + + return 0; +} + +static const struct hc_driver oxu_hc_driver; +static struct usb_hcd *oxu_create(struct platform_device *pdev, + unsigned long memstart, unsigned long memlen, + void *base, int irq, int otg) +{ + struct device *dev = &pdev->dev; + + struct usb_hcd *hcd; + struct oxu_hcd *oxu; + int ret; + + /* Set endian mode and host mode */ + oxu_writel(base + (otg ? OXU_OTG_CORE_OFFSET : OXU_SPH_CORE_OFFSET), + OXU_USBMODE, + OXU_CM_HOST_ONLY | OXU_ES_LITTLE | OXU_VBPS); + + hcd = usb_create_hcd(&oxu_hc_driver, dev, + otg ? "oxu210hp_otg" : "oxu210hp_sph"); + if (!hcd) + return ERR_PTR(-ENOMEM); + + hcd->rsrc_start = memstart; + hcd->rsrc_len = memlen; + hcd->regs = base; + hcd->irq = irq; + hcd->state = HC_STATE_HALT; + + oxu = hcd_to_oxu(hcd); + oxu->is_otg = otg; + + ret = usb_add_hcd(hcd, irq, IRQF_SHARED); + if (ret < 0) + return ERR_PTR(ret); + + return hcd; +} + +static int oxu_init(struct platform_device *pdev, + unsigned long memstart, unsigned long memlen, + void *base, int irq) +{ + struct oxu_info *info = platform_get_drvdata(pdev); + struct usb_hcd *hcd; + int ret; + + /* First time configuration at start up */ + oxu_configuration(pdev, base); + + ret = oxu_verify_id(pdev, base); + if (ret) { + dev_err(&pdev->dev, "no devices found!\n"); + return -ENODEV; + } + + /* Create the OTG controller */ + hcd = oxu_create(pdev, memstart, memlen, base, irq, 1); + if (IS_ERR(hcd)) { + dev_err(&pdev->dev, "cannot create OTG controller!\n"); + ret = PTR_ERR(hcd); + goto error_create_otg; + } + info->hcd[0] = hcd; + + /* Create the SPH host controller */ + hcd = oxu_create(pdev, memstart, memlen, base, irq, 0); + if (IS_ERR(hcd)) { + dev_err(&pdev->dev, "cannot create SPH controller!\n"); + ret = PTR_ERR(hcd); + goto error_create_sph; + } + info->hcd[1] = hcd; + + oxu_writel(base, OXU_CHIPIRQEN_SET, + oxu_readl(base, OXU_CHIPIRQEN_SET) | 3); + + return 0; + +error_create_sph: + usb_remove_hcd(info->hcd[0]); + usb_put_hcd(info->hcd[0]); + +error_create_otg: + return ret; +} + +static int oxu_drv_probe(struct platform_device *pdev) +{ + struct resource *res; + void *base; + unsigned long memstart, memlen; + int irq, ret; + struct oxu_info *info; + + if (usb_disabled()) + return -ENODEV; + + /* + * Get the platform resources + */ + res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (!res) { + dev_err(&pdev->dev, + "no IRQ! Check %s setup!\n", dev_name(&pdev->dev)); + return -ENODEV; + } + irq = res->start; + dev_dbg(&pdev->dev, "IRQ resource %d\n", irq); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "no registers address! Check %s setup!\n", + dev_name(&pdev->dev)); + return -ENODEV; + } + memstart = res->start; + memlen = res->end - res->start + 1; + dev_dbg(&pdev->dev, "MEM resource %lx-%lx\n", memstart, memlen); + if (!request_mem_region(memstart, memlen, + oxu_hc_driver.description)) { + dev_dbg(&pdev->dev, "memory area already in use\n"); + return -EBUSY; + } + + ret = set_irq_type(irq, IRQF_TRIGGER_FALLING); + if (ret) { + dev_err(&pdev->dev, "error setting irq type\n"); + ret = -EFAULT; + goto error_set_irq_type; + } + + base = ioremap(memstart, memlen); + if (!base) { + dev_dbg(&pdev->dev, "error mapping memory\n"); + ret = -EFAULT; + goto error_ioremap; + } + + /* Allocate a driver data struct to hold useful info for both + * SPH & OTG devices + */ + info = kzalloc(sizeof(struct oxu_info), GFP_KERNEL); + if (!info) { + dev_dbg(&pdev->dev, "error allocating memory\n"); + ret = -EFAULT; + goto error_alloc; + } + platform_set_drvdata(pdev, info); + + ret = oxu_init(pdev, memstart, memlen, base, irq); + if (ret < 0) { + dev_dbg(&pdev->dev, "cannot init USB devices\n"); + goto error_init; + } + + dev_info(&pdev->dev, "devices enabled and running\n"); + platform_set_drvdata(pdev, info); + + return 0; + +error_init: + kfree(info); + platform_set_drvdata(pdev, NULL); + +error_alloc: + iounmap(base); + +error_set_irq_type: +error_ioremap: + release_mem_region(memstart, memlen); + + dev_err(&pdev->dev, "init %s fail, %d\n", dev_name(&pdev->dev), ret); + return ret; +} + +static void oxu_remove(struct platform_device *pdev, struct usb_hcd *hcd) +{ + usb_remove_hcd(hcd); + usb_put_hcd(hcd); +} + +static int oxu_drv_remove(struct platform_device *pdev) +{ + struct oxu_info *info = platform_get_drvdata(pdev); + unsigned long memstart = info->hcd[0]->rsrc_start, + memlen = info->hcd[0]->rsrc_len; + void *base = info->hcd[0]->regs; + + oxu_remove(pdev, info->hcd[0]); + oxu_remove(pdev, info->hcd[1]); + + iounmap(base); + release_mem_region(memstart, memlen); + + kfree(info); + platform_set_drvdata(pdev, NULL); + + return 0; +} + +static void oxu_drv_shutdown(struct platform_device *pdev) +{ + oxu_drv_remove(pdev); +} + +#if 0 +/* FIXME: TODO */ +static int oxu_drv_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct usb_hcd *hcd = dev_get_drvdata(dev); + + return 0; +} + +static int oxu_drv_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct usb_hcd *hcd = dev_get_drvdata(dev); + + return 0; +} +#else +#define oxu_drv_suspend NULL +#define oxu_drv_resume NULL +#endif + +static struct platform_driver oxu_driver = { + .probe = oxu_drv_probe, + .remove = oxu_drv_remove, + .shutdown = oxu_drv_shutdown, + .suspend = oxu_drv_suspend, + .resume = oxu_drv_resume, + .driver = { + .name = "oxu210hp-hcd", + .bus = &platform_bus_type + } +}; + +static int __init oxu_module_init(void) +{ + int retval = 0; + + retval = platform_driver_register(&oxu_driver); + if (retval < 0) + return retval; + + return retval; +} + +static void __exit oxu_module_cleanup(void) +{ + platform_driver_unregister(&oxu_driver); +} + +module_init(oxu_module_init); +module_exit(oxu_module_cleanup); + +MODULE_DESCRIPTION("Oxford OXU210HP HCD driver - ver. " DRIVER_VERSION); +MODULE_AUTHOR("Rodolfo Giometti <giometti@linux.it>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/host/oxu210hp.h b/drivers/usb/host/oxu210hp.h new file mode 100644 index 00000000000..8910e271cc7 --- /dev/null +++ b/drivers/usb/host/oxu210hp.h @@ -0,0 +1,447 @@ +/* + * Host interface registers + */ + +#define OXU_DEVICEID 0x00 + #define OXU_REV_MASK 0xffff0000 + #define OXU_REV_SHIFT 16 + #define OXU_REV_2100 0x2100 + #define OXU_BO_SHIFT 8 + #define OXU_BO_MASK (0x3 << OXU_BO_SHIFT) + #define OXU_MAJ_REV_SHIFT 4 + #define OXU_MAJ_REV_MASK (0xf << OXU_MAJ_REV_SHIFT) + #define OXU_MIN_REV_SHIFT 0 + #define OXU_MIN_REV_MASK (0xf << OXU_MIN_REV_SHIFT) +#define OXU_HOSTIFCONFIG 0x04 +#define OXU_SOFTRESET 0x08 + #define OXU_SRESET (1 << 0) + +#define OXU_PIOBURSTREADCTRL 0x0C + +#define OXU_CHIPIRQSTATUS 0x10 +#define OXU_CHIPIRQEN_SET 0x14 +#define OXU_CHIPIRQEN_CLR 0x18 + #define OXU_USBSPHLPWUI 0x00000080 + #define OXU_USBOTGLPWUI 0x00000040 + #define OXU_USBSPHI 0x00000002 + #define OXU_USBOTGI 0x00000001 + +#define OXU_CLKCTRL_SET 0x1C + #define OXU_SYSCLKEN 0x00000008 + #define OXU_USBSPHCLKEN 0x00000002 + #define OXU_USBOTGCLKEN 0x00000001 + +#define OXU_ASO 0x68 + #define OXU_SPHPOEN 0x00000100 + #define OXU_OVRCCURPUPDEN 0x00000800 + #define OXU_ASO_OP (1 << 10) + #define OXU_COMPARATOR 0x000004000 + +#define OXU_USBMODE 0x1A8 + #define OXU_VBPS 0x00000020 + #define OXU_ES_LITTLE 0x00000000 + #define OXU_CM_HOST_ONLY 0x00000003 + +/* + * Proper EHCI structs & defines + */ + +/* Magic numbers that can affect system performance */ +#define EHCI_TUNE_CERR 3 /* 0-3 qtd retries; 0 == don't stop */ +#define EHCI_TUNE_RL_HS 4 /* nak throttle; see 4.9 */ +#define EHCI_TUNE_RL_TT 0 +#define EHCI_TUNE_MULT_HS 1 /* 1-3 transactions/uframe; 4.10.3 */ +#define EHCI_TUNE_MULT_TT 1 +#define EHCI_TUNE_FLS 2 /* (small) 256 frame schedule */ + +struct oxu_hcd; + +/* EHCI register interface, corresponds to EHCI Revision 0.95 specification */ + +/* Section 2.2 Host Controller Capability Registers */ +struct ehci_caps { + /* these fields are specified as 8 and 16 bit registers, + * but some hosts can't perform 8 or 16 bit PCI accesses. + */ + u32 hc_capbase; +#define HC_LENGTH(p) (((p)>>00)&0x00ff) /* bits 7:0 */ +#define HC_VERSION(p) (((p)>>16)&0xffff) /* bits 31:16 */ + u32 hcs_params; /* HCSPARAMS - offset 0x4 */ +#define HCS_DEBUG_PORT(p) (((p)>>20)&0xf) /* bits 23:20, debug port? */ +#define HCS_INDICATOR(p) ((p)&(1 << 16)) /* true: has port indicators */ +#define HCS_N_CC(p) (((p)>>12)&0xf) /* bits 15:12, #companion HCs */ +#define HCS_N_PCC(p) (((p)>>8)&0xf) /* bits 11:8, ports per CC */ +#define HCS_PORTROUTED(p) ((p)&(1 << 7)) /* true: port routing */ +#define HCS_PPC(p) ((p)&(1 << 4)) /* true: port power control */ +#define HCS_N_PORTS(p) (((p)>>0)&0xf) /* bits 3:0, ports on HC */ + + u32 hcc_params; /* HCCPARAMS - offset 0x8 */ +#define HCC_EXT_CAPS(p) (((p)>>8)&0xff) /* for pci extended caps */ +#define HCC_ISOC_CACHE(p) ((p)&(1 << 7)) /* true: can cache isoc frame */ +#define HCC_ISOC_THRES(p) (((p)>>4)&0x7) /* bits 6:4, uframes cached */ +#define HCC_CANPARK(p) ((p)&(1 << 2)) /* true: can park on async qh */ +#define HCC_PGM_FRAMELISTLEN(p) ((p)&(1 << 1)) /* true: periodic_size changes*/ +#define HCC_64BIT_ADDR(p) ((p)&(1)) /* true: can use 64-bit addr */ + u8 portroute[8]; /* nibbles for routing - offset 0xC */ +} __attribute__ ((packed)); + + +/* Section 2.3 Host Controller Operational Registers */ +struct ehci_regs { + /* USBCMD: offset 0x00 */ + u32 command; +/* 23:16 is r/w intr rate, in microframes; default "8" == 1/msec */ +#define CMD_PARK (1<<11) /* enable "park" on async qh */ +#define CMD_PARK_CNT(c) (((c)>>8)&3) /* how many transfers to park for */ +#define CMD_LRESET (1<<7) /* partial reset (no ports, etc) */ +#define CMD_IAAD (1<<6) /* "doorbell" interrupt async advance */ +#define CMD_ASE (1<<5) /* async schedule enable */ +#define CMD_PSE (1<<4) /* periodic schedule enable */ +/* 3:2 is periodic frame list size */ +#define CMD_RESET (1<<1) /* reset HC not bus */ +#define CMD_RUN (1<<0) /* start/stop HC */ + + /* USBSTS: offset 0x04 */ + u32 status; +#define STS_ASS (1<<15) /* Async Schedule Status */ +#define STS_PSS (1<<14) /* Periodic Schedule Status */ +#define STS_RECL (1<<13) /* Reclamation */ +#define STS_HALT (1<<12) /* Not running (any reason) */ +/* some bits reserved */ + /* these STS_* flags are also intr_enable bits (USBINTR) */ +#define STS_IAA (1<<5) /* Interrupted on async advance */ +#define STS_FATAL (1<<4) /* such as some PCI access errors */ +#define STS_FLR (1<<3) /* frame list rolled over */ +#define STS_PCD (1<<2) /* port change detect */ +#define STS_ERR (1<<1) /* "error" completion (overflow, ...) */ +#define STS_INT (1<<0) /* "normal" completion (short, ...) */ + +#define INTR_MASK (STS_IAA | STS_FATAL | STS_PCD | STS_ERR | STS_INT) + + /* USBINTR: offset 0x08 */ + u32 intr_enable; + + /* FRINDEX: offset 0x0C */ + u32 frame_index; /* current microframe number */ + /* CTRLDSSEGMENT: offset 0x10 */ + u32 segment; /* address bits 63:32 if needed */ + /* PERIODICLISTBASE: offset 0x14 */ + u32 frame_list; /* points to periodic list */ + /* ASYNCLISTADDR: offset 0x18 */ + u32 async_next; /* address of next async queue head */ + + u32 reserved[9]; + + /* CONFIGFLAG: offset 0x40 */ + u32 configured_flag; +#define FLAG_CF (1<<0) /* true: we'll support "high speed" */ + + /* PORTSC: offset 0x44 */ + u32 port_status[0]; /* up to N_PORTS */ +/* 31:23 reserved */ +#define PORT_WKOC_E (1<<22) /* wake on overcurrent (enable) */ +#define PORT_WKDISC_E (1<<21) /* wake on disconnect (enable) */ +#define PORT_WKCONN_E (1<<20) /* wake on connect (enable) */ +/* 19:16 for port testing */ +#define PORT_LED_OFF (0<<14) +#define PORT_LED_AMBER (1<<14) +#define PORT_LED_GREEN (2<<14) +#define PORT_LED_MASK (3<<14) +#define PORT_OWNER (1<<13) /* true: companion hc owns this port */ +#define PORT_POWER (1<<12) /* true: has power (see PPC) */ +#define PORT_USB11(x) (((x)&(3<<10)) == (1<<10)) /* USB 1.1 device */ +/* 11:10 for detecting lowspeed devices (reset vs release ownership) */ +/* 9 reserved */ +#define PORT_RESET (1<<8) /* reset port */ +#define PORT_SUSPEND (1<<7) /* suspend port */ +#define PORT_RESUME (1<<6) /* resume it */ +#define PORT_OCC (1<<5) /* over current change */ +#define PORT_OC (1<<4) /* over current active */ +#define PORT_PEC (1<<3) /* port enable change */ +#define PORT_PE (1<<2) /* port enable */ +#define PORT_CSC (1<<1) /* connect status change */ +#define PORT_CONNECT (1<<0) /* device connected */ +#define PORT_RWC_BITS (PORT_CSC | PORT_PEC | PORT_OCC) +} __attribute__ ((packed)); + +/* Appendix C, Debug port ... intended for use with special "debug devices" + * that can help if there's no serial console. (nonstandard enumeration.) + */ +struct ehci_dbg_port { + u32 control; +#define DBGP_OWNER (1<<30) +#define DBGP_ENABLED (1<<28) +#define DBGP_DONE (1<<16) +#define DBGP_INUSE (1<<10) +#define DBGP_ERRCODE(x) (((x)>>7)&0x07) +# define DBGP_ERR_BAD 1 +# define DBGP_ERR_SIGNAL 2 +#define DBGP_ERROR (1<<6) +#define DBGP_GO (1<<5) +#define DBGP_OUT (1<<4) +#define DBGP_LEN(x) (((x)>>0)&0x0f) + u32 pids; +#define DBGP_PID_GET(x) (((x)>>16)&0xff) +#define DBGP_PID_SET(data, tok) (((data)<<8)|(tok)) + u32 data03; + u32 data47; + u32 address; +#define DBGP_EPADDR(dev, ep) (((dev)<<8)|(ep)) +} __attribute__ ((packed)); + + +#define QTD_NEXT(dma) cpu_to_le32((u32)dma) + +/* + * EHCI Specification 0.95 Section 3.5 + * QTD: describe data transfer components (buffer, direction, ...) + * See Fig 3-6 "Queue Element Transfer Descriptor Block Diagram". + * + * These are associated only with "QH" (Queue Head) structures, + * used with control, bulk, and interrupt transfers. + */ +struct ehci_qtd { + /* first part defined by EHCI spec */ + __le32 hw_next; /* see EHCI 3.5.1 */ + __le32 hw_alt_next; /* see EHCI 3.5.2 */ + __le32 hw_token; /* see EHCI 3.5.3 */ +#define QTD_TOGGLE (1 << 31) /* data toggle */ +#define QTD_LENGTH(tok) (((tok)>>16) & 0x7fff) +#define QTD_IOC (1 << 15) /* interrupt on complete */ +#define QTD_CERR(tok) (((tok)>>10) & 0x3) +#define QTD_PID(tok) (((tok)>>8) & 0x3) +#define QTD_STS_ACTIVE (1 << 7) /* HC may execute this */ +#define QTD_STS_HALT (1 << 6) /* halted on error */ +#define QTD_STS_DBE (1 << 5) /* data buffer error (in HC) */ +#define QTD_STS_BABBLE (1 << 4) /* device was babbling (qtd halted) */ +#define QTD_STS_XACT (1 << 3) /* device gave illegal response */ +#define QTD_STS_MMF (1 << 2) /* incomplete split transaction */ +#define QTD_STS_STS (1 << 1) /* split transaction state */ +#define QTD_STS_PING (1 << 0) /* issue PING? */ + __le32 hw_buf[5]; /* see EHCI 3.5.4 */ + __le32 hw_buf_hi[5]; /* Appendix B */ + + /* the rest is HCD-private */ + dma_addr_t qtd_dma; /* qtd address */ + struct list_head qtd_list; /* sw qtd list */ + struct urb *urb; /* qtd's urb */ + size_t length; /* length of buffer */ + + u32 qtd_buffer_len; + void *buffer; + dma_addr_t buffer_dma; + void *transfer_buffer; + void *transfer_dma; +} __attribute__ ((aligned(32))); + +/* mask NakCnt+T in qh->hw_alt_next */ +#define QTD_MASK __constant_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)) + +/* values for that type tag */ +#define Q_TYPE_QH __constant_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 */ + +/* + * Entries in periodic shadow table are pointers to one of four kinds + * of data structure. That's dictated by the hardware; a type tag is + * encoded in the low bits of the hardware's periodic schedule. Use + * Q_NEXT_TYPE to get the tag. + * + * For entries in the async schedule, the type tag always says "qh". + */ +union ehci_shadow { + struct ehci_qh *qh; /* Q_TYPE_QH */ + __le32 *hw_next; /* (all types) */ + void *ptr; +}; + +/* + * EHCI Specification 0.95 Section 3.6 + * QH: describes control/bulk/interrupt endpoints + * See Fig 3-7 "Queue Head Structure Layout". + * + * These appear in both the async and (for interrupt) periodic schedules. + */ + +struct ehci_qh { + /* first part defined by EHCI spec */ + __le32 hw_next; /* see EHCI 3.6.1 */ + __le32 hw_info1; /* see EHCI 3.6.2 */ +#define QH_HEAD 0x00008000 + __le32 hw_info2; /* see EHCI 3.6.2 */ +#define QH_SMASK 0x000000ff +#define QH_CMASK 0x0000ff00 +#define QH_HUBADDR 0x007f0000 +#define QH_HUBPORT 0x3f800000 +#define QH_MULT 0xc0000000 + __le32 hw_current; /* qtd list - see EHCI 3.6.4 */ + + /* qtd overlay (hardware parts of a struct ehci_qtd) */ + __le32 hw_qtd_next; + __le32 hw_alt_next; + __le32 hw_token; + __le32 hw_buf[5]; + __le32 hw_buf_hi[5]; + + /* the rest is HCD-private */ + dma_addr_t qh_dma; /* address of qh */ + union ehci_shadow qh_next; /* ptr to qh; or periodic */ + struct list_head qtd_list; /* sw qtd list */ + struct ehci_qtd *dummy; + struct ehci_qh *reclaim; /* next to reclaim */ + + struct oxu_hcd *oxu; + struct kref kref; + unsigned stamp; + + u8 qh_state; +#define QH_STATE_LINKED 1 /* HC sees this */ +#define QH_STATE_UNLINK 2 /* HC may still see this */ +#define QH_STATE_IDLE 3 /* HC doesn't see this */ +#define QH_STATE_UNLINK_WAIT 4 /* LINKED and on reclaim q */ +#define QH_STATE_COMPLETING 5 /* don't touch token.HALT */ + + /* periodic schedule info */ + u8 usecs; /* intr bandwidth */ + u8 gap_uf; /* uframes split/csplit gap */ + u8 c_usecs; /* ... split completion bw */ + u16 tt_usecs; /* tt downstream bandwidth */ + unsigned short period; /* polling interval */ + unsigned short start; /* where polling starts */ +#define NO_FRAME ((unsigned short)~0) /* pick new start */ + struct usb_device *dev; /* access to TT */ +} __attribute__ ((aligned(32))); + +/* + * Proper OXU210HP structs + */ + +#define OXU_OTG_CORE_OFFSET 0x00400 +#define OXU_OTG_CAP_OFFSET (OXU_OTG_CORE_OFFSET + 0x100) +#define OXU_SPH_CORE_OFFSET 0x00800 +#define OXU_SPH_CAP_OFFSET (OXU_SPH_CORE_OFFSET + 0x100) + +#define OXU_OTG_MEM 0xE000 +#define OXU_SPH_MEM 0x16000 + +/* Only how many elements & element structure are specifies here. */ +/* 2 host controllers are enabled - total size <= 28 kbytes */ +#define DEFAULT_I_TDPS 1024 +#define QHEAD_NUM 16 +#define QTD_NUM 32 +#define SITD_NUM 8 +#define MURB_NUM 8 + +#define BUFFER_NUM 8 +#define BUFFER_SIZE 512 + +struct oxu_info { + struct usb_hcd *hcd[2]; +}; + +struct oxu_buf { + u8 buffer[BUFFER_SIZE]; +} __attribute__ ((aligned(BUFFER_SIZE))); + +struct oxu_onchip_mem { + struct oxu_buf db_pool[BUFFER_NUM]; + + u32 frame_list[DEFAULT_I_TDPS]; + struct ehci_qh qh_pool[QHEAD_NUM]; + struct ehci_qtd qtd_pool[QTD_NUM]; +} __attribute__ ((aligned(4 << 10))); + +#define EHCI_MAX_ROOT_PORTS 15 /* see HCS_N_PORTS */ + +struct oxu_murb { + struct urb urb; + struct urb *main; + u8 last; +}; + +struct oxu_hcd { /* one per controller */ + unsigned int is_otg:1; + + u8 qh_used[QHEAD_NUM]; + u8 qtd_used[QTD_NUM]; + u8 db_used[BUFFER_NUM]; + u8 murb_used[MURB_NUM]; + + struct oxu_onchip_mem __iomem *mem; + spinlock_t mem_lock; + + struct timer_list urb_timer; + + struct ehci_caps __iomem *caps; + struct ehci_regs __iomem *regs; + + __u32 hcs_params; /* cached register copy */ + spinlock_t lock; + + /* async schedule support */ + struct ehci_qh *async; + struct ehci_qh *reclaim; + unsigned reclaim_ready:1; + unsigned scanning:1; + + /* periodic schedule support */ + unsigned periodic_size; + __le32 *periodic; /* hw periodic table */ + dma_addr_t periodic_dma; + unsigned i_thresh; /* uframes HC might cache */ + + union ehci_shadow *pshadow; /* mirror hw periodic table */ + int next_uframe; /* scan periodic, start here */ + unsigned periodic_sched; /* periodic activity count */ + + /* per root hub port */ + unsigned long reset_done[EHCI_MAX_ROOT_PORTS]; + /* bit vectors (one bit per port) */ + unsigned long bus_suspended; /* which ports were + * already suspended at the + * start of a bus suspend + */ + unsigned long companion_ports;/* which ports are dedicated + * to the companion controller + */ + + struct timer_list watchdog; + unsigned long actions; + unsigned stamp; + unsigned long next_statechange; + u32 command; + + /* SILICON QUIRKS */ + struct list_head urb_list; /* this is the head to urb + * queue that didn't get enough + * resources + */ + struct oxu_murb *murb_pool; /* murb per split big urb */ + unsigned urb_len; + + u8 sbrn; /* packed release number */ +}; + +#define EHCI_IAA_JIFFIES (HZ/100) /* arbitrary; ~10 msec */ +#define EHCI_IO_JIFFIES (HZ/10) /* io watchdog > irq_thresh */ +#define EHCI_ASYNC_JIFFIES (HZ/20) /* async idle timeout */ +#define EHCI_SHRINK_JIFFIES (HZ/200) /* async qh unlink delay */ + +enum ehci_timer_action { + TIMER_IO_WATCHDOG, + TIMER_IAA_WATCHDOG, + TIMER_ASYNC_SHRINK, + TIMER_ASYNC_OFF, +}; + +#include <linux/oxu210hp.h> diff --git a/drivers/usb/host/pci-quirks.c b/drivers/usb/host/pci-quirks.c index ae6e70edd74..75b69847918 100644 --- a/drivers/usb/host/pci-quirks.c +++ b/drivers/usb/host/pci-quirks.c @@ -172,9 +172,9 @@ static void __devinit quirk_usb_handoff_ohci(struct pci_dev *pdev) if (!mmio_resource_enabled(pdev, 0)) return; - base = ioremap_nocache(pci_resource_start(pdev, 0), - pci_resource_len(pdev, 0)); - if (base == NULL) return; + base = pci_ioremap_bar(pdev, 0); + if (base == NULL) + return; /* On PA-RISC, PDC can leave IR set incorrectly; ignore it there. */ #ifndef __hppa__ @@ -221,9 +221,9 @@ static void __devinit quirk_usb_disable_ehci(struct pci_dev *pdev) if (!mmio_resource_enabled(pdev, 0)) return; - base = ioremap_nocache(pci_resource_start(pdev, 0), - pci_resource_len(pdev, 0)); - if (base == NULL) return; + base = pci_ioremap_bar(pdev, 0); + if (base == NULL) + return; cap_length = readb(base); op_reg_base = base + cap_length; @@ -271,7 +271,7 @@ static void __devinit quirk_usb_disable_ehci(struct pci_dev *pdev) /* if boot firmware now owns EHCI, spin till * it hands it over. */ - msec = 5000; + msec = 1000; while ((cap & EHCI_USBLEGSUP_BIOS) && (msec > 0)) { tried_handoff = 1; msleep(10); diff --git a/drivers/usb/host/r8a66597-hcd.c b/drivers/usb/host/r8a66597-hcd.c index c21f14e0666..319041205b5 100644 --- a/drivers/usb/host/r8a66597-hcd.c +++ b/drivers/usb/host/r8a66597-hcd.c @@ -2275,7 +2275,6 @@ static int __init_or_module r8a66597_remove(struct platform_device *pdev) return 0; } -#define resource_len(r) (((r)->end - (r)->start) + 1) static int __init r8a66597_probe(struct platform_device *pdev) { #if defined(CONFIG_SUPERH_ON_CHIP_R8A66597) && defined(CONFIG_HAVE_CLK) @@ -2296,11 +2295,10 @@ static int __init r8a66597_probe(struct platform_device *pdev) goto clean_up; } - res = platform_get_resource_byname(pdev, IORESOURCE_MEM, - (char *)hcd_name); + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!res) { ret = -ENODEV; - dev_err(&pdev->dev, "platform_get_resource_byname error.\n"); + dev_err(&pdev->dev, "platform_get_resource error.\n"); goto clean_up; } @@ -2315,7 +2313,7 @@ static int __init r8a66597_probe(struct platform_device *pdev) irq = ires->start; irq_trigger = ires->flags & IRQF_TRIGGER_MASK; - reg = ioremap(res->start, resource_len(res)); + reg = ioremap(res->start, resource_size(res)); if (reg == NULL) { ret = -ENOMEM; dev_err(&pdev->dev, "ioremap error.\n"); diff --git a/drivers/usb/host/uhci-hcd.c b/drivers/usb/host/uhci-hcd.c index cf5e4cf7ea4..944f7e0ca4d 100644 --- a/drivers/usb/host/uhci-hcd.c +++ b/drivers/usb/host/uhci-hcd.c @@ -942,6 +942,7 @@ 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 */ }; diff --git a/drivers/usb/host/whci/Kbuild b/drivers/usb/host/whci/Kbuild index 26a3871ea0f..11e5040b833 100644 --- a/drivers/usb/host/whci/Kbuild +++ b/drivers/usb/host/whci/Kbuild @@ -2,6 +2,7 @@ obj-$(CONFIG_USB_WHCI_HCD) += whci-hcd.o whci-hcd-y := \ asl.o \ + debug.o \ hcd.o \ hw.o \ init.o \ diff --git a/drivers/usb/host/whci/asl.c b/drivers/usb/host/whci/asl.c index 4d7078e5057..2291c5f5af5 100644 --- a/drivers/usb/host/whci/asl.c +++ b/drivers/usb/host/whci/asl.c @@ -19,32 +19,11 @@ #include <linux/dma-mapping.h> #include <linux/uwb/umc.h> #include <linux/usb.h> -#define D_LOCAL 0 -#include <linux/uwb/debug.h> #include "../../wusbcore/wusbhc.h" #include "whcd.h" -#if D_LOCAL >= 4 -static void dump_asl(struct whc *whc, const char *tag) -{ - struct device *dev = &whc->umc->dev; - struct whc_qset *qset; - - d_printf(4, dev, "ASL %s\n", tag); - - list_for_each_entry(qset, &whc->async_list, list_node) { - dump_qset(qset, dev); - } -} -#else -static inline void dump_asl(struct whc *whc, const char *tag) -{ -} -#endif - - static void qset_get_next_prev(struct whc *whc, struct whc_qset *qset, struct whc_qset **next, struct whc_qset **prev) { @@ -179,11 +158,31 @@ void asl_stop(struct whc *whc) 1000, "stop ASL"); } +/** + * asl_update - request an ASL update and wait for the hardware to be synced + * @whc: the WHCI HC + * @wusbcmd: WUSBCMD value to start the update. + * + * If the WUSB HC is inactive (i.e., the ASL is stopped) then the + * update must be skipped as the hardware may not respond to update + * requests. + */ void asl_update(struct whc *whc, uint32_t wusbcmd) { - whc_write_wusbcmd(whc, wusbcmd, wusbcmd); - wait_event(whc->async_list_wq, - (le_readl(whc->base + WUSBCMD) & WUSBCMD_ASYNC_UPDATED) == 0); + struct wusbhc *wusbhc = &whc->wusbhc; + long t; + + mutex_lock(&wusbhc->mutex); + if (wusbhc->active) { + whc_write_wusbcmd(whc, wusbcmd, wusbcmd); + 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); } /** @@ -202,8 +201,6 @@ void scan_async_work(struct work_struct *work) spin_lock_irq(&whc->lock); - dump_asl(whc, "before processing"); - /* * Transerve the software list backwards so new qsets can be * safely inserted into the ASL without making it non-circular. @@ -217,8 +214,6 @@ void scan_async_work(struct work_struct *work) update |= process_qset(whc, qset); } - dump_asl(whc, "after processing"); - spin_unlock_irq(&whc->lock); if (update) { diff --git a/drivers/usb/host/whci/debug.c b/drivers/usb/host/whci/debug.c new file mode 100644 index 00000000000..cf2d45946c5 --- /dev/null +++ b/drivers/usb/host/whci/debug.c @@ -0,0 +1,189 @@ +/* + * Wireless Host Controller (WHC) debug. + * + * Copyright (C) 2008 Cambridge Silicon Radio Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ +#include <linux/kernel.h> +#include <linux/debugfs.h> +#include <linux/seq_file.h> + +#include "../../wusbcore/wusbhc.h" + +#include "whcd.h" + +struct whc_dbg { + struct dentry *di_f; + struct dentry *asl_f; + struct dentry *pzl_f; +}; + +void qset_print(struct seq_file *s, struct whc_qset *qset) +{ + struct whc_std *std; + struct urb *urb = NULL; + int i; + + seq_printf(s, "qset %08x\n", (u32)qset->qset_dma); + seq_printf(s, " -> %08x\n", (u32)qset->qh.link); + seq_printf(s, " info: %08x %08x %08x\n", + qset->qh.info1, qset->qh.info2, qset->qh.info3); + seq_printf(s, " sts: %04x errs: %d\n", qset->qh.status, qset->qh.err_count); + seq_printf(s, " TD: sts: %08x opts: %08x\n", + qset->qh.overlay.qtd.status, qset->qh.overlay.qtd.options); + + for (i = 0; i < WHCI_QSET_TD_MAX; i++) { + seq_printf(s, " %c%c TD[%d]: sts: %08x opts: %08x ptr: %08x\n", + i == qset->td_start ? 'S' : ' ', + i == qset->td_end ? 'E' : ' ', + i, qset->qtd[i].status, qset->qtd[i].options, + (u32)qset->qtd[i].page_list_ptr); + } + seq_printf(s, " ntds: %d\n", qset->ntds); + list_for_each_entry(std, &qset->stds, list_node) { + if (urb != std->urb) { + urb = std->urb; + seq_printf(s, " urb %p transferred: %d bytes\n", urb, + urb->actual_length); + } + if (std->qtd) + seq_printf(s, " sTD[%td]: %zu bytes @ %08x\n", + std->qtd - &qset->qtd[0], + std->len, std->num_pointers ? + (u32)(std->pl_virt[0].buf_ptr) : (u32)std->dma_addr); + else + seq_printf(s, " sTD[-]: %zd bytes @ %08x\n", + std->len, std->num_pointers ? + (u32)(std->pl_virt[0].buf_ptr) : (u32)std->dma_addr); + } +} + +static int di_print(struct seq_file *s, void *p) +{ + struct whc *whc = s->private; + char buf[72]; + int d; + + for (d = 0; d < whc->n_devices; d++) { + struct di_buf_entry *di = &whc->di_buf[d]; + + bitmap_scnprintf(buf, sizeof(buf), + (unsigned long *)di->availability_info, UWB_NUM_MAS); + + seq_printf(s, "DI[%d]\n", d); + seq_printf(s, " availability: %s\n", buf); + seq_printf(s, " %c%c key idx: %d dev addr: %d\n", + (di->addr_sec_info & WHC_DI_SECURE) ? 'S' : ' ', + (di->addr_sec_info & WHC_DI_DISABLE) ? 'D' : ' ', + (di->addr_sec_info & WHC_DI_KEY_IDX_MASK) >> 8, + (di->addr_sec_info & WHC_DI_DEV_ADDR_MASK)); + } + return 0; +} + +static int asl_print(struct seq_file *s, void *p) +{ + struct whc *whc = s->private; + struct whc_qset *qset; + + list_for_each_entry(qset, &whc->async_list, list_node) { + qset_print(s, qset); + } + + return 0; +} + +static int pzl_print(struct seq_file *s, void *p) +{ + struct whc *whc = s->private; + struct whc_qset *qset; + int period; + + for (period = 0; period < 5; period++) { + seq_printf(s, "Period %d\n", period); + list_for_each_entry(qset, &whc->periodic_list[period], list_node) { + qset_print(s, qset); + } + } + return 0; +} + +static int di_open(struct inode *inode, struct file *file) +{ + return single_open(file, di_print, inode->i_private); +} + +static int asl_open(struct inode *inode, struct file *file) +{ + return single_open(file, asl_print, inode->i_private); +} + +static int pzl_open(struct inode *inode, struct file *file) +{ + return single_open(file, pzl_print, inode->i_private); +} + +static struct file_operations di_fops = { + .open = di_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static struct file_operations asl_fops = { + .open = asl_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static struct file_operations pzl_fops = { + .open = pzl_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +void whc_dbg_init(struct whc *whc) +{ + if (whc->wusbhc.pal.debugfs_dir == NULL) + return; + + whc->dbg = kzalloc(sizeof(struct whc_dbg), GFP_KERNEL); + if (whc->dbg == NULL) + return; + + whc->dbg->di_f = debugfs_create_file("di", 0444, + whc->wusbhc.pal.debugfs_dir, whc, + &di_fops); + whc->dbg->asl_f = debugfs_create_file("asl", 0444, + whc->wusbhc.pal.debugfs_dir, whc, + &asl_fops); + whc->dbg->pzl_f = debugfs_create_file("pzl", 0444, + whc->wusbhc.pal.debugfs_dir, whc, + &pzl_fops); +} + +void whc_dbg_clean_up(struct whc *whc) +{ + if (whc->dbg) { + debugfs_remove(whc->dbg->pzl_f); + debugfs_remove(whc->dbg->asl_f); + debugfs_remove(whc->dbg->di_f); + kfree(whc->dbg); + } +} diff --git a/drivers/usb/host/whci/hcd.c b/drivers/usb/host/whci/hcd.c index ef3ad4dca94..1569afd6245 100644 --- a/drivers/usb/host/whci/hcd.c +++ b/drivers/usb/host/whci/hcd.c @@ -15,7 +15,6 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#include <linux/version.h> #include <linux/kernel.h> #include <linux/init.h> #include <linux/uwb/umc.h> @@ -92,8 +91,6 @@ static void whc_stop(struct usb_hcd *usb_hcd) mutex_lock(&wusbhc->mutex); - wusbhc_stop(wusbhc); - /* stop HC */ le_writel(0, whc->base + WUSBINTR); whc_write_wusbcmd(whc, WUSBCMD_RUN, 0); @@ -276,6 +273,8 @@ static int whc_probe(struct umc_dev *umc) goto error_wusbhc_b_create; } + whc_dbg_init(whc); + return 0; error_wusbhc_b_create: @@ -299,6 +298,7 @@ static void whc_remove(struct umc_dev *umc) struct whc *whc = wusbhc_to_whc(wusbhc); if (usb_hcd) { + whc_dbg_clean_up(whc); wusbhc_b_destroy(wusbhc); usb_remove_hcd(usb_hcd); wusbhc_destroy(wusbhc); diff --git a/drivers/usb/host/whci/hw.c b/drivers/usb/host/whci/hw.c index ac86e59c122..6afa2e37916 100644 --- a/drivers/usb/host/whci/hw.c +++ b/drivers/usb/host/whci/hw.c @@ -50,6 +50,7 @@ int whc_do_gencmd(struct whc *whc, u32 cmd, u32 params, void *addr, size_t len) unsigned long flags; dma_addr_t dma_addr; int t; + int ret = 0; mutex_lock(&whc->mutex); @@ -61,7 +62,8 @@ int whc_do_gencmd(struct whc *whc, u32 cmd, u32 params, void *addr, size_t len) dev_err(&whc->umc->dev, "generic command timeout (%04x/%04x)\n", le_readl(whc->base + WUSBGENCMDSTS), le_readl(whc->base + WUSBGENCMDPARAMS)); - return -ETIMEDOUT; + ret = -ETIMEDOUT; + goto out; } if (addr) { @@ -80,8 +82,23 @@ int whc_do_gencmd(struct whc *whc, u32 cmd, u32 params, void *addr, size_t len) whc->base + WUSBGENCMDSTS); spin_unlock_irqrestore(&whc->lock, flags); - +out: mutex_unlock(&whc->mutex); - return 0; + 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/int.c b/drivers/usb/host/whci/int.c index fce01174aa9..6aae7002810 100644 --- a/drivers/usb/host/whci/int.c +++ b/drivers/usb/host/whci/int.c @@ -15,7 +15,6 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#include <linux/version.h> #include <linux/kernel.h> #include <linux/init.h> #include <linux/uwb/umc.h> diff --git a/drivers/usb/host/whci/pzl.c b/drivers/usb/host/whci/pzl.c index 8d62df0c330..7dc85a0bee7 100644 --- a/drivers/usb/host/whci/pzl.c +++ b/drivers/usb/host/whci/pzl.c @@ -19,35 +19,11 @@ #include <linux/dma-mapping.h> #include <linux/uwb/umc.h> #include <linux/usb.h> -#define D_LOCAL 0 -#include <linux/uwb/debug.h> #include "../../wusbcore/wusbhc.h" #include "whcd.h" -#if D_LOCAL >= 4 -static void dump_pzl(struct whc *whc, const char *tag) -{ - struct device *dev = &whc->umc->dev; - struct whc_qset *qset; - int period = 0; - - d_printf(4, dev, "PZL %s\n", tag); - - for (period = 0; period < 5; period++) { - d_printf(4, dev, "Period %d\n", period); - list_for_each_entry(qset, &whc->periodic_list[period], list_node) { - dump_qset(qset, dev); - } - } -} -#else -static inline void dump_pzl(struct whc *whc, const char *tag) -{ -} -#endif - static void update_pzl_pointers(struct whc *whc, int period, u64 addr) { switch (period) { @@ -195,11 +171,31 @@ void pzl_stop(struct whc *whc) 1000, "stop PZL"); } +/** + * pzl_update - request a PZL update and wait for the hardware to be synced + * @whc: the WHCI HC + * @wusbcmd: WUSBCMD value to start the update. + * + * If the WUSB HC is inactive (i.e., the PZL is stopped) then the + * update must be skipped as the hardware may not respond to update + * requests. + */ void pzl_update(struct whc *whc, uint32_t wusbcmd) { - whc_write_wusbcmd(whc, wusbcmd, wusbcmd); - wait_event(whc->periodic_list_wq, - (le_readl(whc->base + WUSBCMD) & WUSBCMD_PERIODIC_UPDATED) == 0); + struct wusbhc *wusbhc = &whc->wusbhc; + long t; + + mutex_lock(&wusbhc->mutex); + if (wusbhc->active) { + whc_write_wusbcmd(whc, wusbcmd, wusbcmd); + 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); } static void update_pzl_hw_view(struct whc *whc) @@ -235,8 +231,6 @@ void scan_periodic_work(struct work_struct *work) spin_lock_irq(&whc->lock); - dump_pzl(whc, "before processing"); - for (period = 4; period >= 0; period--) { list_for_each_entry_safe(qset, t, &whc->periodic_list[period], list_node) { if (!qset->in_hw_list) @@ -248,8 +242,6 @@ void scan_periodic_work(struct work_struct *work) if (update & (WHC_UPDATE_ADDED | WHC_UPDATE_REMOVED)) update_pzl_hw_view(whc); - dump_pzl(whc, "after processing"); - spin_unlock_irq(&whc->lock); if (update) { diff --git a/drivers/usb/host/whci/qset.c b/drivers/usb/host/whci/qset.c index 0420037d2e1..7be74314ee1 100644 --- a/drivers/usb/host/whci/qset.c +++ b/drivers/usb/host/whci/qset.c @@ -24,46 +24,6 @@ #include "whcd.h" -void dump_qset(struct whc_qset *qset, struct device *dev) -{ - struct whc_std *std; - struct urb *urb = NULL; - int i; - - dev_dbg(dev, "qset %08x\n", (u32)qset->qset_dma); - dev_dbg(dev, " -> %08x\n", (u32)qset->qh.link); - dev_dbg(dev, " info: %08x %08x %08x\n", - qset->qh.info1, qset->qh.info2, qset->qh.info3); - dev_dbg(dev, " sts: %04x errs: %d\n", qset->qh.status, qset->qh.err_count); - dev_dbg(dev, " TD: sts: %08x opts: %08x\n", - qset->qh.overlay.qtd.status, qset->qh.overlay.qtd.options); - - for (i = 0; i < WHCI_QSET_TD_MAX; i++) { - dev_dbg(dev, " %c%c TD[%d]: sts: %08x opts: %08x ptr: %08x\n", - i == qset->td_start ? 'S' : ' ', - i == qset->td_end ? 'E' : ' ', - i, qset->qtd[i].status, qset->qtd[i].options, - (u32)qset->qtd[i].page_list_ptr); - } - dev_dbg(dev, " ntds: %d\n", qset->ntds); - list_for_each_entry(std, &qset->stds, list_node) { - if (urb != std->urb) { - urb = std->urb; - dev_dbg(dev, " urb %p transferred: %d bytes\n", urb, - urb->actual_length); - } - if (std->qtd) - dev_dbg(dev, " sTD[%td]: %zu bytes @ %08x\n", - std->qtd - &qset->qtd[0], - std->len, std->num_pointers ? - (u32)(std->pl_virt[0].buf_ptr) : (u32)std->dma_addr); - else - dev_dbg(dev, " sTD[-]: %zd bytes @ %08x\n", - std->len, std->num_pointers ? - (u32)(std->pl_virt[0].buf_ptr) : (u32)std->dma_addr); - } -} - struct whc_qset *qset_alloc(struct whc *whc, gfp_t mem_flags) { struct whc_qset *qset; diff --git a/drivers/usb/host/whci/whcd.h b/drivers/usb/host/whci/whcd.h index 1d2a53bd39f..d3543a181dc 100644 --- a/drivers/usb/host/whci/whcd.h +++ b/drivers/usb/host/whci/whcd.h @@ -21,6 +21,7 @@ #define __WHCD_H #include <linux/uwb/whci.h> +#include <linux/uwb/umc.h> #include <linux/workqueue.h> #include "whci-hc.h" @@ -28,6 +29,7 @@ /* Generic command timeout. */ #define WHC_GENCMD_TIMEOUT_MS 100 +struct whc_dbg; struct whc { struct wusbhc wusbhc; @@ -69,6 +71,8 @@ struct whc { struct list_head periodic_removed_list; wait_queue_head_t periodic_list_wq; struct work_struct periodic_work; + + struct whc_dbg *dbg; }; #define wusbhc_to_whc(w) (container_of((w), struct whc, wusbhc)) @@ -133,10 +137,11 @@ 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); -void whc_wusbhc_stop(struct wusbhc *wusbhc); +void whc_wusbhc_stop(struct wusbhc *wusbhc, int delay); int whc_mmcie_add(struct wusbhc *wusbhc, u8 interval, u8 repeat_cnt, u8 handle, struct wuie_hdr *wuie); int whc_mmcie_rm(struct wusbhc *wusbhc, u8 handle); @@ -190,8 +195,11 @@ void process_inactive_qtd(struct whc *whc, struct whc_qset *qset, struct whc_qtd *qtd); enum whc_update qset_add_qtds(struct whc *whc, struct whc_qset *qset); void qset_remove_complete(struct whc *whc, struct whc_qset *qset); -void dump_qset(struct whc_qset *qset, struct device *dev); void pzl_update(struct whc *whc, uint32_t wusbcmd); void asl_update(struct whc *whc, uint32_t wusbcmd); +/* debug.c */ +void whc_dbg_init(struct whc *whc); +void whc_dbg_clean_up(struct whc *whc); + #endif /* #ifndef __WHCD_H */ diff --git a/drivers/usb/host/whci/whci-hc.h b/drivers/usb/host/whci/whci-hc.h index bff1eb7a35c..51df7e313b3 100644 --- a/drivers/usb/host/whci/whci-hc.h +++ b/drivers/usb/host/whci/whci-hc.h @@ -410,6 +410,8 @@ struct dn_buf_entry { # define WUSBDNTSCTRL_SLOTS(s) ((s) << 0) #define WUSBTIME 0x68 +# define WUSBTIME_CHANNEL_TIME_MASK 0x00ffffff + #define WUSBBPST 0x6c #define WUSBDIBUPDATED 0x70 diff --git a/drivers/usb/host/whci/wusb.c b/drivers/usb/host/whci/wusb.c index 66e4ddcd961..f24efdebad1 100644 --- a/drivers/usb/host/whci/wusb.c +++ b/drivers/usb/host/whci/wusb.c @@ -15,47 +15,19 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#include <linux/version.h> #include <linux/kernel.h> #include <linux/init.h> #include <linux/uwb/umc.h> -#define D_LOCAL 1 -#include <linux/uwb/debug.h> #include "../../wusbcore/wusbhc.h" #include "whcd.h" -#if D_LOCAL >= 1 -static void dump_di(struct whc *whc, int idx) -{ - struct di_buf_entry *di = &whc->di_buf[idx]; - struct device *dev = &whc->umc->dev; - char buf[128]; - - bitmap_scnprintf(buf, sizeof(buf), (unsigned long *)di->availability_info, UWB_NUM_MAS); - - d_printf(1, dev, "DI[%d]\n", idx); - d_printf(1, dev, " availability: %s\n", buf); - d_printf(1, dev, " %c%c key idx: %d dev addr: %d\n", - (di->addr_sec_info & WHC_DI_SECURE) ? 'S' : ' ', - (di->addr_sec_info & WHC_DI_DISABLE) ? 'D' : ' ', - (di->addr_sec_info & WHC_DI_KEY_IDX_MASK) >> 8, - (di->addr_sec_info & WHC_DI_DEV_ADDR_MASK)); -} -#else -static inline void dump_di(struct whc *whc, int idx) -{ -} -#endif - static int whc_update_di(struct whc *whc, int idx) { int offset = idx / 32; u32 bit = 1 << (idx % 32); - dump_di(whc, idx); - le_writel(bit, whc->base + WUSBDIBUPDATED + offset); return whci_wait_for(&whc->umc->dev, @@ -64,8 +36,9 @@ static int whc_update_di(struct whc *whc, int idx) } /* - * WHCI starts and stops MMCs based on there being a valid GTK so - * these need only start/stop the asynchronous and periodic schedules. + * WHCI starts MMCs based on there being a valid GTK so these need + * only start/stop the asynchronous and periodic schedules and send a + * channel stop command. */ int whc_wusbhc_start(struct wusbhc *wusbhc) @@ -78,12 +51,20 @@ int whc_wusbhc_start(struct wusbhc *wusbhc) return 0; } -void whc_wusbhc_stop(struct wusbhc *wusbhc) +void whc_wusbhc_stop(struct wusbhc *wusbhc, int delay) { struct whc *whc = wusbhc_to_whc(wusbhc); + u32 stop_time, now_time; + int ret; pzl_stop(whc); asl_stop(whc); + + now_time = le_readl(whc->base + WUSBTIME) & WUSBTIME_CHANNEL_TIME_MASK; + stop_time = (now_time + ((delay * 8) << 7)) & 0x00ffffff; + ret = whc_do_gencmd(whc, WUSBGENCMDSTS_CHAN_STOP, stop_time, NULL, 0); + if (ret == 0) + msleep(delay); } int whc_mmcie_add(struct wusbhc *wusbhc, u8 interval, u8 repeat_cnt, |