diff options
Diffstat (limited to 'drivers/usb/host')
63 files changed, 7620 insertions, 906 deletions
diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig index 344d5e2..7d0aa5f 100644 --- a/drivers/usb/host/Kconfig +++ b/drivers/usb/host/Kconfig @@ -17,7 +17,6 @@ config USB_C67X00_HCD config USB_XHCI_HCD tristate "xHCI HCD (USB 3.0) support" - depends on USB_ARCH_HAS_XHCI ---help--- The eXtensible Host Controller Interface (xHCI) is standard for USB 3.0 "SuperSpeed" host controller hardware. @@ -43,7 +42,6 @@ endif # USB_XHCI_HCD config USB_EHCI_HCD tristate "EHCI HCD (USB 2.0) support" - depends on USB_ARCH_HAS_EHCI ---help--- The Enhanced Host Controller Interface (EHCI) is standard for USB 2.0 "high speed" (480 Mbit/sec, 60 Mbyte/sec) host controller hardware. @@ -200,7 +198,7 @@ config USB_EHCI_MSM has an external PHY. config USB_EHCI_TEGRA - boolean "NVIDIA Tegra HCD support" + tristate "NVIDIA Tegra HCD support" depends on ARCH_TEGRA select USB_EHCI_ROOT_HUB_TT select USB_PHY @@ -345,9 +343,19 @@ config USB_ISP1362_HCD To compile this driver as a module, choose M here: the module will be called isp1362-hcd. +config USB_FUSBH200_HCD + tristate "FUSBH200 HCD support" + depends on USB + default N + ---help--- + Faraday FUSBH200 is designed to meet USB2.0 EHCI specification + with minor modification. + + To compile this driver as a module, choose M here: the + module will be called fusbh200-hcd. + config USB_OHCI_HCD - tristate "OHCI HCD support" - depends on USB_ARCH_HAS_OHCI + tristate "OHCI HCD (USB 1.1) support" select ISP1301_OMAP if MACH_OMAP_H2 || MACH_OMAP_H3 depends on USB_ISP1301 || !ARCH_LPC32XX ---help--- @@ -415,8 +423,8 @@ config USB_OHCI_HCD_PPC_OF default USB_OHCI_HCD_PPC_OF_BE || USB_OHCI_HCD_PPC_OF_LE config USB_OHCI_HCD_PCI - bool "OHCI support for PCI-bus USB controllers" - depends on PCI && (STB03xxx || PPC_MPC52xx || USB_OHCI_HCD_PPC_OF) + tristate "OHCI support for PCI-bus USB controllers" + depends on PCI default y select USB_OHCI_LITTLE_ENDIAN ---help--- @@ -470,7 +478,7 @@ config USB_CNS3XXX_OHCI It is needed for low-speed USB 1.0 device support. config USB_OHCI_HCD_PLATFORM - bool "Generic OHCI driver for a platform device" + tristate "Generic OHCI driver for a platform device" default n ---help--- Adds an OHCI host driver for a generic platform device, which diff --git a/drivers/usb/host/Makefile b/drivers/usb/host/Makefile index 4fb73c1..bea7112 100644 --- a/drivers/usb/host/Makefile +++ b/drivers/usb/host/Makefile @@ -33,11 +33,16 @@ obj-$(CONFIG_USB_EHCI_HCD_SPEAR) += ehci-spear.o obj-$(CONFIG_USB_EHCI_S5P) += ehci-s5p.o obj-$(CONFIG_USB_EHCI_HCD_AT91) += ehci-atmel.o obj-$(CONFIG_USB_EHCI_MSM) += ehci-msm.o +obj-$(CONFIG_USB_EHCI_TEGRA) += ehci-tegra.o obj-$(CONFIG_USB_OXU210HP_HCD) += oxu210hp-hcd.o obj-$(CONFIG_USB_ISP116X_HCD) += isp116x-hcd.o obj-$(CONFIG_USB_ISP1362_HCD) += isp1362-hcd.o + obj-$(CONFIG_USB_OHCI_HCD) += ohci-hcd.o +obj-$(CONFIG_USB_OHCI_HCD_PCI) += ohci-pci.o +obj-$(CONFIG_USB_OHCI_HCD_PLATFORM) += ohci-platform.o + obj-$(CONFIG_USB_UHCI_HCD) += uhci-hcd.o obj-$(CONFIG_USB_FHCI_HCD) += fhci.o obj-$(CONFIG_USB_XHCI_HCD) += xhci-hcd.o @@ -52,3 +57,4 @@ obj-$(CONFIG_USB_FSL_MPH_DR_OF) += fsl-mph-dr-of.o obj-$(CONFIG_USB_OCTEON2_COMMON) += octeon2-common.o obj-$(CONFIG_USB_HCD_BCMA) += bcma-hcd.o obj-$(CONFIG_USB_HCD_SSB) += ssb-hcd.o +obj-$(CONFIG_USB_FUSBH200_HCD) += fusbh200-hcd.o diff --git a/drivers/usb/host/ehci-atmel.c b/drivers/usb/host/ehci-atmel.c index 02f4611..3b645ff 100644 --- a/drivers/usb/host/ehci-atmel.c +++ b/drivers/usb/host/ehci-atmel.c @@ -37,15 +37,15 @@ static int clocked; static void atmel_start_clock(void) { - clk_enable(iclk); - clk_enable(fclk); + clk_prepare_enable(iclk); + clk_prepare_enable(fclk); clocked = 1; } static void atmel_stop_clock(void) { - clk_disable(fclk); - clk_disable(iclk); + clk_disable_unprepare(fclk); + clk_disable_unprepare(iclk); clocked = 0; } diff --git a/drivers/usb/host/ehci-fsl.c b/drivers/usb/host/ehci-fsl.c index 3be3df2..bd831ec 100644 --- a/drivers/usb/host/ehci-fsl.c +++ b/drivers/usb/host/ehci-fsl.c @@ -732,6 +732,7 @@ static struct platform_driver ehci_fsl_driver = { .shutdown = usb_hcd_platform_shutdown, .driver = { .name = "fsl-ehci", + .owner = THIS_MODULE, .pm = EHCI_FSL_PM_OPS, }, }; diff --git a/drivers/usb/host/ehci-grlib.c b/drivers/usb/host/ehci-grlib.c index 5d75de9..a77bd8d 100644 --- a/drivers/usb/host/ehci-grlib.c +++ b/drivers/usb/host/ehci-grlib.c @@ -153,9 +153,7 @@ err_irq: static int ehci_hcd_grlib_remove(struct platform_device *op) { - struct usb_hcd *hcd = dev_get_drvdata(&op->dev); - - dev_set_drvdata(&op->dev, NULL); + struct usb_hcd *hcd = platform_get_drvdata(op); dev_dbg(&op->dev, "stopping GRLIB GRUSBHC EHCI USB Controller\n"); @@ -171,7 +169,7 @@ static int ehci_hcd_grlib_remove(struct platform_device *op) static void ehci_hcd_grlib_shutdown(struct platform_device *op) { - struct usb_hcd *hcd = dev_get_drvdata(&op->dev); + struct usb_hcd *hcd = platform_get_drvdata(op); if (hcd->driver->shutdown) hcd->driver->shutdown(hcd); diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c index 246e124..7abf1ce 100644 --- a/drivers/usb/host/ehci-hcd.c +++ b/drivers/usb/host/ehci-hcd.c @@ -139,7 +139,7 @@ static inline unsigned ehci_read_frame_index(struct ehci_hcd *ehci) /*-------------------------------------------------------------------------*/ /* - * handshake - spin reading hc until handshake completes or fails + * ehci_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 @@ -155,8 +155,8 @@ static inline unsigned ehci_read_frame_index(struct ehci_hcd *ehci) * 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 ehci_hcd *ehci, void __iomem *ptr, - u32 mask, u32 done, int usec) +int ehci_handshake(struct ehci_hcd *ehci, void __iomem *ptr, + u32 mask, u32 done, int usec) { u32 result; @@ -172,6 +172,7 @@ static int handshake (struct ehci_hcd *ehci, void __iomem *ptr, } while (usec > 0); return -ETIMEDOUT; } +EXPORT_SYMBOL_GPL(ehci_handshake); /* check TDI/ARC silicon is in host mode */ static int tdi_in_host_mode (struct ehci_hcd *ehci) @@ -212,7 +213,7 @@ static int ehci_halt (struct ehci_hcd *ehci) spin_unlock_irq(&ehci->lock); synchronize_irq(ehci_to_hcd(ehci)->irq); - return handshake(ehci, &ehci->regs->status, + return ehci_handshake(ehci, &ehci->regs->status, STS_HALT, STS_HALT, 16 * 125); } @@ -251,7 +252,7 @@ static int ehci_reset (struct ehci_hcd *ehci) ehci_writel(ehci, command, &ehci->regs->command); ehci->rh_state = EHCI_RH_HALTED; ehci->next_statechange = jiffies; - retval = handshake (ehci, &ehci->regs->command, + retval = ehci_handshake(ehci, &ehci->regs->command, CMD_RESET, 0, 250 * 1000); if (ehci->has_hostpc) { @@ -286,7 +287,8 @@ static void ehci_quiesce (struct ehci_hcd *ehci) /* wait for any schedule enables/disables to take effect */ temp = (ehci->command << 10) & (STS_ASS | STS_PSS); - handshake(ehci, &ehci->regs->status, STS_ASS | STS_PSS, temp, 16 * 125); + ehci_handshake(ehci, &ehci->regs->status, STS_ASS | STS_PSS, temp, + 16 * 125); /* then disable anything that's still active */ spin_lock_irq(&ehci->lock); @@ -295,7 +297,8 @@ static void ehci_quiesce (struct ehci_hcd *ehci) spin_unlock_irq(&ehci->lock); /* hardware can take 16 microframes to turn off ... */ - handshake(ehci, &ehci->regs->status, STS_ASS | STS_PSS, 0, 16 * 125); + ehci_handshake(ehci, &ehci->regs->status, STS_ASS | STS_PSS, 0, + 16 * 125); } /*-------------------------------------------------------------------------*/ @@ -1266,11 +1269,6 @@ MODULE_LICENSE ("GPL"); #define PLATFORM_DRIVER ehci_hcd_msp_driver #endif -#ifdef CONFIG_USB_EHCI_TEGRA -#include "ehci-tegra.c" -#define PLATFORM_DRIVER tegra_ehci_driver -#endif - #ifdef CONFIG_SPARC_LEON #include "ehci-grlib.c" #define PLATFORM_DRIVER ehci_grlib_driver diff --git a/drivers/usb/host/ehci-hub.c b/drivers/usb/host/ehci-hub.c index 9ab4a4d..2b70277 100644 --- a/drivers/usb/host/ehci-hub.c +++ b/drivers/usb/host/ehci-hub.c @@ -42,6 +42,12 @@ static int ehci_hub_control( u16 wLength ); +static int persist_enabled_on_companion(struct usb_device *udev, void *unused) +{ + return !udev->maxchild && udev->persist_enabled && + udev->bus->root_hub->speed < USB_SPEED_HIGH; +} + /* After a power loss, ports that were owned by the companion must be * reset so that the companion can still own them. */ @@ -56,6 +62,16 @@ static void ehci_handover_companion_ports(struct ehci_hcd *ehci) if (!ehci->owned_ports) return; + /* + * USB 1.1 devices are mostly HIDs, which don't need to persist across + * suspends. If we ensure that none of our companion's devices have + * persist_enabled (by looking through all USB 1.1 buses in the system), + * we can skip this and avoid slowing resume down. Devices without + * persist will just get reenumerated shortly after resume anyway. + */ + if (!usb_for_each_dev(NULL, persist_enabled_on_companion)) + return; + /* Make sure the ports are powered */ port = HCS_N_PORTS(ehci->hcs_params); while (port--) { @@ -876,7 +892,7 @@ static int ehci_hub_control ( PORT_SUSPEND | PORT_RESUME); ehci_writel(ehci, temp, status_reg); clear_bit(wIndex, &ehci->resuming_ports); - retval = handshake(ehci, status_reg, + retval = ehci_handshake(ehci, status_reg, PORT_RESUME, 0, 2000 /* 2msec */); if (retval != 0) { ehci_err(ehci, @@ -902,7 +918,7 @@ static int ehci_hub_control ( /* REVISIT: some hardware needs 550+ usec to clear * this bit; seems too long to spin routinely... */ - retval = handshake(ehci, status_reg, + retval = ehci_handshake(ehci, status_reg, PORT_RESET, 0, 1000); if (retval != 0) { ehci_err (ehci, "port %d reset error %d\n", diff --git a/drivers/usb/host/ehci-mv.c b/drivers/usb/host/ehci-mv.c index 4020629..915c2db 100644 --- a/drivers/usb/host/ehci-mv.c +++ b/drivers/usb/host/ehci-mv.c @@ -166,14 +166,14 @@ static int mv_ehci_probe(struct platform_device *pdev) if (IS_ERR(ehci_mv->clk)) { dev_err(&pdev->dev, "error getting clock\n"); retval = PTR_ERR(ehci_mv->clk); - goto err_clear_drvdata; + goto err_put_hcd; } r = platform_get_resource_byname(pdev, IORESOURCE_MEM, "phyregs"); if (r == NULL) { dev_err(&pdev->dev, "no phy I/O memory resource defined\n"); retval = -ENODEV; - goto err_clear_drvdata; + goto err_put_hcd; } ehci_mv->phy_regs = devm_ioremap(&pdev->dev, r->start, @@ -181,14 +181,14 @@ static int mv_ehci_probe(struct platform_device *pdev) if (ehci_mv->phy_regs == 0) { dev_err(&pdev->dev, "failed to map phy I/O memory\n"); retval = -EFAULT; - goto err_clear_drvdata; + goto err_put_hcd; } r = platform_get_resource_byname(pdev, IORESOURCE_MEM, "capregs"); if (!r) { dev_err(&pdev->dev, "no I/O memory resource defined\n"); retval = -ENODEV; - goto err_clear_drvdata; + goto err_put_hcd; } ehci_mv->cap_regs = devm_ioremap(&pdev->dev, r->start, @@ -196,13 +196,13 @@ static int mv_ehci_probe(struct platform_device *pdev) if (ehci_mv->cap_regs == NULL) { dev_err(&pdev->dev, "failed to map I/O memory\n"); retval = -EFAULT; - goto err_clear_drvdata; + goto err_put_hcd; } retval = mv_ehci_enable(ehci_mv); if (retval) { dev_err(&pdev->dev, "init phy error %d\n", retval); - goto err_clear_drvdata; + goto err_put_hcd; } offset = readl(ehci_mv->cap_regs) & CAPLENGTH_MASK; @@ -274,8 +274,6 @@ err_set_vbus: pdata->set_vbus(0); err_disable_clk: mv_ehci_disable(ehci_mv); -err_clear_drvdata: - platform_set_drvdata(pdev, NULL); err_put_hcd: usb_put_hcd(hcd); @@ -300,8 +298,6 @@ static int mv_ehci_remove(struct platform_device *pdev) mv_ehci_disable(ehci_mv); } - platform_set_drvdata(pdev, NULL); - usb_put_hcd(hcd); return 0; diff --git a/drivers/usb/host/ehci-mxc.c b/drivers/usb/host/ehci-mxc.c index c369767..e4c34ac 100644 --- a/drivers/usb/host/ehci-mxc.c +++ b/drivers/usb/host/ehci-mxc.c @@ -194,7 +194,6 @@ static int ehci_mxc_drv_remove(struct platform_device *pdev) clk_disable_unprepare(priv->phyclk); usb_put_hcd(hcd); - platform_set_drvdata(pdev, NULL); return 0; } diff --git a/drivers/usb/host/ehci-octeon.c b/drivers/usb/host/ehci-octeon.c index a89750f..45cc001 100644 --- a/drivers/usb/host/ehci-octeon.c +++ b/drivers/usb/host/ehci-octeon.c @@ -182,8 +182,6 @@ static int ehci_octeon_drv_remove(struct platform_device *pdev) release_mem_region(hcd->rsrc_start, hcd->rsrc_len); usb_put_hcd(hcd); - platform_set_drvdata(pdev, NULL); - return 0; } diff --git a/drivers/usb/host/ehci-omap.c b/drivers/usb/host/ehci-omap.c index 16d7150..9bd7dfe 100644 --- a/drivers/usb/host/ehci-omap.c +++ b/drivers/usb/host/ehci-omap.c @@ -187,6 +187,12 @@ static int ehci_hcd_omap_probe(struct platform_device *pdev) } omap->phy[i] = phy; + + if (pdata->port_mode[i] == OMAP_EHCI_PORT_MODE_PHY) { + usb_phy_init(omap->phy[i]); + /* bring PHY out of suspend */ + usb_phy_set_suspend(omap->phy[i], 0); + } } pm_runtime_enable(dev); @@ -211,13 +217,14 @@ static int ehci_hcd_omap_probe(struct platform_device *pdev) } /* - * Bring PHYs out of reset. + * Bring PHYs out of reset for non PHY modes. * Even though HSIC mode is a PHY-less mode, the reset * line exists between the chips and can be modelled * as a PHY device for reset control. */ for (i = 0; i < omap->nports; i++) { - if (!omap->phy[i]) + if (!omap->phy[i] || + pdata->port_mode[i] == OMAP_EHCI_PORT_MODE_PHY) continue; usb_phy_init(omap->phy[i]); @@ -294,7 +301,7 @@ static struct platform_driver ehci_hcd_omap_driver = { /*.resume = ehci_hcd_omap_resume, */ .driver = { .name = hcd_name, - .of_match_table = of_match_ptr(omap_ehci_dt_ids), + .of_match_table = omap_ehci_dt_ids, } }; diff --git a/drivers/usb/host/ehci-orion.c b/drivers/usb/host/ehci-orion.c index efbc588..1a450aa 100644 --- a/drivers/usb/host/ehci-orion.c +++ b/drivers/usb/host/ehci-orion.c @@ -303,7 +303,7 @@ static struct platform_driver ehci_orion_driver = { .driver = { .name = "orion-ehci", .owner = THIS_MODULE, - .of_match_table = of_match_ptr(ehci_orion_dt_ids), + .of_match_table = ehci_orion_dt_ids, }, }; diff --git a/drivers/usb/host/ehci-platform.c b/drivers/usb/host/ehci-platform.c index f47f259..5733f8e 100644 --- a/drivers/usb/host/ehci-platform.c +++ b/drivers/usb/host/ehci-platform.c @@ -146,7 +146,6 @@ static int ehci_platform_remove(struct platform_device *dev) usb_remove_hcd(hcd); usb_put_hcd(hcd); - platform_set_drvdata(dev, NULL); if (pdata->power_off) pdata->power_off(dev); @@ -224,7 +223,7 @@ static struct platform_driver ehci_platform_driver = { .owner = THIS_MODULE, .name = "ehci-platform", .pm = &ehci_platform_pm_ops, - .of_match_table = of_match_ptr(vt8500_ehci_ids), + .of_match_table = vt8500_ehci_ids, } }; diff --git a/drivers/usb/host/ehci-pmcmsp.c b/drivers/usb/host/ehci-pmcmsp.c index 363890e..601e208 100644 --- a/drivers/usb/host/ehci-pmcmsp.c +++ b/drivers/usb/host/ehci-pmcmsp.c @@ -291,8 +291,7 @@ static const struct hc_driver ehci_msp_hc_driver = { /* * basic lifecycle operations */ - .reset = ehci_msp_setup, - .start = ehci_run, + .reset = ehci_msp_setup, .shutdown = ehci_shutdown, .start = ehci_run, .stop = ehci_stop, diff --git a/drivers/usb/host/ehci-ppc-of.c b/drivers/usb/host/ehci-ppc-of.c index 56dc732..86da09c 100644 --- a/drivers/usb/host/ehci-ppc-of.c +++ b/drivers/usb/host/ehci-ppc-of.c @@ -180,14 +180,12 @@ err_irq: static int ehci_hcd_ppc_of_remove(struct platform_device *op) { - struct usb_hcd *hcd = dev_get_drvdata(&op->dev); + struct usb_hcd *hcd = platform_get_drvdata(op); 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"); usb_remove_hcd(hcd); @@ -219,7 +217,7 @@ static int ehci_hcd_ppc_of_remove(struct platform_device *op) static void ehci_hcd_ppc_of_shutdown(struct platform_device *op) { - struct usb_hcd *hcd = dev_get_drvdata(&op->dev); + struct usb_hcd *hcd = platform_get_drvdata(op); if (hcd->driver->shutdown) hcd->driver->shutdown(hcd); diff --git a/drivers/usb/host/ehci-s5p.c b/drivers/usb/host/ehci-s5p.c index 379037f..7cc26e6 100644 --- a/drivers/usb/host/ehci-s5p.c +++ b/drivers/usb/host/ehci-s5p.c @@ -50,6 +50,8 @@ struct s5p_ehci_hcd { struct s5p_ehci_platdata *pdata; }; +static struct s5p_ehci_platdata empty_platdata; + #define to_s5p_ehci(hcd) (struct s5p_ehci_hcd *)(hcd_to_ehci(hcd)->priv) static void s5p_setup_vbus_gpio(struct platform_device *pdev) @@ -101,6 +103,13 @@ static int s5p_ehci_probe(struct platform_device *pdev) return -ENOMEM; } s5p_ehci = to_s5p_ehci(hcd); + + if (of_device_is_compatible(pdev->dev.of_node, + "samsung,exynos5440-ehci")) { + s5p_ehci->pdata = &empty_platdata; + goto skip_phy; + } + phy = devm_usb_get_phy(&pdev->dev, USB_PHY_TYPE_USB2); if (IS_ERR(phy)) { /* Fallback to pdata */ @@ -116,6 +125,8 @@ static int s5p_ehci_probe(struct platform_device *pdev) s5p_ehci->otg = phy->otg; } +skip_phy: + s5p_ehci->clk = devm_clk_get(&pdev->dev, "usbhost"); if (IS_ERR(s5p_ehci->clk)) { @@ -277,6 +288,7 @@ static const struct dev_pm_ops s5p_ehci_pm_ops = { #ifdef CONFIG_OF static const struct of_device_id exynos_ehci_match[] = { { .compatible = "samsung,exynos4210-ehci" }, + { .compatible = "samsung,exynos5440-ehci" }, {}, }; MODULE_DEVICE_TABLE(of, exynos_ehci_match); diff --git a/drivers/usb/host/ehci-sead3.c b/drivers/usb/host/ehci-sead3.c index f55477c..b2de52d 100644 --- a/drivers/usb/host/ehci-sead3.c +++ b/drivers/usb/host/ehci-sead3.c @@ -140,7 +140,6 @@ static int ehci_hcd_sead3_drv_remove(struct platform_device *pdev) usb_remove_hcd(hcd); usb_put_hcd(hcd); - platform_set_drvdata(pdev, NULL); return 0; } diff --git a/drivers/usb/host/ehci-sh.c b/drivers/usb/host/ehci-sh.c index b44d716..c4c0ee9 100644 --- a/drivers/usb/host/ehci-sh.c +++ b/drivers/usb/host/ehci-sh.c @@ -176,7 +176,6 @@ static int ehci_hcd_sh_remove(struct platform_device *pdev) usb_remove_hcd(hcd); usb_put_hcd(hcd); - platform_set_drvdata(pdev, NULL); clk_disable(priv->fclk); clk_disable(priv->iclk); diff --git a/drivers/usb/host/ehci-spear.c b/drivers/usb/host/ehci-spear.c index bd3e5cb..1cf0adb 100644 --- a/drivers/usb/host/ehci-spear.c +++ b/drivers/usb/host/ehci-spear.c @@ -148,10 +148,6 @@ static int spear_ehci_hcd_drv_remove(struct platform_device *pdev) struct usb_hcd *hcd = platform_get_drvdata(pdev); struct spear_ehci *sehci = to_spear_ehci(hcd); - if (!hcd) - return 0; - if (in_interrupt()) - BUG(); usb_remove_hcd(hcd); if (sehci->clk) @@ -174,7 +170,7 @@ static struct platform_driver spear_ehci_hcd_driver = { .name = "spear-ehci", .bus = &platform_bus_type, .pm = &ehci_spear_pm_ops, - .of_match_table = of_match_ptr(spear_ehci_id_table), + .of_match_table = spear_ehci_id_table, } }; diff --git a/drivers/usb/host/ehci-tegra.c b/drivers/usb/host/ehci-tegra.c index 59d111b..6ee7ef7 100644 --- a/drivers/usb/host/ehci-tegra.c +++ b/drivers/usb/host/ehci-tegra.c @@ -17,59 +17,53 @@ */ #include <linux/clk.h> +#include <linux/clk/tegra.h> +#include <linux/dma-mapping.h> #include <linux/err.h> -#include <linux/platform_device.h> -#include <linux/platform_data/tegra_usb.h> -#include <linux/irq.h> -#include <linux/usb/otg.h> #include <linux/gpio.h> +#include <linux/io.h> +#include <linux/irq.h> +#include <linux/module.h> #include <linux/of.h> #include <linux/of_gpio.h> +#include <linux/platform_device.h> +#include <linux/platform_data/tegra_usb.h> #include <linux/pm_runtime.h> +#include <linux/slab.h> #include <linux/usb/ehci_def.h> #include <linux/usb/tegra_usb_phy.h> -#include <linux/clk/tegra.h> +#include <linux/usb.h> +#include <linux/usb/hcd.h> +#include <linux/usb/otg.h> + +#include "ehci.h" #define TEGRA_USB_BASE 0xC5000000 #define TEGRA_USB2_BASE 0xC5004000 #define TEGRA_USB3_BASE 0xC5008000 -/* PORTSC registers */ -#define TEGRA_USB_PORTSC1 0x184 -#define TEGRA_USB_PORTSC1_PTS(x) (((x) & 0x3) << 30) -#define TEGRA_USB_PORTSC1_PHCD (1 << 23) +#define PORT_WAKE_BITS (PORT_WKOC_E|PORT_WKDISC_E|PORT_WKCONN_E) #define TEGRA_USB_DMA_ALIGN 32 +#define DRIVER_DESC "Tegra EHCI driver" +#define DRV_NAME "tegra-ehci" + +static struct hc_driver __read_mostly tegra_ehci_hc_driver; + +static int (*orig_hub_control)(struct usb_hcd *hcd, + u16 typeReq, u16 wValue, u16 wIndex, + char *buf, u16 wLength); + struct tegra_ehci_hcd { - struct ehci_hcd *ehci; struct tegra_usb_phy *phy; struct clk *clk; struct usb_phy *transceiver; - int host_resumed; int port_resuming; bool needs_double_reset; enum tegra_usb_phy_port_speed port_speed; }; -static void tegra_ehci_power_up(struct usb_hcd *hcd) -{ - struct tegra_ehci_hcd *tegra = dev_get_drvdata(hcd->self.controller); - - clk_prepare_enable(tegra->clk); - usb_phy_set_suspend(hcd->phy, 0); - tegra->host_resumed = 1; -} - -static void tegra_ehci_power_down(struct usb_hcd *hcd) -{ - struct tegra_ehci_hcd *tegra = dev_get_drvdata(hcd->self.controller); - - tegra->host_resumed = 0; - usb_phy_set_suspend(hcd->phy, 1); - clk_disable_unprepare(tegra->clk); -} - static int tegra_ehci_internal_port_reset( struct ehci_hcd *ehci, u32 __iomem *portsc_reg @@ -144,8 +138,8 @@ static int tegra_ehci_hub_control( u16 wLength ) { - struct ehci_hcd *ehci = hcd_to_ehci(hcd); - struct tegra_ehci_hcd *tegra = dev_get_drvdata(hcd->self.controller); + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + struct tegra_ehci_hcd *tegra = (struct tegra_ehci_hcd *)ehci->priv; u32 __iomem *status_reg; u32 temp; unsigned long flags; @@ -179,7 +173,7 @@ static int tegra_ehci_hub_control( * If a transaction is in progress, there may be a delay in * suspending the port. Poll until the port is suspended. */ - if (handshake(ehci, status_reg, PORT_SUSPEND, + if (ehci_handshake(ehci, status_reg, PORT_SUSPEND, PORT_SUSPEND, 5000)) pr_err("%s: timeout waiting for SUSPEND\n", __func__); @@ -227,9 +221,9 @@ static int tegra_ehci_hub_control( spin_lock_irqsave(&ehci->lock, flags); /* Poll until the controller clears RESUME and SUSPEND */ - if (handshake(ehci, status_reg, PORT_RESUME, 0, 2000)) + if (ehci_handshake(ehci, status_reg, PORT_RESUME, 0, 2000)) pr_err("%s: timeout waiting for RESUME\n", __func__); - if (handshake(ehci, status_reg, PORT_SUSPEND, 0, 2000)) + if (ehci_handshake(ehci, status_reg, PORT_SUSPEND, 0, 2000)) pr_err("%s: timeout waiting for SUSPEND\n", __func__); ehci->reset_done[wIndex-1] = 0; @@ -242,58 +236,13 @@ static int tegra_ehci_hub_control( spin_unlock_irqrestore(&ehci->lock, flags); /* Handle the hub control events here */ - return ehci_hub_control(hcd, typeReq, wValue, wIndex, buf, wLength); + return orig_hub_control(hcd, typeReq, wValue, wIndex, buf, wLength); + done: spin_unlock_irqrestore(&ehci->lock, flags); return retval; } -static void tegra_ehci_restart(struct usb_hcd *hcd) -{ - struct ehci_hcd *ehci = hcd_to_ehci(hcd); - - ehci_reset(ehci); - - /* setup the frame list and Async q heads */ - ehci_writel(ehci, ehci->periodic_dma, &ehci->regs->frame_list); - ehci_writel(ehci, (u32)ehci->async->qh_dma, &ehci->regs->async_next); - /* setup the command register and set the controller in RUN mode */ - ehci->command &= ~(CMD_LRESET|CMD_IAAD|CMD_PSE|CMD_ASE|CMD_RESET); - ehci->command |= CMD_RUN; - ehci_writel(ehci, ehci->command, &ehci->regs->command); - - down_write(&ehci_cf_port_reset_rwsem); - ehci_writel(ehci, FLAG_CF, &ehci->regs->configured_flag); - /* flush posted writes */ - ehci_readl(ehci, &ehci->regs->command); - up_write(&ehci_cf_port_reset_rwsem); -} - -static void tegra_ehci_shutdown(struct usb_hcd *hcd) -{ - struct tegra_ehci_hcd *tegra = dev_get_drvdata(hcd->self.controller); - - /* ehci_shutdown touches the USB controller registers, make sure - * controller has clocks to it */ - if (!tegra->host_resumed) - tegra_ehci_power_up(hcd); - - ehci_shutdown(hcd); -} - -static int tegra_ehci_setup(struct usb_hcd *hcd) -{ - struct ehci_hcd *ehci = hcd_to_ehci(hcd); - - /* EHCI registers start at offset 0x100 */ - ehci->caps = hcd->regs + 0x100; - - /* switch to host mode */ - hcd->has_tt = 1; - - return ehci_setup(hcd); -} - struct dma_aligned_buffer { void *kmalloc_ptr; void *old_xfer_buffer; @@ -373,38 +322,6 @@ static void tegra_ehci_unmap_urb_for_dma(struct usb_hcd *hcd, struct urb *urb) free_dma_aligned_buffer(urb); } -static const struct hc_driver tegra_ehci_hc_driver = { - .description = hcd_name, - .product_desc = "Tegra EHCI Host Controller", - .hcd_priv_size = sizeof(struct ehci_hcd), - .flags = HCD_USB2 | HCD_MEMORY, - - /* standard ehci functions */ - .irq = ehci_irq, - .start = ehci_run, - .stop = ehci_stop, - .urb_enqueue = ehci_urb_enqueue, - .urb_dequeue = ehci_urb_dequeue, - .endpoint_disable = ehci_endpoint_disable, - .endpoint_reset = ehci_endpoint_reset, - .get_frame_number = ehci_get_frame, - .hub_status_data = ehci_hub_status_data, - .clear_tt_buffer_complete = ehci_clear_tt_buffer_complete, - .relinquish_port = ehci_relinquish_port, - .port_handed_over = ehci_port_handed_over, - - /* modified ehci functions for tegra */ - .reset = tegra_ehci_setup, - .shutdown = tegra_ehci_shutdown, - .map_urb_for_dma = tegra_ehci_map_urb_for_dma, - .unmap_urb_for_dma = tegra_ehci_unmap_urb_for_dma, - .hub_control = tegra_ehci_hub_control, -#ifdef CONFIG_PM - .bus_suspend = ehci_bus_suspend, - .bus_resume = ehci_bus_resume, -#endif -}; - static int setup_vbus_gpio(struct platform_device *pdev, struct tegra_ehci_platform_data *pdata) { @@ -432,220 +349,16 @@ static int setup_vbus_gpio(struct platform_device *pdev, return err; } -#ifdef CONFIG_PM - -static int controller_suspend(struct device *dev) -{ - struct tegra_ehci_hcd *tegra = - platform_get_drvdata(to_platform_device(dev)); - struct ehci_hcd *ehci = tegra->ehci; - struct usb_hcd *hcd = ehci_to_hcd(ehci); - struct ehci_regs __iomem *hw = ehci->regs; - unsigned long flags; - - if (time_before(jiffies, ehci->next_statechange)) - msleep(10); - - ehci_halt(ehci); - - spin_lock_irqsave(&ehci->lock, flags); - tegra->port_speed = (readl(&hw->port_status[0]) >> 26) & 0x3; - clear_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); - spin_unlock_irqrestore(&ehci->lock, flags); - - tegra_ehci_power_down(hcd); - return 0; -} - -static int controller_resume(struct device *dev) -{ - struct tegra_ehci_hcd *tegra = - platform_get_drvdata(to_platform_device(dev)); - struct ehci_hcd *ehci = tegra->ehci; - struct usb_hcd *hcd = ehci_to_hcd(ehci); - struct ehci_regs __iomem *hw = ehci->regs; - unsigned long val; - - set_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); - tegra_ehci_power_up(hcd); - - if (tegra->port_speed > TEGRA_USB_PHY_PORT_SPEED_HIGH) { - /* Wait for the phy to detect new devices - * before we restart the controller */ - msleep(10); - goto restart; - } - - /* Force the phy to keep data lines in suspend state */ - tegra_ehci_phy_restore_start(hcd->phy, tegra->port_speed); - - /* Enable host mode */ - tdi_reset(ehci); - - /* Enable Port Power */ - val = readl(&hw->port_status[0]); - val |= PORT_POWER; - writel(val, &hw->port_status[0]); - udelay(10); - - /* Check if the phy resume from LP0. When the phy resume from LP0 - * USB register will be reset. */ - if (!readl(&hw->async_next)) { - /* Program the field PTC based on the saved speed mode */ - val = readl(&hw->port_status[0]); - val &= ~PORT_TEST(~0); - if (tegra->port_speed == TEGRA_USB_PHY_PORT_SPEED_HIGH) - val |= PORT_TEST_FORCE; - else if (tegra->port_speed == TEGRA_USB_PHY_PORT_SPEED_FULL) - val |= PORT_TEST(6); - else if (tegra->port_speed == TEGRA_USB_PHY_PORT_SPEED_LOW) - val |= PORT_TEST(7); - writel(val, &hw->port_status[0]); - udelay(10); - - /* Disable test mode by setting PTC field to NORMAL_OP */ - val = readl(&hw->port_status[0]); - val &= ~PORT_TEST(~0); - writel(val, &hw->port_status[0]); - udelay(10); - } - - /* Poll until CCS is enabled */ - if (handshake(ehci, &hw->port_status[0], PORT_CONNECT, - PORT_CONNECT, 2000)) { - pr_err("%s: timeout waiting for PORT_CONNECT\n", __func__); - goto restart; - } - - /* Poll until PE is enabled */ - if (handshake(ehci, &hw->port_status[0], PORT_PE, - PORT_PE, 2000)) { - pr_err("%s: timeout waiting for USB_PORTSC1_PE\n", __func__); - goto restart; - } - - /* Clear the PCI status, to avoid an interrupt taken upon resume */ - val = readl(&hw->status); - val |= STS_PCD; - writel(val, &hw->status); - - /* Put controller in suspend mode by writing 1 to SUSP bit of PORTSC */ - val = readl(&hw->port_status[0]); - if ((val & PORT_POWER) && (val & PORT_PE)) { - val |= PORT_SUSPEND; - writel(val, &hw->port_status[0]); - - /* Wait until port suspend completes */ - if (handshake(ehci, &hw->port_status[0], PORT_SUSPEND, - PORT_SUSPEND, 1000)) { - pr_err("%s: timeout waiting for PORT_SUSPEND\n", - __func__); - goto restart; - } - } - - tegra_ehci_phy_restore_end(hcd->phy); - goto done; - - restart: - if (tegra->port_speed <= TEGRA_USB_PHY_PORT_SPEED_HIGH) - tegra_ehci_phy_restore_end(hcd->phy); - - tegra_ehci_restart(hcd); - - done: - tegra_usb_phy_preresume(hcd->phy); - tegra->port_resuming = 1; - return 0; -} - -static int tegra_ehci_suspend(struct device *dev) -{ - struct tegra_ehci_hcd *tegra = - platform_get_drvdata(to_platform_device(dev)); - struct usb_hcd *hcd = ehci_to_hcd(tegra->ehci); - int rc = 0; - - /* - * When system sleep is supported and USB controller wakeup is - * implemented: If the controller is runtime-suspended and the - * wakeup setting needs to be changed, call pm_runtime_resume(). - */ - if (HCD_HW_ACCESSIBLE(hcd)) - rc = controller_suspend(dev); - return rc; -} - -static int tegra_ehci_resume(struct device *dev) -{ - int rc; - - rc = controller_resume(dev); - if (rc == 0) { - pm_runtime_disable(dev); - pm_runtime_set_active(dev); - pm_runtime_enable(dev); - } - return rc; -} - -static int tegra_ehci_runtime_suspend(struct device *dev) -{ - return controller_suspend(dev); -} - -static int tegra_ehci_runtime_resume(struct device *dev) -{ - return controller_resume(dev); -} - -static const struct dev_pm_ops tegra_ehci_pm_ops = { - .suspend = tegra_ehci_suspend, - .resume = tegra_ehci_resume, - .runtime_suspend = tegra_ehci_runtime_suspend, - .runtime_resume = tegra_ehci_runtime_resume, -}; - -#endif - -/* Bits of PORTSC1, which will get cleared by writing 1 into them */ -#define TEGRA_PORTSC1_RWC_BITS (PORT_CSC | PORT_PEC | PORT_OCC) - -static void tegra_ehci_set_pts(struct usb_phy *x, u8 pts_val) -{ - unsigned long val; - struct usb_hcd *hcd = bus_to_hcd(x->otg->host); - void __iomem *base = hcd->regs; - - val = readl(base + TEGRA_USB_PORTSC1) & ~TEGRA_PORTSC1_RWC_BITS; - val &= ~TEGRA_USB_PORTSC1_PTS(3); - val |= TEGRA_USB_PORTSC1_PTS(pts_val & 3); - writel(val, base + TEGRA_USB_PORTSC1); -} - -static void tegra_ehci_set_phcd(struct usb_phy *x, bool enable) -{ - unsigned long val; - struct usb_hcd *hcd = bus_to_hcd(x->otg->host); - void __iomem *base = hcd->regs; - - val = readl(base + TEGRA_USB_PORTSC1) & ~TEGRA_PORTSC1_RWC_BITS; - if (enable) - val |= TEGRA_USB_PORTSC1_PHCD; - else - val &= ~TEGRA_USB_PORTSC1_PHCD; - writel(val, base + TEGRA_USB_PORTSC1); -} - static int tegra_ehci_probe(struct platform_device *pdev) { struct resource *res; struct usb_hcd *hcd; + struct ehci_hcd *ehci; struct tegra_ehci_hcd *tegra; struct tegra_ehci_platform_data *pdata; int err = 0; int irq; - int instance = pdev->id; + struct device_node *np_phy; struct usb_phy *u_phy; pdata = pdev->dev.platform_data; @@ -665,35 +378,47 @@ static int tegra_ehci_probe(struct platform_device *pdev) setup_vbus_gpio(pdev, pdata); - tegra = devm_kzalloc(&pdev->dev, sizeof(struct tegra_ehci_hcd), - GFP_KERNEL); - if (!tegra) - return -ENOMEM; - hcd = usb_create_hcd(&tegra_ehci_hc_driver, &pdev->dev, dev_name(&pdev->dev)); if (!hcd) { dev_err(&pdev->dev, "Unable to create HCD\n"); - return -ENOMEM; + err = -ENOMEM; + goto cleanup_vbus_gpio; } + platform_set_drvdata(pdev, hcd); + ehci = hcd_to_ehci(hcd); + tegra = (struct tegra_ehci_hcd *)ehci->priv; - platform_set_drvdata(pdev, tegra); + hcd->has_tt = 1; tegra->clk = devm_clk_get(&pdev->dev, NULL); if (IS_ERR(tegra->clk)) { dev_err(&pdev->dev, "Can't get ehci clock\n"); err = PTR_ERR(tegra->clk); - goto fail_clk; + goto cleanup_hcd_create; } err = clk_prepare_enable(tegra->clk); if (err) - goto fail_clk; + goto cleanup_clk_get; tegra_periph_reset_assert(tegra->clk); udelay(1); tegra_periph_reset_deassert(tegra->clk); + np_phy = of_parse_phandle(pdev->dev.of_node, "nvidia,phy", 0); + if (!np_phy) { + err = -ENODEV; + goto cleanup_clk_en; + } + + u_phy = tegra_usb_get_phy(np_phy); + if (IS_ERR(u_phy)) { + err = PTR_ERR(u_phy); + goto cleanup_clk_en; + } + hcd->phy = u_phy; + tegra->needs_double_reset = of_property_read_bool(pdev->dev.of_node, "nvidia,needs-double-reset"); @@ -701,7 +426,7 @@ static int tegra_ehci_probe(struct platform_device *pdev) if (!res) { dev_err(&pdev->dev, "Failed to get I/O memory\n"); err = -ENXIO; - goto fail_io; + goto cleanup_clk_en; } hcd->rsrc_start = res->start; hcd->rsrc_len = resource_size(res); @@ -709,68 +434,36 @@ static int tegra_ehci_probe(struct platform_device *pdev) if (!hcd->regs) { dev_err(&pdev->dev, "Failed to remap I/O memory\n"); err = -ENOMEM; - goto fail_io; - } - - /* This is pretty ugly and needs to be fixed when we do only - * device-tree probing. Old code relies on the platform_device - * numbering that we lack for device-tree-instantiated devices. - */ - if (instance < 0) { - switch (res->start) { - case TEGRA_USB_BASE: - instance = 0; - break; - case TEGRA_USB2_BASE: - instance = 1; - break; - case TEGRA_USB3_BASE: - instance = 2; - break; - default: - err = -ENODEV; - dev_err(&pdev->dev, "unknown usb instance\n"); - goto fail_io; - } + goto cleanup_clk_en; } + ehci->caps = hcd->regs + 0x100; - tegra->phy = tegra_usb_phy_open(&pdev->dev, instance, hcd->regs, - pdata->phy_config, - TEGRA_USB_PHY_MODE_HOST, - tegra_ehci_set_pts, - tegra_ehci_set_phcd); - if (IS_ERR(tegra->phy)) { - dev_err(&pdev->dev, "Failed to open USB phy\n"); - err = -ENXIO; - goto fail_io; + err = usb_phy_init(hcd->phy); + if (err) { + dev_err(&pdev->dev, "Failed to initialize phy\n"); + goto cleanup_clk_en; } - hcd->phy = u_phy = &tegra->phy->u_phy; - usb_phy_init(hcd->phy); - u_phy->otg = devm_kzalloc(&pdev->dev, sizeof(struct usb_otg), GFP_KERNEL); if (!u_phy->otg) { dev_err(&pdev->dev, "Failed to alloc memory for otg\n"); err = -ENOMEM; - goto fail_io; + goto cleanup_phy; } u_phy->otg->host = hcd_to_bus(hcd); err = usb_phy_set_suspend(hcd->phy, 0); if (err) { dev_err(&pdev->dev, "Failed to power on the phy\n"); - goto fail_phy; + goto cleanup_phy; } - tegra->host_resumed = 1; - tegra->ehci = hcd_to_ehci(hcd); - irq = platform_get_irq(pdev, 0); if (!irq) { dev_err(&pdev->dev, "Failed to get IRQ\n"); err = -ENODEV; - goto fail_phy; + goto cleanup_phy; } if (pdata->operating_mode == TEGRA_USB_OTG) { @@ -785,39 +478,32 @@ static int tegra_ehci_probe(struct platform_device *pdev) err = usb_add_hcd(hcd, irq, IRQF_SHARED); if (err) { dev_err(&pdev->dev, "Failed to add USB HCD\n"); - goto fail; + goto cleanup_transceiver; } - pm_runtime_set_active(&pdev->dev); - pm_runtime_get_noresume(&pdev->dev); - - /* Don't skip the pm_runtime_forbid call if wakeup isn't working */ - /* if (!pdata->power_down_on_bus_suspend) */ - pm_runtime_forbid(&pdev->dev); - pm_runtime_enable(&pdev->dev); - pm_runtime_put_sync(&pdev->dev); return err; -fail: +cleanup_transceiver: if (!IS_ERR(tegra->transceiver)) otg_set_host(tegra->transceiver->otg, NULL); -fail_phy: +cleanup_phy: usb_phy_shutdown(hcd->phy); -fail_io: +cleanup_clk_en: clk_disable_unprepare(tegra->clk); -fail_clk: +cleanup_clk_get: + clk_put(tegra->clk); +cleanup_hcd_create: usb_put_hcd(hcd); +cleanup_vbus_gpio: + /* FIXME: Undo setup_vbus_gpio() here */ return err; } static int tegra_ehci_remove(struct platform_device *pdev) { - struct tegra_ehci_hcd *tegra = platform_get_drvdata(pdev); - struct usb_hcd *hcd = ehci_to_hcd(tegra->ehci); - - pm_runtime_get_sync(&pdev->dev); - pm_runtime_disable(&pdev->dev); - pm_runtime_put_noidle(&pdev->dev); + struct usb_hcd *hcd = platform_get_drvdata(pdev); + struct tegra_ehci_hcd *tegra = + (struct tegra_ehci_hcd *)hcd_to_ehci(hcd)->priv; if (!IS_ERR(tegra->transceiver)) otg_set_host(tegra->transceiver->otg, NULL); @@ -833,8 +519,7 @@ static int tegra_ehci_remove(struct platform_device *pdev) static void tegra_ehci_hcd_shutdown(struct platform_device *pdev) { - struct tegra_ehci_hcd *tegra = platform_get_drvdata(pdev); - struct usb_hcd *hcd = ehci_to_hcd(tegra->ehci); + struct usb_hcd *hcd = platform_get_drvdata(pdev); if (hcd->driver->shutdown) hcd->driver->shutdown(hcd); @@ -850,10 +535,50 @@ static struct platform_driver tegra_ehci_driver = { .remove = tegra_ehci_remove, .shutdown = tegra_ehci_hcd_shutdown, .driver = { - .name = "tegra-ehci", + .name = DRV_NAME, .of_match_table = tegra_ehci_of_match, -#ifdef CONFIG_PM - .pm = &tegra_ehci_pm_ops, -#endif } }; + +static const struct ehci_driver_overrides tegra_overrides __initconst = { + .extra_priv_size = sizeof(struct tegra_ehci_hcd), +}; + +static int __init ehci_tegra_init(void) +{ + if (usb_disabled()) + return -ENODEV; + + pr_info(DRV_NAME ": " DRIVER_DESC "\n"); + + ehci_init_driver(&tegra_ehci_hc_driver, &tegra_overrides); + + /* + * The Tegra HW has some unusual quirks, which require Tegra-specific + * workarounds. We override certain hc_driver functions here to + * achieve that. We explicitly do not enhance ehci_driver_overrides to + * allow this more easily, since this is an unusual case, and we don't + * want to encourage others to override these functions by making it + * too easy. + */ + + orig_hub_control = tegra_ehci_hc_driver.hub_control; + + tegra_ehci_hc_driver.map_urb_for_dma = tegra_ehci_map_urb_for_dma; + tegra_ehci_hc_driver.unmap_urb_for_dma = tegra_ehci_unmap_urb_for_dma; + tegra_ehci_hc_driver.hub_control = tegra_ehci_hub_control; + + return platform_driver_register(&tegra_ehci_driver); +} +module_init(ehci_tegra_init); + +static void __exit ehci_tegra_cleanup(void) +{ + platform_driver_unregister(&tegra_ehci_driver); +} +module_exit(ehci_tegra_cleanup); + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" DRV_NAME); +MODULE_DEVICE_TABLE(of, tegra_ehci_of_match); diff --git a/drivers/usb/host/ehci-tilegx.c b/drivers/usb/host/ehci-tilegx.c index b083a35..d72b292 100644 --- a/drivers/usb/host/ehci-tilegx.c +++ b/drivers/usb/host/ehci-tilegx.c @@ -193,7 +193,6 @@ static int ehci_hcd_tilegx_drv_remove(struct platform_device *pdev) tilegx_stop_ehc(); gxio_usb_host_destroy(&pdata->usb_ctx); destroy_irq(pdata->irq); - platform_set_drvdata(pdev, NULL); return 0; } diff --git a/drivers/usb/host/ehci-xilinx-of.c b/drivers/usb/host/ehci-xilinx-of.c index d845e3b..35c7f90 100644 --- a/drivers/usb/host/ehci-xilinx-of.c +++ b/drivers/usb/host/ehci-xilinx-of.c @@ -209,8 +209,7 @@ err_irq: */ static int ehci_hcd_xilinx_of_remove(struct platform_device *op) { - struct usb_hcd *hcd = dev_get_drvdata(&op->dev); - dev_set_drvdata(&op->dev, NULL); + struct usb_hcd *hcd = platform_get_drvdata(op); dev_dbg(&op->dev, "stopping XILINX-OF USB Controller\n"); @@ -229,7 +228,7 @@ static int ehci_hcd_xilinx_of_remove(struct platform_device *op) */ static void ehci_hcd_xilinx_of_shutdown(struct platform_device *op) { - struct usb_hcd *hcd = dev_get_drvdata(&op->dev); + struct usb_hcd *hcd = platform_get_drvdata(op); if (hcd->driver->shutdown) hcd->driver->shutdown(hcd); diff --git a/drivers/usb/host/ehci.h b/drivers/usb/host/ehci.h index 7c978b2..64f9a08 100644 --- a/drivers/usb/host/ehci.h +++ b/drivers/usb/host/ehci.h @@ -800,6 +800,8 @@ struct ehci_driver_overrides { extern void ehci_init_driver(struct hc_driver *drv, const struct ehci_driver_overrides *over); extern int ehci_setup(struct usb_hcd *hcd); +extern int ehci_handshake(struct ehci_hcd *ehci, void __iomem *ptr, + u32 mask, u32 done, int usec); #ifdef CONFIG_PM extern int ehci_suspend(struct usb_hcd *hcd, bool do_wakeup); diff --git a/drivers/usb/host/fhci-sched.c b/drivers/usb/host/fhci-sched.c index 8f18538..95ca598 100644 --- a/drivers/usb/host/fhci-sched.c +++ b/drivers/usb/host/fhci-sched.c @@ -739,9 +739,13 @@ void fhci_queue_urb(struct fhci_hcd *fhci, struct urb *urb) } /* 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 : + if (ed->mode == FHCI_TF_ISO) { + /* Ignore the possibility of underruns */ + urb->start_frame = ed->td_head ? ed->next_iso : get_frame_num(fhci); + ed->next_iso = (urb->start_frame + urb->interval * + urb->number_of_packets) & 0x07ff; + } /* * OHCI handles the DATA toggle itself,we just use the USB diff --git a/drivers/usb/host/fhci.h b/drivers/usb/host/fhci.h index 7cc1c32..154e6a0 100644 --- a/drivers/usb/host/fhci.h +++ b/drivers/usb/host/fhci.h @@ -338,7 +338,7 @@ struct ed { /* 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 */ + u16 next_iso; /* time stamp of next queued ISO transfer */ struct td *td_head; /* a pointer to the current TD handled */ }; diff --git a/drivers/usb/host/fusbh200-hcd.c b/drivers/usb/host/fusbh200-hcd.c new file mode 100644 index 0000000..299253c --- /dev/null +++ b/drivers/usb/host/fusbh200-hcd.c @@ -0,0 +1,5972 @@ +/* + * Faraday FUSBH200 EHCI-like driver + * + * Copyright (c) 2013 Faraday Technology Corporation + * + * Author: Yuan-Hsin Chen <yhchen@faraday-tech.com> + * Feng-Hsin Chiang <john453@faraday-tech.com> + * Po-Yu Chuang <ratbert.chuang@gmail.com> + * + * Most of code borrowed from the Linux-3.7 EHCI driver + * + * 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/device.h> +#include <linux/dmapool.h> +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/ioport.h> +#include <linux/sched.h> +#include <linux/vmalloc.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/hrtimer.h> +#include <linux/list.h> +#include <linux/interrupt.h> +#include <linux/usb.h> +#include <linux/usb/hcd.h> +#include <linux/moduleparam.h> +#include <linux/dma-mapping.h> +#include <linux/debugfs.h> +#include <linux/slab.h> +#include <linux/uaccess.h> +#include <linux/platform_device.h> + +#include <asm/byteorder.h> +#include <asm/io.h> +#include <asm/irq.h> +#include <asm/unaligned.h> + +/*-------------------------------------------------------------------------*/ +#define DRIVER_AUTHOR "Yuan-Hsin Chen" +#define DRIVER_DESC "FUSBH200 Host Controller (EHCI) Driver" + +static const char hcd_name [] = "fusbh200_hcd"; + +#undef VERBOSE_DEBUG +#undef FUSBH200_URB_TRACE + +#ifdef DEBUG +#define FUSBH200_STATS +#endif + +/* magic numbers that can affect system performance */ +#define FUSBH200_TUNE_CERR 3 /* 0-3 qtd retries; 0 == don't stop */ +#define FUSBH200_TUNE_RL_HS 4 /* nak throttle; see 4.9 */ +#define FUSBH200_TUNE_RL_TT 0 +#define FUSBH200_TUNE_MULT_HS 1 /* 1-3 transactions/uframe; 4.10.3 */ +#define FUSBH200_TUNE_MULT_TT 1 +/* + * Some drivers think it's safe to schedule isochronous transfers more than + * 256 ms into the future (partly as a result of an old bug in the scheduling + * code). In an attempt to avoid trouble, we will use a minimum scheduling + * length of 512 frames instead of 256. + */ +#define FUSBH200_TUNE_FLS 1 /* (medium) 512-frame schedule */ + +/* Initial IRQ latency: faster than hw default */ +static int log2_irq_thresh = 0; // 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 = 0; +module_param (park, uint, S_IRUGO); +MODULE_PARM_DESC (park, "park setting; 1-3 back-to-back async packets"); + +/* for link power management(LPM) feature */ +static unsigned int hird; +module_param(hird, int, S_IRUGO); +MODULE_PARM_DESC(hird, "host initiated resume duration, +1 for each 75us"); + +#define INTR_MASK (STS_IAA | STS_FATAL | STS_PCD | STS_ERR | STS_INT) + +#include "fusbh200.h" + +/*-------------------------------------------------------------------------*/ + +#define fusbh200_dbg(fusbh200, fmt, args...) \ + dev_dbg (fusbh200_to_hcd(fusbh200)->self.controller , fmt , ## args ) +#define fusbh200_err(fusbh200, fmt, args...) \ + dev_err (fusbh200_to_hcd(fusbh200)->self.controller , fmt , ## args ) +#define fusbh200_info(fusbh200, fmt, args...) \ + dev_info (fusbh200_to_hcd(fusbh200)->self.controller , fmt , ## args ) +#define fusbh200_warn(fusbh200, fmt, args...) \ + dev_warn (fusbh200_to_hcd(fusbh200)->self.controller , fmt , ## args ) + +#ifdef VERBOSE_DEBUG +# define fusbh200_vdbg fusbh200_dbg +#else + static inline void fusbh200_vdbg(struct fusbh200_hcd *fusbh200, ...) {} +#endif + +#ifdef DEBUG + +/* check the values in the HCSPARAMS register + * (host controller _Structural_ parameters) + * see EHCI spec, Table 2-4 for each value + */ +static void dbg_hcs_params (struct fusbh200_hcd *fusbh200, char *label) +{ + u32 params = fusbh200_readl(fusbh200, &fusbh200->caps->hcs_params); + + fusbh200_dbg (fusbh200, + "%s hcs_params 0x%x ports=%d\n", + label, params, + HCS_N_PORTS (params) + ); +} +#else + +static inline void dbg_hcs_params (struct fusbh200_hcd *fusbh200, char *label) {} + +#endif + +#ifdef DEBUG + +/* check the values in the HCCPARAMS register + * (host controller _Capability_ parameters) + * see EHCI Spec, Table 2-5 for each value + * */ +static void dbg_hcc_params (struct fusbh200_hcd *fusbh200, char *label) +{ + u32 params = fusbh200_readl(fusbh200, &fusbh200->caps->hcc_params); + + fusbh200_dbg (fusbh200, + "%s hcc_params %04x uframes %s%s\n", + label, + params, + HCC_PGM_FRAMELISTLEN(params) ? "256/512/1024" : "1024", + HCC_CANPARK(params) ? " park" : ""); +} +#else + +static inline void dbg_hcc_params (struct fusbh200_hcd *fusbh200, char *label) {} + +#endif + +#ifdef DEBUG + +static void __maybe_unused +dbg_qtd (const char *label, struct fusbh200_hcd *fusbh200, struct fusbh200_qtd *qtd) +{ + fusbh200_dbg(fusbh200, "%s td %p n%08x %08x t%08x p0=%08x\n", label, qtd, + hc32_to_cpup(fusbh200, &qtd->hw_next), + hc32_to_cpup(fusbh200, &qtd->hw_alt_next), + hc32_to_cpup(fusbh200, &qtd->hw_token), + hc32_to_cpup(fusbh200, &qtd->hw_buf [0])); + if (qtd->hw_buf [1]) + fusbh200_dbg(fusbh200, " p1=%08x p2=%08x p3=%08x p4=%08x\n", + hc32_to_cpup(fusbh200, &qtd->hw_buf[1]), + hc32_to_cpup(fusbh200, &qtd->hw_buf[2]), + hc32_to_cpup(fusbh200, &qtd->hw_buf[3]), + hc32_to_cpup(fusbh200, &qtd->hw_buf[4])); +} + +static void __maybe_unused +dbg_qh (const char *label, struct fusbh200_hcd *fusbh200, struct fusbh200_qh *qh) +{ + struct fusbh200_qh_hw *hw = qh->hw; + + fusbh200_dbg (fusbh200, "%s qh %p n%08x info %x %x qtd %x\n", label, + qh, hw->hw_next, hw->hw_info1, hw->hw_info2, hw->hw_current); + dbg_qtd("overlay", fusbh200, (struct fusbh200_qtd *) &hw->hw_qtd_next); +} + +static void __maybe_unused +dbg_itd (const char *label, struct fusbh200_hcd *fusbh200, struct fusbh200_itd *itd) +{ + fusbh200_dbg (fusbh200, "%s [%d] itd %p, next %08x, urb %p\n", + label, itd->frame, itd, hc32_to_cpu(fusbh200, itd->hw_next), + itd->urb); + fusbh200_dbg (fusbh200, + " trans: %08x %08x %08x %08x %08x %08x %08x %08x\n", + hc32_to_cpu(fusbh200, itd->hw_transaction[0]), + hc32_to_cpu(fusbh200, itd->hw_transaction[1]), + hc32_to_cpu(fusbh200, itd->hw_transaction[2]), + hc32_to_cpu(fusbh200, itd->hw_transaction[3]), + hc32_to_cpu(fusbh200, itd->hw_transaction[4]), + hc32_to_cpu(fusbh200, itd->hw_transaction[5]), + hc32_to_cpu(fusbh200, itd->hw_transaction[6]), + hc32_to_cpu(fusbh200, itd->hw_transaction[7])); + fusbh200_dbg (fusbh200, + " buf: %08x %08x %08x %08x %08x %08x %08x\n", + hc32_to_cpu(fusbh200, itd->hw_bufp[0]), + hc32_to_cpu(fusbh200, itd->hw_bufp[1]), + hc32_to_cpu(fusbh200, itd->hw_bufp[2]), + hc32_to_cpu(fusbh200, itd->hw_bufp[3]), + hc32_to_cpu(fusbh200, itd->hw_bufp[4]), + hc32_to_cpu(fusbh200, itd->hw_bufp[5]), + hc32_to_cpu(fusbh200, itd->hw_bufp[6])); + fusbh200_dbg (fusbh200, " index: %d %d %d %d %d %d %d %d\n", + itd->index[0], itd->index[1], itd->index[2], + itd->index[3], itd->index[4], itd->index[5], + itd->index[6], itd->index[7]); +} + +static int __maybe_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 __maybe_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 %07x %s=%d ithresh=%d%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_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"; break; /* low speed */ + case 2 << 10: sig = "j"; break; + default: sig = "?"; break; + } + + return scnprintf (buf, len, + "%s%sport:%d status %06x %d " + "sig=%s%s%s%s%s%s%s%s", + label, label [0] ? " " : "", port, status, + status>>25,/*device address */ + sig, + (status & PORT_RESET) ? " RESET" : "", + (status & PORT_SUSPEND) ? " SUSPEND" : "", + (status & PORT_RESUME) ? " RESUME" : "", + (status & PORT_PEC) ? " PEC" : "", + (status & PORT_PE) ? " PE" : "", + (status & PORT_CSC) ? " CSC" : "", + (status & PORT_CONNECT) ? " CONNECT" : ""); +} + +#else +static inline void __maybe_unused +dbg_qh (char *label, struct fusbh200_hcd *fusbh200, struct fusbh200_qh *qh) +{} + +static inline int __maybe_unused +dbg_status_buf (char *buf, unsigned len, const char *label, u32 status) +{ return 0; } + +static inline int __maybe_unused +dbg_command_buf (char *buf, unsigned len, const char *label, u32 command) +{ return 0; } + +static inline int __maybe_unused +dbg_intr_buf (char *buf, unsigned len, const char *label, u32 enable) +{ return 0; } + +static inline int __maybe_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(fusbh200, label, status) { \ + char _buf [80]; \ + dbg_status_buf (_buf, sizeof _buf, label, status); \ + fusbh200_dbg (fusbh200, "%s\n", _buf); \ +} + +#define dbg_cmd(fusbh200, label, command) { \ + char _buf [80]; \ + dbg_command_buf (_buf, sizeof _buf, label, command); \ + fusbh200_dbg (fusbh200, "%s\n", _buf); \ +} + +#define dbg_port(fusbh200, label, port, status) { \ + char _buf [80]; \ + dbg_port_buf (_buf, sizeof _buf, label, port, status); \ + fusbh200_dbg (fusbh200, "%s\n", _buf); \ +} + +/*-------------------------------------------------------------------------*/ + +#ifdef STUB_DEBUG_FILES + +static inline void create_debug_files (struct fusbh200_hcd *bus) { } +static inline void remove_debug_files (struct fusbh200_hcd *bus) { } + +#else + +/* troubleshooting help: expose state in debugfs */ + +static int debug_async_open(struct inode *, struct file *); +static int debug_periodic_open(struct inode *, struct file *); +static int debug_registers_open(struct inode *, struct file *); +static int debug_async_open(struct inode *, struct file *); + +static ssize_t debug_output(struct file*, char __user*, size_t, loff_t*); +static int debug_close(struct inode *, struct file *); + +static const struct file_operations debug_async_fops = { + .owner = THIS_MODULE, + .open = debug_async_open, + .read = debug_output, + .release = debug_close, + .llseek = default_llseek, +}; +static const struct file_operations debug_periodic_fops = { + .owner = THIS_MODULE, + .open = debug_periodic_open, + .read = debug_output, + .release = debug_close, + .llseek = default_llseek, +}; +static const struct file_operations debug_registers_fops = { + .owner = THIS_MODULE, + .open = debug_registers_open, + .read = debug_output, + .release = debug_close, + .llseek = default_llseek, +}; + +static struct dentry *fusbh200_debug_root; + +struct debug_buffer { + ssize_t (*fill_func)(struct debug_buffer *); /* fill method */ + struct usb_bus *bus; + struct mutex mutex; /* protect filling of buffer */ + size_t count; /* number of characters filled into buffer */ + char *output_buf; + size_t alloc_size; +}; + +#define speed_char(info1) ({ char tmp; \ + switch (info1 & (3 << 12)) { \ + case QH_FULL_SPEED: tmp = 'f'; break; \ + case QH_LOW_SPEED: tmp = 'l'; break; \ + case QH_HIGH_SPEED: tmp = 'h'; break; \ + default: tmp = '?'; break; \ + }; tmp; }) + +static inline char token_mark(struct fusbh200_hcd *fusbh200, __hc32 token) +{ + __u32 v = hc32_to_cpu(fusbh200, token); + + if (v & QTD_STS_ACTIVE) + return '*'; + if (v & QTD_STS_HALT) + return '-'; + if (!IS_SHORT_READ (v)) + return ' '; + /* tries to advance through hw_alt_next */ + return '/'; +} + +static void qh_lines ( + struct fusbh200_hcd *fusbh200, + struct fusbh200_qh *qh, + char **nextp, + unsigned *sizep +) +{ + u32 scratch; + u32 hw_curr; + struct fusbh200_qtd *td; + unsigned temp; + unsigned size = *sizep; + char *next = *nextp; + char mark; + __le32 list_end = FUSBH200_LIST_END(fusbh200); + struct fusbh200_qh_hw *hw = qh->hw; + + if (hw->hw_qtd_next == list_end) /* NEC does this */ + mark = '@'; + else + mark = token_mark(fusbh200, hw->hw_token); + if (mark == '/') { /* qh_alt_next controls qh advance? */ + if ((hw->hw_alt_next & QTD_MASK(fusbh200)) + == fusbh200->async->hw->hw_alt_next) + mark = '#'; /* blocked */ + else if (hw->hw_alt_next == list_end) + mark = '.'; /* use hw_qtd_next */ + /* else alt_next points to some other qtd */ + } + scratch = hc32_to_cpup(fusbh200, &hw->hw_info1); + hw_curr = (mark == '*') ? hc32_to_cpup(fusbh200, &hw->hw_current) : 0; + temp = scnprintf (next, size, + "qh/%p dev%d %cs ep%d %08x %08x (%08x%c %s nak%d)", + qh, scratch & 0x007f, + speed_char (scratch), + (scratch >> 8) & 0x000f, + scratch, hc32_to_cpup(fusbh200, &hw->hw_info2), + hc32_to_cpup(fusbh200, &hw->hw_token), mark, + (cpu_to_hc32(fusbh200, QTD_TOGGLE) & hw->hw_token) + ? "data1" : "data0", + (hc32_to_cpup(fusbh200, &hw->hw_alt_next) >> 1) & 0x0f); + size -= temp; + next += temp; + + /* hc may be modifying the list as we read it ... */ + list_for_each_entry(td, &qh->qtd_list, qtd_list) { + scratch = hc32_to_cpup(fusbh200, &td->hw_token); + mark = ' '; + if (hw_curr == td->qtd_dma) + mark = '*'; + else if (hw->hw_qtd_next == cpu_to_hc32(fusbh200, td->qtd_dma)) + mark = '+'; + else if (QTD_LENGTH (scratch)) { + if (td->hw_alt_next == fusbh200->async->hw->hw_alt_next) + mark = '#'; + else if (td->hw_alt_next != list_end) + mark = '/'; + } + temp = snprintf (next, size, + "\n\t%p%c%s len=%d %08x urb %p", + td, mark, ({ char *tmp; + switch ((scratch>>8)&0x03) { + case 0: tmp = "out"; break; + case 1: tmp = "in"; break; + case 2: tmp = "setup"; break; + default: tmp = "?"; break; + } tmp;}), + (scratch >> 16) & 0x7fff, + scratch, + td->urb); + if (size < temp) + temp = size; + size -= temp; + next += temp; + if (temp == size) + goto done; + } + + temp = snprintf (next, size, "\n"); + if (size < temp) + temp = size; + size -= temp; + next += temp; + +done: + *sizep = size; + *nextp = next; +} + +static ssize_t fill_async_buffer(struct debug_buffer *buf) +{ + struct usb_hcd *hcd; + struct fusbh200_hcd *fusbh200; + unsigned long flags; + unsigned temp, size; + char *next; + struct fusbh200_qh *qh; + + hcd = bus_to_hcd(buf->bus); + fusbh200 = hcd_to_fusbh200 (hcd); + next = buf->output_buf; + size = buf->alloc_size; + + *next = 0; + + /* dumps a snapshot of the async schedule. + * usually empty except for long-term bulk reads, or head. + * one QH per line, and TDs we know about + */ + spin_lock_irqsave (&fusbh200->lock, flags); + for (qh = fusbh200->async->qh_next.qh; size > 0 && qh; qh = qh->qh_next.qh) + qh_lines (fusbh200, qh, &next, &size); + if (fusbh200->async_unlink && size > 0) { + temp = scnprintf(next, size, "\nunlink =\n"); + size -= temp; + next += temp; + + for (qh = fusbh200->async_unlink; size > 0 && qh; + qh = qh->unlink_next) + qh_lines (fusbh200, qh, &next, &size); + } + spin_unlock_irqrestore (&fusbh200->lock, flags); + + return strlen(buf->output_buf); +} + +#define DBG_SCHED_LIMIT 64 +static ssize_t fill_periodic_buffer(struct debug_buffer *buf) +{ + struct usb_hcd *hcd; + struct fusbh200_hcd *fusbh200; + unsigned long flags; + union fusbh200_shadow p, *seen; + unsigned temp, size, seen_count; + char *next; + unsigned i; + __hc32 tag; + + if (!(seen = kmalloc (DBG_SCHED_LIMIT * sizeof *seen, GFP_ATOMIC))) + return 0; + seen_count = 0; + + hcd = bus_to_hcd(buf->bus); + fusbh200 = hcd_to_fusbh200 (hcd); + next = buf->output_buf; + size = buf->alloc_size; + + temp = scnprintf (next, size, "size = %d\n", fusbh200->periodic_size); + size -= temp; + next += temp; + + /* dump a snapshot of the periodic schedule. + * iso changes, interrupt usually doesn't. + */ + spin_lock_irqsave (&fusbh200->lock, flags); + for (i = 0; i < fusbh200->periodic_size; i++) { + p = fusbh200->pshadow [i]; + if (likely (!p.ptr)) + continue; + tag = Q_NEXT_TYPE(fusbh200, fusbh200->periodic [i]); + + temp = scnprintf (next, size, "%4d: ", i); + size -= temp; + next += temp; + + do { + struct fusbh200_qh_hw *hw; + + switch (hc32_to_cpu(fusbh200, tag)) { + case Q_TYPE_QH: + hw = p.qh->hw; + temp = scnprintf (next, size, " qh%d-%04x/%p", + p.qh->period, + hc32_to_cpup(fusbh200, + &hw->hw_info2) + /* uframe masks */ + & (QH_CMASK | QH_SMASK), + p.qh); + size -= temp; + next += temp; + /* don't repeat what follows this qh */ + for (temp = 0; temp < seen_count; temp++) { + if (seen [temp].ptr != p.ptr) + continue; + if (p.qh->qh_next.ptr) { + temp = scnprintf (next, size, + " ..."); + size -= temp; + next += temp; + } + break; + } + /* show more info the first time around */ + if (temp == seen_count) { + u32 scratch = hc32_to_cpup(fusbh200, + &hw->hw_info1); + struct fusbh200_qtd *qtd; + char *type = ""; + + /* count tds, get ep direction */ + temp = 0; + list_for_each_entry (qtd, + &p.qh->qtd_list, + qtd_list) { + temp++; + switch (0x03 & (hc32_to_cpu( + fusbh200, + qtd->hw_token) >> 8)) { + case 0: type = "out"; continue; + case 1: type = "in"; continue; + } + } + + temp = scnprintf (next, size, + " (%c%d ep%d%s " + "[%d/%d] q%d p%d)", + speed_char (scratch), + scratch & 0x007f, + (scratch >> 8) & 0x000f, type, + p.qh->usecs, p.qh->c_usecs, + temp, + 0x7ff & (scratch >> 16)); + + if (seen_count < DBG_SCHED_LIMIT) + seen [seen_count++].qh = p.qh; + } else + temp = 0; + tag = Q_NEXT_TYPE(fusbh200, hw->hw_next); + p = p.qh->qh_next; + break; + case Q_TYPE_FSTN: + temp = scnprintf (next, size, + " fstn-%8x/%p", p.fstn->hw_prev, + p.fstn); + tag = Q_NEXT_TYPE(fusbh200, p.fstn->hw_next); + p = p.fstn->fstn_next; + break; + case Q_TYPE_ITD: + temp = scnprintf (next, size, + " itd/%p", p.itd); + tag = Q_NEXT_TYPE(fusbh200, p.itd->hw_next); + p = p.itd->itd_next; + break; + } + size -= temp; + next += temp; + } while (p.ptr); + + temp = scnprintf (next, size, "\n"); + size -= temp; + next += temp; + } + spin_unlock_irqrestore (&fusbh200->lock, flags); + kfree (seen); + + return buf->alloc_size - size; +} +#undef DBG_SCHED_LIMIT + +static const char *rh_state_string(struct fusbh200_hcd *fusbh200) +{ + switch (fusbh200->rh_state) { + case FUSBH200_RH_HALTED: + return "halted"; + case FUSBH200_RH_SUSPENDED: + return "suspended"; + case FUSBH200_RH_RUNNING: + return "running"; + case FUSBH200_RH_STOPPING: + return "stopping"; + } + return "?"; +} + +static ssize_t fill_registers_buffer(struct debug_buffer *buf) +{ + struct usb_hcd *hcd; + struct fusbh200_hcd *fusbh200; + unsigned long flags; + unsigned temp, size, i; + char *next, scratch [80]; + static char fmt [] = "%*s\n"; + static char label [] = ""; + + hcd = bus_to_hcd(buf->bus); + fusbh200 = hcd_to_fusbh200 (hcd); + next = buf->output_buf; + size = buf->alloc_size; + + spin_lock_irqsave (&fusbh200->lock, flags); + + if (!HCD_HW_ACCESSIBLE(hcd)) { + size = scnprintf (next, size, + "bus %s, device %s\n" + "%s\n" + "SUSPENDED (no register access)\n", + hcd->self.controller->bus->name, + dev_name(hcd->self.controller), + hcd->product_desc); + goto done; + } + + /* Capability Registers */ + i = HC_VERSION(fusbh200, fusbh200_readl(fusbh200, &fusbh200->caps->hc_capbase)); + temp = scnprintf (next, size, + "bus %s, device %s\n" + "%s\n" + "EHCI %x.%02x, rh state %s\n", + hcd->self.controller->bus->name, + dev_name(hcd->self.controller), + hcd->product_desc, + i >> 8, i & 0x0ff, rh_state_string(fusbh200)); + size -= temp; + next += temp; + + // FIXME interpret both types of params + i = fusbh200_readl(fusbh200, &fusbh200->caps->hcs_params); + temp = scnprintf (next, size, "structural params 0x%08x\n", i); + size -= temp; + next += temp; + + i = fusbh200_readl(fusbh200, &fusbh200->caps->hcc_params); + temp = scnprintf (next, size, "capability params 0x%08x\n", i); + size -= temp; + next += temp; + + /* Operational Registers */ + temp = dbg_status_buf (scratch, sizeof scratch, label, + fusbh200_readl(fusbh200, &fusbh200->regs->status)); + temp = scnprintf (next, size, fmt, temp, scratch); + size -= temp; + next += temp; + + temp = dbg_command_buf (scratch, sizeof scratch, label, + fusbh200_readl(fusbh200, &fusbh200->regs->command)); + temp = scnprintf (next, size, fmt, temp, scratch); + size -= temp; + next += temp; + + temp = dbg_intr_buf (scratch, sizeof scratch, label, + fusbh200_readl(fusbh200, &fusbh200->regs->intr_enable)); + temp = scnprintf (next, size, fmt, temp, scratch); + size -= temp; + next += temp; + + temp = scnprintf (next, size, "uframe %04x\n", + fusbh200_read_frame_index(fusbh200)); + size -= temp; + next += temp; + + if (fusbh200->async_unlink) { + temp = scnprintf(next, size, "async unlink qh %p\n", + fusbh200->async_unlink); + size -= temp; + next += temp; + } + +#ifdef FUSBH200_STATS + temp = scnprintf (next, size, + "irq normal %ld err %ld iaa %ld (lost %ld)\n", + fusbh200->stats.normal, fusbh200->stats.error, fusbh200->stats.iaa, + fusbh200->stats.lost_iaa); + size -= temp; + next += temp; + + temp = scnprintf (next, size, "complete %ld unlink %ld\n", + fusbh200->stats.complete, fusbh200->stats.unlink); + size -= temp; + next += temp; +#endif + +done: + spin_unlock_irqrestore (&fusbh200->lock, flags); + + return buf->alloc_size - size; +} + +static struct debug_buffer *alloc_buffer(struct usb_bus *bus, + ssize_t (*fill_func)(struct debug_buffer *)) +{ + struct debug_buffer *buf; + + buf = kzalloc(sizeof(struct debug_buffer), GFP_KERNEL); + + if (buf) { + buf->bus = bus; + buf->fill_func = fill_func; + mutex_init(&buf->mutex); + buf->alloc_size = PAGE_SIZE; + } + + return buf; +} + +static int fill_buffer(struct debug_buffer *buf) +{ + int ret = 0; + + if (!buf->output_buf) + buf->output_buf = vmalloc(buf->alloc_size); + + if (!buf->output_buf) { + ret = -ENOMEM; + goto out; + } + + ret = buf->fill_func(buf); + + if (ret >= 0) { + buf->count = ret; + ret = 0; + } + +out: + return ret; +} + +static ssize_t debug_output(struct file *file, char __user *user_buf, + size_t len, loff_t *offset) +{ + struct debug_buffer *buf = file->private_data; + int ret = 0; + + mutex_lock(&buf->mutex); + if (buf->count == 0) { + ret = fill_buffer(buf); + if (ret != 0) { + mutex_unlock(&buf->mutex); + goto out; + } + } + mutex_unlock(&buf->mutex); + + ret = simple_read_from_buffer(user_buf, len, offset, + buf->output_buf, buf->count); + +out: + return ret; + +} + +static int debug_close(struct inode *inode, struct file *file) +{ + struct debug_buffer *buf = file->private_data; + + if (buf) { + vfree(buf->output_buf); + kfree(buf); + } + + return 0; +} +static int debug_async_open(struct inode *inode, struct file *file) +{ + file->private_data = alloc_buffer(inode->i_private, fill_async_buffer); + + return file->private_data ? 0 : -ENOMEM; +} + +static int debug_periodic_open(struct inode *inode, struct file *file) +{ + struct debug_buffer *buf; + buf = alloc_buffer(inode->i_private, fill_periodic_buffer); + if (!buf) + return -ENOMEM; + + buf->alloc_size = (sizeof(void *) == 4 ? 6 : 8)*PAGE_SIZE; + file->private_data = buf; + return 0; +} + +static int debug_registers_open(struct inode *inode, struct file *file) +{ + file->private_data = alloc_buffer(inode->i_private, + fill_registers_buffer); + + return file->private_data ? 0 : -ENOMEM; +} + +static inline void create_debug_files (struct fusbh200_hcd *fusbh200) +{ + struct usb_bus *bus = &fusbh200_to_hcd(fusbh200)->self; + + fusbh200->debug_dir = debugfs_create_dir(bus->bus_name, fusbh200_debug_root); + if (!fusbh200->debug_dir) + return; + + if (!debugfs_create_file("async", S_IRUGO, fusbh200->debug_dir, bus, + &debug_async_fops)) + goto file_error; + + if (!debugfs_create_file("periodic", S_IRUGO, fusbh200->debug_dir, bus, + &debug_periodic_fops)) + goto file_error; + + if (!debugfs_create_file("registers", S_IRUGO, fusbh200->debug_dir, bus, + &debug_registers_fops)) + goto file_error; + + return; + +file_error: + debugfs_remove_recursive(fusbh200->debug_dir); +} + +static inline void remove_debug_files (struct fusbh200_hcd *fusbh200) +{ + debugfs_remove_recursive(fusbh200->debug_dir); +} + +#endif /* STUB_DEBUG_FILES */ +/*-------------------------------------------------------------------------*/ + +/* + * 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 fusbh200_hcd *fusbh200, void __iomem *ptr, + u32 mask, u32 done, int usec) +{ + u32 result; + + do { + result = fusbh200_readl(fusbh200, 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). + * Must be called with interrupts enabled and the lock not held. + */ +static int fusbh200_halt (struct fusbh200_hcd *fusbh200) +{ + u32 temp; + + spin_lock_irq(&fusbh200->lock); + + /* disable any irqs left enabled by previous code */ + fusbh200_writel(fusbh200, 0, &fusbh200->regs->intr_enable); + + /* + * This routine gets called during probe before fusbh200->command + * has been initialized, so we can't rely on its value. + */ + fusbh200->command &= ~CMD_RUN; + temp = fusbh200_readl(fusbh200, &fusbh200->regs->command); + temp &= ~(CMD_RUN | CMD_IAAD); + fusbh200_writel(fusbh200, temp, &fusbh200->regs->command); + + spin_unlock_irq(&fusbh200->lock); + synchronize_irq(fusbh200_to_hcd(fusbh200)->irq); + + return handshake(fusbh200, &fusbh200->regs->status, + STS_HALT, STS_HALT, 16 * 125); +} + +/* + * Reset a non-running (STS_HALT == 1) controller. + * Must be called with interrupts enabled and the lock not held. + */ +static int fusbh200_reset (struct fusbh200_hcd *fusbh200) +{ + int retval; + u32 command = fusbh200_readl(fusbh200, &fusbh200->regs->command); + + /* If the EHCI debug controller is active, special care must be + * taken before and after a host controller reset */ + if (fusbh200->debug && !dbgp_reset_prep(fusbh200_to_hcd(fusbh200))) + fusbh200->debug = NULL; + + command |= CMD_RESET; + dbg_cmd (fusbh200, "reset", command); + fusbh200_writel(fusbh200, command, &fusbh200->regs->command); + fusbh200->rh_state = FUSBH200_RH_HALTED; + fusbh200->next_statechange = jiffies; + retval = handshake (fusbh200, &fusbh200->regs->command, + CMD_RESET, 0, 250 * 1000); + + if (retval) + return retval; + + if (fusbh200->debug) + dbgp_external_startup(fusbh200_to_hcd(fusbh200)); + + fusbh200->port_c_suspend = fusbh200->suspended_ports = + fusbh200->resuming_ports = 0; + return retval; +} + +/* + * Idle the controller (turn off the schedules). + * Must be called with interrupts enabled and the lock not held. + */ +static void fusbh200_quiesce (struct fusbh200_hcd *fusbh200) +{ + u32 temp; + + if (fusbh200->rh_state != FUSBH200_RH_RUNNING) + return; + + /* wait for any schedule enables/disables to take effect */ + temp = (fusbh200->command << 10) & (STS_ASS | STS_PSS); + handshake(fusbh200, &fusbh200->regs->status, STS_ASS | STS_PSS, temp, 16 * 125); + + /* then disable anything that's still active */ + spin_lock_irq(&fusbh200->lock); + fusbh200->command &= ~(CMD_ASE | CMD_PSE); + fusbh200_writel(fusbh200, fusbh200->command, &fusbh200->regs->command); + spin_unlock_irq(&fusbh200->lock); + + /* hardware can take 16 microframes to turn off ... */ + handshake(fusbh200, &fusbh200->regs->status, STS_ASS | STS_PSS, 0, 16 * 125); +} + +/*-------------------------------------------------------------------------*/ + +static void end_unlink_async(struct fusbh200_hcd *fusbh200); +static void unlink_empty_async(struct fusbh200_hcd *fusbh200); +static void fusbh200_work(struct fusbh200_hcd *fusbh200); +static void start_unlink_intr(struct fusbh200_hcd *fusbh200, struct fusbh200_qh *qh); +static void end_unlink_intr(struct fusbh200_hcd *fusbh200, struct fusbh200_qh *qh); + +/*-------------------------------------------------------------------------*/ + +/* Set a bit in the USBCMD register */ +static void fusbh200_set_command_bit(struct fusbh200_hcd *fusbh200, u32 bit) +{ + fusbh200->command |= bit; + fusbh200_writel(fusbh200, fusbh200->command, &fusbh200->regs->command); + + /* unblock posted write */ + fusbh200_readl(fusbh200, &fusbh200->regs->command); +} + +/* Clear a bit in the USBCMD register */ +static void fusbh200_clear_command_bit(struct fusbh200_hcd *fusbh200, u32 bit) +{ + fusbh200->command &= ~bit; + fusbh200_writel(fusbh200, fusbh200->command, &fusbh200->regs->command); + + /* unblock posted write */ + fusbh200_readl(fusbh200, &fusbh200->regs->command); +} + +/*-------------------------------------------------------------------------*/ + +/* + * EHCI timer support... Now using hrtimers. + * + * Lots of different events are triggered from fusbh200->hrtimer. Whenever + * the timer routine runs, it checks each possible event; events that are + * currently enabled and whose expiration time has passed get handled. + * The set of enabled events is stored as a collection of bitflags in + * fusbh200->enabled_hrtimer_events, and they are numbered in order of + * increasing delay values (ranging between 1 ms and 100 ms). + * + * Rather than implementing a sorted list or tree of all pending events, + * we keep track only of the lowest-numbered pending event, in + * fusbh200->next_hrtimer_event. Whenever fusbh200->hrtimer gets restarted, its + * expiration time is set to the timeout value for this event. + * + * As a result, events might not get handled right away; the actual delay + * could be anywhere up to twice the requested delay. This doesn't + * matter, because none of the events are especially time-critical. The + * ones that matter most all have a delay of 1 ms, so they will be + * handled after 2 ms at most, which is okay. In addition to this, we + * allow for an expiration range of 1 ms. + */ + +/* + * Delay lengths for the hrtimer event types. + * Keep this list sorted by delay length, in the same order as + * the event types indexed by enum fusbh200_hrtimer_event in fusbh200.h. + */ +static unsigned event_delays_ns[] = { + 1 * NSEC_PER_MSEC, /* FUSBH200_HRTIMER_POLL_ASS */ + 1 * NSEC_PER_MSEC, /* FUSBH200_HRTIMER_POLL_PSS */ + 1 * NSEC_PER_MSEC, /* FUSBH200_HRTIMER_POLL_DEAD */ + 1125 * NSEC_PER_USEC, /* FUSBH200_HRTIMER_UNLINK_INTR */ + 2 * NSEC_PER_MSEC, /* FUSBH200_HRTIMER_FREE_ITDS */ + 6 * NSEC_PER_MSEC, /* FUSBH200_HRTIMER_ASYNC_UNLINKS */ + 10 * NSEC_PER_MSEC, /* FUSBH200_HRTIMER_IAA_WATCHDOG */ + 10 * NSEC_PER_MSEC, /* FUSBH200_HRTIMER_DISABLE_PERIODIC */ + 15 * NSEC_PER_MSEC, /* FUSBH200_HRTIMER_DISABLE_ASYNC */ + 100 * NSEC_PER_MSEC, /* FUSBH200_HRTIMER_IO_WATCHDOG */ +}; + +/* Enable a pending hrtimer event */ +static void fusbh200_enable_event(struct fusbh200_hcd *fusbh200, unsigned event, + bool resched) +{ + ktime_t *timeout = &fusbh200->hr_timeouts[event]; + + if (resched) + *timeout = ktime_add(ktime_get(), + ktime_set(0, event_delays_ns[event])); + fusbh200->enabled_hrtimer_events |= (1 << event); + + /* Track only the lowest-numbered pending event */ + if (event < fusbh200->next_hrtimer_event) { + fusbh200->next_hrtimer_event = event; + hrtimer_start_range_ns(&fusbh200->hrtimer, *timeout, + NSEC_PER_MSEC, HRTIMER_MODE_ABS); + } +} + + +/* Poll the STS_ASS status bit; see when it agrees with CMD_ASE */ +static void fusbh200_poll_ASS(struct fusbh200_hcd *fusbh200) +{ + unsigned actual, want; + + /* Don't enable anything if the controller isn't running (e.g., died) */ + if (fusbh200->rh_state != FUSBH200_RH_RUNNING) + return; + + want = (fusbh200->command & CMD_ASE) ? STS_ASS : 0; + actual = fusbh200_readl(fusbh200, &fusbh200->regs->status) & STS_ASS; + + if (want != actual) { + + /* Poll again later, but give up after about 20 ms */ + if (fusbh200->ASS_poll_count++ < 20) { + fusbh200_enable_event(fusbh200, FUSBH200_HRTIMER_POLL_ASS, true); + return; + } + fusbh200_dbg(fusbh200, "Waited too long for the async schedule status (%x/%x), giving up\n", + want, actual); + } + fusbh200->ASS_poll_count = 0; + + /* The status is up-to-date; restart or stop the schedule as needed */ + if (want == 0) { /* Stopped */ + if (fusbh200->async_count > 0) + fusbh200_set_command_bit(fusbh200, CMD_ASE); + + } else { /* Running */ + if (fusbh200->async_count == 0) { + + /* Turn off the schedule after a while */ + fusbh200_enable_event(fusbh200, FUSBH200_HRTIMER_DISABLE_ASYNC, + true); + } + } +} + +/* Turn off the async schedule after a brief delay */ +static void fusbh200_disable_ASE(struct fusbh200_hcd *fusbh200) +{ + fusbh200_clear_command_bit(fusbh200, CMD_ASE); +} + + +/* Poll the STS_PSS status bit; see when it agrees with CMD_PSE */ +static void fusbh200_poll_PSS(struct fusbh200_hcd *fusbh200) +{ + unsigned actual, want; + + /* Don't do anything if the controller isn't running (e.g., died) */ + if (fusbh200->rh_state != FUSBH200_RH_RUNNING) + return; + + want = (fusbh200->command & CMD_PSE) ? STS_PSS : 0; + actual = fusbh200_readl(fusbh200, &fusbh200->regs->status) & STS_PSS; + + if (want != actual) { + + /* Poll again later, but give up after about 20 ms */ + if (fusbh200->PSS_poll_count++ < 20) { + fusbh200_enable_event(fusbh200, FUSBH200_HRTIMER_POLL_PSS, true); + return; + } + fusbh200_dbg(fusbh200, "Waited too long for the periodic schedule status (%x/%x), giving up\n", + want, actual); + } + fusbh200->PSS_poll_count = 0; + + /* The status is up-to-date; restart or stop the schedule as needed */ + if (want == 0) { /* Stopped */ + if (fusbh200->periodic_count > 0) + fusbh200_set_command_bit(fusbh200, CMD_PSE); + + } else { /* Running */ + if (fusbh200->periodic_count == 0) { + + /* Turn off the schedule after a while */ + fusbh200_enable_event(fusbh200, FUSBH200_HRTIMER_DISABLE_PERIODIC, + true); + } + } +} + +/* Turn off the periodic schedule after a brief delay */ +static void fusbh200_disable_PSE(struct fusbh200_hcd *fusbh200) +{ + fusbh200_clear_command_bit(fusbh200, CMD_PSE); +} + + +/* Poll the STS_HALT status bit; see when a dead controller stops */ +static void fusbh200_handle_controller_death(struct fusbh200_hcd *fusbh200) +{ + if (!(fusbh200_readl(fusbh200, &fusbh200->regs->status) & STS_HALT)) { + + /* Give up after a few milliseconds */ + if (fusbh200->died_poll_count++ < 5) { + /* Try again later */ + fusbh200_enable_event(fusbh200, FUSBH200_HRTIMER_POLL_DEAD, true); + return; + } + fusbh200_warn(fusbh200, "Waited too long for the controller to stop, giving up\n"); + } + + /* Clean up the mess */ + fusbh200->rh_state = FUSBH200_RH_HALTED; + fusbh200_writel(fusbh200, 0, &fusbh200->regs->intr_enable); + fusbh200_work(fusbh200); + end_unlink_async(fusbh200); + + /* Not in process context, so don't try to reset the controller */ +} + + +/* Handle unlinked interrupt QHs once they are gone from the hardware */ +static void fusbh200_handle_intr_unlinks(struct fusbh200_hcd *fusbh200) +{ + bool stopped = (fusbh200->rh_state < FUSBH200_RH_RUNNING); + + /* + * Process all the QHs on the intr_unlink list that were added + * before the current unlink cycle began. The list is in + * temporal order, so stop when we reach the first entry in the + * current cycle. But if the root hub isn't running then + * process all the QHs on the list. + */ + fusbh200->intr_unlinking = true; + while (fusbh200->intr_unlink) { + struct fusbh200_qh *qh = fusbh200->intr_unlink; + + if (!stopped && qh->unlink_cycle == fusbh200->intr_unlink_cycle) + break; + fusbh200->intr_unlink = qh->unlink_next; + qh->unlink_next = NULL; + end_unlink_intr(fusbh200, qh); + } + + /* Handle remaining entries later */ + if (fusbh200->intr_unlink) { + fusbh200_enable_event(fusbh200, FUSBH200_HRTIMER_UNLINK_INTR, true); + ++fusbh200->intr_unlink_cycle; + } + fusbh200->intr_unlinking = false; +} + + +/* Start another free-iTDs/siTDs cycle */ +static void start_free_itds(struct fusbh200_hcd *fusbh200) +{ + if (!(fusbh200->enabled_hrtimer_events & BIT(FUSBH200_HRTIMER_FREE_ITDS))) { + fusbh200->last_itd_to_free = list_entry( + fusbh200->cached_itd_list.prev, + struct fusbh200_itd, itd_list); + fusbh200_enable_event(fusbh200, FUSBH200_HRTIMER_FREE_ITDS, true); + } +} + +/* Wait for controller to stop using old iTDs and siTDs */ +static void end_free_itds(struct fusbh200_hcd *fusbh200) +{ + struct fusbh200_itd *itd, *n; + + if (fusbh200->rh_state < FUSBH200_RH_RUNNING) { + fusbh200->last_itd_to_free = NULL; + } + + list_for_each_entry_safe(itd, n, &fusbh200->cached_itd_list, itd_list) { + list_del(&itd->itd_list); + dma_pool_free(fusbh200->itd_pool, itd, itd->itd_dma); + if (itd == fusbh200->last_itd_to_free) + break; + } + + if (!list_empty(&fusbh200->cached_itd_list)) + start_free_itds(fusbh200); +} + + +/* Handle lost (or very late) IAA interrupts */ +static void fusbh200_iaa_watchdog(struct fusbh200_hcd *fusbh200) +{ + if (fusbh200->rh_state != FUSBH200_RH_RUNNING) + return; + + /* + * Lost IAA irqs wedge things badly; seen first with a vt8235. + * So we need this watchdog, but must protect it against both + * (a) SMP races against real IAA firing and retriggering, and + * (b) clean HC shutdown, when IAA watchdog was pending. + */ + if (fusbh200->async_iaa) { + u32 cmd, status; + + /* If we get here, IAA is *REALLY* late. It's barely + * conceivable that the system is so busy that CMD_IAAD + * is still legitimately set, so let's be sure it's + * clear before we read STS_IAA. (The HC should clear + * CMD_IAAD when it sets STS_IAA.) + */ + cmd = fusbh200_readl(fusbh200, &fusbh200->regs->command); + + /* + * If IAA is set here it either legitimately triggered + * after the watchdog timer expired (_way_ late, so we'll + * still count it as lost) ... or a silicon erratum: + * - VIA seems to set IAA without triggering the IRQ; + * - IAAD potentially cleared without setting IAA. + */ + status = fusbh200_readl(fusbh200, &fusbh200->regs->status); + if ((status & STS_IAA) || !(cmd & CMD_IAAD)) { + COUNT(fusbh200->stats.lost_iaa); + fusbh200_writel(fusbh200, STS_IAA, &fusbh200->regs->status); + } + + fusbh200_vdbg(fusbh200, "IAA watchdog: status %x cmd %x\n", + status, cmd); + end_unlink_async(fusbh200); + } +} + + +/* Enable the I/O watchdog, if appropriate */ +static void turn_on_io_watchdog(struct fusbh200_hcd *fusbh200) +{ + /* Not needed if the controller isn't running or it's already enabled */ + if (fusbh200->rh_state != FUSBH200_RH_RUNNING || + (fusbh200->enabled_hrtimer_events & + BIT(FUSBH200_HRTIMER_IO_WATCHDOG))) + return; + + /* + * Isochronous transfers always need the watchdog. + * For other sorts we use it only if the flag is set. + */ + if (fusbh200->isoc_count > 0 || (fusbh200->need_io_watchdog && + fusbh200->async_count + fusbh200->intr_count > 0)) + fusbh200_enable_event(fusbh200, FUSBH200_HRTIMER_IO_WATCHDOG, true); +} + + +/* + * Handler functions for the hrtimer event types. + * Keep this array in the same order as the event types indexed by + * enum fusbh200_hrtimer_event in fusbh200.h. + */ +static void (*event_handlers[])(struct fusbh200_hcd *) = { + fusbh200_poll_ASS, /* FUSBH200_HRTIMER_POLL_ASS */ + fusbh200_poll_PSS, /* FUSBH200_HRTIMER_POLL_PSS */ + fusbh200_handle_controller_death, /* FUSBH200_HRTIMER_POLL_DEAD */ + fusbh200_handle_intr_unlinks, /* FUSBH200_HRTIMER_UNLINK_INTR */ + end_free_itds, /* FUSBH200_HRTIMER_FREE_ITDS */ + unlink_empty_async, /* FUSBH200_HRTIMER_ASYNC_UNLINKS */ + fusbh200_iaa_watchdog, /* FUSBH200_HRTIMER_IAA_WATCHDOG */ + fusbh200_disable_PSE, /* FUSBH200_HRTIMER_DISABLE_PERIODIC */ + fusbh200_disable_ASE, /* FUSBH200_HRTIMER_DISABLE_ASYNC */ + fusbh200_work, /* FUSBH200_HRTIMER_IO_WATCHDOG */ +}; + +static enum hrtimer_restart fusbh200_hrtimer_func(struct hrtimer *t) +{ + struct fusbh200_hcd *fusbh200 = container_of(t, struct fusbh200_hcd, hrtimer); + ktime_t now; + unsigned long events; + unsigned long flags; + unsigned e; + + spin_lock_irqsave(&fusbh200->lock, flags); + + events = fusbh200->enabled_hrtimer_events; + fusbh200->enabled_hrtimer_events = 0; + fusbh200->next_hrtimer_event = FUSBH200_HRTIMER_NO_EVENT; + + /* + * Check each pending event. If its time has expired, handle + * the event; otherwise re-enable it. + */ + now = ktime_get(); + for_each_set_bit(e, &events, FUSBH200_HRTIMER_NUM_EVENTS) { + if (now.tv64 >= fusbh200->hr_timeouts[e].tv64) + event_handlers[e](fusbh200); + else + fusbh200_enable_event(fusbh200, e, false); + } + + spin_unlock_irqrestore(&fusbh200->lock, flags); + return HRTIMER_NORESTART; +} + +/*-------------------------------------------------------------------------*/ + +#define fusbh200_bus_suspend NULL +#define fusbh200_bus_resume NULL + +/*-------------------------------------------------------------------------*/ + +static int check_reset_complete ( + struct fusbh200_hcd *fusbh200, + int index, + u32 __iomem *status_reg, + int port_status +) { + if (!(port_status & PORT_CONNECT)) + return port_status; + + /* if reset finished and it's still not enabled -- handoff */ + if (!(port_status & PORT_PE)) { + /* with integrated TT, there's nobody to hand it to! */ + fusbh200_dbg (fusbh200, + "Failed to enable port %d on root hub TT\n", + index+1); + return port_status; + } else { + fusbh200_dbg(fusbh200, "port %d reset complete, port enabled\n", + index + 1); + } + + return port_status; +} + +/*-------------------------------------------------------------------------*/ + + +/* build "status change" packet (one or two bytes) from HC registers */ + +static int +fusbh200_hub_status_data (struct usb_hcd *hcd, char *buf) +{ + struct fusbh200_hcd *fusbh200 = hcd_to_fusbh200 (hcd); + u32 temp, status; + u32 mask; + int retval = 1; + unsigned long flags; + + /* init status to no-changes */ + buf [0] = 0; + + /* Inform the core about resumes-in-progress by returning + * a non-zero value even if there are no status changes. + */ + status = fusbh200->resuming_ports; + + mask = PORT_CSC | PORT_PEC; + // PORT_RESUME from hardware ~= PORT_STAT_C_SUSPEND + + /* no hub change reports (bit 0) for now (power, ...) */ + + /* port N changes (bit N)? */ + spin_lock_irqsave (&fusbh200->lock, flags); + + temp = fusbh200_readl(fusbh200, &fusbh200->regs->port_status); + + /* + * Return status information even for ports with OWNER set. + * Otherwise khubd wouldn't see the disconnect event when a + * high-speed device is switched over to the companion + * controller by the user. + */ + + if ((temp & mask) != 0 || test_bit(0, &fusbh200->port_c_suspend) + || (fusbh200->reset_done[0] && time_after_eq( + jiffies, fusbh200->reset_done[0]))) { + buf [0] |= 1 << 1; + status = STS_PCD; + } + /* FIXME autosuspend idle root hubs */ + spin_unlock_irqrestore (&fusbh200->lock, flags); + return status ? retval : 0; +} + +/*-------------------------------------------------------------------------*/ + +static void +fusbh200_hub_descriptor ( + struct fusbh200_hcd *fusbh200, + struct usb_hub_descriptor *desc +) { + int ports = HCS_N_PORTS (fusbh200->hcs_params); + u16 temp; + + desc->bDescriptorType = 0x29; + desc->bPwrOn2PwrGood = 10; /* fusbh200 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->u.hs.DeviceRemovable[0], 0, temp); + memset(&desc->u.hs.DeviceRemovable[temp], 0xff, temp); + + temp = 0x0008; /* per-port overcurrent reporting */ + temp |= 0x0002; /* no power switching */ + desc->wHubCharacteristics = cpu_to_le16(temp); +} + +/*-------------------------------------------------------------------------*/ + +static int fusbh200_hub_control ( + struct usb_hcd *hcd, + u16 typeReq, + u16 wValue, + u16 wIndex, + char *buf, + u16 wLength +) { + struct fusbh200_hcd *fusbh200 = hcd_to_fusbh200 (hcd); + int ports = HCS_N_PORTS (fusbh200->hcs_params); + u32 __iomem *status_reg = &fusbh200->regs->port_status; + u32 temp, temp1, 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 (&fusbh200->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 = fusbh200_readl(fusbh200, status_reg); + temp &= ~PORT_RWC_BITS; + + /* + * 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_STAT_C_CONNECTION). + */ + + switch (wValue) { + case USB_PORT_FEAT_ENABLE: + fusbh200_writel(fusbh200, temp & ~PORT_PE, status_reg); + break; + case USB_PORT_FEAT_C_ENABLE: + fusbh200_writel(fusbh200, temp | PORT_PEC, status_reg); + break; + case USB_PORT_FEAT_SUSPEND: + if (temp & PORT_RESET) + goto error; + if (!(temp & PORT_SUSPEND)) + break; + if ((temp & PORT_PE) == 0) + goto error; + + /* resume signaling for 20 msec */ + fusbh200_writel(fusbh200, temp | PORT_RESUME, status_reg); + fusbh200->reset_done[wIndex] = jiffies + + msecs_to_jiffies(20); + break; + case USB_PORT_FEAT_C_SUSPEND: + clear_bit(wIndex, &fusbh200->port_c_suspend); + break; + case USB_PORT_FEAT_C_CONNECTION: + fusbh200_writel(fusbh200, temp | PORT_CSC, status_reg); + break; + case USB_PORT_FEAT_C_OVER_CURRENT: + fusbh200_writel(fusbh200, temp | BMISR_OVC, &fusbh200->regs->bmisr); + break; + case USB_PORT_FEAT_C_RESET: + /* GetPortStatus clears reset */ + break; + default: + goto error; + } + fusbh200_readl(fusbh200, &fusbh200->regs->command); /* unblock posted write */ + break; + case GetHubDescriptor: + fusbh200_hub_descriptor (fusbh200, (struct usb_hub_descriptor *) + buf); + break; + case GetHubStatus: + /* no hub-wide feature/status flags */ + memset (buf, 0, 4); + //cpu_to_le32s ((u32 *) buf); + break; + case GetPortStatus: + if (!wIndex || wIndex > ports) + goto error; + wIndex--; + status = 0; + temp = fusbh200_readl(fusbh200, status_reg); + + // wPortChange bits + if (temp & PORT_CSC) + status |= USB_PORT_STAT_C_CONNECTION << 16; + if (temp & PORT_PEC) + status |= USB_PORT_STAT_C_ENABLE << 16; + + temp1 = fusbh200_readl(fusbh200, &fusbh200->regs->bmisr); + if (temp1 & BMISR_OVC) + status |= USB_PORT_STAT_C_OVERCURRENT << 16; + + /* whoever resumes must GetPortStatus to complete it!! */ + if (temp & PORT_RESUME) { + + /* Remote Wakeup received? */ + if (!fusbh200->reset_done[wIndex]) { + /* resume signaling for 20 msec */ + fusbh200->reset_done[wIndex] = jiffies + + msecs_to_jiffies(20); + /* check the port again */ + mod_timer(&fusbh200_to_hcd(fusbh200)->rh_timer, + fusbh200->reset_done[wIndex]); + } + + /* resume completed? */ + else if (time_after_eq(jiffies, + fusbh200->reset_done[wIndex])) { + clear_bit(wIndex, &fusbh200->suspended_ports); + set_bit(wIndex, &fusbh200->port_c_suspend); + fusbh200->reset_done[wIndex] = 0; + + /* stop resume signaling */ + temp = fusbh200_readl(fusbh200, status_reg); + fusbh200_writel(fusbh200, + temp & ~(PORT_RWC_BITS | PORT_RESUME), + status_reg); + clear_bit(wIndex, &fusbh200->resuming_ports); + retval = handshake(fusbh200, status_reg, + PORT_RESUME, 0, 2000 /* 2msec */); + if (retval != 0) { + fusbh200_err(fusbh200, + "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, + fusbh200->reset_done[wIndex])) { + status |= USB_PORT_STAT_C_RESET << 16; + fusbh200->reset_done [wIndex] = 0; + clear_bit(wIndex, &fusbh200->resuming_ports); + + /* force reset to complete */ + fusbh200_writel(fusbh200, 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(fusbh200, status_reg, + PORT_RESET, 0, 1000); + if (retval != 0) { + fusbh200_err (fusbh200, "port %d reset error %d\n", + wIndex + 1, retval); + goto error; + } + + /* see what we found out */ + temp = check_reset_complete (fusbh200, wIndex, status_reg, + fusbh200_readl(fusbh200, status_reg)); + } + + if (!(temp & (PORT_RESUME|PORT_RESET))) { + fusbh200->reset_done[wIndex] = 0; + clear_bit(wIndex, &fusbh200->resuming_ports); + } + + /* transfer dedicated ports to the companion hc */ + if ((temp & PORT_CONNECT) && + test_bit(wIndex, &fusbh200->companion_ports)) { + temp &= ~PORT_RWC_BITS; + fusbh200_writel(fusbh200, temp, status_reg); + fusbh200_dbg(fusbh200, "port %d --> companion\n", wIndex + 1); + temp = fusbh200_readl(fusbh200, 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 |= USB_PORT_STAT_CONNECTION; + status |= fusbh200_port_speed(fusbh200, temp); + } + if (temp & PORT_PE) + status |= USB_PORT_STAT_ENABLE; + + /* maybe the port was unsuspended without our knowledge */ + if (temp & (PORT_SUSPEND|PORT_RESUME)) { + status |= USB_PORT_STAT_SUSPEND; + } else if (test_bit(wIndex, &fusbh200->suspended_ports)) { + clear_bit(wIndex, &fusbh200->suspended_ports); + clear_bit(wIndex, &fusbh200->resuming_ports); + fusbh200->reset_done[wIndex] = 0; + if (temp & PORT_PE) + set_bit(wIndex, &fusbh200->port_c_suspend); + } + + temp1 = fusbh200_readl(fusbh200, &fusbh200->regs->bmisr); + if (temp1 & BMISR_OVC) + status |= USB_PORT_STAT_OVERCURRENT; + if (temp & PORT_RESET) + status |= USB_PORT_STAT_RESET; + if (test_bit(wIndex, &fusbh200->port_c_suspend)) + status |= USB_PORT_STAT_C_SUSPEND << 16; + +#ifndef VERBOSE_DEBUG + if (status & ~0xffff) /* only if wPortChange is interesting */ +#endif + dbg_port (fusbh200, "GetStatus", wIndex + 1, temp); + put_unaligned_le32(status, 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 = fusbh200_readl(fusbh200, status_reg); + temp &= ~PORT_RWC_BITS; + switch (wValue) { + case USB_PORT_FEAT_SUSPEND: + if ((temp & PORT_PE) == 0 + || (temp & PORT_RESET) != 0) + goto error; + + /* After above check the port must be connected. + * Set appropriate bit thus could put phy into low power + * mode if we have hostpc feature + */ + fusbh200_writel(fusbh200, temp | PORT_SUSPEND, status_reg); + set_bit(wIndex, &fusbh200->suspended_ports); + 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. + */ + fusbh200_vdbg (fusbh200, "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 + */ + fusbh200->reset_done [wIndex] = jiffies + + msecs_to_jiffies (50); + fusbh200_writel(fusbh200, 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; + spin_unlock_irqrestore(&fusbh200->lock, flags); + fusbh200_quiesce(fusbh200); + spin_lock_irqsave(&fusbh200->lock, flags); + + /* Put all enabled ports into suspend */ + temp = fusbh200_readl(fusbh200, status_reg) & ~PORT_RWC_BITS; + if (temp & PORT_PE) + fusbh200_writel(fusbh200, temp | PORT_SUSPEND, + status_reg); + + spin_unlock_irqrestore(&fusbh200->lock, flags); + fusbh200_halt(fusbh200); + spin_lock_irqsave(&fusbh200->lock, flags); + + temp = fusbh200_readl(fusbh200, status_reg); + temp |= selector << 16; + fusbh200_writel(fusbh200, temp, status_reg); + break; + + default: + goto error; + } + fusbh200_readl(fusbh200, &fusbh200->regs->command); /* unblock posted writes */ + break; + + default: +error: + /* "stall" on error */ + retval = -EPIPE; + } + spin_unlock_irqrestore (&fusbh200->lock, flags); + return retval; +} + +static void __maybe_unused fusbh200_relinquish_port(struct usb_hcd *hcd, + int portnum) +{ + return; +} + +static int __maybe_unused fusbh200_port_handed_over(struct usb_hcd *hcd, + int portnum) +{ + return 0; +} +/*-------------------------------------------------------------------------*/ +/* + * There's basically three types of memory: + * - data used only by the HCD ... kmalloc is fine + * - async and periodic schedules, shared by HC and HCD ... these + * need to use dma_pool or dma_alloc_coherent + * - driver buffers, read/written by HC ... single shot DMA mapped + * + * There's also "register" data (e.g. PCI or SOC), which is memory mapped. + * No memory seen by this driver is pageable. + */ + +/*-------------------------------------------------------------------------*/ + +/* Allocate the key transfer structures from the previously allocated pool */ + +static inline void fusbh200_qtd_init(struct fusbh200_hcd *fusbh200, struct fusbh200_qtd *qtd, + dma_addr_t dma) +{ + memset (qtd, 0, sizeof *qtd); + qtd->qtd_dma = dma; + qtd->hw_token = cpu_to_hc32(fusbh200, QTD_STS_HALT); + qtd->hw_next = FUSBH200_LIST_END(fusbh200); + qtd->hw_alt_next = FUSBH200_LIST_END(fusbh200); + INIT_LIST_HEAD (&qtd->qtd_list); +} + +static struct fusbh200_qtd *fusbh200_qtd_alloc (struct fusbh200_hcd *fusbh200, gfp_t flags) +{ + struct fusbh200_qtd *qtd; + dma_addr_t dma; + + qtd = dma_pool_alloc (fusbh200->qtd_pool, flags, &dma); + if (qtd != NULL) { + fusbh200_qtd_init(fusbh200, qtd, dma); + } + return qtd; +} + +static inline void fusbh200_qtd_free (struct fusbh200_hcd *fusbh200, struct fusbh200_qtd *qtd) +{ + dma_pool_free (fusbh200->qtd_pool, qtd, qtd->qtd_dma); +} + + +static void qh_destroy(struct fusbh200_hcd *fusbh200, struct fusbh200_qh *qh) +{ + /* clean qtds first, and know this is not linked */ + if (!list_empty (&qh->qtd_list) || qh->qh_next.ptr) { + fusbh200_dbg (fusbh200, "unused qh not empty!\n"); + BUG (); + } + if (qh->dummy) + fusbh200_qtd_free (fusbh200, qh->dummy); + dma_pool_free(fusbh200->qh_pool, qh->hw, qh->qh_dma); + kfree(qh); +} + +static struct fusbh200_qh *fusbh200_qh_alloc (struct fusbh200_hcd *fusbh200, gfp_t flags) +{ + struct fusbh200_qh *qh; + dma_addr_t dma; + + qh = kzalloc(sizeof *qh, GFP_ATOMIC); + if (!qh) + goto done; + qh->hw = (struct fusbh200_qh_hw *) + dma_pool_alloc(fusbh200->qh_pool, flags, &dma); + if (!qh->hw) + goto fail; + memset(qh->hw, 0, sizeof *qh->hw); + qh->qh_dma = dma; + // INIT_LIST_HEAD (&qh->qh_list); + INIT_LIST_HEAD (&qh->qtd_list); + + /* dummy td enables safe urb queuing */ + qh->dummy = fusbh200_qtd_alloc (fusbh200, flags); + if (qh->dummy == NULL) { + fusbh200_dbg (fusbh200, "no dummy td\n"); + goto fail1; + } +done: + return qh; +fail1: + dma_pool_free(fusbh200->qh_pool, qh->hw, qh->qh_dma); +fail: + kfree(qh); + return NULL; +} + +/*-------------------------------------------------------------------------*/ + +/* 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 fusbh200_mem_cleanup (struct fusbh200_hcd *fusbh200) +{ + if (fusbh200->async) + qh_destroy(fusbh200, fusbh200->async); + fusbh200->async = NULL; + + if (fusbh200->dummy) + qh_destroy(fusbh200, fusbh200->dummy); + fusbh200->dummy = NULL; + + /* DMA consistent memory and pools */ + if (fusbh200->qtd_pool) + dma_pool_destroy (fusbh200->qtd_pool); + fusbh200->qtd_pool = NULL; + + if (fusbh200->qh_pool) { + dma_pool_destroy (fusbh200->qh_pool); + fusbh200->qh_pool = NULL; + } + + if (fusbh200->itd_pool) + dma_pool_destroy (fusbh200->itd_pool); + fusbh200->itd_pool = NULL; + + if (fusbh200->periodic) + dma_free_coherent (fusbh200_to_hcd(fusbh200)->self.controller, + fusbh200->periodic_size * sizeof (u32), + fusbh200->periodic, fusbh200->periodic_dma); + fusbh200->periodic = NULL; + + /* shadow periodic table */ + kfree(fusbh200->pshadow); + fusbh200->pshadow = NULL; +} + +/* remember to add cleanup code (above) if you add anything here */ +static int fusbh200_mem_init (struct fusbh200_hcd *fusbh200, gfp_t flags) +{ + int i; + + /* QTDs for control/bulk/intr transfers */ + fusbh200->qtd_pool = dma_pool_create ("fusbh200_qtd", + fusbh200_to_hcd(fusbh200)->self.controller, + sizeof (struct fusbh200_qtd), + 32 /* byte alignment (for hw parts) */, + 4096 /* can't cross 4K */); + if (!fusbh200->qtd_pool) { + goto fail; + } + + /* QHs for control/bulk/intr transfers */ + fusbh200->qh_pool = dma_pool_create ("fusbh200_qh", + fusbh200_to_hcd(fusbh200)->self.controller, + sizeof(struct fusbh200_qh_hw), + 32 /* byte alignment (for hw parts) */, + 4096 /* can't cross 4K */); + if (!fusbh200->qh_pool) { + goto fail; + } + fusbh200->async = fusbh200_qh_alloc (fusbh200, flags); + if (!fusbh200->async) { + goto fail; + } + + /* ITD for high speed ISO transfers */ + fusbh200->itd_pool = dma_pool_create ("fusbh200_itd", + fusbh200_to_hcd(fusbh200)->self.controller, + sizeof (struct fusbh200_itd), + 64 /* byte alignment (for hw parts) */, + 4096 /* can't cross 4K */); + if (!fusbh200->itd_pool) { + goto fail; + } + + /* Hardware periodic table */ + fusbh200->periodic = (__le32 *) + dma_alloc_coherent (fusbh200_to_hcd(fusbh200)->self.controller, + fusbh200->periodic_size * sizeof(__le32), + &fusbh200->periodic_dma, 0); + if (fusbh200->periodic == NULL) { + goto fail; + } + + for (i = 0; i < fusbh200->periodic_size; i++) + fusbh200->periodic[i] = FUSBH200_LIST_END(fusbh200); + + /* software shadow of hardware table */ + fusbh200->pshadow = kcalloc(fusbh200->periodic_size, sizeof(void *), flags); + if (fusbh200->pshadow != NULL) + return 0; + +fail: + fusbh200_dbg (fusbh200, "couldn't init memory\n"); + fusbh200_mem_cleanup (fusbh200); + return -ENOMEM; +} +/*-------------------------------------------------------------------------*/ +/* + * EHCI hardware queue manipulation ... the core. QH/QTD manipulation. + * + * Control, bulk, and interrupt traffic all use "qh" lists. They list "qtd" + * entries describing USB transactions, max 16-20kB/entry (with 4kB-aligned + * buffers needed for the larger number). We use one QH per endpoint, queue + * multiple urbs (all three types) per endpoint. URBs may need several qtds. + * + * ISO traffic uses "ISO TD" (itd) records, and (along with + * interrupts) needs careful scheduling. Performance improvements can be + * an ongoing challenge. That's in "ehci-sched.c". + * + * USB 1.1 devices are handled (a) by "companion" OHCI or UHCI root hubs, + * or otherwise through transaction translators (TTs) in USB 2.0 hubs using + * (b) special fields in qh entries or (c) split iso entries. TTs will + * buffer low/full speed data so the host collects it at high speed. + */ + +/*-------------------------------------------------------------------------*/ + +/* fill a qtd, returning how much of the buffer we were able to queue up */ + +static int +qtd_fill(struct fusbh200_hcd *fusbh200, struct fusbh200_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_hc32(fusbh200, (u32)addr); + qtd->hw_buf_hi[0] = cpu_to_hc32(fusbh200, (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_hc32(fusbh200, (u32)addr); + qtd->hw_buf_hi[i] = cpu_to_hc32(fusbh200, + (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_hc32(fusbh200, (count << 16) | token); + qtd->length = count; + + return count; +} + +/*-------------------------------------------------------------------------*/ + +static inline void +qh_update (struct fusbh200_hcd *fusbh200, struct fusbh200_qh *qh, struct fusbh200_qtd *qtd) +{ + struct fusbh200_qh_hw *hw = qh->hw; + + /* writes to an active overlay are unsafe */ + BUG_ON(qh->qh_state != QH_STATE_IDLE); + + hw->hw_qtd_next = QTD_NEXT(fusbh200, qtd->qtd_dma); + hw->hw_alt_next = FUSBH200_LIST_END(fusbh200); + + /* 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 (!(hw->hw_info1 & cpu_to_hc32(fusbh200, QH_TOGGLE_CTL))) { + unsigned is_out, epnum; + + is_out = qh->is_out; + epnum = (hc32_to_cpup(fusbh200, &hw->hw_info1) >> 8) & 0x0f; + if (unlikely (!usb_gettoggle (qh->dev, epnum, is_out))) { + hw->hw_token &= ~cpu_to_hc32(fusbh200, QTD_TOGGLE); + usb_settoggle (qh->dev, epnum, is_out, 1); + } + } + + hw->hw_token &= cpu_to_hc32(fusbh200, 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 fusbh200_hcd *fusbh200, struct fusbh200_qh *qh) +{ + struct fusbh200_qtd *qtd; + + if (list_empty (&qh->qtd_list)) + qtd = qh->dummy; + else { + qtd = list_entry (qh->qtd_list.next, + struct fusbh200_qtd, qtd_list); + /* + * first qtd may already be partially processed. + * If we come here during unlink, the QH overlay region + * might have reference to the just unlinked qtd. The + * qtd is updated in qh_completions(). Update the QH + * overlay here. + */ + if (cpu_to_hc32(fusbh200, qtd->qtd_dma) == qh->hw->hw_current) { + qh->hw->hw_qtd_next = qtd->hw_next; + qtd = NULL; + } + } + + if (qtd) + qh_update (fusbh200, qh, qtd); +} + +/*-------------------------------------------------------------------------*/ + +static void qh_link_async(struct fusbh200_hcd *fusbh200, struct fusbh200_qh *qh); + +static void fusbh200_clear_tt_buffer_complete(struct usb_hcd *hcd, + struct usb_host_endpoint *ep) +{ + struct fusbh200_hcd *fusbh200 = hcd_to_fusbh200(hcd); + struct fusbh200_qh *qh = ep->hcpriv; + unsigned long flags; + + spin_lock_irqsave(&fusbh200->lock, flags); + qh->clearing_tt = 0; + if (qh->qh_state == QH_STATE_IDLE && !list_empty(&qh->qtd_list) + && fusbh200->rh_state == FUSBH200_RH_RUNNING) + qh_link_async(fusbh200, qh); + spin_unlock_irqrestore(&fusbh200->lock, flags); +} + +static void fusbh200_clear_tt_buffer(struct fusbh200_hcd *fusbh200, struct fusbh200_qh *qh, + struct urb *urb, u32 token) +{ + + /* If an async split transaction gets an error or is unlinked, + * the TT buffer may be left in an indeterminate state. We + * have to clear the TT buffer. + * + * Note: this routine is never called for Isochronous transfers. + */ + if (urb->dev->tt && !usb_pipeint(urb->pipe) && !qh->clearing_tt) { +#ifdef DEBUG + struct usb_device *tt = urb->dev->tt->hub; + dev_dbg(&tt->dev, + "clear tt buffer port %d, a%d ep%d t%08x\n", + urb->dev->ttport, urb->dev->devnum, + usb_pipeendpoint(urb->pipe), token); +#endif /* DEBUG */ + if (urb->dev->tt->hub != + fusbh200_to_hcd(fusbh200)->self.root_hub) { + if (usb_hub_clear_tt_buffer(urb) == 0) + qh->clearing_tt = 1; + } + } +} + +static int qtd_copy_status ( + struct fusbh200_hcd *fusbh200, + struct urb *urb, + size_t length, + u32 token +) +{ + int status = -EINPROGRESS; + + /* 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->unlinked)) + return status; + + /* force cleanup after short read; not always an error */ + if (unlikely (IS_SHORT_READ (token))) + 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 */ + status = -EOVERFLOW; + /* CERR nonzero + halt --> stall */ + } else if (QTD_CERR(token)) { + status = -EPIPE; + + /* In theory, more than one of the following bits can be set + * since they are sticky and the transaction is retried. + * Which to test first is rather arbitrary. + */ + } else if (token & QTD_STS_MMF) { + /* fs/ls interrupt xfer missed the complete-split */ + status = -EPROTO; + } else if (token & QTD_STS_DBE) { + 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 */ + fusbh200_dbg(fusbh200, "devpath %s ep%d%s 3strikes\n", + urb->dev->devpath, + usb_pipeendpoint(urb->pipe), + usb_pipein(urb->pipe) ? "in" : "out"); + status = -EPROTO; + } else { /* unknown */ + status = -EPROTO; + } + + fusbh200_vdbg (fusbh200, + "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, status); + } + + return status; +} + +static void +fusbh200_urb_done(struct fusbh200_hcd *fusbh200, struct urb *urb, int status) +__releases(fusbh200->lock) +__acquires(fusbh200->lock) +{ + if (likely (urb->hcpriv != NULL)) { + struct fusbh200_qh *qh = (struct fusbh200_qh *) urb->hcpriv; + + /* S-mask in a QH means it's an interrupt urb */ + if ((qh->hw->hw_info2 & cpu_to_hc32(fusbh200, QH_SMASK)) != 0) { + + /* ... update hc-wide periodic stats (for usbfs) */ + fusbh200_to_hcd(fusbh200)->self.bandwidth_int_reqs--; + } + } + + if (unlikely(urb->unlinked)) { + COUNT(fusbh200->stats.unlink); + } else { + /* report non-error and short read status as zero */ + if (status == -EINPROGRESS || status == -EREMOTEIO) + status = 0; + COUNT(fusbh200->stats.complete); + } + +#ifdef FUSBH200_URB_TRACE + fusbh200_dbg (fusbh200, + "%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", + status, + urb->actual_length, urb->transfer_buffer_length); +#endif + + /* complete() can reenter this HCD */ + usb_hcd_unlink_urb_from_ep(fusbh200_to_hcd(fusbh200), urb); + spin_unlock (&fusbh200->lock); + usb_hcd_giveback_urb(fusbh200_to_hcd(fusbh200), urb, status); + spin_lock (&fusbh200->lock); +} + +static int qh_schedule (struct fusbh200_hcd *fusbh200, struct fusbh200_qh *qh); + +/* + * 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 fusbh200_hcd *fusbh200, struct fusbh200_qh *qh) +{ + struct fusbh200_qtd *last, *end = qh->dummy; + struct list_head *entry, *tmp; + int last_status; + int stopped; + unsigned count = 0; + u8 state; + struct fusbh200_qh_hw *hw = qh->hw; + + 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. + * + * It's a bug for qh->qh_state to be anything other than + * QH_STATE_IDLE, unless our caller is scan_async() or + * scan_intr(). + */ + state = qh->qh_state; + qh->qh_state = QH_STATE_COMPLETING; + stopped = (state == QH_STATE_IDLE); + + rescan: + last = NULL; + last_status = -EINPROGRESS; + qh->needs_rescan = 0; + + /* 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 fusbh200_qtd *qtd; + struct urb *urb; + u32 token = 0; + + qtd = list_entry (entry, struct fusbh200_qtd, qtd_list); + urb = qtd->urb; + + /* clean up any state from previous QTD ...*/ + if (last) { + if (likely (last->urb != urb)) { + fusbh200_urb_done(fusbh200, last->urb, last_status); + count++; + last_status = -EINPROGRESS; + } + fusbh200_qtd_free (fusbh200, last); + last = NULL; + } + + /* ignore urbs submitted during completions we reported */ + if (qtd == end) + break; + + /* hardware copies qtd out of qh overlay */ + rmb (); + token = hc32_to_cpu(fusbh200, qtd->hw_token); + + /* always clean up qtds the hc de-activated */ + retry_xacterr: + if ((token & QTD_STS_ACTIVE) == 0) { + + /* Report Data Buffer Error: non-fatal but useful */ + if (token & QTD_STS_DBE) + fusbh200_dbg(fusbh200, + "detected DataBufferErr for urb %p ep%d%s len %d, qtd %p [qh %p]\n", + urb, + usb_endpoint_num(&urb->ep->desc), + usb_endpoint_dir_in(&urb->ep->desc) ? "in" : "out", + urb->transfer_buffer_length, + qtd, + qh); + + /* on STALL, error, and short reads this urb must + * complete and all its qtds must be recycled. + */ + if ((token & QTD_STS_HALT) != 0) { + + /* retry transaction errors until we + * reach the software xacterr limit + */ + if ((token & QTD_STS_XACT) && + QTD_CERR(token) == 0 && + ++qh->xacterrs < QH_XACTERR_MAX && + !urb->unlinked) { + fusbh200_dbg(fusbh200, + "detected XactErr len %zu/%zu retry %d\n", + qtd->length - QTD_LENGTH(token), qtd->length, qh->xacterrs); + + /* reset the token in the qtd and the + * qh overlay (which still contains + * the qtd) so that we pick up from + * where we left off + */ + token &= ~QTD_STS_HALT; + token |= QTD_STS_ACTIVE | + (FUSBH200_TUNE_CERR << 10); + qtd->hw_token = cpu_to_hc32(fusbh200, + token); + wmb(); + hw->hw_token = cpu_to_hc32(fusbh200, + token); + goto retry_xacterr; + } + stopped = 1; + + /* magic dummy for some short reads; qh won't advance. + * that silicon quirk can kick in with this dummy too. + * + * other short reads won't stop the queue, including + * control transfers (status stage handles that) or + * most other single-qtd reads ... the queue stops if + * URB_SHORT_NOT_OK was set so the driver submitting + * the urbs could clean it up. + */ + } else if (IS_SHORT_READ (token) + && !(qtd->hw_alt_next + & FUSBH200_LIST_END(fusbh200))) { + stopped = 1; + } + + /* stop scanning when we reach qtds the hc is using */ + } else if (likely (!stopped + && fusbh200->rh_state >= FUSBH200_RH_RUNNING)) { + break; + + /* scan the whole queue for unlinks whenever it stops */ + } else { + stopped = 1; + + /* cancel everything if we halt, suspend, etc */ + if (fusbh200->rh_state < FUSBH200_RH_RUNNING) + last_status = -ESHUTDOWN; + + /* this qtd is active; skip it unless a previous qtd + * for its urb faulted, or its urb was canceled. + */ + else if (last_status == -EINPROGRESS && !urb->unlinked) + continue; + + /* qh unlinked; token in overlay may be most current */ + if (state == QH_STATE_IDLE + && cpu_to_hc32(fusbh200, qtd->qtd_dma) + == hw->hw_current) { + token = hc32_to_cpu(fusbh200, hw->hw_token); + + /* An unlink may leave an incomplete + * async transaction in the TT buffer. + * We have to clear it. + */ + fusbh200_clear_tt_buffer(fusbh200, qh, urb, token); + } + } + + /* unless we already know the urb's status, collect qtd status + * and update count of bytes transferred. in common short read + * cases with only one data qtd (including control transfers), + * queue processing won't halt. but with two or more qtds (for + * example, with a 32 KB transfer), when the first qtd gets a + * short read the second must be removed by hand. + */ + if (last_status == -EINPROGRESS) { + last_status = qtd_copy_status(fusbh200, urb, + qtd->length, token); + if (last_status == -EREMOTEIO + && (qtd->hw_alt_next + & FUSBH200_LIST_END(fusbh200))) + last_status = -EINPROGRESS; + + /* As part of low/full-speed endpoint-halt processing + * we must clear the TT buffer (11.17.5). + */ + if (unlikely(last_status != -EINPROGRESS && + last_status != -EREMOTEIO)) { + /* The TT's in some hubs malfunction when they + * receive this request following a STALL (they + * stop sending isochronous packets). Since a + * STALL can't leave the TT buffer in a busy + * state (if you believe Figures 11-48 - 11-51 + * in the USB 2.0 spec), we won't clear the TT + * buffer in this case. Strictly speaking this + * is a violation of the spec. + */ + if (last_status != -EPIPE) + fusbh200_clear_tt_buffer(fusbh200, qh, urb, + token); + } + } + + /* if we're removing something not at the queue head, + * patch the hardware queue pointer. + */ + if (stopped && qtd->qtd_list.prev != &qh->qtd_list) { + last = list_entry (qtd->qtd_list.prev, + struct fusbh200_qtd, qtd_list); + last->hw_next = qtd->hw_next; + } + + /* remove qtd; it's recycled after possible urb completion */ + list_del (&qtd->qtd_list); + last = qtd; + + /* reinit the xacterr counter for the next qtd */ + qh->xacterrs = 0; + } + + /* last urb's completion might still need calling */ + if (likely (last != NULL)) { + fusbh200_urb_done(fusbh200, last->urb, last_status); + count++; + fusbh200_qtd_free (fusbh200, last); + } + + /* Do we need to rescan for URBs dequeued during a giveback? */ + if (unlikely(qh->needs_rescan)) { + /* If the QH is already unlinked, do the rescan now. */ + if (state == QH_STATE_IDLE) + goto rescan; + + /* Otherwise we have to wait until the QH is fully unlinked. + * Our caller will start an unlink if qh->needs_rescan is + * set. But if an unlink has already started, nothing needs + * to be done. + */ + if (state != QH_STATE_LINKED) + qh->needs_rescan = 0; + } + + /* 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 || hw->hw_qtd_next == FUSBH200_LIST_END(fusbh200)) { + switch (state) { + case QH_STATE_IDLE: + qh_refresh(fusbh200, qh); + break; + case QH_STATE_LINKED: + /* We won't refresh a QH that's linked (after the HC + * stopped the queue). That avoids a race: + * - HC reads first part of QH; + * - CPU updates that first part and the token; + * - HC reads rest of that QH, including token + * Result: HC gets an inconsistent image, and then + * DMAs to/from the wrong memory (corrupting it). + * + * That should be rare for interrupt transfers, + * except maybe high bandwidth ... + */ + + /* Tell the caller to start an unlink */ + qh->needs_rescan = 1; + 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 fusbh200_hcd *fusbh200, + struct urb *urb, + struct list_head *qtd_list +) { + struct list_head *entry, *temp; + + list_for_each_safe (entry, temp, qtd_list) { + struct fusbh200_qtd *qtd; + + qtd = list_entry (entry, struct fusbh200_qtd, qtd_list); + list_del (&qtd->qtd_list); + fusbh200_qtd_free (fusbh200, qtd); + } +} + +/* + * create a list of filled qtds for this URB; won't link into qh. + */ +static struct list_head * +qh_urb_transaction ( + struct fusbh200_hcd *fusbh200, + struct urb *urb, + struct list_head *head, + gfp_t flags +) { + struct fusbh200_qtd *qtd, *qtd_prev; + dma_addr_t buf; + int len, this_sg_len, maxpacket; + int is_input; + u32 token; + int i; + struct scatterlist *sg; + + /* + * URBs map to sequences of QTDs: one logical transaction + */ + qtd = fusbh200_qtd_alloc (fusbh200, flags); + if (unlikely (!qtd)) + return NULL; + list_add_tail (&qtd->qtd_list, head); + qtd->urb = urb; + + token = QTD_STS_ACTIVE; + token |= (FUSBH200_TUNE_CERR << 10); + /* for split transactions, SplitXState initialized to zero */ + + len = urb->transfer_buffer_length; + is_input = usb_pipein (urb->pipe); + if (usb_pipecontrol (urb->pipe)) { + /* SETUP pid */ + qtd_fill(fusbh200, qtd, urb->setup_dma, + sizeof (struct usb_ctrlrequest), + token | (2 /* "setup" */ << 8), 8); + + /* ... and always at least one more pid */ + token ^= QTD_TOGGLE; + qtd_prev = qtd; + qtd = fusbh200_qtd_alloc (fusbh200, flags); + if (unlikely (!qtd)) + goto cleanup; + qtd->urb = urb; + qtd_prev->hw_next = QTD_NEXT(fusbh200, 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 + */ + i = urb->num_mapped_sgs; + if (len > 0 && i > 0) { + sg = urb->sg; + buf = sg_dma_address(sg); + + /* urb->transfer_buffer_length may be smaller than the + * size of the scatterlist (or vice versa) + */ + this_sg_len = min_t(int, sg_dma_len(sg), len); + } else { + sg = NULL; + buf = urb->transfer_dma; + this_sg_len = 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(fusbh200, qtd, buf, this_sg_len, token, + maxpacket); + this_sg_len -= this_qtd_len; + len -= this_qtd_len; + buf += this_qtd_len; + + /* + * short reads advance to a "magic" dummy instead of the next + * qtd ... that forces the queue to stop, for manual cleanup. + * (this will usually be overridden later.) + */ + if (is_input) + qtd->hw_alt_next = fusbh200->async->hw->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(this_sg_len <= 0)) { + if (--i <= 0 || len <= 0) + break; + sg = sg_next(sg); + buf = sg_dma_address(sg); + this_sg_len = min_t(int, sg_dma_len(sg), len); + } + + qtd_prev = qtd; + qtd = fusbh200_qtd_alloc (fusbh200, flags); + if (unlikely (!qtd)) + goto cleanup; + qtd->urb = urb; + qtd_prev->hw_next = QTD_NEXT(fusbh200, qtd->qtd_dma); + list_add_tail (&qtd->qtd_list, head); + } + + /* + * unless the caller requires manual cleanup after short reads, + * have the alt_next mechanism keep the queue running after the + * last data qtd (the only one, for control and most other cases). + */ + if (likely ((urb->transfer_flags & URB_SHORT_NOT_OK) == 0 + || usb_pipecontrol (urb->pipe))) + qtd->hw_alt_next = FUSBH200_LIST_END(fusbh200); + + /* + * control requests may need a terminating data "status" ack; + * other OUT 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_pipeout(urb->pipe) + && (urb->transfer_flags & URB_ZERO_PACKET) + && !(urb->transfer_buffer_length % maxpacket)) { + one_more = 1; + } + if (one_more) { + qtd_prev = qtd; + qtd = fusbh200_qtd_alloc (fusbh200, flags); + if (unlikely (!qtd)) + goto cleanup; + qtd->urb = urb; + qtd_prev->hw_next = QTD_NEXT(fusbh200, qtd->qtd_dma); + list_add_tail (&qtd->qtd_list, head); + + /* never any data in such packets */ + qtd_fill(fusbh200, qtd, 0, 0, token, 0); + } + } + + /* by default, enable interrupt on urb completion */ + if (likely (!(urb->transfer_flags & URB_NO_INTERRUPT))) + qtd->hw_token |= cpu_to_hc32(fusbh200, QTD_IOC); + return head; + +cleanup: + qtd_list_free (fusbh200, urb, head); + return NULL; +} + +/*-------------------------------------------------------------------------*/ + +// Would be best to create all qh's from config descriptors, +// when each interface/altsetting is established. Unlink +// any previous qh and cancel its urbs first; endpoints are +// implicitly reset then (data toggle too). +// That'd mean updating how usbcore talks to HCDs. (2.7?) + + +/* + * 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 fusbh200_qh * +qh_make ( + struct fusbh200_hcd *fusbh200, + struct urb *urb, + gfp_t flags +) { + struct fusbh200_qh *qh = fusbh200_qh_alloc (fusbh200, flags); + u32 info1 = 0, info2 = 0; + int is_input, type; + int maxp = 0; + struct usb_tt *tt = urb->dev->tt; + struct fusbh200_qh_hw *hw; + + 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); + + /* 1024 byte maxpacket is a hardware ceiling. High bandwidth + * acts like up to 3KB, but is built from smaller packets. + */ + if (max_packet(maxp) > 1024) { + fusbh200_dbg(fusbh200, "bogus qh maxpacket %d\n", max_packet(maxp)); + goto done; + } + + /* 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. + */ + urb->interval = 1; + } else if (qh->period > fusbh200->periodic_size) { + qh->period = fusbh200->periodic_size; + urb->interval = qh->period << 3; + } + } else { + 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; + if (qh->period > fusbh200->periodic_size) { + qh->period = fusbh200->periodic_size; + urb->interval = qh->period; + } + } + } + + /* support for tt scheduling, and access to toggles */ + qh->dev = urb->dev; + + /* using TT? */ + switch (urb->dev->speed) { + case USB_SPEED_LOW: + info1 |= QH_LOW_SPEED; + /* FALL THROUGH */ + + case USB_SPEED_FULL: + /* EPS 0 means "full" */ + if (type != PIPE_INTERRUPT) + info1 |= (FUSBH200_TUNE_RL_TT << 28); + if (type == PIPE_CONTROL) { + info1 |= QH_CONTROL_EP; /* for TT */ + info1 |= QH_TOGGLE_CTL; /* toggle from qtd */ + } + info1 |= maxp << 16; + + info2 |= (FUSBH200_TUNE_MULT_TT << 30); + + /* Some Freescale processors have an erratum in which the + * port number in the queue head was 0..N-1 instead of 1..N. + */ + if (fusbh200_has_fsl_portno_bug(fusbh200)) + info2 |= (urb->dev->ttport-1) << 23; + else + info2 |= urb->dev->ttport << 23; + + /* set the address of the TT; for TDI's integrated + * root hub tt, leave it zeroed. + */ + if (tt && tt->hub != fusbh200_to_hcd(fusbh200)->self.root_hub) + info2 |= tt->hub->devnum << 16; + + /* NOTE: if (PIPE_INTERRUPT) { scheduler sets c-mask } */ + + break; + + case USB_SPEED_HIGH: /* no TT involved */ + info1 |= QH_HIGH_SPEED; + if (type == PIPE_CONTROL) { + info1 |= (FUSBH200_TUNE_RL_HS << 28); + info1 |= 64 << 16; /* usb2 fixed maxpacket */ + info1 |= QH_TOGGLE_CTL; /* toggle from qtd */ + info2 |= (FUSBH200_TUNE_MULT_HS << 30); + } else if (type == PIPE_BULK) { + info1 |= (FUSBH200_TUNE_RL_HS << 28); + /* The USB spec says that high speed bulk endpoints + * always use 512 byte maxpacket. But some device + * vendors decided to ignore that, and MSFT is happy + * to help them do so. So now people expect to use + * such nonconformant devices with Linux too; sigh. + */ + info1 |= max_packet(maxp) << 16; + info2 |= (FUSBH200_TUNE_MULT_HS << 30); + } else { /* PIPE_INTERRUPT */ + info1 |= max_packet (maxp) << 16; + info2 |= hb_mult (maxp) << 30; + } + break; + default: + fusbh200_dbg(fusbh200, "bogus dev %p speed %d\n", urb->dev, + urb->dev->speed); +done: + qh_destroy(fusbh200, 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; + hw = qh->hw; + hw->hw_info1 = cpu_to_hc32(fusbh200, info1); + hw->hw_info2 = cpu_to_hc32(fusbh200, info2); + qh->is_out = !is_input; + usb_settoggle (urb->dev, usb_pipeendpoint (urb->pipe), !is_input, 1); + qh_refresh (fusbh200, qh); + return qh; +} + +/*-------------------------------------------------------------------------*/ + +static void enable_async(struct fusbh200_hcd *fusbh200) +{ + if (fusbh200->async_count++) + return; + + /* Stop waiting to turn off the async schedule */ + fusbh200->enabled_hrtimer_events &= ~BIT(FUSBH200_HRTIMER_DISABLE_ASYNC); + + /* Don't start the schedule until ASS is 0 */ + fusbh200_poll_ASS(fusbh200); + turn_on_io_watchdog(fusbh200); +} + +static void disable_async(struct fusbh200_hcd *fusbh200) +{ + if (--fusbh200->async_count) + return; + + /* The async schedule and async_unlink list are supposed to be empty */ + WARN_ON(fusbh200->async->qh_next.qh || fusbh200->async_unlink); + + /* Don't turn off the schedule until ASS is 1 */ + fusbh200_poll_ASS(fusbh200); +} + +/* move qh (and its qtds) onto async queue; maybe enable queue. */ + +static void qh_link_async (struct fusbh200_hcd *fusbh200, struct fusbh200_qh *qh) +{ + __hc32 dma = QH_NEXT(fusbh200, qh->qh_dma); + struct fusbh200_qh *head; + + /* Don't link a QH if there's a Clear-TT-Buffer pending */ + if (unlikely(qh->clearing_tt)) + return; + + WARN_ON(qh->qh_state != QH_STATE_IDLE); + + /* clear halt and/or toggle; and maybe recover from silicon quirk */ + qh_refresh(fusbh200, qh); + + /* splice right after start */ + head = fusbh200->async; + qh->qh_next = head->qh_next; + qh->hw->hw_next = head->hw->hw_next; + wmb (); + + head->qh_next.qh = qh; + head->hw->hw_next = dma; + + qh->xacterrs = 0; + qh->qh_state = QH_STATE_LINKED; + /* qtd completions reported later by interrupt */ + + enable_async(fusbh200); +} + +/*-------------------------------------------------------------------------*/ + +/* + * 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 fusbh200_qh *qh_append_tds ( + struct fusbh200_hcd *fusbh200, + struct urb *urb, + struct list_head *qtd_list, + int epnum, + void **ptr +) +{ + struct fusbh200_qh *qh = NULL; + __hc32 qh_addr_mask = cpu_to_hc32(fusbh200, 0x7f); + + qh = (struct fusbh200_qh *) *ptr; + if (unlikely (qh == NULL)) { + /* can't sleep here, we have fusbh200->lock... */ + qh = qh_make (fusbh200, urb, GFP_ATOMIC); + *ptr = qh; + } + if (likely (qh != NULL)) { + struct fusbh200_qtd *qtd; + + if (unlikely (list_empty (qtd_list))) + qtd = NULL; + else + qtd = list_entry (qtd_list->next, struct fusbh200_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->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 fusbh200_qtd *dummy; + dma_addr_t dma; + __hc32 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(fusbh200); + + 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_tail(qtd_list, &qh->qtd_list); + + fusbh200_qtd_init(fusbh200, 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 fusbh200_qtd, qtd_list); + qtd->hw_next = QTD_NEXT(fusbh200, dma); + + /* let the hc process these next qtds */ + wmb (); + dummy->hw_token = token; + + urb->hcpriv = qh; + } + } + return qh; +} + +/*-------------------------------------------------------------------------*/ + +static int +submit_async ( + struct fusbh200_hcd *fusbh200, + struct urb *urb, + struct list_head *qtd_list, + gfp_t mem_flags +) { + int epnum; + unsigned long flags; + struct fusbh200_qh *qh = NULL; + int rc; + + epnum = urb->ep->desc.bEndpointAddress; + +#ifdef FUSBH200_URB_TRACE + { + struct fusbh200_qtd *qtd; + qtd = list_entry(qtd_list->next, struct fusbh200_qtd, qtd_list); + fusbh200_dbg(fusbh200, + "%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 (&fusbh200->lock, flags); + if (unlikely(!HCD_HW_ACCESSIBLE(fusbh200_to_hcd(fusbh200)))) { + rc = -ESHUTDOWN; + goto done; + } + rc = usb_hcd_link_urb_to_ep(fusbh200_to_hcd(fusbh200), urb); + if (unlikely(rc)) + goto done; + + qh = qh_append_tds(fusbh200, urb, qtd_list, epnum, &urb->ep->hcpriv); + if (unlikely(qh == NULL)) { + usb_hcd_unlink_urb_from_ep(fusbh200_to_hcd(fusbh200), urb); + 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(fusbh200, qh); + done: + spin_unlock_irqrestore (&fusbh200->lock, flags); + if (unlikely (qh == NULL)) + qtd_list_free (fusbh200, urb, qtd_list); + return rc; +} + +/*-------------------------------------------------------------------------*/ + +static void single_unlink_async(struct fusbh200_hcd *fusbh200, struct fusbh200_qh *qh) +{ + struct fusbh200_qh *prev; + + /* Add to the end of the list of QHs waiting for the next IAAD */ + qh->qh_state = QH_STATE_UNLINK; + if (fusbh200->async_unlink) + fusbh200->async_unlink_last->unlink_next = qh; + else + fusbh200->async_unlink = qh; + fusbh200->async_unlink_last = qh; + + /* Unlink it from the schedule */ + prev = fusbh200->async; + while (prev->qh_next.qh != qh) + prev = prev->qh_next.qh; + + prev->hw->hw_next = qh->hw->hw_next; + prev->qh_next = qh->qh_next; + if (fusbh200->qh_scan_next == qh) + fusbh200->qh_scan_next = qh->qh_next.qh; +} + +static void start_iaa_cycle(struct fusbh200_hcd *fusbh200, bool nested) +{ + /* + * Do nothing if an IAA cycle is already running or + * if one will be started shortly. + */ + if (fusbh200->async_iaa || fusbh200->async_unlinking) + return; + + /* Do all the waiting QHs at once */ + fusbh200->async_iaa = fusbh200->async_unlink; + fusbh200->async_unlink = NULL; + + /* If the controller isn't running, we don't have to wait for it */ + if (unlikely(fusbh200->rh_state < FUSBH200_RH_RUNNING)) { + if (!nested) /* Avoid recursion */ + end_unlink_async(fusbh200); + + /* Otherwise start a new IAA cycle */ + } else if (likely(fusbh200->rh_state == FUSBH200_RH_RUNNING)) { + /* Make sure the unlinks are all visible to the hardware */ + wmb(); + + fusbh200_writel(fusbh200, fusbh200->command | CMD_IAAD, + &fusbh200->regs->command); + fusbh200_readl(fusbh200, &fusbh200->regs->command); + fusbh200_enable_event(fusbh200, FUSBH200_HRTIMER_IAA_WATCHDOG, true); + } +} + +/* the async qh for the qtds being unlinked are now gone from the HC */ + +static void end_unlink_async(struct fusbh200_hcd *fusbh200) +{ + struct fusbh200_qh *qh; + + /* Process the idle QHs */ + restart: + fusbh200->async_unlinking = true; + while (fusbh200->async_iaa) { + qh = fusbh200->async_iaa; + fusbh200->async_iaa = qh->unlink_next; + qh->unlink_next = NULL; + + qh->qh_state = QH_STATE_IDLE; + qh->qh_next.qh = NULL; + + qh_completions(fusbh200, qh); + if (!list_empty(&qh->qtd_list) && + fusbh200->rh_state == FUSBH200_RH_RUNNING) + qh_link_async(fusbh200, qh); + disable_async(fusbh200); + } + fusbh200->async_unlinking = false; + + /* Start a new IAA cycle if any QHs are waiting for it */ + if (fusbh200->async_unlink) { + start_iaa_cycle(fusbh200, true); + if (unlikely(fusbh200->rh_state < FUSBH200_RH_RUNNING)) + goto restart; + } +} + +static void unlink_empty_async(struct fusbh200_hcd *fusbh200) +{ + struct fusbh200_qh *qh, *next; + bool stopped = (fusbh200->rh_state < FUSBH200_RH_RUNNING); + bool check_unlinks_later = false; + + /* Unlink all the async QHs that have been empty for a timer cycle */ + next = fusbh200->async->qh_next.qh; + while (next) { + qh = next; + next = qh->qh_next.qh; + + if (list_empty(&qh->qtd_list) && + qh->qh_state == QH_STATE_LINKED) { + if (!stopped && qh->unlink_cycle == + fusbh200->async_unlink_cycle) + check_unlinks_later = true; + else + single_unlink_async(fusbh200, qh); + } + } + + /* Start a new IAA cycle if any QHs are waiting for it */ + if (fusbh200->async_unlink) + start_iaa_cycle(fusbh200, false); + + /* QHs that haven't been empty for long enough will be handled later */ + if (check_unlinks_later) { + fusbh200_enable_event(fusbh200, FUSBH200_HRTIMER_ASYNC_UNLINKS, true); + ++fusbh200->async_unlink_cycle; + } +} + +/* makes sure the async qh will become idle */ +/* caller must own fusbh200->lock */ + +static void start_unlink_async(struct fusbh200_hcd *fusbh200, struct fusbh200_qh *qh) +{ + /* + * If the QH isn't linked then there's nothing we can do + * unless we were called during a giveback, in which case + * qh_completions() has to deal with it. + */ + if (qh->qh_state != QH_STATE_LINKED) { + if (qh->qh_state == QH_STATE_COMPLETING) + qh->needs_rescan = 1; + return; + } + + single_unlink_async(fusbh200, qh); + start_iaa_cycle(fusbh200, false); +} + +/*-------------------------------------------------------------------------*/ + +static void scan_async (struct fusbh200_hcd *fusbh200) +{ + struct fusbh200_qh *qh; + bool check_unlinks_later = false; + + fusbh200->qh_scan_next = fusbh200->async->qh_next.qh; + while (fusbh200->qh_scan_next) { + qh = fusbh200->qh_scan_next; + fusbh200->qh_scan_next = qh->qh_next.qh; + rescan: + /* clean any finished work for this qh */ + if (!list_empty(&qh->qtd_list)) { + int temp; + + /* + * Unlinks could happen here; completion reporting + * drops the lock. That's why fusbh200->qh_scan_next + * always holds the next qh to scan; if the next qh + * gets unlinked then fusbh200->qh_scan_next is adjusted + * in single_unlink_async(). + */ + temp = qh_completions(fusbh200, qh); + if (qh->needs_rescan) { + start_unlink_async(fusbh200, qh); + } else if (list_empty(&qh->qtd_list) + && qh->qh_state == QH_STATE_LINKED) { + qh->unlink_cycle = fusbh200->async_unlink_cycle; + check_unlinks_later = true; + } else if (temp != 0) + goto rescan; + } + } + + /* + * Unlink empty entries, reducing DMA 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. + */ + if (check_unlinks_later && fusbh200->rh_state == FUSBH200_RH_RUNNING && + !(fusbh200->enabled_hrtimer_events & + BIT(FUSBH200_HRTIMER_ASYNC_UNLINKS))) { + fusbh200_enable_event(fusbh200, FUSBH200_HRTIMER_ASYNC_UNLINKS, true); + ++fusbh200->async_unlink_cycle; + } +} +/*-------------------------------------------------------------------------*/ +/* + * EHCI scheduled transaction support: interrupt, iso, split iso + * These are called "periodic" transactions in the EHCI spec. + * + * Note that for interrupt transfers, the QH/QTD manipulation is shared + * with the "asynchronous" transaction support (control/bulk transfers). + * The only real difference is in how interrupt transfers are scheduled. + * + * For ISO, we make an "iso_stream" head to serve the same role as a QH. + * It keeps track of every ITD (or SITD) that's linked, and holds enough + * pre-calculated schedule data to make appending to the queue be quick. + */ + +static int fusbh200_get_frame (struct usb_hcd *hcd); + +/*-------------------------------------------------------------------------*/ + +/* + * periodic_next_shadow - return "next" pointer on shadow list + * @periodic: host pointer to qh/itd + * @tag: hardware tag for type of this record + */ +static union fusbh200_shadow * +periodic_next_shadow(struct fusbh200_hcd *fusbh200, union fusbh200_shadow *periodic, + __hc32 tag) +{ + switch (hc32_to_cpu(fusbh200, tag)) { + case Q_TYPE_QH: + return &periodic->qh->qh_next; + case Q_TYPE_FSTN: + return &periodic->fstn->fstn_next; + default: + return &periodic->itd->itd_next; + } +} + +static __hc32 * +shadow_next_periodic(struct fusbh200_hcd *fusbh200, union fusbh200_shadow *periodic, + __hc32 tag) +{ + switch (hc32_to_cpu(fusbh200, tag)) { + /* our fusbh200_shadow.qh is actually software part */ + case Q_TYPE_QH: + return &periodic->qh->hw->hw_next; + /* others are hw parts */ + default: + return periodic->hw_next; + } +} + +/* caller must hold fusbh200->lock */ +static void periodic_unlink (struct fusbh200_hcd *fusbh200, unsigned frame, void *ptr) +{ + union fusbh200_shadow *prev_p = &fusbh200->pshadow[frame]; + __hc32 *hw_p = &fusbh200->periodic[frame]; + union fusbh200_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(fusbh200, prev_p, + Q_NEXT_TYPE(fusbh200, *hw_p)); + hw_p = shadow_next_periodic(fusbh200, &here, + Q_NEXT_TYPE(fusbh200, *hw_p)); + 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(fusbh200, &here, + Q_NEXT_TYPE(fusbh200, *hw_p)); + + *hw_p = *shadow_next_periodic(fusbh200, &here, + Q_NEXT_TYPE(fusbh200, *hw_p)); +} + +/* how many of the uframe's 125 usecs are allocated? */ +static unsigned short +periodic_usecs (struct fusbh200_hcd *fusbh200, unsigned frame, unsigned uframe) +{ + __hc32 *hw_p = &fusbh200->periodic [frame]; + union fusbh200_shadow *q = &fusbh200->pshadow [frame]; + unsigned usecs = 0; + struct fusbh200_qh_hw *hw; + + while (q->ptr) { + switch (hc32_to_cpu(fusbh200, Q_NEXT_TYPE(fusbh200, *hw_p))) { + case Q_TYPE_QH: + hw = q->qh->hw; + /* is it in the S-mask? */ + if (hw->hw_info2 & cpu_to_hc32(fusbh200, 1 << uframe)) + usecs += q->qh->usecs; + /* ... or C-mask? */ + if (hw->hw_info2 & cpu_to_hc32(fusbh200, + 1 << (8 + uframe))) + usecs += q->qh->c_usecs; + hw_p = &hw->hw_next; + q = &q->qh->qh_next; + break; + // case Q_TYPE_FSTN: + default: + /* for "save place" FSTNs, count the relevant INTR + * bandwidth from the previous frame + */ + if (q->fstn->hw_prev != FUSBH200_LIST_END(fusbh200)) { + fusbh200_dbg (fusbh200, "ignoring FSTN cost ...\n"); + } + hw_p = &q->fstn->hw_next; + q = &q->fstn->fstn_next; + break; + case Q_TYPE_ITD: + if (q->itd->hw_transaction[uframe]) + usecs += q->itd->stream->usecs; + hw_p = &q->itd->hw_next; + q = &q->itd->itd_next; + break; + } + } +#ifdef DEBUG + if (usecs > fusbh200->uframe_periodic_max) + fusbh200_err (fusbh200, "uframe %d sched overrun: %d usecs\n", + frame * 8 + uframe, usecs); +#endif + return usecs; +} + +/*-------------------------------------------------------------------------*/ + +static int same_tt (struct usb_device *dev1, struct usb_device *dev2) +{ + if (!dev1->tt || !dev2->tt) + return 0; + if (dev1->tt != dev2->tt) + return 0; + if (dev1->tt->multi) + return dev1->ttport == dev2->ttport; + else + return 1; +} + +/* return true iff the device's transaction translator is available + * for a periodic transfer starting at the specified frame, using + * all the uframes in the mask. + */ +static int tt_no_collision ( + struct fusbh200_hcd *fusbh200, + unsigned period, + struct usb_device *dev, + unsigned frame, + u32 uf_mask +) +{ + if (period == 0) /* error */ + return 0; + + /* note bandwidth wastage: split never follows csplit + * (different dev or endpoint) until the next uframe. + * calling convention doesn't make that distinction. + */ + for (; frame < fusbh200->periodic_size; frame += period) { + union fusbh200_shadow here; + __hc32 type; + struct fusbh200_qh_hw *hw; + + here = fusbh200->pshadow [frame]; + type = Q_NEXT_TYPE(fusbh200, fusbh200->periodic [frame]); + while (here.ptr) { + switch (hc32_to_cpu(fusbh200, type)) { + case Q_TYPE_ITD: + type = Q_NEXT_TYPE(fusbh200, here.itd->hw_next); + here = here.itd->itd_next; + continue; + case Q_TYPE_QH: + hw = here.qh->hw; + if (same_tt (dev, here.qh->dev)) { + u32 mask; + + mask = hc32_to_cpu(fusbh200, + hw->hw_info2); + /* "knows" no gap is needed */ + mask |= mask >> 8; + if (mask & uf_mask) + break; + } + type = Q_NEXT_TYPE(fusbh200, hw->hw_next); + here = here.qh->qh_next; + continue; + // case Q_TYPE_FSTN: + default: + fusbh200_dbg (fusbh200, + "periodic frame %d bogus type %d\n", + frame, type); + } + + /* collision or error */ + return 0; + } + } + + /* no collision */ + return 1; +} + +/*-------------------------------------------------------------------------*/ + +static void enable_periodic(struct fusbh200_hcd *fusbh200) +{ + if (fusbh200->periodic_count++) + return; + + /* Stop waiting to turn off the periodic schedule */ + fusbh200->enabled_hrtimer_events &= ~BIT(FUSBH200_HRTIMER_DISABLE_PERIODIC); + + /* Don't start the schedule until PSS is 0 */ + fusbh200_poll_PSS(fusbh200); + turn_on_io_watchdog(fusbh200); +} + +static void disable_periodic(struct fusbh200_hcd *fusbh200) +{ + if (--fusbh200->periodic_count) + return; + + /* Don't turn off the schedule until PSS is 1 */ + fusbh200_poll_PSS(fusbh200); +} + +/*-------------------------------------------------------------------------*/ + +/* 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; fusbh200 0.96+) + */ +static void qh_link_periodic(struct fusbh200_hcd *fusbh200, struct fusbh200_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, hc32_to_cpup(fusbh200, &qh->hw->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 < fusbh200->periodic_size; i += period) { + union fusbh200_shadow *prev = &fusbh200->pshadow[i]; + __hc32 *hw_p = &fusbh200->periodic[i]; + union fusbh200_shadow here = *prev; + __hc32 type = 0; + + /* skip the iso nodes at list head */ + while (here.ptr) { + type = Q_NEXT_TYPE(fusbh200, *hw_p); + if (type == cpu_to_hc32(fusbh200, Q_TYPE_QH)) + break; + prev = periodic_next_shadow(fusbh200, prev, type); + hw_p = shadow_next_periodic(fusbh200, &here, type); + 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->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->hw_next = *hw_p; + wmb (); + prev->qh = qh; + *hw_p = QH_NEXT (fusbh200, qh->qh_dma); + } + } + qh->qh_state = QH_STATE_LINKED; + qh->xacterrs = 0; + + /* update per-qh bandwidth for usbfs */ + fusbh200_to_hcd(fusbh200)->self.bandwidth_allocated += qh->period + ? ((qh->usecs + qh->c_usecs) / qh->period) + : (qh->usecs * 8); + + list_add(&qh->intr_node, &fusbh200->intr_qh_list); + + /* maybe enable periodic schedule processing */ + ++fusbh200->intr_count; + enable_periodic(fusbh200); +} + +static void qh_unlink_periodic(struct fusbh200_hcd *fusbh200, struct fusbh200_qh *qh) +{ + unsigned i; + unsigned period; + + /* + * If qh is for a low/full-speed device, simply unlinking it + * could interfere with an ongoing split transaction. To unlink + * it safely would require setting the QH_INACTIVATE bit and + * waiting at least one frame, as described in EHCI 4.12.2.5. + * + * We won't bother with any of this. Instead, we assume that the + * only reason for unlinking an interrupt QH while the current URB + * is still active is to dequeue all the URBs (flush the whole + * endpoint queue). + * + * If rebalancing the periodic schedule is ever implemented, this + * approach will no longer be valid. + */ + + /* high bandwidth, or otherwise part of every microframe */ + if ((period = qh->period) == 0) + period = 1; + + for (i = qh->start; i < fusbh200->periodic_size; i += period) + periodic_unlink (fusbh200, i, qh); + + /* update per-qh bandwidth for usbfs */ + fusbh200_to_hcd(fusbh200)->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, + hc32_to_cpup(fusbh200, &qh->hw->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; + + if (fusbh200->qh_scan_next == qh) + fusbh200->qh_scan_next = list_entry(qh->intr_node.next, + struct fusbh200_qh, intr_node); + list_del(&qh->intr_node); +} + +static void start_unlink_intr(struct fusbh200_hcd *fusbh200, struct fusbh200_qh *qh) +{ + /* If the QH isn't linked then there's nothing we can do + * unless we were called during a giveback, in which case + * qh_completions() has to deal with it. + */ + if (qh->qh_state != QH_STATE_LINKED) { + if (qh->qh_state == QH_STATE_COMPLETING) + qh->needs_rescan = 1; + return; + } + + qh_unlink_periodic (fusbh200, qh); + + /* Make sure the unlinks are visible before starting the timer */ + wmb(); + + /* + * The EHCI spec doesn't say how long it takes the controller to + * stop accessing an unlinked interrupt QH. The timer delay is + * 9 uframes; presumably that will be long enough. + */ + qh->unlink_cycle = fusbh200->intr_unlink_cycle; + + /* New entries go at the end of the intr_unlink list */ + if (fusbh200->intr_unlink) + fusbh200->intr_unlink_last->unlink_next = qh; + else + fusbh200->intr_unlink = qh; + fusbh200->intr_unlink_last = qh; + + if (fusbh200->intr_unlinking) + ; /* Avoid recursive calls */ + else if (fusbh200->rh_state < FUSBH200_RH_RUNNING) + fusbh200_handle_intr_unlinks(fusbh200); + else if (fusbh200->intr_unlink == qh) { + fusbh200_enable_event(fusbh200, FUSBH200_HRTIMER_UNLINK_INTR, true); + ++fusbh200->intr_unlink_cycle; + } +} + +static void end_unlink_intr(struct fusbh200_hcd *fusbh200, struct fusbh200_qh *qh) +{ + struct fusbh200_qh_hw *hw = qh->hw; + int rc; + + qh->qh_state = QH_STATE_IDLE; + hw->hw_next = FUSBH200_LIST_END(fusbh200); + + qh_completions(fusbh200, qh); + + /* reschedule QH iff another request is queued */ + if (!list_empty(&qh->qtd_list) && fusbh200->rh_state == FUSBH200_RH_RUNNING) { + rc = qh_schedule(fusbh200, qh); + + /* An error here likely indicates handshake failure + * or no space left in the schedule. Neither fault + * should happen often ... + * + * FIXME kill the now-dysfunctional queued urbs + */ + if (rc != 0) + fusbh200_err(fusbh200, "can't reschedule qh %p, err %d\n", + qh, rc); + } + + /* maybe turn off periodic schedule */ + --fusbh200->intr_count; + disable_periodic(fusbh200); +} + +/*-------------------------------------------------------------------------*/ + +static int check_period ( + struct fusbh200_hcd *fusbh200, + 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; + + /* convert "usecs we need" to "max already claimed" */ + usecs = fusbh200->uframe_periodic_max - 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 (fusbh200, frame, uframe); + if (claimed > usecs) + return 0; + } + } while ((frame += 1) < fusbh200->periodic_size); + + /* just check the specified uframe, at that period */ + } else { + do { + claimed = periodic_usecs (fusbh200, frame, uframe); + if (claimed > usecs) + return 0; + } while ((frame += period) < fusbh200->periodic_size); + } + + // success! + return 1; +} + +static int check_intr_schedule ( + struct fusbh200_hcd *fusbh200, + unsigned frame, + unsigned uframe, + const struct fusbh200_qh *qh, + __hc32 *c_maskp +) +{ + int retval = -ENOSPC; + u8 mask = 0; + + if (qh->c_usecs && uframe >= 6) /* FSTN territory? */ + goto done; + + if (!check_period (fusbh200, frame, uframe, qh->period, qh->usecs)) + goto done; + if (!qh->c_usecs) { + retval = 0; + *c_maskp = 0; + goto done; + } + + /* Make sure this tt's buffer is also available for CSPLITs. + * We pessimize a bit; probably the typical full speed case + * doesn't need the second CSPLIT. + * + * NOTE: both SPLIT and CSPLIT could be checked in just + * one smart pass... + */ + mask = 0x03 << (uframe + qh->gap_uf); + *c_maskp = cpu_to_hc32(fusbh200, mask << 8); + + mask |= 1 << uframe; + if (tt_no_collision (fusbh200, qh->period, qh->dev, frame, mask)) { + if (!check_period (fusbh200, frame, uframe + qh->gap_uf + 1, + qh->period, qh->c_usecs)) + goto done; + if (!check_period (fusbh200, frame, uframe + qh->gap_uf, + qh->period, qh->c_usecs)) + goto done; + retval = 0; + } +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 fusbh200_hcd *fusbh200, struct fusbh200_qh *qh) +{ + int status; + unsigned uframe; + __hc32 c_mask; + unsigned frame; /* 0..(qh->period - 1), or NO_FRAME */ + struct fusbh200_qh_hw *hw = qh->hw; + + qh_refresh(fusbh200, qh); + hw->hw_next = FUSBH200_LIST_END(fusbh200); + frame = qh->start; + + /* reuse the previous schedule slots, if we can */ + if (frame < qh->period) { + uframe = ffs(hc32_to_cpup(fusbh200, &hw->hw_info2) & QH_SMASK); + status = check_intr_schedule (fusbh200, 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) { + int i; + + for (i = qh->period; status && i > 0; --i) { + frame = ++fusbh200->random_frame % qh->period; + for (uframe = 0; uframe < 8; uframe++) { + status = check_intr_schedule (fusbh200, + frame, uframe, qh, + &c_mask); + if (status == 0) + break; + } + } + + /* qh->period == 0 means every uframe */ + } else { + frame = 0; + status = check_intr_schedule (fusbh200, 0, 0, qh, &c_mask); + } + if (status) + goto done; + qh->start = frame; + + /* reset S-frame and (maybe) C-frame masks */ + hw->hw_info2 &= cpu_to_hc32(fusbh200, ~(QH_CMASK | QH_SMASK)); + hw->hw_info2 |= qh->period + ? cpu_to_hc32(fusbh200, 1 << uframe) + : cpu_to_hc32(fusbh200, QH_SMASK); + hw->hw_info2 |= c_mask; + } else + fusbh200_dbg (fusbh200, "reused qh %p schedule\n", qh); + + /* stuff into the periodic schedule */ + qh_link_periodic(fusbh200, qh); +done: + return status; +} + +static int intr_submit ( + struct fusbh200_hcd *fusbh200, + struct urb *urb, + struct list_head *qtd_list, + gfp_t mem_flags +) { + unsigned epnum; + unsigned long flags; + struct fusbh200_qh *qh; + int status; + struct list_head empty; + + /* get endpoint and transfer/schedule data */ + epnum = urb->ep->desc.bEndpointAddress; + + spin_lock_irqsave (&fusbh200->lock, flags); + + if (unlikely(!HCD_HW_ACCESSIBLE(fusbh200_to_hcd(fusbh200)))) { + status = -ESHUTDOWN; + goto done_not_linked; + } + status = usb_hcd_link_urb_to_ep(fusbh200_to_hcd(fusbh200), urb); + if (unlikely(status)) + goto done_not_linked; + + /* get qh and force any scheduling errors */ + INIT_LIST_HEAD (&empty); + qh = qh_append_tds(fusbh200, urb, &empty, epnum, &urb->ep->hcpriv); + if (qh == NULL) { + status = -ENOMEM; + goto done; + } + if (qh->qh_state == QH_STATE_IDLE) { + if ((status = qh_schedule (fusbh200, qh)) != 0) + goto done; + } + + /* then queue the urb's tds to the qh */ + qh = qh_append_tds(fusbh200, urb, qtd_list, epnum, &urb->ep->hcpriv); + BUG_ON (qh == NULL); + + /* ... update usbfs periodic stats */ + fusbh200_to_hcd(fusbh200)->self.bandwidth_int_reqs++; + +done: + if (unlikely(status)) + usb_hcd_unlink_urb_from_ep(fusbh200_to_hcd(fusbh200), urb); +done_not_linked: + spin_unlock_irqrestore (&fusbh200->lock, flags); + if (status) + qtd_list_free (fusbh200, urb, qtd_list); + + return status; +} + +static void scan_intr(struct fusbh200_hcd *fusbh200) +{ + struct fusbh200_qh *qh; + + list_for_each_entry_safe(qh, fusbh200->qh_scan_next, &fusbh200->intr_qh_list, + intr_node) { + rescan: + /* clean any finished work for this qh */ + if (!list_empty(&qh->qtd_list)) { + int temp; + + /* + * Unlinks could happen here; completion reporting + * drops the lock. That's why fusbh200->qh_scan_next + * always holds the next qh to scan; if the next qh + * gets unlinked then fusbh200->qh_scan_next is adjusted + * in qh_unlink_periodic(). + */ + temp = qh_completions(fusbh200, qh); + if (unlikely(qh->needs_rescan || + (list_empty(&qh->qtd_list) && + qh->qh_state == QH_STATE_LINKED))) + start_unlink_intr(fusbh200, qh); + else if (temp != 0) + goto rescan; + } + } +} + +/*-------------------------------------------------------------------------*/ + +/* fusbh200_iso_stream ops work with both ITD and SITD */ + +static struct fusbh200_iso_stream * +iso_stream_alloc (gfp_t mem_flags) +{ + struct fusbh200_iso_stream *stream; + + stream = kzalloc(sizeof *stream, mem_flags); + if (likely (stream != NULL)) { + INIT_LIST_HEAD(&stream->td_list); + INIT_LIST_HEAD(&stream->free_list); + stream->next_uframe = -1; + } + return stream; +} + +static void +iso_stream_init ( + struct fusbh200_hcd *fusbh200, + struct fusbh200_iso_stream *stream, + struct usb_device *dev, + int pipe, + unsigned interval +) +{ + u32 buf1; + unsigned epnum, maxp; + int is_input; + long bandwidth; + unsigned multi; + + /* + * this might be a "high bandwidth" highspeed endpoint, + * as encoded in the ep descriptor's wMaxPacket field + */ + epnum = usb_pipeendpoint (pipe); + is_input = usb_pipein (pipe) ? USB_DIR_IN : 0; + maxp = usb_maxpacket(dev, pipe, !is_input); + if (is_input) { + buf1 = (1 << 11); + } else { + buf1 = 0; + } + + maxp = max_packet(maxp); + multi = hb_mult(maxp); + buf1 |= maxp; + maxp *= multi; + + stream->buf0 = cpu_to_hc32(fusbh200, (epnum << 8) | dev->devnum); + stream->buf1 = cpu_to_hc32(fusbh200, buf1); + stream->buf2 = cpu_to_hc32(fusbh200, multi); + + /* usbfs wants to report the average usecs per frame tied up + * when transfers on this endpoint are scheduled ... + */ + if (dev->speed == USB_SPEED_FULL) { + interval <<= 3; + stream->usecs = NS_TO_US(usb_calc_bus_time(dev->speed, + is_input, 1, maxp)); + stream->usecs /= 8; + } else { + stream->highspeed = 1; + stream->usecs = HS_USECS_ISO (maxp); + } + bandwidth = stream->usecs * 8; + bandwidth /= interval; + + stream->bandwidth = bandwidth; + stream->udev = dev; + stream->bEndpointAddress = is_input | epnum; + stream->interval = interval; + stream->maxp = maxp; +} + +static struct fusbh200_iso_stream * +iso_stream_find (struct fusbh200_hcd *fusbh200, struct urb *urb) +{ + unsigned epnum; + struct fusbh200_iso_stream *stream; + struct usb_host_endpoint *ep; + unsigned long flags; + + epnum = usb_pipeendpoint (urb->pipe); + if (usb_pipein(urb->pipe)) + ep = urb->dev->ep_in[epnum]; + else + ep = urb->dev->ep_out[epnum]; + + spin_lock_irqsave (&fusbh200->lock, flags); + stream = ep->hcpriv; + + if (unlikely (stream == NULL)) { + stream = iso_stream_alloc(GFP_ATOMIC); + if (likely (stream != NULL)) { + ep->hcpriv = stream; + stream->ep = ep; + iso_stream_init(fusbh200, stream, urb->dev, urb->pipe, + urb->interval); + } + + /* if dev->ep [epnum] is a QH, hw is set */ + } else if (unlikely (stream->hw != NULL)) { + fusbh200_dbg (fusbh200, "dev %s ep%d%s, not iso??\n", + urb->dev->devpath, epnum, + usb_pipein(urb->pipe) ? "in" : "out"); + stream = NULL; + } + + spin_unlock_irqrestore (&fusbh200->lock, flags); + return stream; +} + +/*-------------------------------------------------------------------------*/ + +/* fusbh200_iso_sched ops can be ITD-only or SITD-only */ + +static struct fusbh200_iso_sched * +iso_sched_alloc (unsigned packets, gfp_t mem_flags) +{ + struct fusbh200_iso_sched *iso_sched; + int size = sizeof *iso_sched; + + size += packets * sizeof (struct fusbh200_iso_packet); + iso_sched = kzalloc(size, mem_flags); + if (likely (iso_sched != NULL)) { + INIT_LIST_HEAD (&iso_sched->td_list); + } + return iso_sched; +} + +static inline void +itd_sched_init( + struct fusbh200_hcd *fusbh200, + struct fusbh200_iso_sched *iso_sched, + struct fusbh200_iso_stream *stream, + struct urb *urb +) +{ + unsigned i; + dma_addr_t dma = urb->transfer_dma; + + /* how many uframes are needed for these transfers */ + iso_sched->span = urb->number_of_packets * stream->interval; + + /* figure out per-uframe itd fields that we'll need later + * when we fit new itds into the schedule. + */ + for (i = 0; i < urb->number_of_packets; i++) { + struct fusbh200_iso_packet *uframe = &iso_sched->packet [i]; + unsigned length; + dma_addr_t buf; + u32 trans; + + length = urb->iso_frame_desc [i].length; + buf = dma + urb->iso_frame_desc [i].offset; + + trans = FUSBH200_ISOC_ACTIVE; + trans |= buf & 0x0fff; + if (unlikely (((i + 1) == urb->number_of_packets)) + && !(urb->transfer_flags & URB_NO_INTERRUPT)) + trans |= FUSBH200_ITD_IOC; + trans |= length << 16; + uframe->transaction = cpu_to_hc32(fusbh200, trans); + + /* might need to cross a buffer page within a uframe */ + uframe->bufp = (buf & ~(u64)0x0fff); + buf += length; + if (unlikely ((uframe->bufp != (buf & ~(u64)0x0fff)))) + uframe->cross = 1; + } +} + +static void +iso_sched_free ( + struct fusbh200_iso_stream *stream, + struct fusbh200_iso_sched *iso_sched +) +{ + if (!iso_sched) + return; + // caller must hold fusbh200->lock! + list_splice (&iso_sched->td_list, &stream->free_list); + kfree (iso_sched); +} + +static int +itd_urb_transaction ( + struct fusbh200_iso_stream *stream, + struct fusbh200_hcd *fusbh200, + struct urb *urb, + gfp_t mem_flags +) +{ + struct fusbh200_itd *itd; + dma_addr_t itd_dma; + int i; + unsigned num_itds; + struct fusbh200_iso_sched *sched; + unsigned long flags; + + sched = iso_sched_alloc (urb->number_of_packets, mem_flags); + if (unlikely (sched == NULL)) + return -ENOMEM; + + itd_sched_init(fusbh200, sched, stream, urb); + + if (urb->interval < 8) + num_itds = 1 + (sched->span + 7) / 8; + else + num_itds = urb->number_of_packets; + + /* allocate/init ITDs */ + spin_lock_irqsave (&fusbh200->lock, flags); + for (i = 0; i < num_itds; i++) { + + /* + * Use iTDs from the free list, but not iTDs that may + * still be in use by the hardware. + */ + if (likely(!list_empty(&stream->free_list))) { + itd = list_first_entry(&stream->free_list, + struct fusbh200_itd, itd_list); + if (itd->frame == fusbh200->now_frame) + goto alloc_itd; + list_del (&itd->itd_list); + itd_dma = itd->itd_dma; + } else { + alloc_itd: + spin_unlock_irqrestore (&fusbh200->lock, flags); + itd = dma_pool_alloc (fusbh200->itd_pool, mem_flags, + &itd_dma); + spin_lock_irqsave (&fusbh200->lock, flags); + if (!itd) { + iso_sched_free(stream, sched); + spin_unlock_irqrestore(&fusbh200->lock, flags); + return -ENOMEM; + } + } + + memset (itd, 0, sizeof *itd); + itd->itd_dma = itd_dma; + list_add (&itd->itd_list, &sched->td_list); + } + spin_unlock_irqrestore (&fusbh200->lock, flags); + + /* temporarily store schedule info in hcpriv */ + urb->hcpriv = sched; + urb->error_count = 0; + return 0; +} + +/*-------------------------------------------------------------------------*/ + +static inline int +itd_slot_ok ( + struct fusbh200_hcd *fusbh200, + u32 mod, + u32 uframe, + u8 usecs, + u32 period +) +{ + uframe %= period; + do { + /* can't commit more than uframe_periodic_max usec */ + if (periodic_usecs (fusbh200, uframe >> 3, uframe & 0x7) + > (fusbh200->uframe_periodic_max - usecs)) + return 0; + + /* we know urb->interval is 2^N uframes */ + uframe += period; + } while (uframe < mod); + return 1; +} + +/* + * This scheduler plans almost as far into the future as it has actual + * periodic schedule slots. (Affected by TUNE_FLS, which defaults to + * "as small as possible" to be cache-friendlier.) That limits the size + * transfers you can stream reliably; avoid more than 64 msec per urb. + * Also avoid queue depths of less than fusbh200's worst irq latency (affected + * by the per-urb URB_NO_INTERRUPT hint, the log2_irq_thresh module parameter, + * and other factors); or more than about 230 msec total (for portability, + * given FUSBH200_TUNE_FLS and the slop). Or, write a smarter scheduler! + */ + +#define SCHEDULE_SLOP 80 /* microframes */ + +static int +iso_stream_schedule ( + struct fusbh200_hcd *fusbh200, + struct urb *urb, + struct fusbh200_iso_stream *stream +) +{ + u32 now, next, start, period, span; + int status; + unsigned mod = fusbh200->periodic_size << 3; + struct fusbh200_iso_sched *sched = urb->hcpriv; + + period = urb->interval; + span = sched->span; + + if (span > mod - SCHEDULE_SLOP) { + fusbh200_dbg (fusbh200, "iso request %p too long\n", urb); + status = -EFBIG; + goto fail; + } + + now = fusbh200_read_frame_index(fusbh200) & (mod - 1); + + /* Typical case: reuse current schedule, stream is still active. + * Hopefully there are no gaps from the host falling behind + * (irq delays etc), but if there are we'll take the next + * slot in the schedule, implicitly assuming URB_ISO_ASAP. + */ + if (likely (!list_empty (&stream->td_list))) { + u32 excess; + + /* For high speed devices, allow scheduling within the + * isochronous scheduling threshold. For full speed devices + * and Intel PCI-based controllers, don't (work around for + * Intel ICH9 bug). + */ + if (!stream->highspeed && fusbh200->fs_i_thresh) + next = now + fusbh200->i_thresh; + else + next = now; + + /* Fell behind (by up to twice the slop amount)? + * We decide based on the time of the last currently-scheduled + * slot, not the time of the next available slot. + */ + excess = (stream->next_uframe - period - next) & (mod - 1); + if (excess >= mod - 2 * SCHEDULE_SLOP) + start = next + excess - mod + period * + DIV_ROUND_UP(mod - excess, period); + else + start = next + excess + period; + if (start - now >= mod) { + fusbh200_dbg(fusbh200, "request %p would overflow (%d+%d >= %d)\n", + urb, start - now - period, period, + mod); + status = -EFBIG; + goto fail; + } + } + + /* need to schedule; when's the next (u)frame we could start? + * this is bigger than fusbh200->i_thresh allows; scheduling itself + * isn't free, the slop should handle reasonably slow cpus. it + * can also help high bandwidth if the dma and irq loads don't + * jump until after the queue is primed. + */ + else { + int done = 0; + start = SCHEDULE_SLOP + (now & ~0x07); + + /* NOTE: assumes URB_ISO_ASAP, to limit complexity/bugs */ + + /* find a uframe slot with enough bandwidth. + * Early uframes are more precious because full-speed + * iso IN transfers can't use late uframes, + * and therefore they should be allocated last. + */ + next = start; + start += period; + do { + start--; + /* check schedule: enough space? */ + if (itd_slot_ok(fusbh200, mod, start, + stream->usecs, period)) + done = 1; + } while (start > next && !done); + + /* no room in the schedule */ + if (!done) { + fusbh200_dbg(fusbh200, "iso resched full %p (now %d max %d)\n", + urb, now, now + mod); + status = -ENOSPC; + goto fail; + } + } + + /* Tried to schedule too far into the future? */ + if (unlikely(start - now + span - period + >= mod - 2 * SCHEDULE_SLOP)) { + fusbh200_dbg(fusbh200, "request %p would overflow (%d+%d >= %d)\n", + urb, start - now, span - period, + mod - 2 * SCHEDULE_SLOP); + status = -EFBIG; + goto fail; + } + + stream->next_uframe = start & (mod - 1); + + /* report high speed start in uframes; full speed, in frames */ + urb->start_frame = stream->next_uframe; + if (!stream->highspeed) + urb->start_frame >>= 3; + + /* Make sure scan_isoc() sees these */ + if (fusbh200->isoc_count == 0) + fusbh200->next_frame = now >> 3; + return 0; + + fail: + iso_sched_free(stream, sched); + urb->hcpriv = NULL; + return status; +} + +/*-------------------------------------------------------------------------*/ + +static inline void +itd_init(struct fusbh200_hcd *fusbh200, struct fusbh200_iso_stream *stream, + struct fusbh200_itd *itd) +{ + int i; + + /* it's been recently zeroed */ + itd->hw_next = FUSBH200_LIST_END(fusbh200); + itd->hw_bufp [0] = stream->buf0; + itd->hw_bufp [1] = stream->buf1; + itd->hw_bufp [2] = stream->buf2; + + for (i = 0; i < 8; i++) + itd->index[i] = -1; + + /* All other fields are filled when scheduling */ +} + +static inline void +itd_patch( + struct fusbh200_hcd *fusbh200, + struct fusbh200_itd *itd, + struct fusbh200_iso_sched *iso_sched, + unsigned index, + u16 uframe +) +{ + struct fusbh200_iso_packet *uf = &iso_sched->packet [index]; + unsigned pg = itd->pg; + + // BUG_ON (pg == 6 && uf->cross); + + uframe &= 0x07; + itd->index [uframe] = index; + + itd->hw_transaction[uframe] = uf->transaction; + itd->hw_transaction[uframe] |= cpu_to_hc32(fusbh200, pg << 12); + itd->hw_bufp[pg] |= cpu_to_hc32(fusbh200, uf->bufp & ~(u32)0); + itd->hw_bufp_hi[pg] |= cpu_to_hc32(fusbh200, (u32)(uf->bufp >> 32)); + + /* iso_frame_desc[].offset must be strictly increasing */ + if (unlikely (uf->cross)) { + u64 bufp = uf->bufp + 4096; + + itd->pg = ++pg; + itd->hw_bufp[pg] |= cpu_to_hc32(fusbh200, bufp & ~(u32)0); + itd->hw_bufp_hi[pg] |= cpu_to_hc32(fusbh200, (u32)(bufp >> 32)); + } +} + +static inline void +itd_link (struct fusbh200_hcd *fusbh200, unsigned frame, struct fusbh200_itd *itd) +{ + union fusbh200_shadow *prev = &fusbh200->pshadow[frame]; + __hc32 *hw_p = &fusbh200->periodic[frame]; + union fusbh200_shadow here = *prev; + __hc32 type = 0; + + /* skip any iso nodes which might belong to previous microframes */ + while (here.ptr) { + type = Q_NEXT_TYPE(fusbh200, *hw_p); + if (type == cpu_to_hc32(fusbh200, Q_TYPE_QH)) + break; + prev = periodic_next_shadow(fusbh200, prev, type); + hw_p = shadow_next_periodic(fusbh200, &here, type); + here = *prev; + } + + itd->itd_next = here; + itd->hw_next = *hw_p; + prev->itd = itd; + itd->frame = frame; + wmb (); + *hw_p = cpu_to_hc32(fusbh200, itd->itd_dma | Q_TYPE_ITD); +} + +/* fit urb's itds into the selected schedule slot; activate as needed */ +static void itd_link_urb( + struct fusbh200_hcd *fusbh200, + struct urb *urb, + unsigned mod, + struct fusbh200_iso_stream *stream +) +{ + int packet; + unsigned next_uframe, uframe, frame; + struct fusbh200_iso_sched *iso_sched = urb->hcpriv; + struct fusbh200_itd *itd; + + next_uframe = stream->next_uframe & (mod - 1); + + if (unlikely (list_empty(&stream->td_list))) { + fusbh200_to_hcd(fusbh200)->self.bandwidth_allocated + += stream->bandwidth; + fusbh200_vdbg (fusbh200, + "schedule devp %s ep%d%s-iso period %d start %d.%d\n", + urb->dev->devpath, stream->bEndpointAddress & 0x0f, + (stream->bEndpointAddress & USB_DIR_IN) ? "in" : "out", + urb->interval, + next_uframe >> 3, next_uframe & 0x7); + } + + /* fill iTDs uframe by uframe */ + for (packet = 0, itd = NULL; packet < urb->number_of_packets; ) { + if (itd == NULL) { + /* ASSERT: we have all necessary itds */ + // BUG_ON (list_empty (&iso_sched->td_list)); + + /* ASSERT: no itds for this endpoint in this uframe */ + + itd = list_entry (iso_sched->td_list.next, + struct fusbh200_itd, itd_list); + list_move_tail (&itd->itd_list, &stream->td_list); + itd->stream = stream; + itd->urb = urb; + itd_init (fusbh200, stream, itd); + } + + uframe = next_uframe & 0x07; + frame = next_uframe >> 3; + + itd_patch(fusbh200, itd, iso_sched, packet, uframe); + + next_uframe += stream->interval; + next_uframe &= mod - 1; + packet++; + + /* link completed itds into the schedule */ + if (((next_uframe >> 3) != frame) + || packet == urb->number_of_packets) { + itd_link(fusbh200, frame & (fusbh200->periodic_size - 1), itd); + itd = NULL; + } + } + stream->next_uframe = next_uframe; + + /* don't need that schedule data any more */ + iso_sched_free (stream, iso_sched); + urb->hcpriv = NULL; + + ++fusbh200->isoc_count; + enable_periodic(fusbh200); +} + +#define ISO_ERRS (FUSBH200_ISOC_BUF_ERR | FUSBH200_ISOC_BABBLE | FUSBH200_ISOC_XACTERR) + +/* Process and recycle a completed ITD. Return true iff its urb completed, + * and hence its completion callback probably added things to the hardware + * schedule. + * + * Note that we carefully avoid recycling this descriptor until after any + * completion callback runs, so that it won't be reused quickly. That is, + * assuming (a) no more than two urbs per frame on this endpoint, and also + * (b) only this endpoint's completions submit URBs. It seems some silicon + * corrupts things if you reuse completed descriptors very quickly... + */ +static bool itd_complete(struct fusbh200_hcd *fusbh200, struct fusbh200_itd *itd) +{ + struct urb *urb = itd->urb; + struct usb_iso_packet_descriptor *desc; + u32 t; + unsigned uframe; + int urb_index = -1; + struct fusbh200_iso_stream *stream = itd->stream; + struct usb_device *dev; + bool retval = false; + + /* for each uframe with a packet */ + for (uframe = 0; uframe < 8; uframe++) { + if (likely (itd->index[uframe] == -1)) + continue; + urb_index = itd->index[uframe]; + desc = &urb->iso_frame_desc [urb_index]; + + t = hc32_to_cpup(fusbh200, &itd->hw_transaction [uframe]); + itd->hw_transaction [uframe] = 0; + + /* report transfer status */ + if (unlikely (t & ISO_ERRS)) { + urb->error_count++; + if (t & FUSBH200_ISOC_BUF_ERR) + desc->status = usb_pipein (urb->pipe) + ? -ENOSR /* hc couldn't read */ + : -ECOMM; /* hc couldn't write */ + else if (t & FUSBH200_ISOC_BABBLE) + desc->status = -EOVERFLOW; + else /* (t & FUSBH200_ISOC_XACTERR) */ + desc->status = -EPROTO; + + /* HC need not update length with this error */ + if (!(t & FUSBH200_ISOC_BABBLE)) { + desc->actual_length = fusbh200_itdlen(urb, desc, t); + urb->actual_length += desc->actual_length; + } + } else if (likely ((t & FUSBH200_ISOC_ACTIVE) == 0)) { + desc->status = 0; + desc->actual_length = fusbh200_itdlen(urb, desc, t); + urb->actual_length += desc->actual_length; + } else { + /* URB was too late */ + desc->status = -EXDEV; + } + } + + /* handle completion now? */ + if (likely ((urb_index + 1) != urb->number_of_packets)) + goto done; + + /* ASSERT: it's really the last itd for this urb + list_for_each_entry (itd, &stream->td_list, itd_list) + BUG_ON (itd->urb == urb); + */ + + /* give urb back to the driver; completion often (re)submits */ + dev = urb->dev; + fusbh200_urb_done(fusbh200, urb, 0); + retval = true; + urb = NULL; + + --fusbh200->isoc_count; + disable_periodic(fusbh200); + + if (unlikely(list_is_singular(&stream->td_list))) { + fusbh200_to_hcd(fusbh200)->self.bandwidth_allocated + -= stream->bandwidth; + fusbh200_vdbg (fusbh200, + "deschedule devp %s ep%d%s-iso\n", + dev->devpath, stream->bEndpointAddress & 0x0f, + (stream->bEndpointAddress & USB_DIR_IN) ? "in" : "out"); + } + +done: + itd->urb = NULL; + + /* Add to the end of the free list for later reuse */ + list_move_tail(&itd->itd_list, &stream->free_list); + + /* Recycle the iTDs when the pipeline is empty (ep no longer in use) */ + if (list_empty(&stream->td_list)) { + list_splice_tail_init(&stream->free_list, + &fusbh200->cached_itd_list); + start_free_itds(fusbh200); + } + + return retval; +} + +/*-------------------------------------------------------------------------*/ + +static int itd_submit (struct fusbh200_hcd *fusbh200, struct urb *urb, + gfp_t mem_flags) +{ + int status = -EINVAL; + unsigned long flags; + struct fusbh200_iso_stream *stream; + + /* Get iso_stream head */ + stream = iso_stream_find (fusbh200, urb); + if (unlikely (stream == NULL)) { + fusbh200_dbg (fusbh200, "can't get iso stream\n"); + return -ENOMEM; + } + if (unlikely (urb->interval != stream->interval && + fusbh200_port_speed(fusbh200, 0) == USB_PORT_STAT_HIGH_SPEED)) { + fusbh200_dbg (fusbh200, "can't change iso interval %d --> %d\n", + stream->interval, urb->interval); + goto done; + } + +#ifdef FUSBH200_URB_TRACE + fusbh200_dbg (fusbh200, + "%s %s urb %p ep%d%s len %d, %d pkts %d uframes [%p]\n", + __func__, urb->dev->devpath, urb, + usb_pipeendpoint (urb->pipe), + usb_pipein (urb->pipe) ? "in" : "out", + urb->transfer_buffer_length, + urb->number_of_packets, urb->interval, + stream); +#endif + + /* allocate ITDs w/o locking anything */ + status = itd_urb_transaction (stream, fusbh200, urb, mem_flags); + if (unlikely (status < 0)) { + fusbh200_dbg (fusbh200, "can't init itds\n"); + goto done; + } + + /* schedule ... need to lock */ + spin_lock_irqsave (&fusbh200->lock, flags); + if (unlikely(!HCD_HW_ACCESSIBLE(fusbh200_to_hcd(fusbh200)))) { + status = -ESHUTDOWN; + goto done_not_linked; + } + status = usb_hcd_link_urb_to_ep(fusbh200_to_hcd(fusbh200), urb); + if (unlikely(status)) + goto done_not_linked; + status = iso_stream_schedule(fusbh200, urb, stream); + if (likely (status == 0)) + itd_link_urb (fusbh200, urb, fusbh200->periodic_size << 3, stream); + else + usb_hcd_unlink_urb_from_ep(fusbh200_to_hcd(fusbh200), urb); + done_not_linked: + spin_unlock_irqrestore (&fusbh200->lock, flags); + done: + return status; +} + +/*-------------------------------------------------------------------------*/ + +static void scan_isoc(struct fusbh200_hcd *fusbh200) +{ + unsigned uf, now_frame, frame; + unsigned fmask = fusbh200->periodic_size - 1; + bool modified, live; + + /* + * 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. + */ + if (fusbh200->rh_state >= FUSBH200_RH_RUNNING) { + uf = fusbh200_read_frame_index(fusbh200); + now_frame = (uf >> 3) & fmask; + live = true; + } else { + now_frame = (fusbh200->next_frame - 1) & fmask; + live = false; + } + fusbh200->now_frame = now_frame; + + frame = fusbh200->next_frame; + for (;;) { + union fusbh200_shadow q, *q_p; + __hc32 type, *hw_p; + +restart: + /* scan each element in frame's queue for completions */ + q_p = &fusbh200->pshadow [frame]; + hw_p = &fusbh200->periodic [frame]; + q.ptr = q_p->ptr; + type = Q_NEXT_TYPE(fusbh200, *hw_p); + modified = false; + + while (q.ptr != NULL) { + switch (hc32_to_cpu(fusbh200, type)) { + case Q_TYPE_ITD: + /* If this ITD is still active, leave it for + * later processing ... check the next entry. + * No need to check for activity unless the + * frame is current. + */ + if (frame == now_frame && live) { + rmb(); + for (uf = 0; uf < 8; uf++) { + if (q.itd->hw_transaction[uf] & + ITD_ACTIVE(fusbh200)) + break; + } + if (uf < 8) { + q_p = &q.itd->itd_next; + hw_p = &q.itd->hw_next; + type = Q_NEXT_TYPE(fusbh200, + q.itd->hw_next); + q = *q_p; + break; + } + } + + /* Take finished ITDs out of the schedule + * and process them: recycle, maybe report + * URB completion. HC won't cache the + * pointer for much longer, if at all. + */ + *q_p = q.itd->itd_next; + *hw_p = q.itd->hw_next; + type = Q_NEXT_TYPE(fusbh200, q.itd->hw_next); + wmb(); + modified = itd_complete (fusbh200, q.itd); + q = *q_p; + break; + default: + fusbh200_dbg(fusbh200, "corrupt type %d frame %d shadow %p\n", + type, frame, q.ptr); + // BUG (); + /* FALL THROUGH */ + case Q_TYPE_QH: + case Q_TYPE_FSTN: + /* End of the iTDs and siTDs */ + q.ptr = NULL; + break; + } + + /* assume completion callbacks modify the queue */ + if (unlikely(modified && fusbh200->isoc_count > 0)) + goto restart; + } + + /* Stop when we have reached the current frame */ + if (frame == now_frame) + break; + frame = (frame + 1) & fmask; + } + fusbh200->next_frame = now_frame; +} +/*-------------------------------------------------------------------------*/ +/* + * Display / Set uframe_periodic_max + */ +static ssize_t show_uframe_periodic_max(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct fusbh200_hcd *fusbh200; + int n; + + fusbh200 = hcd_to_fusbh200(bus_to_hcd(dev_get_drvdata(dev))); + n = scnprintf(buf, PAGE_SIZE, "%d\n", fusbh200->uframe_periodic_max); + return n; +} + + +static ssize_t store_uframe_periodic_max(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct fusbh200_hcd *fusbh200; + unsigned uframe_periodic_max; + unsigned frame, uframe; + unsigned short allocated_max; + unsigned long flags; + ssize_t ret; + + fusbh200 = hcd_to_fusbh200(bus_to_hcd(dev_get_drvdata(dev))); + if (kstrtouint(buf, 0, &uframe_periodic_max) < 0) + return -EINVAL; + + if (uframe_periodic_max < 100 || uframe_periodic_max >= 125) { + fusbh200_info(fusbh200, "rejecting invalid request for " + "uframe_periodic_max=%u\n", uframe_periodic_max); + return -EINVAL; + } + + ret = -EINVAL; + + /* + * lock, so that our checking does not race with possible periodic + * bandwidth allocation through submitting new urbs. + */ + spin_lock_irqsave (&fusbh200->lock, flags); + + /* + * for request to decrease max periodic bandwidth, we have to check + * every microframe in the schedule to see whether the decrease is + * possible. + */ + if (uframe_periodic_max < fusbh200->uframe_periodic_max) { + allocated_max = 0; + + for (frame = 0; frame < fusbh200->periodic_size; ++frame) + for (uframe = 0; uframe < 7; ++uframe) + allocated_max = max(allocated_max, + periodic_usecs (fusbh200, frame, uframe)); + + if (allocated_max > uframe_periodic_max) { + fusbh200_info(fusbh200, + "cannot decrease uframe_periodic_max becase " + "periodic bandwidth is already allocated " + "(%u > %u)\n", + allocated_max, uframe_periodic_max); + goto out_unlock; + } + } + + /* increasing is always ok */ + + fusbh200_info(fusbh200, "setting max periodic bandwidth to %u%% " + "(== %u usec/uframe)\n", + 100*uframe_periodic_max/125, uframe_periodic_max); + + if (uframe_periodic_max != 100) + fusbh200_warn(fusbh200, "max periodic bandwidth set is non-standard\n"); + + fusbh200->uframe_periodic_max = uframe_periodic_max; + ret = count; + +out_unlock: + spin_unlock_irqrestore (&fusbh200->lock, flags); + return ret; +} +static DEVICE_ATTR(uframe_periodic_max, 0644, show_uframe_periodic_max, store_uframe_periodic_max); + + +static inline int create_sysfs_files(struct fusbh200_hcd *fusbh200) +{ + struct device *controller = fusbh200_to_hcd(fusbh200)->self.controller; + int i = 0; + + if (i) + goto out; + + i = device_create_file(controller, &dev_attr_uframe_periodic_max); +out: + return i; +} + +static inline void remove_sysfs_files(struct fusbh200_hcd *fusbh200) +{ + struct device *controller = fusbh200_to_hcd(fusbh200)->self.controller; + + device_remove_file(controller, &dev_attr_uframe_periodic_max); +} +/*-------------------------------------------------------------------------*/ + +/* 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 fusbh200_turn_off_all_ports(struct fusbh200_hcd *fusbh200) +{ + u32 __iomem *status_reg = &fusbh200->regs->port_status; + + fusbh200_writel(fusbh200, PORT_RWC_BITS, status_reg); +} + +/* + * Halt HC, turn off all ports, and let the BIOS use the companion controllers. + * Must be called with interrupts enabled and the lock not held. + */ +static void fusbh200_silence_controller(struct fusbh200_hcd *fusbh200) +{ + fusbh200_halt(fusbh200); + + spin_lock_irq(&fusbh200->lock); + fusbh200->rh_state = FUSBH200_RH_HALTED; + fusbh200_turn_off_all_ports(fusbh200); + spin_unlock_irq(&fusbh200->lock); +} + +/* fusbh200_shutdown kick in for silicon on any bus (not just pci, etc). + * This forcibly disables dma and IRQs, helping kexec and other cases + * where the next system software may expect clean state. + */ +static void fusbh200_shutdown(struct usb_hcd *hcd) +{ + struct fusbh200_hcd *fusbh200 = hcd_to_fusbh200(hcd); + + spin_lock_irq(&fusbh200->lock); + fusbh200->shutdown = true; + fusbh200->rh_state = FUSBH200_RH_STOPPING; + fusbh200->enabled_hrtimer_events = 0; + spin_unlock_irq(&fusbh200->lock); + + fusbh200_silence_controller(fusbh200); + + hrtimer_cancel(&fusbh200->hrtimer); +} + +/*-------------------------------------------------------------------------*/ + +/* + * fusbh200_work is called from some interrupts, timers, and so on. + * it calls driver completion functions, after dropping fusbh200->lock. + */ +static void fusbh200_work (struct fusbh200_hcd *fusbh200) +{ + /* another CPU may drop fusbh200->lock during a schedule scan while + * it reports urb completions. this flag guards against bogus + * attempts at re-entrant schedule scanning. + */ + if (fusbh200->scanning) { + fusbh200->need_rescan = true; + return; + } + fusbh200->scanning = true; + + rescan: + fusbh200->need_rescan = false; + if (fusbh200->async_count) + scan_async(fusbh200); + if (fusbh200->intr_count > 0) + scan_intr(fusbh200); + if (fusbh200->isoc_count > 0) + scan_isoc(fusbh200); + if (fusbh200->need_rescan) + goto rescan; + fusbh200->scanning = false; + + /* 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. + */ + turn_on_io_watchdog(fusbh200); +} + +/* + * Called when the fusbh200_hcd module is removed. + */ +static void fusbh200_stop (struct usb_hcd *hcd) +{ + struct fusbh200_hcd *fusbh200 = hcd_to_fusbh200 (hcd); + + fusbh200_dbg (fusbh200, "stop\n"); + + /* no more interrupts ... */ + + spin_lock_irq(&fusbh200->lock); + fusbh200->enabled_hrtimer_events = 0; + spin_unlock_irq(&fusbh200->lock); + + fusbh200_quiesce(fusbh200); + fusbh200_silence_controller(fusbh200); + fusbh200_reset (fusbh200); + + hrtimer_cancel(&fusbh200->hrtimer); + remove_sysfs_files(fusbh200); + remove_debug_files (fusbh200); + + /* root hub is shut down separately (first, when possible) */ + spin_lock_irq (&fusbh200->lock); + end_free_itds(fusbh200); + spin_unlock_irq (&fusbh200->lock); + fusbh200_mem_cleanup (fusbh200); + +#ifdef FUSBH200_STATS + fusbh200_dbg(fusbh200, "irq normal %ld err %ld iaa %ld (lost %ld)\n", + fusbh200->stats.normal, fusbh200->stats.error, fusbh200->stats.iaa, + fusbh200->stats.lost_iaa); + fusbh200_dbg (fusbh200, "complete %ld unlink %ld\n", + fusbh200->stats.complete, fusbh200->stats.unlink); +#endif + + dbg_status (fusbh200, "fusbh200_stop completed", + fusbh200_readl(fusbh200, &fusbh200->regs->status)); +} + +/* one-time init, only for memory state */ +static int hcd_fusbh200_init(struct usb_hcd *hcd) +{ + struct fusbh200_hcd *fusbh200 = hcd_to_fusbh200(hcd); + u32 temp; + int retval; + u32 hcc_params; + struct fusbh200_qh_hw *hw; + + spin_lock_init(&fusbh200->lock); + + /* + * keep io watchdog by default, those good HCDs could turn off it later + */ + fusbh200->need_io_watchdog = 1; + + hrtimer_init(&fusbh200->hrtimer, CLOCK_MONOTONIC, HRTIMER_MODE_ABS); + fusbh200->hrtimer.function = fusbh200_hrtimer_func; + fusbh200->next_hrtimer_event = FUSBH200_HRTIMER_NO_EVENT; + + hcc_params = fusbh200_readl(fusbh200, &fusbh200->caps->hcc_params); + + /* + * by default set standard 80% (== 100 usec/uframe) max periodic + * bandwidth as required by USB 2.0 + */ + fusbh200->uframe_periodic_max = 100; + + /* + * hw default: 1K periodic list heads, one per frame. + * periodic_size can shrink by USBCMD update if hcc_params allows. + */ + fusbh200->periodic_size = DEFAULT_I_TDPS; + INIT_LIST_HEAD(&fusbh200->intr_qh_list); + INIT_LIST_HEAD(&fusbh200->cached_itd_list); + + if (HCC_PGM_FRAMELISTLEN(hcc_params)) { + /* periodic schedule size can be smaller than default */ + switch (FUSBH200_TUNE_FLS) { + case 0: fusbh200->periodic_size = 1024; break; + case 1: fusbh200->periodic_size = 512; break; + case 2: fusbh200->periodic_size = 256; break; + default: BUG(); + } + } + if ((retval = fusbh200_mem_init(fusbh200, GFP_KERNEL)) < 0) + return retval; + + /* controllers may cache some of the periodic schedule ... */ + fusbh200->i_thresh = 2; + + /* + * 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. + */ + fusbh200->async->qh_next.qh = NULL; + hw = fusbh200->async->hw; + hw->hw_next = QH_NEXT(fusbh200, fusbh200->async->qh_dma); + hw->hw_info1 = cpu_to_hc32(fusbh200, QH_HEAD); + hw->hw_token = cpu_to_hc32(fusbh200, QTD_STS_HALT); + hw->hw_qtd_next = FUSBH200_LIST_END(fusbh200); + fusbh200->async->qh_state = QH_STATE_LINKED; + hw->hw_alt_next = QTD_NEXT(fusbh200, fusbh200->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; + } + fusbh200_dbg(fusbh200, "park %d\n", park); + } + if (HCC_PGM_FRAMELISTLEN(hcc_params)) { + /* periodic schedule size can be smaller than default */ + temp &= ~(3 << 2); + temp |= (FUSBH200_TUNE_FLS << 2); + } + fusbh200->command = temp; + + /* Accept arbitrarily long scatter-gather lists */ + if (!(hcd->driver->flags & HCD_LOCAL_MEM)) + hcd->self.sg_tablesize = ~0; + return 0; +} + +/* start HC running; it's halted, hcd_fusbh200_init() has been run (once) */ +static int fusbh200_run (struct usb_hcd *hcd) +{ + struct fusbh200_hcd *fusbh200 = hcd_to_fusbh200 (hcd); + u32 temp; + u32 hcc_params; + + hcd->uses_new_polling = 1; + + /* EHCI spec section 4.1 */ + + fusbh200_writel(fusbh200, fusbh200->periodic_dma, &fusbh200->regs->frame_list); + fusbh200_writel(fusbh200, (u32)fusbh200->async->qh_dma, &fusbh200->regs->async_next); + + /* + * hcc_params controls whether fusbh200->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 = fusbh200_readl(fusbh200, &fusbh200->caps->hcc_params); + + // Philips, Intel, and maybe others need CMD_RUN before the + // root hub will detect new devices (why?); NEC doesn't + fusbh200->command &= ~(CMD_IAAD|CMD_PSE|CMD_ASE|CMD_RESET); + fusbh200->command |= CMD_RUN; + fusbh200_writel(fusbh200, fusbh200->command, &fusbh200->regs->command); + dbg_cmd (fusbh200, "init", fusbh200->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.) + * + * Turning on the CF flag will transfer ownership of all ports + * from the companions to the EHCI controller. If any of the + * companions are in the middle of a port reset at the time, it + * could cause trouble. Write-locking ehci_cf_port_reset_rwsem + * guarantees that no resets are in progress. After we set CF, + * a short delay lets the hardware catch up; new resets shouldn't + * be started before the port switching actions could complete. + */ + down_write(&ehci_cf_port_reset_rwsem); + fusbh200->rh_state = FUSBH200_RH_RUNNING; + fusbh200_readl(fusbh200, &fusbh200->regs->command); /* unblock posted writes */ + msleep(5); + up_write(&ehci_cf_port_reset_rwsem); + fusbh200->last_periodic_enable = ktime_get_real(); + + temp = HC_VERSION(fusbh200, fusbh200_readl(fusbh200, &fusbh200->caps->hc_capbase)); + fusbh200_info (fusbh200, + "USB %x.%x started, EHCI %x.%02x\n", + ((fusbh200->sbrn & 0xf0)>>4), (fusbh200->sbrn & 0x0f), + temp >> 8, temp & 0xff); + + fusbh200_writel(fusbh200, INTR_MASK, + &fusbh200->regs->intr_enable); /* Turn On Interrupts */ + + /* GRR this is run-once init(), being done every time the HC starts. + * So long as they're part of class devices, we can't do it init() + * since the class device isn't created that early. + */ + create_debug_files(fusbh200); + create_sysfs_files(fusbh200); + + return 0; +} + +static int fusbh200_setup(struct usb_hcd *hcd) +{ + struct fusbh200_hcd *fusbh200 = hcd_to_fusbh200(hcd); + int retval; + + fusbh200->regs = (void __iomem *)fusbh200->caps + + HC_LENGTH(fusbh200, fusbh200_readl(fusbh200, &fusbh200->caps->hc_capbase)); + dbg_hcs_params(fusbh200, "reset"); + dbg_hcc_params(fusbh200, "reset"); + + /* cache this readonly data; minimize chip reads */ + fusbh200->hcs_params = fusbh200_readl(fusbh200, &fusbh200->caps->hcs_params); + + fusbh200->sbrn = HCD_USB2; + + /* data structure init */ + retval = hcd_fusbh200_init(hcd); + if (retval) + return retval; + + retval = fusbh200_halt(fusbh200); + if (retval) + return retval; + + fusbh200_reset(fusbh200); + + return 0; +} + +/*-------------------------------------------------------------------------*/ + +static irqreturn_t fusbh200_irq (struct usb_hcd *hcd) +{ + struct fusbh200_hcd *fusbh200 = hcd_to_fusbh200 (hcd); + u32 status, masked_status, pcd_status = 0, cmd; + int bh; + + spin_lock (&fusbh200->lock); + + status = fusbh200_readl(fusbh200, &fusbh200->regs->status); + + /* e.g. cardbus physical eject */ + if (status == ~(u32) 0) { + fusbh200_dbg (fusbh200, "device removed\n"); + goto dead; + } + + /* + * We don't use STS_FLR, but some controllers don't like it to + * remain on, so mask it out along with the other status bits. + */ + masked_status = status & (INTR_MASK | STS_FLR); + + /* Shared IRQ? */ + if (!masked_status || unlikely(fusbh200->rh_state == FUSBH200_RH_HALTED)) { + spin_unlock(&fusbh200->lock); + return IRQ_NONE; + } + + /* clear (just) interrupts */ + fusbh200_writel(fusbh200, masked_status, &fusbh200->regs->status); + cmd = fusbh200_readl(fusbh200, &fusbh200->regs->command); + bh = 0; + +#ifdef VERBOSE_DEBUG + /* unrequested/ignored: Frame List Rollover */ + dbg_status (fusbh200, "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)) { + if (likely ((status & STS_ERR) == 0)) + COUNT (fusbh200->stats.normal); + else + COUNT (fusbh200->stats.error); + bh = 1; + } + + /* complete the unlinking of some qh [4.15.2.3] */ + if (status & STS_IAA) { + + /* Turn off the IAA watchdog */ + fusbh200->enabled_hrtimer_events &= ~BIT(FUSBH200_HRTIMER_IAA_WATCHDOG); + + /* + * Mild optimization: Allow another IAAD to reset the + * hrtimer, if one occurs before the next expiration. + * In theory we could always cancel the hrtimer, but + * tests show that about half the time it will be reset + * for some other event anyway. + */ + if (fusbh200->next_hrtimer_event == FUSBH200_HRTIMER_IAA_WATCHDOG) + ++fusbh200->next_hrtimer_event; + + /* guard against (alleged) silicon errata */ + if (cmd & CMD_IAAD) + fusbh200_dbg(fusbh200, "IAA with IAAD still set?\n"); + if (fusbh200->async_iaa) { + COUNT(fusbh200->stats.iaa); + end_unlink_async(fusbh200); + } else + fusbh200_dbg(fusbh200, "IAA with nothing unlinked?\n"); + } + + /* remote wakeup [4.3.1] */ + if (status & STS_PCD) { + int pstatus; + u32 __iomem *status_reg = &fusbh200->regs->port_status; + + /* kick root hub later */ + pcd_status = status; + + /* resume root hub? */ + if (fusbh200->rh_state == FUSBH200_RH_SUSPENDED) + usb_hcd_resume_root_hub(hcd); + + pstatus = fusbh200_readl(fusbh200, status_reg); + + if (test_bit(0, &fusbh200->suspended_ports) && + ((pstatus & PORT_RESUME) || + !(pstatus & PORT_SUSPEND)) && + (pstatus & PORT_PE) && + fusbh200->reset_done[0] == 0) { + + /* start 20 msec resume signaling from this port, + * and make khubd collect PORT_STAT_C_SUSPEND to + * stop that signaling. Use 5 ms extra for safety, + * like usb_port_resume() does. + */ + fusbh200->reset_done[0] = jiffies + msecs_to_jiffies(25); + set_bit(0, &fusbh200->resuming_ports); + fusbh200_dbg (fusbh200, "port 1 remote wakeup\n"); + mod_timer(&hcd->rh_timer, fusbh200->reset_done[0]); + } + } + + /* PCI errors [4.15.2.4] */ + if (unlikely ((status & STS_FATAL) != 0)) { + fusbh200_err(fusbh200, "fatal error\n"); + dbg_cmd(fusbh200, "fatal", cmd); + dbg_status(fusbh200, "fatal", status); +dead: + usb_hc_died(hcd); + + /* Don't let the controller do anything more */ + fusbh200->shutdown = true; + fusbh200->rh_state = FUSBH200_RH_STOPPING; + fusbh200->command &= ~(CMD_RUN | CMD_ASE | CMD_PSE); + fusbh200_writel(fusbh200, fusbh200->command, &fusbh200->regs->command); + fusbh200_writel(fusbh200, 0, &fusbh200->regs->intr_enable); + fusbh200_handle_controller_death(fusbh200); + + /* Handle completions when the controller stops */ + bh = 0; + } + + if (bh) + fusbh200_work (fusbh200); + spin_unlock (&fusbh200->lock); + if (pcd_status) + usb_hcd_poll_rh_status(hcd); + return IRQ_HANDLED; +} + +/*-------------------------------------------------------------------------*/ + +/* + * 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 fusbh200_urb_enqueue ( + struct usb_hcd *hcd, + struct urb *urb, + gfp_t mem_flags +) { + struct fusbh200_hcd *fusbh200 = hcd_to_fusbh200 (hcd); + struct list_head qtd_list; + + INIT_LIST_HEAD (&qtd_list); + + switch (usb_pipetype (urb->pipe)) { + case PIPE_CONTROL: + /* qh_completions() code doesn't handle all the fault cases + * in multi-TD control transfers. Even 1KB is rare anyway. + */ + if (urb->transfer_buffer_length > (16 * 1024)) + return -EMSGSIZE; + /* FALLTHROUGH */ + /* case PIPE_BULK: */ + default: + if (!qh_urb_transaction (fusbh200, urb, &qtd_list, mem_flags)) + return -ENOMEM; + return submit_async(fusbh200, urb, &qtd_list, mem_flags); + + case PIPE_INTERRUPT: + if (!qh_urb_transaction (fusbh200, urb, &qtd_list, mem_flags)) + return -ENOMEM; + return intr_submit(fusbh200, urb, &qtd_list, mem_flags); + + case PIPE_ISOCHRONOUS: + return itd_submit (fusbh200, urb, mem_flags); + } +} + +/* remove from hardware lists + * completions normally happen asynchronously + */ + +static int fusbh200_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status) +{ + struct fusbh200_hcd *fusbh200 = hcd_to_fusbh200 (hcd); + struct fusbh200_qh *qh; + unsigned long flags; + int rc; + + spin_lock_irqsave (&fusbh200->lock, flags); + rc = usb_hcd_check_unlink_urb(hcd, urb, status); + if (rc) + goto done; + + switch (usb_pipetype (urb->pipe)) { + // case PIPE_CONTROL: + // case PIPE_BULK: + default: + qh = (struct fusbh200_qh *) urb->hcpriv; + if (!qh) + break; + switch (qh->qh_state) { + case QH_STATE_LINKED: + case QH_STATE_COMPLETING: + start_unlink_async(fusbh200, qh); + break; + case QH_STATE_UNLINK: + case QH_STATE_UNLINK_WAIT: + /* already started */ + break; + case QH_STATE_IDLE: + /* QH might be waiting for a Clear-TT-Buffer */ + qh_completions(fusbh200, qh); + break; + } + break; + + case PIPE_INTERRUPT: + qh = (struct fusbh200_qh *) urb->hcpriv; + if (!qh) + break; + switch (qh->qh_state) { + case QH_STATE_LINKED: + case QH_STATE_COMPLETING: + start_unlink_intr(fusbh200, qh); + break; + case QH_STATE_IDLE: + qh_completions (fusbh200, qh); + break; + default: + fusbh200_dbg (fusbh200, "bogus qh %p state %d\n", + qh, qh->qh_state); + goto done; + } + break; + + case PIPE_ISOCHRONOUS: + // itd... + + // wait till next completion, do it then. + // completion irqs can wait up to 1024 msec, + break; + } +done: + spin_unlock_irqrestore (&fusbh200->lock, flags); + return rc; +} + +/*-------------------------------------------------------------------------*/ + +// bulk qh holds the data toggle + +static void +fusbh200_endpoint_disable (struct usb_hcd *hcd, struct usb_host_endpoint *ep) +{ + struct fusbh200_hcd *fusbh200 = hcd_to_fusbh200 (hcd); + unsigned long flags; + struct fusbh200_qh *qh, *tmp; + + /* ASSERT: any requests/urbs are being unlinked */ + /* ASSERT: nobody can be submitting urbs for this any more */ + +rescan: + spin_lock_irqsave (&fusbh200->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 == NULL) { + struct fusbh200_iso_stream *stream = ep->hcpriv; + + if (!list_empty(&stream->td_list)) + goto idle_timeout; + + /* BUG_ON(!list_empty(&stream->free_list)); */ + kfree(stream); + goto done; + } + + if (fusbh200->rh_state < FUSBH200_RH_RUNNING) + qh->qh_state = QH_STATE_IDLE; + switch (qh->qh_state) { + case QH_STATE_LINKED: + case QH_STATE_COMPLETING: + for (tmp = fusbh200->async->qh_next.qh; + tmp && tmp != qh; + tmp = tmp->qh_next.qh) + continue; + /* periodic qh self-unlinks on empty, and a COMPLETING qh + * may already be unlinked. + */ + if (tmp) + start_unlink_async(fusbh200, qh); + /* FALL THROUGH */ + case QH_STATE_UNLINK: /* wait for hw to finish? */ + case QH_STATE_UNLINK_WAIT: +idle_timeout: + spin_unlock_irqrestore (&fusbh200->lock, flags); + schedule_timeout_uninterruptible(1); + goto rescan; + case QH_STATE_IDLE: /* fully unlinked */ + if (qh->clearing_tt) + goto idle_timeout; + if (list_empty (&qh->qtd_list)) { + qh_destroy(fusbh200, qh); + break; + } + /* else FALL THROUGH */ + default: + /* caller was supposed to have unlinked any requests; + * that's not our job. just leak this memory. + */ + fusbh200_err (fusbh200, "qh %p (#%02x) state %d%s\n", + qh, ep->desc.bEndpointAddress, qh->qh_state, + list_empty (&qh->qtd_list) ? "" : "(has tds)"); + break; + } + done: + ep->hcpriv = NULL; + spin_unlock_irqrestore (&fusbh200->lock, flags); +} + +static void +fusbh200_endpoint_reset(struct usb_hcd *hcd, struct usb_host_endpoint *ep) +{ + struct fusbh200_hcd *fusbh200 = hcd_to_fusbh200(hcd); + struct fusbh200_qh *qh; + int eptype = usb_endpoint_type(&ep->desc); + int epnum = usb_endpoint_num(&ep->desc); + int is_out = usb_endpoint_dir_out(&ep->desc); + unsigned long flags; + + if (eptype != USB_ENDPOINT_XFER_BULK && eptype != USB_ENDPOINT_XFER_INT) + return; + + spin_lock_irqsave(&fusbh200->lock, flags); + qh = ep->hcpriv; + + /* For Bulk and Interrupt endpoints we maintain the toggle state + * in the hardware; the toggle bits in udev aren't used at all. + * When an endpoint is reset by usb_clear_halt() we must reset + * the toggle bit in the QH. + */ + if (qh) { + usb_settoggle(qh->dev, epnum, is_out, 0); + if (!list_empty(&qh->qtd_list)) { + WARN_ONCE(1, "clear_halt for a busy endpoint\n"); + } else if (qh->qh_state == QH_STATE_LINKED || + qh->qh_state == QH_STATE_COMPLETING) { + + /* The toggle value in the QH can't be updated + * while the QH is active. Unlink it now; + * re-linking will call qh_refresh(). + */ + if (eptype == USB_ENDPOINT_XFER_BULK) + start_unlink_async(fusbh200, qh); + else + start_unlink_intr(fusbh200, qh); + } + } + spin_unlock_irqrestore(&fusbh200->lock, flags); +} + +static int fusbh200_get_frame (struct usb_hcd *hcd) +{ + struct fusbh200_hcd *fusbh200 = hcd_to_fusbh200 (hcd); + return (fusbh200_read_frame_index(fusbh200) >> 3) % fusbh200->periodic_size; +} + +/*-------------------------------------------------------------------------*/ + +/* + * The EHCI in ChipIdea HDRC cannot be a separate module or device, + * because its registers (and irq) are shared between host/gadget/otg + * functions and in order to facilitate role switching we cannot + * give the fusbh200 driver exclusive access to those. + */ +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_AUTHOR (DRIVER_AUTHOR); +MODULE_LICENSE ("GPL"); + +static const struct hc_driver fusbh200_fusbh200_hc_driver = { + .description = hcd_name, + .product_desc = "Faraday USB2.0 Host Controller", + .hcd_priv_size = sizeof(struct fusbh200_hcd), + + /* + * generic hardware linkage + */ + .irq = fusbh200_irq, + .flags = HCD_MEMORY | HCD_USB2, + + /* + * basic lifecycle operations + */ + .reset = hcd_fusbh200_init, + .start = fusbh200_run, + .stop = fusbh200_stop, + .shutdown = fusbh200_shutdown, + + /* + * managing i/o requests and associated device resources + */ + .urb_enqueue = fusbh200_urb_enqueue, + .urb_dequeue = fusbh200_urb_dequeue, + .endpoint_disable = fusbh200_endpoint_disable, + .endpoint_reset = fusbh200_endpoint_reset, + + /* + * scheduling support + */ + .get_frame_number = fusbh200_get_frame, + + /* + * root hub support + */ + .hub_status_data = fusbh200_hub_status_data, + .hub_control = fusbh200_hub_control, + .bus_suspend = fusbh200_bus_suspend, + .bus_resume = fusbh200_bus_resume, + + .relinquish_port = fusbh200_relinquish_port, + .port_handed_over = fusbh200_port_handed_over, + + .clear_tt_buffer_complete = fusbh200_clear_tt_buffer_complete, +}; + +static void fusbh200_init(struct fusbh200_hcd *fusbh200) +{ + u32 reg; + + reg = fusbh200_readl(fusbh200, &fusbh200->regs->bmcsr); + reg |= BMCSR_INT_POLARITY; + reg &= ~BMCSR_VBUS_OFF; + fusbh200_writel(fusbh200, reg, &fusbh200->regs->bmcsr); + + reg = fusbh200_readl(fusbh200, &fusbh200->regs->bmier); + fusbh200_writel(fusbh200, reg | BMIER_OVC_EN | BMIER_VBUS_ERR_EN, + &fusbh200->regs->bmier); +} + +/** + * fusbh200_hcd_probe - initialize faraday FUSBH200 HCDs + * + * Allocates basic resources for this USB host controller, and + * then invokes the start() method for the HCD associated with it + * through the hotplug entry's driver_data. + */ +static int fusbh200_hcd_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct usb_hcd *hcd; + struct resource *res; + int irq; + int retval = -ENODEV; + struct fusbh200_hcd *fusbh200; + + if (usb_disabled()) + return -ENODEV; + + pdev->dev.power.power_state = PMSG_ON; + + res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (!res) { + dev_err(dev, + "Found HC with no IRQ. Check %s setup!\n", + dev_name(dev)); + return -ENODEV; + } + + irq = res->start; + + hcd = usb_create_hcd(&fusbh200_fusbh200_hc_driver, dev, + dev_name(dev)); + if (!hcd) { + dev_err(dev, "failed to create hcd with err %d\n", retval); + retval = -ENOMEM; + goto fail_create_hcd; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(dev, + "Found HC with no register addr. Check %s setup!\n", + dev_name(dev)); + retval = -ENODEV; + goto fail_request_resource; + } + + hcd->rsrc_start = res->start; + hcd->rsrc_len = resource_size(res); + hcd->has_tt = 1; + + if (!request_mem_region(hcd->rsrc_start, hcd->rsrc_len, + fusbh200_fusbh200_hc_driver.description)) { + dev_dbg(dev, "controller already in use\n"); + retval = -EBUSY; + goto fail_request_resource; + } + + res = platform_get_resource(pdev, IORESOURCE_IO, 0); + if (!res) { + dev_err(dev, + "Found HC with no register addr. Check %s setup!\n", + dev_name(dev)); + retval = -ENODEV; + goto fail_request_resource; + } + + hcd->regs = ioremap_nocache(res->start, resource_size(res)); + if (hcd->regs == NULL) { + dev_dbg(dev, "error mapping memory\n"); + retval = -EFAULT; + goto fail_ioremap; + } + + fusbh200 = hcd_to_fusbh200(hcd); + + fusbh200->caps = hcd->regs; + + retval = fusbh200_setup(hcd); + if (retval) + goto fail_add_hcd; + + fusbh200_init(fusbh200); + + retval = usb_add_hcd(hcd, irq, IRQF_SHARED); + if (retval) { + dev_err(dev, "failed to add hcd with err %d\n", retval); + goto fail_add_hcd; + } + + return retval; + +fail_add_hcd: + iounmap(hcd->regs); +fail_ioremap: + release_mem_region(hcd->rsrc_start, hcd->rsrc_len); +fail_request_resource: + usb_put_hcd(hcd); +fail_create_hcd: + dev_err(dev, "init %s fail, %d\n", dev_name(dev), retval); + return retval; +} + +/** + * fusbh200_hcd_remove - shutdown processing for EHCI HCDs + * @dev: USB Host Controller being removed + * + * Reverses the effect of fotg2xx_usb_hcd_probe(), first invoking + * the HCD's stop() method. It is always called from a thread + * context, normally "rmmod", "apmd", or something similar. + */ +static int fusbh200_hcd_remove(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct usb_hcd *hcd = dev_get_drvdata(dev); + + if (!hcd) + return 0; + + usb_remove_hcd(hcd); + iounmap(hcd->regs); + release_mem_region(hcd->rsrc_start, hcd->rsrc_len); + usb_put_hcd(hcd); + + return 0; +} + +static struct platform_driver fusbh200_hcd_fusbh200_driver = { + .driver = { + .name = "fusbh200", + }, + .probe = fusbh200_hcd_probe, + .remove = fusbh200_hcd_remove, +}; + +static int __init fusbh200_hcd_init(void) +{ + int retval = 0; + + if (usb_disabled()) + return -ENODEV; + + printk(KERN_INFO "%s: " DRIVER_DESC "\n", hcd_name); + set_bit(USB_EHCI_LOADED, &usb_hcds_loaded); + if (test_bit(USB_UHCI_LOADED, &usb_hcds_loaded) || + test_bit(USB_OHCI_LOADED, &usb_hcds_loaded)) + printk(KERN_WARNING "Warning! fusbh200_hcd should always be loaded" + " before uhci_hcd and ohci_hcd, not after\n"); + + pr_debug("%s: block sizes: qh %Zd qtd %Zd itd %Zd\n", + hcd_name, + sizeof(struct fusbh200_qh), sizeof(struct fusbh200_qtd), + sizeof(struct fusbh200_itd)); + +#ifdef DEBUG + fusbh200_debug_root = debugfs_create_dir("fusbh200", usb_debug_root); + if (!fusbh200_debug_root) { + retval = -ENOENT; + goto err_debug; + } +#endif + + retval = platform_driver_register(&fusbh200_hcd_fusbh200_driver); + if (retval < 0) + goto clean; + return retval; + + platform_driver_unregister(&fusbh200_hcd_fusbh200_driver); +clean: +#ifdef DEBUG + debugfs_remove(fusbh200_debug_root); + fusbh200_debug_root = NULL; +err_debug: +#endif + clear_bit(USB_EHCI_LOADED, &usb_hcds_loaded); + return retval; +} +module_init(fusbh200_hcd_init); + +static void __exit fusbh200_hcd_cleanup(void) +{ + platform_driver_unregister(&fusbh200_hcd_fusbh200_driver); +#ifdef DEBUG + debugfs_remove(fusbh200_debug_root); +#endif + clear_bit(USB_EHCI_LOADED, &usb_hcds_loaded); +} +module_exit(fusbh200_hcd_cleanup); diff --git a/drivers/usb/host/fusbh200.h b/drivers/usb/host/fusbh200.h new file mode 100644 index 0000000..797c9e8 --- /dev/null +++ b/drivers/usb/host/fusbh200.h @@ -0,0 +1,743 @@ +#ifndef __LINUX_FUSBH200_H +#define __LINUX_FUSBH200_H + +/* definitions used for the EHCI driver */ + +/* + * __hc32 and __hc16 are "Host Controller" types, they may be equivalent to + * __leXX (normally) or __beXX (given FUSBH200_BIG_ENDIAN_DESC), depending on + * the host controller implementation. + * + * To facilitate the strongest possible byte-order checking from "sparse" + * and so on, we use __leXX unless that's not practical. + */ +#define __hc32 __le32 +#define __hc16 __le16 + +/* statistics can be kept for tuning/monitoring */ +struct fusbh200_stats { + /* irq usage */ + unsigned long normal; + unsigned long error; + unsigned long iaa; + unsigned long lost_iaa; + + /* termination of urbs from core */ + unsigned long complete; + unsigned long unlink; +}; + +/* fusbh200_hcd->lock guards shared data against other CPUs: + * fusbh200_hcd: async, unlink, periodic (and shadow), ... + * usb_host_endpoint: hcpriv + * fusbh200_qh: qh_next, qtd_list + * fusbh200_qtd: qtd_list + * + * Also, hold this lock when talking to HC registers or + * when updating hw_* fields in shared qh/qtd/... structures. + */ + +#define FUSBH200_MAX_ROOT_PORTS 1 /* see HCS_N_PORTS */ + +/* + * fusbh200_rh_state values of FUSBH200_RH_RUNNING or above mean that the + * controller may be doing DMA. Lower values mean there's no DMA. + */ +enum fusbh200_rh_state { + FUSBH200_RH_HALTED, + FUSBH200_RH_SUSPENDED, + FUSBH200_RH_RUNNING, + FUSBH200_RH_STOPPING +}; + +/* + * Timer events, ordered by increasing delay length. + * Always update event_delays_ns[] and event_handlers[] (defined in + * ehci-timer.c) in parallel with this list. + */ +enum fusbh200_hrtimer_event { + FUSBH200_HRTIMER_POLL_ASS, /* Poll for async schedule off */ + FUSBH200_HRTIMER_POLL_PSS, /* Poll for periodic schedule off */ + FUSBH200_HRTIMER_POLL_DEAD, /* Wait for dead controller to stop */ + FUSBH200_HRTIMER_UNLINK_INTR, /* Wait for interrupt QH unlink */ + FUSBH200_HRTIMER_FREE_ITDS, /* Wait for unused iTDs and siTDs */ + FUSBH200_HRTIMER_ASYNC_UNLINKS, /* Unlink empty async QHs */ + FUSBH200_HRTIMER_IAA_WATCHDOG, /* Handle lost IAA interrupts */ + FUSBH200_HRTIMER_DISABLE_PERIODIC, /* Wait to disable periodic sched */ + FUSBH200_HRTIMER_DISABLE_ASYNC, /* Wait to disable async sched */ + FUSBH200_HRTIMER_IO_WATCHDOG, /* Check for missing IRQs */ + FUSBH200_HRTIMER_NUM_EVENTS /* Must come last */ +}; +#define FUSBH200_HRTIMER_NO_EVENT 99 + +struct fusbh200_hcd { /* one per controller */ + /* timing support */ + enum fusbh200_hrtimer_event next_hrtimer_event; + unsigned enabled_hrtimer_events; + ktime_t hr_timeouts[FUSBH200_HRTIMER_NUM_EVENTS]; + struct hrtimer hrtimer; + + int PSS_poll_count; + int ASS_poll_count; + int died_poll_count; + + /* glue to PCI and HCD framework */ + struct fusbh200_caps __iomem *caps; + struct fusbh200_regs __iomem *regs; + struct fusbh200_dbg_port __iomem *debug; + + __u32 hcs_params; /* cached register copy */ + spinlock_t lock; + enum fusbh200_rh_state rh_state; + + /* general schedule support */ + bool scanning:1; + bool need_rescan:1; + bool intr_unlinking:1; + bool async_unlinking:1; + bool shutdown:1; + struct fusbh200_qh *qh_scan_next; + + /* async schedule support */ + struct fusbh200_qh *async; + struct fusbh200_qh *dummy; /* For AMD quirk use */ + struct fusbh200_qh *async_unlink; + struct fusbh200_qh *async_unlink_last; + struct fusbh200_qh *async_iaa; + unsigned async_unlink_cycle; + unsigned async_count; /* async activity count */ + + /* periodic schedule support */ +#define DEFAULT_I_TDPS 1024 /* some HCs can do less */ + unsigned periodic_size; + __hc32 *periodic; /* hw periodic table */ + dma_addr_t periodic_dma; + struct list_head intr_qh_list; + unsigned i_thresh; /* uframes HC might cache */ + + union fusbh200_shadow *pshadow; /* mirror hw periodic table */ + struct fusbh200_qh *intr_unlink; + struct fusbh200_qh *intr_unlink_last; + unsigned intr_unlink_cycle; + unsigned now_frame; /* frame from HC hardware */ + unsigned next_frame; /* scan periodic, start here */ + unsigned intr_count; /* intr activity count */ + unsigned isoc_count; /* isoc activity count */ + unsigned periodic_count; /* periodic activity count */ + unsigned uframe_periodic_max; /* max periodic time per uframe */ + + + /* list of itds completed while now_frame was still active */ + struct list_head cached_itd_list; + struct fusbh200_itd *last_itd_to_free; + + /* per root hub port */ + unsigned long reset_done [FUSBH200_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 */ + unsigned long owned_ports; /* which ports are + owned by the companion during a bus suspend */ + unsigned long port_c_suspend; /* which ports have + the change-suspend feature turned on */ + unsigned long suspended_ports; /* which ports are + suspended */ + unsigned long resuming_ports; /* which ports have + started to resume */ + + /* per-HC memory pools (could be per-bus, but ...) */ + struct dma_pool *qh_pool; /* qh per active urb */ + struct dma_pool *qtd_pool; /* one or more per qh */ + struct dma_pool *itd_pool; /* itd per iso urb */ + + unsigned random_frame; + unsigned long next_statechange; + ktime_t last_periodic_enable; + u32 command; + + /* SILICON QUIRKS */ + unsigned need_io_watchdog:1; + unsigned fs_i_thresh:1; /* Intel iso scheduling */ + + u8 sbrn; /* packed release number */ + + /* irq statistics */ +#ifdef FUSBH200_STATS + struct fusbh200_stats stats; +# define COUNT(x) do { (x)++; } while (0) +#else +# define COUNT(x) do {} while (0) +#endif + + /* debug files */ +#ifdef DEBUG + struct dentry *debug_dir; +#endif +}; + +/* convert between an HCD pointer and the corresponding FUSBH200_HCD */ +static inline struct fusbh200_hcd *hcd_to_fusbh200 (struct usb_hcd *hcd) +{ + return (struct fusbh200_hcd *) (hcd->hcd_priv); +} +static inline struct usb_hcd *fusbh200_to_hcd (struct fusbh200_hcd *fusbh200) +{ + return container_of ((void *) fusbh200, struct usb_hcd, hcd_priv); +} + +/*-------------------------------------------------------------------------*/ + +/* EHCI register interface, corresponds to EHCI Revision 0.95 specification */ + +/* Section 2.2 Host Controller Capability Registers */ +struct fusbh200_caps { + /* these fields are specified as 8 and 16 bit registers, + * but some hosts can't perform 8 or 16 bit PCI accesses. + * some hosts treat caplength and hciversion as parts of a 32-bit + * register, others treat them as two separate registers, this + * affects the memory map for big endian controllers. + */ + u32 hc_capbase; +#define HC_LENGTH(fusbh200, p) (0x00ff&((p) >> /* bits 7:0 / offset 00h */ \ + (fusbh200_big_endian_capbase(fusbh200) ? 24 : 0))) +#define HC_VERSION(fusbh200, p) (0xffff&((p) >> /* bits 31:16 / offset 02h */ \ + (fusbh200_big_endian_capbase(fusbh200) ? 0 : 16))) + u32 hcs_params; /* HCSPARAMS - offset 0x4 */ +#define HCS_N_PORTS(p) (((p)>>0)&0xf) /* bits 3:0, ports on HC */ + + u32 hcc_params; /* HCCPARAMS - offset 0x8 */ +#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*/ + u8 portroute[8]; /* nibbles for routing - offset 0xC */ +}; + + +/* Section 2.3 Host Controller Operational Registers */ +struct fusbh200_regs { + + /* USBCMD: offset 0x00 */ + u32 command; + +/* EHCI 1.1 addendum */ +/* 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_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, ...) */ + + /* 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 reserved1; + /* PORTSC: offset 0x20 */ + u32 port_status; +/* 31:23 reserved */ +#define PORT_USB11(x) (((x)&(3<<10)) == (1<<10)) /* USB 1.1 device */ +#define PORT_RESET (1<<8) /* reset port */ +#define PORT_SUSPEND (1<<7) /* suspend port */ +#define PORT_RESUME (1<<6) /* resume it */ +#define PORT_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) + + u32 reserved2[3]; + + /* BMCSR: offset 0x30 */ + u32 bmcsr; /* Bus Moniter Control/Status Register */ +#define BMCSR_HOST_SPD_TYP (3<<9) +#define BMCSR_VBUS_OFF (1<<4) +#define BMCSR_INT_POLARITY (1<<3) + + /* BMISR: offset 0x34 */ + u32 bmisr; /* Bus Moniter Interrupt Status Register*/ +#define BMISR_OVC (1<<1) + + /* BMIER: offset 0x38 */ + u32 bmier; /* Bus Moniter Interrupt Enable Register */ +#define BMIER_OVC_EN (1<<1) +#define BMIER_VBUS_ERR_EN (1<<0) +}; + +/* Appendix C, Debug port ... intended for use with special "debug devices" + * that can help if there's no serial console. (nonstandard enumeration.) + */ +struct fusbh200_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)) +}; + +#ifdef CONFIG_EARLY_PRINTK_DBGP +#include <linux/init.h> +extern int __init early_dbgp_init(char *s); +extern struct console early_dbgp_console; +#endif /* CONFIG_EARLY_PRINTK_DBGP */ + +struct usb_hcd; + +static inline int xen_dbgp_reset_prep(struct usb_hcd *hcd) +{ + return 1; /* Shouldn't this be 0? */ +} + +static inline int xen_dbgp_external_startup(struct usb_hcd *hcd) +{ + return -1; +} + +#ifdef CONFIG_EARLY_PRINTK_DBGP +/* Call backs from fusbh200 host driver to fusbh200 debug driver */ +extern int dbgp_external_startup(struct usb_hcd *); +extern int dbgp_reset_prep(struct usb_hcd *hcd); +#else +static inline int dbgp_reset_prep(struct usb_hcd *hcd) +{ + return xen_dbgp_reset_prep(hcd); +} +static inline int dbgp_external_startup(struct usb_hcd *hcd) +{ + return xen_dbgp_external_startup(hcd); +} +#endif + +/*-------------------------------------------------------------------------*/ + +#define QTD_NEXT(fusbh200, dma) cpu_to_hc32(fusbh200, (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 fusbh200_qtd { + /* first part defined by EHCI spec */ + __hc32 hw_next; /* see EHCI 3.5.1 */ + __hc32 hw_alt_next; /* see EHCI 3.5.2 */ + __hc32 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? */ + +#define ACTIVE_BIT(fusbh200) cpu_to_hc32(fusbh200, QTD_STS_ACTIVE) +#define HALT_BIT(fusbh200) cpu_to_hc32(fusbh200, QTD_STS_HALT) +#define STATUS_BIT(fusbh200) cpu_to_hc32(fusbh200, QTD_STS_STS) + + __hc32 hw_buf [5]; /* see EHCI 3.5.4 */ + __hc32 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 */ +} __attribute__ ((aligned (32))); + +/* mask NakCnt+T in qh->hw_alt_next */ +#define QTD_MASK(fusbh200) cpu_to_hc32 (fusbh200, ~0x1f) + +#define IS_SHORT_READ(token) (QTD_LENGTH (token) != 0 && QTD_PID (token) == 1) + +/*-------------------------------------------------------------------------*/ + +/* type tag from {qh,itd,fstn}->hw_next */ +#define Q_NEXT_TYPE(fusbh200,dma) ((dma) & cpu_to_hc32(fusbh200, 3 << 1)) + +/* + * Now the following defines are not converted using the + * cpu_to_le32() macro anymore, since we have to support + * "dynamic" switching between be and le support, so that the driver + * can be used on one system with SoC EHCI controller using big-endian + * descriptors as well as a normal little-endian PCI EHCI controller. + */ +/* values for that type tag */ +#define Q_TYPE_ITD (0 << 1) +#define Q_TYPE_QH (1 << 1) +#define Q_TYPE_SITD (2 << 1) +#define Q_TYPE_FSTN (3 << 1) + +/* next async queue entry, or pointer to interrupt/periodic QH */ +#define QH_NEXT(fusbh200,dma) (cpu_to_hc32(fusbh200, (((u32)dma)&~0x01f)|Q_TYPE_QH)) + +/* for periodic/async schedules and qtd lists, mark end of list */ +#define FUSBH200_LIST_END(fusbh200) cpu_to_hc32(fusbh200, 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 fusbh200_shadow { + struct fusbh200_qh *qh; /* Q_TYPE_QH */ + struct fusbh200_itd *itd; /* Q_TYPE_ITD */ + struct fusbh200_fstn *fstn; /* Q_TYPE_FSTN */ + __hc32 *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. + */ + +/* first part defined by EHCI spec */ +struct fusbh200_qh_hw { + __hc32 hw_next; /* see EHCI 3.6.1 */ + __hc32 hw_info1; /* see EHCI 3.6.2 */ +#define QH_CONTROL_EP (1 << 27) /* FS/LS control endpoint */ +#define QH_HEAD (1 << 15) /* Head of async reclamation list */ +#define QH_TOGGLE_CTL (1 << 14) /* Data toggle control */ +#define QH_HIGH_SPEED (2 << 12) /* Endpoint speed */ +#define QH_LOW_SPEED (1 << 12) +#define QH_FULL_SPEED (0 << 12) +#define QH_INACTIVATE (1 << 7) /* Inactivate on next transaction */ + __hc32 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 + __hc32 hw_current; /* qtd list - see EHCI 3.6.4 */ + + /* qtd overlay (hardware parts of a struct fusbh200_qtd) */ + __hc32 hw_qtd_next; + __hc32 hw_alt_next; + __hc32 hw_token; + __hc32 hw_buf [5]; + __hc32 hw_buf_hi [5]; +} __attribute__ ((aligned(32))); + +struct fusbh200_qh { + struct fusbh200_qh_hw *hw; /* Must come first */ + /* the rest is HCD-private */ + dma_addr_t qh_dma; /* address of qh */ + union fusbh200_shadow qh_next; /* ptr to qh; or periodic */ + struct list_head qtd_list; /* sw qtd list */ + struct list_head intr_node; /* list of intr QHs */ + struct fusbh200_qtd *dummy; + struct fusbh200_qh *unlink_next; /* next on unlink list */ + + unsigned unlink_cycle; + + u8 needs_rescan; /* Dequeue during giveback */ + 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 unlink q */ +#define QH_STATE_COMPLETING 5 /* don't touch token.HALT */ + + u8 xacterrs; /* XactErr retry counter */ +#define QH_XACTERR_MAX 32 /* XactErr retry limit */ + + /* periodic schedule info */ + u8 usecs; /* intr bandwidth */ + u8 gap_uf; /* uframes split/csplit gap */ + 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 */ + unsigned is_out:1; /* bulk or intr OUT */ + unsigned clearing_tt:1; /* Clear-TT-Buf in progress */ +}; + +/*-------------------------------------------------------------------------*/ + +/* description of one iso transaction (up to 3 KB data if highspeed) */ +struct fusbh200_iso_packet { + /* These will be copied to iTD when scheduling */ + u64 bufp; /* itd->hw_bufp{,_hi}[pg] |= */ + __hc32 transaction; /* itd->hw_transaction[i] |= */ + u8 cross; /* buf crosses pages */ + /* for full speed OUT splits */ + u32 buf1; +}; + +/* temporary schedule data for packets from iso urbs (both speeds) + * each packet is one logical usb transaction to the device (not TT), + * beginning at stream->next_uframe + */ +struct fusbh200_iso_sched { + struct list_head td_list; + unsigned span; + struct fusbh200_iso_packet packet [0]; +}; + +/* + * fusbh200_iso_stream - groups all (s)itds for this endpoint. + * acts like a qh would, if EHCI had them for ISO. + */ +struct fusbh200_iso_stream { + /* first field matches fusbh200_hq, but is NULL */ + struct fusbh200_qh_hw *hw; + + u8 bEndpointAddress; + u8 highspeed; + struct list_head td_list; /* queued itds */ + struct list_head free_list; /* list of unused itds */ + struct usb_device *udev; + struct usb_host_endpoint *ep; + + /* output of (re)scheduling */ + int next_uframe; + __hc32 splits; + + /* the rest is derived from the endpoint descriptor, + * trusting urb->interval == f(epdesc->bInterval) and + * including the extra info for hw_bufp[0..2] + */ + u8 usecs, c_usecs; + u16 interval; + u16 tt_usecs; + u16 maxp; + u16 raw_mask; + unsigned bandwidth; + + /* This is used to initialize iTD's hw_bufp fields */ + __hc32 buf0; + __hc32 buf1; + __hc32 buf2; + + /* this is used to initialize sITD's tt info */ + __hc32 address; +}; + +/*-------------------------------------------------------------------------*/ + +/* + * EHCI Specification 0.95 Section 3.3 + * Fig 3-4 "Isochronous Transaction Descriptor (iTD)" + * + * Schedule records for high speed iso xfers + */ +struct fusbh200_itd { + /* first part defined by EHCI spec */ + __hc32 hw_next; /* see EHCI 3.3.1 */ + __hc32 hw_transaction [8]; /* see EHCI 3.3.2 */ +#define FUSBH200_ISOC_ACTIVE (1<<31) /* activate transfer this slot */ +#define FUSBH200_ISOC_BUF_ERR (1<<30) /* Data buffer error */ +#define FUSBH200_ISOC_BABBLE (1<<29) /* babble detected */ +#define FUSBH200_ISOC_XACTERR (1<<28) /* XactErr - transaction error */ +#define FUSBH200_ITD_LENGTH(tok) (((tok)>>16) & 0x0fff) +#define FUSBH200_ITD_IOC (1 << 15) /* interrupt on complete */ + +#define ITD_ACTIVE(fusbh200) cpu_to_hc32(fusbh200, FUSBH200_ISOC_ACTIVE) + + __hc32 hw_bufp [7]; /* see EHCI 3.3.3 */ + __hc32 hw_bufp_hi [7]; /* Appendix B */ + + /* the rest is HCD-private */ + dma_addr_t itd_dma; /* for this itd */ + union fusbh200_shadow itd_next; /* ptr to periodic q entry */ + + struct urb *urb; + struct fusbh200_iso_stream *stream; /* endpoint's queue */ + struct list_head itd_list; /* list of stream's itds */ + + /* any/all hw_transactions here may be used by that urb */ + unsigned frame; /* where scheduled */ + unsigned pg; + unsigned index[8]; /* in urb->iso_frame_desc */ +} __attribute__ ((aligned (32))); + +/*-------------------------------------------------------------------------*/ + +/* + * EHCI Specification 0.96 Section 3.7 + * Periodic Frame Span Traversal Node (FSTN) + * + * Manages split interrupt transactions (using TT) that span frame boundaries + * into uframes 0/1; see 4.12.2.2. In those uframes, a "save place" FSTN + * makes the HC jump (back) to a QH to scan for fs/ls QH completions until + * it hits a "restore" FSTN; then it returns to finish other uframe 0/1 work. + */ +struct fusbh200_fstn { + __hc32 hw_next; /* any periodic q entry */ + __hc32 hw_prev; /* qh or FUSBH200_LIST_END */ + + /* the rest is HCD-private */ + dma_addr_t fstn_dma; + union fusbh200_shadow fstn_next; /* ptr to periodic q entry */ +} __attribute__ ((aligned (32))); + +/*-------------------------------------------------------------------------*/ + +/* Prepare the PORTSC wakeup flags during controller suspend/resume */ + +#define fusbh200_prepare_ports_for_controller_suspend(fusbh200, do_wakeup) \ + fusbh200_adjust_port_wakeup_flags(fusbh200, true, do_wakeup); + +#define fusbh200_prepare_ports_for_controller_resume(fusbh200) \ + fusbh200_adjust_port_wakeup_flags(fusbh200, false, false); + +/*-------------------------------------------------------------------------*/ + +/* + * Some EHCI controllers have a Transaction Translator built into the + * root hub. This is a non-standard feature. Each controller will need + * to add code to the following inline functions, and call them as + * needed (mostly in root hub code). + */ + +static inline unsigned int +fusbh200_get_speed(struct fusbh200_hcd *fusbh200, unsigned int portsc) +{ + return (readl(&fusbh200->regs->bmcsr) + & BMCSR_HOST_SPD_TYP) >> 9; +} + +/* Returns the speed of a device attached to a port on the root hub. */ +static inline unsigned int +fusbh200_port_speed(struct fusbh200_hcd *fusbh200, unsigned int portsc) +{ + switch (fusbh200_get_speed(fusbh200, portsc)) { + case 0: + return 0; + case 1: + return USB_PORT_STAT_LOW_SPEED; + case 2: + default: + return USB_PORT_STAT_HIGH_SPEED; + } +} + +/*-------------------------------------------------------------------------*/ + +#define fusbh200_has_fsl_portno_bug(e) (0) + +/* + * While most USB host controllers implement their registers in + * little-endian format, a minority (celleb companion chip) implement + * them in big endian format. + * + * This attempts to support either format at compile time without a + * runtime penalty, or both formats with the additional overhead + * of checking a flag bit. + * + */ + +#define fusbh200_big_endian_mmio(e) 0 +#define fusbh200_big_endian_capbase(e) 0 + +static inline unsigned int fusbh200_readl(const struct fusbh200_hcd *fusbh200, + __u32 __iomem * regs) +{ + return readl(regs); +} + +static inline void fusbh200_writel(const struct fusbh200_hcd *fusbh200, + const unsigned int val, __u32 __iomem *regs) +{ + writel(val, regs); +} + +/* cpu to fusbh200 */ +static inline __hc32 cpu_to_hc32 (const struct fusbh200_hcd *fusbh200, const u32 x) +{ + return cpu_to_le32(x); +} + +/* fusbh200 to cpu */ +static inline u32 hc32_to_cpu (const struct fusbh200_hcd *fusbh200, const __hc32 x) +{ + return le32_to_cpu(x); +} + +static inline u32 hc32_to_cpup (const struct fusbh200_hcd *fusbh200, const __hc32 *x) +{ + return le32_to_cpup(x); +} + +/*-------------------------------------------------------------------------*/ + +static inline unsigned fusbh200_read_frame_index(struct fusbh200_hcd *fusbh200) +{ + return fusbh200_readl(fusbh200, &fusbh200->regs->frame_index); +} + +#define fusbh200_itdlen(urb, desc, t) ({ \ + usb_pipein((urb)->pipe) ? \ + (desc)->length - FUSBH200_ITD_LENGTH(t) : \ + FUSBH200_ITD_LENGTH(t); \ +}) +/*-------------------------------------------------------------------------*/ + +#ifndef DEBUG +#define STUB_DEBUG_FILES +#endif /* DEBUG */ + +/*-------------------------------------------------------------------------*/ + +#endif /* __LINUX_FUSBH200_H */ diff --git a/drivers/usb/host/hwa-hc.c b/drivers/usb/host/hwa-hc.c index 104730d..483990c 100644 --- a/drivers/usb/host/hwa-hc.c +++ b/drivers/usb/host/hwa-hc.c @@ -577,7 +577,7 @@ static struct hc_driver hwahc_hc_driver = { .product_desc = "Wireless USB HWA host controller", .hcd_priv_size = sizeof(struct hwahc) - sizeof(struct usb_hcd), .irq = NULL, /* FIXME */ - .flags = HCD_USB2, /* FIXME */ + .flags = HCD_USB25, .reset = hwahc_op_reset, .start = hwahc_op_start, .stop = hwahc_op_stop, @@ -588,8 +588,6 @@ static struct hc_driver hwahc_hc_driver = { .hub_status_data = wusbhc_rh_status_data, .hub_control = wusbhc_rh_control, - .bus_suspend = wusbhc_rh_suspend, - .bus_resume = wusbhc_rh_resume, .start_port_reset = wusbhc_rh_start_port_reset, }; @@ -685,12 +683,9 @@ static int hwahc_create(struct hwahc *hwahc, struct usb_interface *iface) wa->usb_dev = usb_get_dev(usb_dev); /* bind the USB device */ wa->usb_iface = usb_get_intf(iface); wusbhc->dev = dev; - wusbhc->uwb_rc = uwb_rc_get_by_grandpa(iface->dev.parent); - if (wusbhc->uwb_rc == NULL) { - result = -ENODEV; - dev_err(dev, "Cannot get associated UWB Host Controller\n"); - goto error_rc_get; - } + /* defer getting the uwb_rc handle until it is needed since it + * may not have been registered by the hwa_rc driver yet. */ + wusbhc->uwb_rc = NULL; result = wa_fill_descr(wa); /* Get the device descriptor */ if (result < 0) goto error_fill_descriptor; @@ -733,8 +728,6 @@ error_wusbhc_create: /* WA Descr fill allocs no resources */ error_security_create: error_fill_descriptor: - uwb_rc_put(wusbhc->uwb_rc); -error_rc_get: usb_put_intf(iface); usb_put_dev(usb_dev); return result; @@ -776,6 +769,7 @@ static int hwahc_probe(struct usb_interface *usb_iface, goto error_alloc; } usb_hcd->wireless = 1; + usb_hcd->self.sg_tablesize = ~0; wusbhc = usb_hcd_to_wusbhc(usb_hcd); hwahc = container_of(wusbhc, struct hwahc, wusbhc); hwahc_init(hwahc); diff --git a/drivers/usb/host/imx21-hcd.c b/drivers/usb/host/imx21-hcd.c index f0ebe8e..03dc4d9 100644 --- a/drivers/usb/host/imx21-hcd.c +++ b/drivers/usb/host/imx21-hcd.c @@ -809,26 +809,36 @@ static int imx21_hc_urb_enqueue_isoc(struct usb_hcd *hcd, /* calculate frame */ cur_frame = imx21_hc_get_frame(hcd); - if (urb->transfer_flags & URB_ISO_ASAP) { - if (list_empty(&ep_priv->td_list)) - urb->start_frame = cur_frame + 5; - else - urb->start_frame = list_entry( - ep_priv->td_list.prev, - struct td, list)->frame + urb->interval; - } - urb->start_frame = wrap_frame(urb->start_frame); - if (frame_after(cur_frame, urb->start_frame)) { - dev_dbg(imx21->dev, - "enqueue: adjusting iso start %d (cur=%d) asap=%d\n", - urb->start_frame, cur_frame, - (urb->transfer_flags & URB_ISO_ASAP) != 0); - urb->start_frame = wrap_frame(cur_frame + 1); + i = 0; + if (list_empty(&ep_priv->td_list)) { + urb->start_frame = wrap_frame(cur_frame + 5); + } else { + urb->start_frame = wrap_frame(list_entry(ep_priv->td_list.prev, + struct td, list)->frame + urb->interval); + + if (frame_after(cur_frame, urb->start_frame)) { + dev_dbg(imx21->dev, + "enqueue: adjusting iso start %d (cur=%d) asap=%d\n", + urb->start_frame, cur_frame, + (urb->transfer_flags & URB_ISO_ASAP) != 0); + i = DIV_ROUND_UP(wrap_frame( + cur_frame - urb->start_frame), + urb->interval); + if (urb->transfer_flags & URB_ISO_ASAP) { + urb->start_frame = wrap_frame(urb->start_frame + + i * urb->interval); + i = 0; + } else if (i >= urb->number_of_packets) { + ret = -EXDEV; + goto alloc_dmem_failed; + } + } } /* set up transfers */ + urb_priv->isoc_remaining = urb->number_of_packets - i; td = urb_priv->isoc_td; - for (i = 0; i < urb->number_of_packets; i++, td++) { + for (; i < urb->number_of_packets; i++, td++) { unsigned int offset = urb->iso_frame_desc[i].offset; td->ep = ep; td->urb = urb; @@ -840,7 +850,6 @@ static int imx21_hc_urb_enqueue_isoc(struct usb_hcd *hcd, list_add_tail(&td->list, &ep_priv->td_list); } - urb_priv->isoc_remaining = urb->number_of_packets; dev_vdbg(imx21->dev, "setup %d packets for iso frame %d->%d\n", urb->number_of_packets, urb->start_frame, td->frame); diff --git a/drivers/usb/host/isp1760-if.c b/drivers/usb/host/isp1760-if.c index a13709e..3df49b1 100644 --- a/drivers/usb/host/isp1760-if.c +++ b/drivers/usb/host/isp1760-if.c @@ -118,7 +118,7 @@ static int of_isp1760_probe(struct platform_device *dev) goto free_gpio; } - dev_set_drvdata(&dev->dev, drvdata); + platform_set_drvdata(dev, drvdata); return ret; free_gpio: @@ -133,9 +133,7 @@ free_data: static int of_isp1760_remove(struct platform_device *dev) { - struct isp1760 *drvdata = dev_get_drvdata(&dev->dev); - - dev_set_drvdata(&dev->dev, NULL); + struct isp1760 *drvdata = platform_get_drvdata(dev); usb_remove_hcd(drvdata->hcd); iounmap(drvdata->hcd->regs); @@ -398,7 +396,7 @@ static int isp1760_plat_probe(struct platform_device *pdev) irqflags, -ENOENT, &pdev->dev, dev_name(&pdev->dev), devflags); - dev_set_drvdata(&pdev->dev, hcd); + platform_set_drvdata(pdev, hcd); if (IS_ERR(hcd)) { pr_warning("isp1760: Failed to register the HCD device\n"); @@ -419,7 +417,7 @@ static int isp1760_plat_remove(struct platform_device *pdev) { struct resource *mem_res; resource_size_t mem_size; - struct usb_hcd *hcd = dev_get_drvdata(&pdev->dev); + struct usb_hcd *hcd = platform_get_drvdata(pdev); usb_remove_hcd(hcd); diff --git a/drivers/usb/host/ohci-at91.c b/drivers/usb/host/ohci-at91.c index 2ee1496..9677f68 100644 --- a/drivers/usb/host/ohci-at91.c +++ b/drivers/usb/host/ohci-at91.c @@ -41,17 +41,17 @@ extern int usb_disabled(void); static void at91_start_clock(void) { - clk_enable(hclk); - clk_enable(iclk); - clk_enable(fclk); + clk_prepare_enable(hclk); + clk_prepare_enable(iclk); + clk_prepare_enable(fclk); clocked = 1; } static void at91_stop_clock(void) { - clk_disable(fclk); - clk_disable(iclk); - clk_disable(hclk); + clk_disable_unprepare(fclk); + clk_disable_unprepare(iclk); + clk_disable_unprepare(hclk); clocked = 0; } diff --git a/drivers/usb/host/ohci-da8xx.c b/drivers/usb/host/ohci-da8xx.c index 0b815a8..6aaa9c9 100644 --- a/drivers/usb/host/ohci-da8xx.c +++ b/drivers/usb/host/ohci-da8xx.c @@ -401,7 +401,6 @@ static int ohci_hcd_da8xx_drv_remove(struct platform_device *dev) struct usb_hcd *hcd = platform_get_drvdata(dev); usb_hcd_da8xx_remove(hcd, dev); - platform_set_drvdata(dev, NULL); return 0; } diff --git a/drivers/usb/host/ohci-hcd.c b/drivers/usb/host/ohci-hcd.c index fc627fd..a9d3437 100644 --- a/drivers/usb/host/ohci-hcd.c +++ b/drivers/usb/host/ohci-hcd.c @@ -79,23 +79,8 @@ static const char hcd_name [] = "ohci_hcd"; #include "pci-quirks.h" static void ohci_dump (struct ohci_hcd *ohci, int verbose); -static int ohci_init (struct ohci_hcd *ohci); static void ohci_stop (struct usb_hcd *hcd); -#if defined(CONFIG_PM) || defined(CONFIG_PCI) -static int ohci_restart (struct ohci_hcd *ohci); -#endif - -#ifdef CONFIG_PCI -static void sb800_prefetch(struct ohci_hcd *ohci, int on); -#else -static inline void sb800_prefetch(struct ohci_hcd *ohci, int on) -{ - return; -} -#endif - - #include "ohci-hub.c" #include "ohci-dbg.c" #include "ohci-mem.c" @@ -772,6 +757,32 @@ retry: return 0; } +/* ohci_setup routine for generic controller initialization */ + +int ohci_setup(struct usb_hcd *hcd) +{ + struct ohci_hcd *ohci = hcd_to_ohci(hcd); + + ohci_hcd_init(ohci); + + return ohci_init(ohci); +} +EXPORT_SYMBOL_GPL(ohci_setup); + +/* ohci_start routine for generic controller start of all OHCI bus glue */ +static int ohci_start(struct usb_hcd *hcd) +{ + struct ohci_hcd *ohci = hcd_to_ohci(hcd); + int ret; + + ret = ohci_run(ohci); + if (ret < 0) { + ohci_err(ohci, "can't start\n"); + ohci_stop(hcd); + } + return ret; +} + /*-------------------------------------------------------------------------*/ /* an interrupt happens */ @@ -953,12 +964,13 @@ static void ohci_stop (struct usb_hcd *hcd) #if defined(CONFIG_PM) || defined(CONFIG_PCI) /* must not be called from interrupt context */ -static int ohci_restart (struct ohci_hcd *ohci) +int ohci_restart(struct ohci_hcd *ohci) { int temp; int i; struct urb_priv *priv; + ohci_init(ohci); spin_lock_irq(&ohci->lock); ohci->rh_state = OHCI_RH_HALTED; @@ -1012,12 +1024,13 @@ static int ohci_restart (struct ohci_hcd *ohci) ohci_dbg(ohci, "restart complete\n"); return 0; } +EXPORT_SYMBOL_GPL(ohci_restart); #endif #ifdef CONFIG_PM -static int __maybe_unused ohci_suspend(struct usb_hcd *hcd, bool do_wakeup) +int ohci_suspend(struct usb_hcd *hcd, bool do_wakeup) { struct ohci_hcd *ohci = hcd_to_ohci (hcd); unsigned long flags; @@ -1035,9 +1048,10 @@ static int __maybe_unused ohci_suspend(struct usb_hcd *hcd, bool do_wakeup) return 0; } +EXPORT_SYMBOL_GPL(ohci_suspend); -static int __maybe_unused ohci_resume(struct usb_hcd *hcd, bool hibernated) +int ohci_resume(struct usb_hcd *hcd, bool hibernated) { struct ohci_hcd *ohci = hcd_to_ohci(hcd); int port; @@ -1085,20 +1099,79 @@ static int __maybe_unused ohci_resume(struct usb_hcd *hcd, bool hibernated) return 0; } +EXPORT_SYMBOL_GPL(ohci_resume); #endif /*-------------------------------------------------------------------------*/ +/* + * Generic structure: This gets copied for platform drivers so that + * individual entries can be overridden as needed. + */ + +static const struct hc_driver ohci_hc_driver = { + .description = hcd_name, + .product_desc = "OHCI Host Controller", + .hcd_priv_size = sizeof(struct ohci_hcd), + + /* + * generic hardware linkage + */ + .irq = ohci_irq, + .flags = HCD_MEMORY | HCD_USB11, + + /* + * basic lifecycle operations + */ + .reset = ohci_setup, + .start = ohci_start, + .stop = ohci_stop, + .shutdown = ohci_shutdown, + + /* + * managing i/o requests and associated device resources + */ + .urb_enqueue = ohci_urb_enqueue, + .urb_dequeue = ohci_urb_dequeue, + .endpoint_disable = ohci_endpoint_disable, + + /* + * scheduling support + */ + .get_frame_number = ohci_get_frame, + + /* + * root hub support + */ + .hub_status_data = ohci_hub_status_data, + .hub_control = ohci_hub_control, +#ifdef CONFIG_PM + .bus_suspend = ohci_bus_suspend, + .bus_resume = ohci_bus_resume, +#endif + .start_port_reset = ohci_start_port_reset, +}; + +void ohci_init_driver(struct hc_driver *drv, + const struct ohci_driver_overrides *over) +{ + /* Copy the generic table to drv and then apply the overrides */ + *drv = ohci_hc_driver; + + drv->product_desc = over->product_desc; + drv->hcd_priv_size += over->extra_priv_size; + if (over->reset) + drv->reset = over->reset; +} +EXPORT_SYMBOL_GPL(ohci_init_driver); + +/*-------------------------------------------------------------------------*/ + MODULE_AUTHOR (DRIVER_AUTHOR); MODULE_DESCRIPTION(DRIVER_DESC); MODULE_LICENSE ("GPL"); -#ifdef CONFIG_PCI -#include "ohci-pci.c" -#define PCI_DRIVER ohci_pci_driver -#endif - #if defined(CONFIG_ARCH_SA1100) && defined(CONFIG_SA1111) #include "ohci-sa1111.c" #define SA1111_DRIVER ohci_hcd_sa1111_driver @@ -1189,30 +1262,6 @@ MODULE_LICENSE ("GPL"); #define PLATFORM_DRIVER ohci_hcd_tilegx_driver #endif -#ifdef CONFIG_USB_OHCI_HCD_PLATFORM -#include "ohci-platform.c" -#define PLATFORM_DRIVER ohci_platform_driver -#endif - -#if !defined(PCI_DRIVER) && \ - !defined(PLATFORM_DRIVER) && \ - !defined(OMAP1_PLATFORM_DRIVER) && \ - !defined(OMAP3_PLATFORM_DRIVER) && \ - !defined(OF_PLATFORM_DRIVER) && \ - !defined(SA1111_DRIVER) && \ - !defined(PS3_SYSTEM_BUS_DRIVER) && \ - !defined(SM501_OHCI_DRIVER) && \ - !defined(TMIO_OHCI_DRIVER) && \ - !defined(S3C2410_PLATFORM_DRIVER) && \ - !defined(EXYNOS_PLATFORM_DRIVER) && \ - !defined(EP93XX_PLATFORM_DRIVER) && \ - !defined(AT91_PLATFORM_DRIVER) && \ - !defined(NXP_PLATFORM_DRIVER) && \ - !defined(DAVINCI_PLATFORM_DRIVER) && \ - !defined(SPEAR_PLATFORM_DRIVER) -#error "missing bus glue for ohci-hcd" -#endif - static int __init ohci_hcd_mod_init(void) { int retval = 0; @@ -1269,12 +1318,6 @@ static int __init ohci_hcd_mod_init(void) goto error_sa1111; #endif -#ifdef PCI_DRIVER - retval = pci_register_driver(&PCI_DRIVER); - if (retval < 0) - goto error_pci; -#endif - #ifdef SM501_OHCI_DRIVER retval = platform_driver_register(&SM501_OHCI_DRIVER); if (retval < 0) @@ -1368,10 +1411,6 @@ static int __init ohci_hcd_mod_init(void) platform_driver_unregister(&SM501_OHCI_DRIVER); error_sm501: #endif -#ifdef PCI_DRIVER - pci_unregister_driver(&PCI_DRIVER); - error_pci: -#endif #ifdef SA1111_DRIVER sa1111_driver_unregister(&SA1111_DRIVER); error_sa1111: @@ -1436,9 +1475,6 @@ static void __exit ohci_hcd_mod_exit(void) #ifdef SM501_OHCI_DRIVER platform_driver_unregister(&SM501_OHCI_DRIVER); #endif -#ifdef PCI_DRIVER - pci_unregister_driver(&PCI_DRIVER); -#endif #ifdef SA1111_DRIVER sa1111_driver_unregister(&SA1111_DRIVER); #endif diff --git a/drivers/usb/host/ohci-hub.c b/drivers/usb/host/ohci-hub.c index 60ff422..2347ab8 100644 --- a/drivers/usb/host/ohci-hub.c +++ b/drivers/usb/host/ohci-hub.c @@ -176,7 +176,6 @@ __acquires(ohci->lock) if (status == -EBUSY) { if (!autostopped) { spin_unlock_irq (&ohci->lock); - (void) ohci_init (ohci); status = ohci_restart (ohci); usb_root_hub_lost_power(hcd->self.root_hub); diff --git a/drivers/usb/host/ohci-jz4740.c b/drivers/usb/host/ohci-jz4740.c index 8062bb9..d4ef539 100644 --- a/drivers/usb/host/ohci-jz4740.c +++ b/drivers/usb/host/ohci-jz4740.c @@ -221,7 +221,6 @@ static int jz4740_ohci_probe(struct platform_device *pdev) return 0; err_disable: - platform_set_drvdata(pdev, NULL); if (jz4740_ohci->vbus) { regulator_disable(jz4740_ohci->vbus); regulator_put(jz4740_ohci->vbus); @@ -246,8 +245,6 @@ static int jz4740_ohci_remove(struct platform_device *pdev) usb_remove_hcd(hcd); - platform_set_drvdata(pdev, NULL); - if (jz4740_ohci->vbus) { regulator_disable(jz4740_ohci->vbus); regulator_put(jz4740_ohci->vbus); diff --git a/drivers/usb/host/ohci-nxp.c b/drivers/usb/host/ohci-nxp.c index 5d7eb72..7d7d507 100644 --- a/drivers/usb/host/ohci-nxp.c +++ b/drivers/usb/host/ohci-nxp.c @@ -351,7 +351,6 @@ static int usb_hcd_nxp_remove(struct platform_device *pdev) usb_remove_hcd(hcd); nxp_stop_hc(); - release_mem_region(hcd->rsrc_start, hcd->rsrc_len); usb_put_hcd(hcd); clk_disable(usb_pll_clk); clk_put(usb_pll_clk); @@ -360,8 +359,6 @@ static int usb_hcd_nxp_remove(struct platform_device *pdev) i2c_unregister_device(isp1301_i2c_client); isp1301_i2c_client = NULL; - platform_set_drvdata(pdev, NULL); - return 0; } diff --git a/drivers/usb/host/ohci-octeon.c b/drivers/usb/host/ohci-octeon.c index d44430d..342dc7e 100644 --- a/drivers/usb/host/ohci-octeon.c +++ b/drivers/usb/host/ohci-octeon.c @@ -196,8 +196,6 @@ static int ohci_octeon_drv_remove(struct platform_device *pdev) release_mem_region(hcd->rsrc_start, hcd->rsrc_len); usb_put_hcd(hcd); - platform_set_drvdata(pdev, NULL); - return 0; } diff --git a/drivers/usb/host/ohci-omap.c b/drivers/usb/host/ohci-omap.c index b1d32fb..8747fa6 100644 --- a/drivers/usb/host/ohci-omap.c +++ b/drivers/usb/host/ohci-omap.c @@ -498,7 +498,6 @@ static int ohci_hcd_omap_drv_remove(struct platform_device *dev) struct usb_hcd *hcd = platform_get_drvdata(dev); usb_hcd_omap_remove(hcd, dev); - platform_set_drvdata(dev, NULL); return 0; } diff --git a/drivers/usb/host/ohci-omap3.c b/drivers/usb/host/ohci-omap3.c index 8663851..8f71357 100644 --- a/drivers/usb/host/ohci-omap3.c +++ b/drivers/usb/host/ohci-omap3.c @@ -252,7 +252,7 @@ static struct platform_driver ohci_hcd_omap3_driver = { .shutdown = ohci_hcd_omap3_shutdown, .driver = { .name = "ohci-omap3", - .of_match_table = of_match_ptr(omap_ohci_dt_ids), + .of_match_table = omap_ohci_dt_ids, }, }; diff --git a/drivers/usb/host/ohci-pci.c b/drivers/usb/host/ohci-pci.c index 951514e..08613e2 100644 --- a/drivers/usb/host/ohci-pci.c +++ b/drivers/usb/host/ohci-pci.c @@ -14,12 +14,19 @@ * This file is licenced under the GPL. */ -#ifndef CONFIG_PCI -#error "This file is PCI bus glue. CONFIG_PCI must be defined." -#endif - -#include <linux/pci.h> #include <linux/io.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/usb.h> +#include <linux/usb/hcd.h> + +#include "ohci.h" +#include "pci-quirks.h" + +#define DRIVER_DESC "OHCI PCI platform driver" + +static const char hcd_name[] = "ohci-pci"; /*-------------------------------------------------------------------------*/ @@ -123,13 +130,6 @@ static void ohci_quirk_nec_worker(struct work_struct *work) struct ohci_hcd *ohci = container_of(work, struct ohci_hcd, nec_work); int status; - status = ohci_init(ohci); - if (status != 0) { - ohci_err(ohci, "Restarting NEC controller failed in %s, %d\n", - "ohci_init", status); - return; - } - status = ohci_restart(ohci); if (status != 0) ohci_err(ohci, "Restarting NEC controller failed in %s, %d\n", @@ -175,19 +175,6 @@ static int ohci_quirk_amd700(struct usb_hcd *hcd) return 0; } -static void sb800_prefetch(struct ohci_hcd *ohci, int on) -{ - struct pci_dev *pdev; - u16 misc; - - pdev = to_pci_dev(ohci_to_hcd(ohci)->self.controller); - pci_read_config_word(pdev, 0x50, &misc); - if (on == 0) - pci_write_config_word(pdev, 0x50, misc & 0xfcff); - else - pci_write_config_word(pdev, 0x50, misc | 0x0300); -} - /* List of quirks for OHCI */ static const struct pci_device_id ohci_pci_quirks[] = { { @@ -249,10 +236,10 @@ static const struct pci_device_id ohci_pci_quirks[] = { static int ohci_pci_reset (struct usb_hcd *hcd) { struct ohci_hcd *ohci = hcd_to_ohci (hcd); + struct pci_dev *pdev = to_pci_dev(hcd->self.controller); int ret = 0; if (hcd->self.controller) { - struct pci_dev *pdev = to_pci_dev(hcd->self.controller); const struct pci_device_id *quirk_id; quirk_id = pci_match_id(ohci_pci_quirks, pdev); @@ -262,94 +249,25 @@ static int ohci_pci_reset (struct usb_hcd *hcd) ret = quirk(hcd); } } - if (ret == 0) { - ohci_hcd_init (ohci); - return ohci_init (ohci); - } - return ret; -} - - -static int ohci_pci_start (struct usb_hcd *hcd) -{ - struct ohci_hcd *ohci = hcd_to_ohci (hcd); - int ret; - -#ifdef CONFIG_PM /* avoid warnings about unused pdev */ - if (hcd->self.controller) { - struct pci_dev *pdev = to_pci_dev(hcd->self.controller); - - /* RWC may not be set for add-in PCI cards, since boot - * firmware probably ignored them. This transfers PCI - * PM wakeup capabilities. - */ - if (device_can_wakeup(&pdev->dev)) - ohci->hc_control |= OHCI_CTRL_RWC; - } -#endif /* CONFIG_PM */ - ret = ohci_run (ohci); - if (ret < 0) { - ohci_err (ohci, "can't start\n"); - ohci_stop (hcd); - } + if (ret == 0) + ret = ohci_setup(hcd); + /* + * After ohci setup RWC may not be set for add-in PCI cards. + * This transfers PCI PM wakeup capabilities. + */ + if (device_can_wakeup(&pdev->dev)) + ohci->hc_control |= OHCI_CTRL_RWC; return ret; } +static struct hc_driver __read_mostly ohci_pci_hc_driver; -/*-------------------------------------------------------------------------*/ - -static const struct hc_driver ohci_pci_hc_driver = { - .description = hcd_name, - .product_desc = "OHCI Host Controller", - .hcd_priv_size = sizeof(struct ohci_hcd), - - /* - * generic hardware linkage - */ - .irq = ohci_irq, - .flags = HCD_MEMORY | HCD_USB11, - - /* - * basic lifecycle operations - */ +static const struct ohci_driver_overrides pci_overrides __initconst = { + .product_desc = "OHCI PCI host controller", .reset = ohci_pci_reset, - .start = ohci_pci_start, - .stop = ohci_stop, - .shutdown = ohci_shutdown, - -#ifdef CONFIG_PM - .pci_suspend = ohci_suspend, - .pci_resume = ohci_resume, -#endif - - /* - * managing i/o requests and associated device resources - */ - .urb_enqueue = ohci_urb_enqueue, - .urb_dequeue = ohci_urb_dequeue, - .endpoint_disable = ohci_endpoint_disable, - - /* - * scheduling support - */ - .get_frame_number = ohci_get_frame, - - /* - * root hub support - */ - .hub_status_data = ohci_hub_status_data, - .hub_control = ohci_hub_control, -#ifdef CONFIG_PM - .bus_suspend = ohci_bus_suspend, - .bus_resume = ohci_bus_resume, -#endif - .start_port_reset = ohci_start_port_reset, }; -/*-------------------------------------------------------------------------*/ - - static const struct pci_device_id pci_ids [] = { { /* handle any USB OHCI controller */ PCI_DEVICE_CLASS(PCI_CLASS_SERIAL_USB_OHCI, ~0), @@ -377,3 +295,24 @@ static struct pci_driver ohci_pci_driver = { }, #endif }; + +static int __init ohci_pci_init(void) +{ + if (usb_disabled()) + return -ENODEV; + + pr_info("%s: " DRIVER_DESC "\n", hcd_name); + + ohci_init_driver(&ohci_pci_hc_driver, &pci_overrides); + return pci_register_driver(&ohci_pci_driver); +} +module_init(ohci_pci_init); + +static void __exit ohci_pci_cleanup(void) +{ + pci_unregister_driver(&ohci_pci_driver); +} +module_exit(ohci_pci_cleanup); + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/host/ohci-platform.c b/drivers/usb/host/ohci-platform.c index c3e7287..bc30475 100644 --- a/drivers/usb/host/ohci-platform.c +++ b/drivers/usb/host/ohci-platform.c @@ -13,16 +13,28 @@ * * Licensed under the GNU/GPL. See COPYING for details. */ + +#include <linux/hrtimer.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/module.h> #include <linux/err.h> #include <linux/platform_device.h> #include <linux/usb/ohci_pdriver.h> +#include <linux/usb.h> +#include <linux/usb/hcd.h> + +#include "ohci.h" + +#define DRIVER_DESC "OHCI generic platform driver" + +static const char hcd_name[] = "ohci-platform"; static int ohci_platform_reset(struct usb_hcd *hcd) { struct platform_device *pdev = to_platform_device(hcd->self.controller); struct usb_ohci_pdata *pdata = pdev->dev.platform_data; struct ohci_hcd *ohci = hcd_to_ohci(hcd); - int err; if (pdata->big_endian_desc) ohci->flags |= OHCI_QUIRK_BE_DESC; @@ -30,58 +42,17 @@ static int ohci_platform_reset(struct usb_hcd *hcd) ohci->flags |= OHCI_QUIRK_BE_MMIO; if (pdata->no_big_frame_no) ohci->flags |= OHCI_QUIRK_FRAME_NO; - - ohci_hcd_init(ohci); - if (pdata->num_ports) ohci->num_ports = pdata->num_ports; - err = ohci_init(ohci); - - return err; -} - -static int ohci_platform_start(struct usb_hcd *hcd) -{ - struct ohci_hcd *ohci = hcd_to_ohci(hcd); - int err; - - err = ohci_run(ohci); - if (err < 0) { - ohci_err(ohci, "can't start\n"); - ohci_stop(hcd); - } - - return err; + return ohci_setup(hcd); } -static const struct hc_driver ohci_platform_hc_driver = { - .description = hcd_name, - .product_desc = "Generic Platform OHCI Controller", - .hcd_priv_size = sizeof(struct ohci_hcd), +static struct hc_driver __read_mostly ohci_platform_hc_driver; - .irq = ohci_irq, - .flags = HCD_MEMORY | HCD_USB11, - - .reset = ohci_platform_reset, - .start = ohci_platform_start, - .stop = ohci_stop, - .shutdown = ohci_shutdown, - - .urb_enqueue = ohci_urb_enqueue, - .urb_dequeue = ohci_urb_dequeue, - .endpoint_disable = ohci_endpoint_disable, - - .get_frame_number = ohci_get_frame, - - .hub_status_data = ohci_hub_status_data, - .hub_control = ohci_hub_control, -#ifdef CONFIG_PM - .bus_suspend = ohci_bus_suspend, - .bus_resume = ohci_bus_resume, -#endif - - .start_port_reset = ohci_start_port_reset, +static const struct ohci_driver_overrides platform_overrides __initconst = { + .product_desc = "Generic Platform OHCI controller", + .reset = ohci_platform_reset, }; static int ohci_platform_probe(struct platform_device *dev) @@ -157,7 +128,6 @@ static int ohci_platform_remove(struct platform_device *dev) usb_remove_hcd(hcd); usb_put_hcd(hcd); - platform_set_drvdata(dev, NULL); if (pdata->power_off) pdata->power_off(dev); @@ -223,3 +193,26 @@ static struct platform_driver ohci_platform_driver = { .pm = &ohci_platform_pm_ops, } }; + +static int __init ohci_platform_init(void) +{ + if (usb_disabled()) + return -ENODEV; + + pr_info("%s: " DRIVER_DESC "\n", hcd_name); + + ohci_init_driver(&ohci_platform_hc_driver, &platform_overrides); + return platform_driver_register(&ohci_platform_driver); +} +module_init(ohci_platform_init); + +static void __exit ohci_platform_cleanup(void) +{ + platform_driver_unregister(&ohci_platform_driver); +} +module_exit(ohci_platform_cleanup); + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_AUTHOR("Hauke Mehrtens"); +MODULE_AUTHOR("Alan Stern"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/host/ohci-ppc-of.c b/drivers/usb/host/ohci-ppc-of.c index 64c2ed9f..8294e2f 100644 --- a/drivers/usb/host/ohci-ppc-of.c +++ b/drivers/usb/host/ohci-ppc-of.c @@ -185,8 +185,7 @@ err_rmr: static int ohci_hcd_ppc_of_remove(struct platform_device *op) { - struct usb_hcd *hcd = dev_get_drvdata(&op->dev); - dev_set_drvdata(&op->dev, NULL); + struct usb_hcd *hcd = platform_get_drvdata(op); dev_dbg(&op->dev, "stopping PPC-OF USB Controller\n"); @@ -203,7 +202,7 @@ static int ohci_hcd_ppc_of_remove(struct platform_device *op) static void ohci_hcd_ppc_of_shutdown(struct platform_device *op) { - struct usb_hcd *hcd = dev_get_drvdata(&op->dev); + struct usb_hcd *hcd = platform_get_drvdata(op); if (hcd->driver->shutdown) hcd->driver->shutdown(hcd); diff --git a/drivers/usb/host/ohci-pxa27x.c b/drivers/usb/host/ohci-pxa27x.c index 279b2ef..3a9c01d 100644 --- a/drivers/usb/host/ohci-pxa27x.c +++ b/drivers/usb/host/ohci-pxa27x.c @@ -556,7 +556,6 @@ static int ohci_hcd_pxa27x_drv_remove(struct platform_device *pdev) struct usb_hcd *hcd = platform_get_drvdata(pdev); usb_hcd_pxa27x_remove(hcd, pdev); - platform_set_drvdata(pdev, NULL); return 0; } diff --git a/drivers/usb/host/ohci-q.c b/drivers/usb/host/ohci-q.c index 88731b7..df4a670 100644 --- a/drivers/usb/host/ohci-q.c +++ b/drivers/usb/host/ohci-q.c @@ -41,6 +41,7 @@ finish_urb(struct ohci_hcd *ohci, struct urb *urb, int status) __releases(ohci->lock) __acquires(ohci->lock) { + struct device *dev = ohci_to_hcd(ohci)->self.controller; // ASSERT (urb->hcpriv != 0); urb_free_priv (ohci, urb->hcpriv); @@ -55,7 +56,7 @@ __acquires(ohci->lock) if (quirk_amdiso(ohci)) usb_amd_quirk_pll_enable(); if (quirk_amdprefetch(ohci)) - sb800_prefetch(ohci, 0); + sb800_prefetch(dev, 0); } break; case PIPE_INTERRUPT: @@ -580,6 +581,7 @@ static void td_submit_urb ( struct urb *urb ) { struct urb_priv *urb_priv = urb->hcpriv; + struct device *dev = ohci_to_hcd(ohci)->self.controller; dma_addr_t data; int data_len = urb->transfer_buffer_length; int cnt = 0; @@ -689,7 +691,7 @@ static void td_submit_urb ( if (quirk_amdiso(ohci)) usb_amd_quirk_pll_disable(); if (quirk_amdprefetch(ohci)) - sb800_prefetch(ohci, 1); + sb800_prefetch(dev, 1); } periodic = ohci_to_hcd(ohci)->self.bandwidth_isoc_reqs++ == 0 && ohci_to_hcd(ohci)->self.bandwidth_int_reqs == 0; diff --git a/drivers/usb/host/ohci-sm501.c b/drivers/usb/host/ohci-sm501.c index 3b5b908..d479d5d 100644 --- a/drivers/usb/host/ohci-sm501.c +++ b/drivers/usb/host/ohci-sm501.c @@ -207,7 +207,6 @@ static int ohci_hcd_sm501_drv_remove(struct platform_device *pdev) sm501_modify_reg(pdev->dev.parent, SM501_IRQ_MASK, 0, 1 << 6); sm501_unit_power(pdev->dev.parent, SM501_GATE_USB_HOST, 0); - platform_set_drvdata(pdev, NULL); return 0; } diff --git a/drivers/usb/host/ohci-spear.c b/drivers/usb/host/ohci-spear.c index 3e19e01..cc9dd9e 100644 --- a/drivers/usb/host/ohci-spear.c +++ b/drivers/usb/host/ohci-spear.c @@ -179,8 +179,6 @@ static int spear_ohci_hcd_drv_remove(struct platform_device *pdev) spear_stop_ohci(ohci_p); usb_put_hcd(hcd); - - platform_set_drvdata(pdev, NULL); return 0; } @@ -232,7 +230,7 @@ static struct platform_driver spear_ohci_hcd_driver = { .driver = { .owner = THIS_MODULE, .name = "spear-ohci", - .of_match_table = of_match_ptr(spear_ohci_id_table), + .of_match_table = spear_ohci_id_table, }, }; diff --git a/drivers/usb/host/ohci-tilegx.c b/drivers/usb/host/ohci-tilegx.c index ea73009..197d514 100644 --- a/drivers/usb/host/ohci-tilegx.c +++ b/drivers/usb/host/ohci-tilegx.c @@ -182,7 +182,6 @@ static int ohci_hcd_tilegx_drv_remove(struct platform_device *pdev) tilegx_stop_ohc(); gxio_usb_host_destroy(&pdata->usb_ctx); destroy_irq(pdata->irq); - platform_set_drvdata(pdev, NULL); return 0; } diff --git a/drivers/usb/host/ohci-tmio.c b/drivers/usb/host/ohci-tmio.c index 5e3a6de..ecb09a5 100644 --- a/drivers/usb/host/ohci-tmio.c +++ b/drivers/usb/host/ohci-tmio.c @@ -287,8 +287,6 @@ static int ohci_hcd_tmio_drv_remove(struct platform_device *dev) iounmap(tmio->ccr); usb_put_hcd(hcd); - platform_set_drvdata(dev, NULL); - return 0; } diff --git a/drivers/usb/host/ohci.h b/drivers/usb/host/ohci.h index d329914..e2e5faa 100644 --- a/drivers/usb/host/ohci.h +++ b/drivers/usb/host/ohci.h @@ -421,6 +421,9 @@ struct ohci_hcd { struct dentry *debug_periodic; struct dentry *debug_registers; #endif + /* platform-specific data -- must come last */ + unsigned long priv[0] __aligned(sizeof(s64)); + }; #ifdef CONFIG_PCI @@ -718,3 +721,20 @@ static inline u32 roothub_status (struct ohci_hcd *hc) { return ohci_readl (hc, &hc->regs->roothub.status); } static inline u32 roothub_portstatus (struct ohci_hcd *hc, int i) { return read_roothub (hc, portstatus [i], 0xffe0fce0); } + +/* Declarations of things exported for use by ohci platform drivers */ + +struct ohci_driver_overrides { + const char *product_desc; + size_t extra_priv_size; + int (*reset)(struct usb_hcd *hcd); +}; + +extern void ohci_init_driver(struct hc_driver *drv, + const struct ohci_driver_overrides *over); +extern int ohci_restart(struct ohci_hcd *ohci); +extern int ohci_setup(struct usb_hcd *hcd); +#ifdef CONFIG_PM +extern int ohci_suspend(struct usb_hcd *hcd, bool do_wakeup); +extern int ohci_resume(struct usb_hcd *hcd, bool hibernated); +#endif diff --git a/drivers/usb/host/oxu210hp-hcd.c b/drivers/usb/host/oxu210hp-hcd.c index 0f401db..4a6df2d 100644 --- a/drivers/usb/host/oxu210hp-hcd.c +++ b/drivers/usb/host/oxu210hp-hcd.c @@ -3874,7 +3874,6 @@ static int oxu_drv_probe(struct platform_device *pdev) error_init: kfree(info); - platform_set_drvdata(pdev, NULL); error_alloc: iounmap(base); @@ -3907,7 +3906,6 @@ static int oxu_drv_remove(struct platform_device *pdev) release_mem_region(memstart, memlen); kfree(info); - platform_set_drvdata(pdev, NULL); return 0; } diff --git a/drivers/usb/host/pci-quirks.c b/drivers/usb/host/pci-quirks.c index 4c338ec..b9848e4 100644 --- a/drivers/usb/host/pci-quirks.c +++ b/drivers/usb/host/pci-quirks.c @@ -91,6 +91,19 @@ static struct amd_chipset_info { static DEFINE_SPINLOCK(amd_lock); +void sb800_prefetch(struct device *dev, int on) +{ + u16 misc; + struct pci_dev *pdev = to_pci_dev(dev); + + pci_read_config_word(pdev, 0x50, &misc); + if (on == 0) + pci_write_config_word(pdev, 0x50, misc & 0xfcff); + else + pci_write_config_word(pdev, 0x50, misc | 0x0300); +} +EXPORT_SYMBOL_GPL(sb800_prefetch); + int usb_amd_find_chipset_info(void) { u8 rev = 0; diff --git a/drivers/usb/host/pci-quirks.h b/drivers/usb/host/pci-quirks.h index 7f69a39..4b8a209 100644 --- a/drivers/usb/host/pci-quirks.h +++ b/drivers/usb/host/pci-quirks.h @@ -11,11 +11,13 @@ void usb_amd_quirk_pll_enable(void); bool usb_is_intel_switchable_xhci(struct pci_dev *pdev); void usb_enable_xhci_ports(struct pci_dev *xhci_pdev); void usb_disable_xhci_ports(struct pci_dev *xhci_pdev); +void sb800_prefetch(struct device *dev, int on); #else static inline void usb_amd_quirk_pll_disable(void) {} static inline void usb_amd_quirk_pll_enable(void) {} static inline void usb_amd_dev_put(void) {} static inline void usb_disable_xhci_ports(struct pci_dev *xhci_pdev) {} +static inline void sb800_prefetch(struct device *dev, int on) {} #endif /* CONFIG_PCI */ #endif /* __LINUX_USB_PCI_QUIRKS_H */ diff --git a/drivers/usb/host/uhci-grlib.c b/drivers/usb/host/uhci-grlib.c index 511bfc4..53c23ff 100644 --- a/drivers/usb/host/uhci-grlib.c +++ b/drivers/usb/host/uhci-grlib.c @@ -157,9 +157,7 @@ err_rmr: static int uhci_hcd_grlib_remove(struct platform_device *op) { - struct usb_hcd *hcd = dev_get_drvdata(&op->dev); - - dev_set_drvdata(&op->dev, NULL); + struct usb_hcd *hcd = platform_get_drvdata(op); dev_dbg(&op->dev, "stopping GRLIB GRUSBHC UHCI USB Controller\n"); @@ -183,7 +181,7 @@ static int uhci_hcd_grlib_remove(struct platform_device *op) */ static void uhci_hcd_grlib_shutdown(struct platform_device *op) { - struct usb_hcd *hcd = dev_get_drvdata(&op->dev); + struct usb_hcd *hcd = platform_get_drvdata(op); uhci_hc_died(hcd_to_uhci(hcd)); } diff --git a/drivers/usb/host/uhci-platform.c b/drivers/usb/host/uhci-platform.c index f1db61a..d033a0e 100644 --- a/drivers/usb/host/uhci-platform.c +++ b/drivers/usb/host/uhci-platform.c @@ -130,7 +130,6 @@ static int uhci_hcd_platform_remove(struct platform_device *pdev) iounmap(hcd->regs); release_mem_region(hcd->rsrc_start, hcd->rsrc_len); usb_put_hcd(hcd); - platform_set_drvdata(pdev, NULL); return 0; } @@ -144,7 +143,7 @@ static int uhci_hcd_platform_remove(struct platform_device *pdev) */ static void uhci_hcd_platform_shutdown(struct platform_device *op) { - struct usb_hcd *hcd = dev_get_drvdata(&op->dev); + struct usb_hcd *hcd = platform_get_drvdata(op); uhci_hc_died(hcd_to_uhci(hcd)); } @@ -161,6 +160,6 @@ static struct platform_driver uhci_platform_driver = { .driver = { .name = "platform-uhci", .owner = THIS_MODULE, - .of_match_table = of_match_ptr(platform_uhci_ids), + .of_match_table = platform_uhci_ids, }, }; diff --git a/drivers/usb/host/whci/hcd.c b/drivers/usb/host/whci/hcd.c index c3a6478..ecc88db 100644 --- a/drivers/usb/host/whci/hcd.c +++ b/drivers/usb/host/whci/hcd.c @@ -231,8 +231,6 @@ static struct hc_driver whc_hc_driver = { .hub_status_data = wusbhc_rh_status_data, .hub_control = wusbhc_rh_control, - .bus_suspend = wusbhc_rh_suspend, - .bus_resume = wusbhc_rh_resume, .start_port_reset = wusbhc_rh_start_port_reset, }; diff --git a/drivers/usb/host/xhci-dbg.c b/drivers/usb/host/xhci-dbg.c index 5f3a7c7..5d5e58f 100644 --- a/drivers/usb/host/xhci-dbg.c +++ b/drivers/usb/host/xhci-dbg.c @@ -503,11 +503,14 @@ static void xhci_dbg_ep_ctx(struct xhci_hcd *xhci, if (last_ep < 31) last_ep_ctx = last_ep + 1; for (i = 0; i < last_ep_ctx; ++i) { + unsigned int epaddr = xhci_get_endpoint_address(i); struct xhci_ep_ctx *ep_ctx = xhci_get_ep_ctx(xhci, ctx, i); dma_addr_t dma = ctx->dma + ((unsigned long)ep_ctx - (unsigned long)ctx->bytes); - xhci_dbg(xhci, "Endpoint %02d Context:\n", i); + xhci_dbg(xhci, "%s Endpoint %02d Context (ep_index %02d):\n", + usb_endpoint_out(epaddr) ? "OUT" : "IN", + epaddr & USB_ENDPOINT_NUMBER_MASK, i); xhci_dbg(xhci, "@%p (virt) @%08llx (dma) %#08x - ep_info\n", &ep_ctx->ep_info, (unsigned long long)dma, ep_ctx->ep_info); @@ -550,6 +553,11 @@ void xhci_dbg_ctx(struct xhci_hcd *xhci, if (ctx->type == XHCI_CTX_TYPE_INPUT) { struct xhci_input_control_ctx *ctrl_ctx = xhci_get_input_control_ctx(xhci, ctx); + if (!ctrl_ctx) { + xhci_warn(xhci, "Could not get input context, bad type.\n"); + return; + } + xhci_dbg(xhci, "@%p (virt) @%08llx (dma) %#08x - drop flags\n", &ctrl_ctx->drop_flags, (unsigned long long)dma, ctrl_ctx->drop_flags); diff --git a/drivers/usb/host/xhci-ext-caps.h b/drivers/usb/host/xhci-ext-caps.h index 377f424..8d7a132 100644 --- a/drivers/usb/host/xhci-ext-caps.h +++ b/drivers/usb/host/xhci-ext-caps.h @@ -71,6 +71,7 @@ /* USB 2.0 xHCI 1.0 hardware LMP capability - section 7.2.2.1.3.2 */ #define XHCI_HLC (1 << 19) +#define XHCI_BLC (1 << 19) /* command register values to disable interrupts and halt the HC */ /* start/stop HC execution - do not write unless HC is halted*/ diff --git a/drivers/usb/host/xhci-hub.c b/drivers/usb/host/xhci-hub.c index 187a3ec..1d35459 100644 --- a/drivers/usb/host/xhci-hub.c +++ b/drivers/usb/host/xhci-hub.c @@ -867,18 +867,18 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, case USB_PORT_FEAT_U1_TIMEOUT: if (hcd->speed != HCD_USB3) goto error; - temp = xhci_readl(xhci, port_array[wIndex] + 1); + temp = xhci_readl(xhci, port_array[wIndex] + PORTPMSC); temp &= ~PORT_U1_TIMEOUT_MASK; temp |= PORT_U1_TIMEOUT(timeout); - xhci_writel(xhci, temp, port_array[wIndex] + 1); + xhci_writel(xhci, temp, port_array[wIndex] + PORTPMSC); break; case USB_PORT_FEAT_U2_TIMEOUT: if (hcd->speed != HCD_USB3) goto error; - temp = xhci_readl(xhci, port_array[wIndex] + 1); + temp = xhci_readl(xhci, port_array[wIndex] + PORTPMSC); temp &= ~PORT_U2_TIMEOUT_MASK; temp |= PORT_U2_TIMEOUT(timeout); - xhci_writel(xhci, temp, port_array[wIndex] + 1); + xhci_writel(xhci, temp, port_array[wIndex] + PORTPMSC); break; default: goto error; @@ -1098,10 +1098,8 @@ int xhci_bus_suspend(struct usb_hcd *hcd) __le32 __iomem *addr; u32 tmp; - /* Add one to the port status register address to get - * the port power control register address. - */ - addr = port_array[port_index] + 1; + /* Get the port power control register address. */ + addr = port_array[port_index] + PORTPMSC; tmp = xhci_readl(xhci, addr); tmp |= PORT_RWE; xhci_writel(xhci, tmp, addr); @@ -1193,7 +1191,7 @@ int xhci_bus_resume(struct usb_hcd *hcd) /* Add one to the port status register address to get * the port power control register address. */ - addr = port_array[port_index] + 1; + addr = port_array[port_index] + PORTPMSC; tmp = xhci_readl(xhci, addr); tmp &= ~PORT_RWE; xhci_writel(xhci, tmp, addr); diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c index fbf75e5..df6978a 100644 --- a/drivers/usb/host/xhci-mem.c +++ b/drivers/usb/host/xhci-mem.c @@ -358,17 +358,25 @@ int xhci_ring_expansion(struct xhci_hcd *xhci, struct xhci_ring *ring, static struct xhci_container_ctx *xhci_alloc_container_ctx(struct xhci_hcd *xhci, int type, gfp_t flags) { - struct xhci_container_ctx *ctx = kzalloc(sizeof(*ctx), flags); + struct xhci_container_ctx *ctx; + + if ((type != XHCI_CTX_TYPE_DEVICE) && (type != XHCI_CTX_TYPE_INPUT)) + return NULL; + + ctx = kzalloc(sizeof(*ctx), flags); if (!ctx) return NULL; - BUG_ON((type != XHCI_CTX_TYPE_DEVICE) && (type != XHCI_CTX_TYPE_INPUT)); ctx->type = type; ctx->size = HCC_64BYTE_CONTEXT(xhci->hcc_params) ? 2048 : 1024; if (type == XHCI_CTX_TYPE_INPUT) ctx->size += CTX_SIZE(xhci->hcc_params); ctx->bytes = dma_pool_alloc(xhci->device_pool, flags, &ctx->dma); + if (!ctx->bytes) { + kfree(ctx); + return NULL; + } memset(ctx->bytes, 0, ctx->size); return ctx; } @@ -385,7 +393,9 @@ static void xhci_free_container_ctx(struct xhci_hcd *xhci, struct xhci_input_control_ctx *xhci_get_input_control_ctx(struct xhci_hcd *xhci, struct xhci_container_ctx *ctx) { - BUG_ON(ctx->type != XHCI_CTX_TYPE_INPUT); + if (ctx->type != XHCI_CTX_TYPE_INPUT) + return NULL; + return (struct xhci_input_control_ctx *)ctx->bytes; } @@ -1049,6 +1059,7 @@ int xhci_setup_addressable_virt_dev(struct xhci_hcd *xhci, struct usb_device *ud struct xhci_ep_ctx *ep0_ctx; struct xhci_slot_ctx *slot_ctx; u32 port_num; + u32 max_packets; struct usb_device *top_dev; dev = xhci->devs[udev->slot_id]; @@ -1066,15 +1077,20 @@ int xhci_setup_addressable_virt_dev(struct xhci_hcd *xhci, struct usb_device *ud switch (udev->speed) { case USB_SPEED_SUPER: slot_ctx->dev_info |= cpu_to_le32(SLOT_SPEED_SS); + max_packets = MAX_PACKET(512); break; case USB_SPEED_HIGH: slot_ctx->dev_info |= cpu_to_le32(SLOT_SPEED_HS); + max_packets = MAX_PACKET(64); break; + /* USB core guesses at a 64-byte max packet first for FS devices */ case USB_SPEED_FULL: slot_ctx->dev_info |= cpu_to_le32(SLOT_SPEED_FS); + max_packets = MAX_PACKET(64); break; case USB_SPEED_LOW: slot_ctx->dev_info |= cpu_to_le32(SLOT_SPEED_LS); + max_packets = MAX_PACKET(8); break; case USB_SPEED_WIRELESS: xhci_dbg(xhci, "FIXME xHCI doesn't support wireless speeds\n"); @@ -1082,7 +1098,7 @@ int xhci_setup_addressable_virt_dev(struct xhci_hcd *xhci, struct usb_device *ud break; default: /* Speed was set earlier, this shouldn't happen. */ - BUG(); + return -EINVAL; } /* Find the root hub port this device is under */ port_num = xhci_find_real_port_number(xhci, udev); @@ -1141,31 +1157,10 @@ int xhci_setup_addressable_virt_dev(struct xhci_hcd *xhci, struct usb_device *ud /* Step 4 - ring already allocated */ /* Step 5 */ ep0_ctx->ep_info2 = cpu_to_le32(EP_TYPE(CTRL_EP)); - /* - * XXX: Not sure about wireless USB devices. - */ - switch (udev->speed) { - case USB_SPEED_SUPER: - ep0_ctx->ep_info2 |= cpu_to_le32(MAX_PACKET(512)); - break; - case USB_SPEED_HIGH: - /* USB core guesses at a 64-byte max packet first for FS devices */ - case USB_SPEED_FULL: - ep0_ctx->ep_info2 |= cpu_to_le32(MAX_PACKET(64)); - break; - case USB_SPEED_LOW: - ep0_ctx->ep_info2 |= cpu_to_le32(MAX_PACKET(8)); - break; - case USB_SPEED_WIRELESS: - xhci_dbg(xhci, "FIXME xHCI doesn't support wireless speeds\n"); - return -EINVAL; - break; - default: - /* New speed? */ - BUG(); - } + /* EP 0 can handle "burst" sizes of 1, so Max Burst Size field is 0 */ - ep0_ctx->ep_info2 |= cpu_to_le32(MAX_BURST(0) | ERROR_COUNT(3)); + ep0_ctx->ep_info2 |= cpu_to_le32(MAX_BURST(0) | ERROR_COUNT(3) | + max_packets); ep0_ctx->deq = cpu_to_le64(dev->eps[0].ring->first_seg->dma | dev->eps[0].ring->cycle_state); @@ -1338,7 +1333,7 @@ static u32 xhci_get_endpoint_type(struct usb_device *udev, else type = EP_TYPE(INT_OUT_EP); } else { - BUG(); + type = 0; } return type; } @@ -1384,10 +1379,16 @@ int xhci_endpoint_init(struct xhci_hcd *xhci, unsigned int max_burst; enum xhci_ring_type type; u32 max_esit_payload; + u32 endpoint_type; ep_index = xhci_get_endpoint_index(&ep->desc); ep_ctx = xhci_get_ep_ctx(xhci, virt_dev->in_ctx, ep_index); + endpoint_type = xhci_get_endpoint_type(udev, ep); + if (!endpoint_type) + return -EINVAL; + ep_ctx->ep_info2 = cpu_to_le32(endpoint_type); + type = usb_endpoint_type(&ep->desc); /* Set up the endpoint ring */ virt_dev->eps[ep_index].new_ring = @@ -1416,11 +1417,9 @@ int xhci_endpoint_init(struct xhci_hcd *xhci, * CErr shall be set to 0 for Isoch endpoints. */ if (!usb_endpoint_xfer_isoc(&ep->desc)) - ep_ctx->ep_info2 = cpu_to_le32(ERROR_COUNT(3)); + ep_ctx->ep_info2 |= cpu_to_le32(ERROR_COUNT(3)); else - ep_ctx->ep_info2 = cpu_to_le32(ERROR_COUNT(0)); - - ep_ctx->ep_info2 |= cpu_to_le32(xhci_get_endpoint_type(udev, ep)); + ep_ctx->ep_info2 |= cpu_to_le32(ERROR_COUNT(0)); /* Set the max packet size and max burst */ max_packet = GET_MAX_PACKET(usb_endpoint_maxp(&ep->desc)); @@ -1856,6 +1855,7 @@ no_bw: kfree(xhci->usb3_ports); kfree(xhci->port_array); kfree(xhci->rh_bw); + kfree(xhci->ext_caps); xhci->page_size = 0; xhci->page_shift = 0; @@ -2043,7 +2043,7 @@ static void xhci_set_hc_event_deq(struct xhci_hcd *xhci) } static void xhci_add_in_port(struct xhci_hcd *xhci, unsigned int num_ports, - __le32 __iomem *addr, u8 major_revision) + __le32 __iomem *addr, u8 major_revision, int max_caps) { u32 temp, port_offset, port_count; int i; @@ -2068,6 +2068,10 @@ static void xhci_add_in_port(struct xhci_hcd *xhci, unsigned int num_ports, /* WTF? "Valid values are ‘1’ to MaxPorts" */ return; + /* cache usb2 port capabilities */ + if (major_revision < 0x03 && xhci->num_ext_caps < max_caps) + xhci->ext_caps[xhci->num_ext_caps++] = temp; + /* Check the host's USB2 LPM capability */ if ((xhci->hci_version == 0x96) && (major_revision != 0x03) && (temp & XHCI_L1C)) { @@ -2125,10 +2129,11 @@ static void xhci_add_in_port(struct xhci_hcd *xhci, unsigned int num_ports, */ static int xhci_setup_port_arrays(struct xhci_hcd *xhci, gfp_t flags) { - __le32 __iomem *addr; - u32 offset; + __le32 __iomem *addr, *tmp_addr; + u32 offset, tmp_offset; unsigned int num_ports; int i, j, port_index; + int cap_count = 0; addr = &xhci->cap_regs->hcc_params; offset = XHCI_HCC_EXT_CAPS(xhci_readl(xhci, addr)); @@ -2161,13 +2166,32 @@ static int xhci_setup_port_arrays(struct xhci_hcd *xhci, gfp_t flags) * See section 5.3.6 for offset calculation. */ addr = &xhci->cap_regs->hc_capbase + offset; + + tmp_addr = addr; + tmp_offset = offset; + + /* count extended protocol capability entries for later caching */ + do { + u32 cap_id; + cap_id = xhci_readl(xhci, tmp_addr); + if (XHCI_EXT_CAPS_ID(cap_id) == XHCI_EXT_CAPS_PROTOCOL) + cap_count++; + tmp_offset = XHCI_EXT_CAPS_NEXT(cap_id); + tmp_addr += tmp_offset; + } while (tmp_offset); + + xhci->ext_caps = kzalloc(sizeof(*xhci->ext_caps) * cap_count, flags); + if (!xhci->ext_caps) + return -ENOMEM; + while (1) { u32 cap_id; cap_id = xhci_readl(xhci, addr); if (XHCI_EXT_CAPS_ID(cap_id) == XHCI_EXT_CAPS_PROTOCOL) xhci_add_in_port(xhci, num_ports, addr, - (u8) XHCI_EXT_PORT_MAJOR(cap_id)); + (u8) XHCI_EXT_PORT_MAJOR(cap_id), + cap_count); offset = XHCI_EXT_CAPS_NEXT(cap_id); if (!offset || (xhci->num_usb2_ports + xhci->num_usb3_ports) == num_ports) diff --git a/drivers/usb/host/xhci-plat.c b/drivers/usb/host/xhci-plat.c index df90fe5..51e22bf 100644 --- a/drivers/usb/host/xhci-plat.c +++ b/drivers/usb/host/xhci-plat.c @@ -130,7 +130,7 @@ static int xhci_plat_probe(struct platform_device *pdev) goto unmap_registers; /* USB 2.0 roothub is stored in the platform_device now. */ - hcd = dev_get_drvdata(&pdev->dev); + hcd = platform_get_drvdata(pdev); xhci = hcd_to_xhci(hcd); xhci->shared_hcd = usb_create_shared_hcd(driver, &pdev->dev, dev_name(&pdev->dev), hcd); @@ -179,6 +179,7 @@ static int xhci_plat_remove(struct platform_device *dev) usb_remove_hcd(hcd); iounmap(hcd->regs); + release_mem_region(hcd->rsrc_start, hcd->rsrc_len); usb_put_hcd(hcd); kfree(xhci); diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index 1969c00..1e57eaf 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -1424,6 +1424,10 @@ static void handle_cmd_completion(struct xhci_hcd *xhci, */ ctrl_ctx = xhci_get_input_control_ctx(xhci, virt_dev->in_ctx); + if (!ctrl_ctx) { + xhci_warn(xhci, "Could not get input context, bad type.\n"); + break; + } /* Input ctx add_flags are the endpoint index plus one */ ep_index = xhci_last_valid_endpoint(le32_to_cpu(ctrl_ctx->add_flags)) - 1; /* A usb_set_interface() call directly after clearing a halted @@ -2799,7 +2803,7 @@ hw_died: return IRQ_HANDLED; } -irqreturn_t xhci_msi_irq(int irq, struct usb_hcd *hcd) +irqreturn_t xhci_msi_irq(int irq, void *hcd) { return xhci_irq(hcd); } diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index d8f640b..2c49f00 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -218,7 +218,7 @@ static int xhci_setup_msi(struct xhci_hcd *xhci) return ret; } - ret = request_irq(pdev->irq, (irq_handler_t)xhci_msi_irq, + ret = request_irq(pdev->irq, xhci_msi_irq, 0, "xhci_hcd", xhci_to_hcd(xhci)); if (ret) { xhci_dbg(xhci, "disable MSI interrupt\n"); @@ -290,7 +290,7 @@ static int xhci_setup_msix(struct xhci_hcd *xhci) for (i = 0; i < xhci->msix_count; i++) { ret = request_irq(xhci->msix_entries[i].vector, - (irq_handler_t)xhci_msi_irq, + xhci_msi_irq, 0, "xhci_hcd", xhci_to_hcd(xhci)); if (ret) goto disable_msix; @@ -1121,6 +1121,16 @@ unsigned int xhci_get_endpoint_index(struct usb_endpoint_descriptor *desc) return index; } +/* The reverse operation to xhci_get_endpoint_index. Calculate the USB endpoint + * address from the XHCI endpoint index. + */ +unsigned int xhci_get_endpoint_address(unsigned int ep_index) +{ + unsigned int number = DIV_ROUND_UP(ep_index, 2); + unsigned int direction = ep_index % 2 ? USB_DIR_OUT : USB_DIR_IN; + return direction | number; +} + /* Find the flag for this endpoint (for use in the control context). Use the * endpoint index to create a bitmask. The slot context is bit 0, endpoint 0 is * bit 1, etc. @@ -1225,19 +1235,25 @@ static int xhci_check_maxpacket(struct xhci_hcd *xhci, unsigned int slot_id, hw_max_packet_size); xhci_dbg(xhci, "Issuing evaluate context command.\n"); + /* Set up the input context flags for the command */ + /* FIXME: This won't work if a non-default control endpoint + * changes max packet sizes. + */ + in_ctx = xhci->devs[slot_id]->in_ctx; + ctrl_ctx = xhci_get_input_control_ctx(xhci, in_ctx); + if (!ctrl_ctx) { + xhci_warn(xhci, "%s: Could not get input context, bad type.\n", + __func__); + return -ENOMEM; + } /* Set up the modified control endpoint 0 */ xhci_endpoint_copy(xhci, xhci->devs[slot_id]->in_ctx, xhci->devs[slot_id]->out_ctx, ep_index); - in_ctx = xhci->devs[slot_id]->in_ctx; + ep_ctx = xhci_get_ep_ctx(xhci, in_ctx, ep_index); ep_ctx->ep_info2 &= cpu_to_le32(~MAX_PACKET_MASK); ep_ctx->ep_info2 |= cpu_to_le32(MAX_PACKET(max_packet_size)); - /* Set up the input context flags for the command */ - /* FIXME: This won't work if a non-default control endpoint - * changes max packet sizes. - */ - ctrl_ctx = xhci_get_input_control_ctx(xhci, in_ctx); ctrl_ctx->add_flags = cpu_to_le32(EP0_FLAG); ctrl_ctx->drop_flags = 0; @@ -1597,6 +1613,12 @@ int xhci_drop_endpoint(struct usb_hcd *hcd, struct usb_device *udev, in_ctx = xhci->devs[udev->slot_id]->in_ctx; out_ctx = xhci->devs[udev->slot_id]->out_ctx; ctrl_ctx = xhci_get_input_control_ctx(xhci, in_ctx); + if (!ctrl_ctx) { + xhci_warn(xhci, "%s: Could not get input context, bad type.\n", + __func__); + return 0; + } + ep_index = xhci_get_endpoint_index(&ep->desc); ep_ctx = xhci_get_ep_ctx(xhci, out_ctx, ep_index); /* If the HC already knows the endpoint is disabled, @@ -1691,8 +1713,13 @@ int xhci_add_endpoint(struct usb_hcd *hcd, struct usb_device *udev, in_ctx = virt_dev->in_ctx; out_ctx = virt_dev->out_ctx; ctrl_ctx = xhci_get_input_control_ctx(xhci, in_ctx); - ep_index = xhci_get_endpoint_index(&ep->desc); + if (!ctrl_ctx) { + xhci_warn(xhci, "%s: Could not get input context, bad type.\n", + __func__); + return 0; + } + ep_index = xhci_get_endpoint_index(&ep->desc); /* If this endpoint is already in use, and the upper layers are trying * to add it again without dropping it, reject the addition. */ @@ -1765,12 +1792,18 @@ static void xhci_zero_in_ctx(struct xhci_hcd *xhci, struct xhci_virt_device *vir struct xhci_slot_ctx *slot_ctx; int i; + ctrl_ctx = xhci_get_input_control_ctx(xhci, virt_dev->in_ctx); + if (!ctrl_ctx) { + xhci_warn(xhci, "%s: Could not get input context, bad type.\n", + __func__); + return; + } + /* When a device's add flag and drop flag are zero, any subsequent * configure endpoint command will leave that endpoint's state * untouched. Make sure we don't leave any old state in the input * endpoint contexts. */ - ctrl_ctx = xhci_get_input_control_ctx(xhci, virt_dev->in_ctx); ctrl_ctx->drop_flags = 0; ctrl_ctx->add_flags = 0; slot_ctx = xhci_get_slot_ctx(xhci, virt_dev->in_ctx); @@ -1877,13 +1910,11 @@ static int xhci_evaluate_context_result(struct xhci_hcd *xhci, } static u32 xhci_count_num_new_endpoints(struct xhci_hcd *xhci, - struct xhci_container_ctx *in_ctx) + struct xhci_input_control_ctx *ctrl_ctx) { - struct xhci_input_control_ctx *ctrl_ctx; u32 valid_add_flags; u32 valid_drop_flags; - ctrl_ctx = xhci_get_input_control_ctx(xhci, in_ctx); /* Ignore the slot flag (bit 0), and the default control endpoint flag * (bit 1). The default control endpoint is added during the Address * Device command and is never removed until the slot is disabled. @@ -1900,13 +1931,11 @@ static u32 xhci_count_num_new_endpoints(struct xhci_hcd *xhci, } static unsigned int xhci_count_num_dropped_endpoints(struct xhci_hcd *xhci, - struct xhci_container_ctx *in_ctx) + struct xhci_input_control_ctx *ctrl_ctx) { - struct xhci_input_control_ctx *ctrl_ctx; u32 valid_add_flags; u32 valid_drop_flags; - ctrl_ctx = xhci_get_input_control_ctx(xhci, in_ctx); valid_add_flags = ctrl_ctx->add_flags >> 2; valid_drop_flags = ctrl_ctx->drop_flags >> 2; @@ -1928,11 +1957,11 @@ static unsigned int xhci_count_num_dropped_endpoints(struct xhci_hcd *xhci, * Must be called with xhci->lock held. */ static int xhci_reserve_host_resources(struct xhci_hcd *xhci, - struct xhci_container_ctx *in_ctx) + struct xhci_input_control_ctx *ctrl_ctx) { u32 added_eps; - added_eps = xhci_count_num_new_endpoints(xhci, in_ctx); + added_eps = xhci_count_num_new_endpoints(xhci, ctrl_ctx); if (xhci->num_active_eps + added_eps > xhci->limit_active_eps) { xhci_dbg(xhci, "Not enough ep ctxs: " "%u active, need to add %u, limit is %u.\n", @@ -1953,11 +1982,11 @@ static int xhci_reserve_host_resources(struct xhci_hcd *xhci, * Must be called with xhci->lock held. */ static void xhci_free_host_resources(struct xhci_hcd *xhci, - struct xhci_container_ctx *in_ctx) + struct xhci_input_control_ctx *ctrl_ctx) { u32 num_failed_eps; - num_failed_eps = xhci_count_num_new_endpoints(xhci, in_ctx); + num_failed_eps = xhci_count_num_new_endpoints(xhci, ctrl_ctx); xhci->num_active_eps -= num_failed_eps; xhci_dbg(xhci, "Removing %u failed ep ctxs, %u now active.\n", num_failed_eps, @@ -1971,11 +2000,11 @@ static void xhci_free_host_resources(struct xhci_hcd *xhci, * Must be called with xhci->lock held. */ static void xhci_finish_resource_reservation(struct xhci_hcd *xhci, - struct xhci_container_ctx *in_ctx) + struct xhci_input_control_ctx *ctrl_ctx) { u32 num_dropped_eps; - num_dropped_eps = xhci_count_num_dropped_endpoints(xhci, in_ctx); + num_dropped_eps = xhci_count_num_dropped_endpoints(xhci, ctrl_ctx); xhci->num_active_eps -= num_dropped_eps; if (num_dropped_eps) xhci_dbg(xhci, "Removing %u dropped ep ctxs, %u now active.\n", @@ -2470,6 +2499,11 @@ static int xhci_reserve_bandwidth(struct xhci_hcd *xhci, old_active_eps = virt_dev->tt_info->active_eps; ctrl_ctx = xhci_get_input_control_ctx(xhci, in_ctx); + if (!ctrl_ctx) { + xhci_warn(xhci, "%s: Could not get input context, bad type.\n", + __func__); + return -ENOMEM; + } for (i = 0; i < 31; i++) { if (!EP_IS_ADDED(ctrl_ctx, i) && !EP_IS_DROPPED(ctrl_ctx, i)) @@ -2554,6 +2588,7 @@ static int xhci_configure_endpoint(struct xhci_hcd *xhci, int timeleft; unsigned long flags; struct xhci_container_ctx *in_ctx; + struct xhci_input_control_ctx *ctrl_ctx; struct completion *cmd_completion; u32 *cmd_status; struct xhci_virt_device *virt_dev; @@ -2566,9 +2601,16 @@ static int xhci_configure_endpoint(struct xhci_hcd *xhci, in_ctx = command->in_ctx; else in_ctx = virt_dev->in_ctx; + ctrl_ctx = xhci_get_input_control_ctx(xhci, in_ctx); + if (!ctrl_ctx) { + spin_unlock_irqrestore(&xhci->lock, flags); + xhci_warn(xhci, "%s: Could not get input context, bad type.\n", + __func__); + return -ENOMEM; + } if ((xhci->quirks & XHCI_EP_LIMIT_QUIRK) && - xhci_reserve_host_resources(xhci, in_ctx)) { + xhci_reserve_host_resources(xhci, ctrl_ctx)) { spin_unlock_irqrestore(&xhci->lock, flags); xhci_warn(xhci, "Not enough host resources, " "active endpoint contexts = %u\n", @@ -2578,7 +2620,7 @@ static int xhci_configure_endpoint(struct xhci_hcd *xhci, if ((xhci->quirks & XHCI_SW_BW_CHECKING) && xhci_reserve_bandwidth(xhci, virt_dev, in_ctx)) { if ((xhci->quirks & XHCI_EP_LIMIT_QUIRK)) - xhci_free_host_resources(xhci, in_ctx); + xhci_free_host_resources(xhci, ctrl_ctx); spin_unlock_irqrestore(&xhci->lock, flags); xhci_warn(xhci, "Not enough bandwidth\n"); return -ENOMEM; @@ -2614,7 +2656,7 @@ static int xhci_configure_endpoint(struct xhci_hcd *xhci, if (command) list_del(&command->cmd_list); if ((xhci->quirks & XHCI_EP_LIMIT_QUIRK)) - xhci_free_host_resources(xhci, in_ctx); + xhci_free_host_resources(xhci, ctrl_ctx); spin_unlock_irqrestore(&xhci->lock, flags); xhci_dbg(xhci, "FIXME allocate a new ring segment\n"); return -ENOMEM; @@ -2650,9 +2692,9 @@ static int xhci_configure_endpoint(struct xhci_hcd *xhci, * Otherwise, clean up the estimate to include dropped eps. */ if (ret) - xhci_free_host_resources(xhci, in_ctx); + xhci_free_host_resources(xhci, ctrl_ctx); else - xhci_finish_resource_reservation(xhci, in_ctx); + xhci_finish_resource_reservation(xhci, ctrl_ctx); spin_unlock_irqrestore(&xhci->lock, flags); } return ret; @@ -2689,6 +2731,11 @@ int xhci_check_bandwidth(struct usb_hcd *hcd, struct usb_device *udev) /* See section 4.6.6 - A0 = 1; A1 = D0 = D1 = 0 */ ctrl_ctx = xhci_get_input_control_ctx(xhci, virt_dev->in_ctx); + if (!ctrl_ctx) { + xhci_warn(xhci, "%s: Could not get input context, bad type.\n", + __func__); + return -ENOMEM; + } ctrl_ctx->add_flags |= cpu_to_le32(SLOT_FLAG); ctrl_ctx->add_flags &= cpu_to_le32(~EP0_FLAG); ctrl_ctx->drop_flags &= cpu_to_le32(~(SLOT_FLAG | EP0_FLAG)); @@ -2767,10 +2814,9 @@ void xhci_reset_bandwidth(struct usb_hcd *hcd, struct usb_device *udev) static void xhci_setup_input_ctx_for_config_ep(struct xhci_hcd *xhci, struct xhci_container_ctx *in_ctx, struct xhci_container_ctx *out_ctx, + struct xhci_input_control_ctx *ctrl_ctx, u32 add_flags, u32 drop_flags) { - struct xhci_input_control_ctx *ctrl_ctx; - ctrl_ctx = xhci_get_input_control_ctx(xhci, in_ctx); ctrl_ctx->add_flags = cpu_to_le32(add_flags); ctrl_ctx->drop_flags = cpu_to_le32(drop_flags); xhci_slot_copy(xhci, in_ctx, out_ctx); @@ -2784,14 +2830,22 @@ static void xhci_setup_input_ctx_for_quirk(struct xhci_hcd *xhci, unsigned int slot_id, unsigned int ep_index, struct xhci_dequeue_state *deq_state) { + struct xhci_input_control_ctx *ctrl_ctx; struct xhci_container_ctx *in_ctx; struct xhci_ep_ctx *ep_ctx; u32 added_ctxs; dma_addr_t addr; + in_ctx = xhci->devs[slot_id]->in_ctx; + ctrl_ctx = xhci_get_input_control_ctx(xhci, in_ctx); + if (!ctrl_ctx) { + xhci_warn(xhci, "%s: Could not get input context, bad type.\n", + __func__); + return; + } + xhci_endpoint_copy(xhci, xhci->devs[slot_id]->in_ctx, xhci->devs[slot_id]->out_ctx, ep_index); - in_ctx = xhci->devs[slot_id]->in_ctx; ep_ctx = xhci_get_ep_ctx(xhci, in_ctx, ep_index); addr = xhci_trb_virt_to_dma(deq_state->new_deq_seg, deq_state->new_deq_ptr); @@ -2807,7 +2861,8 @@ static void xhci_setup_input_ctx_for_quirk(struct xhci_hcd *xhci, added_ctxs = xhci_get_endpoint_flag_from_index(ep_index); xhci_setup_input_ctx_for_config_ep(xhci, xhci->devs[slot_id]->in_ctx, - xhci->devs[slot_id]->out_ctx, added_ctxs, added_ctxs); + xhci->devs[slot_id]->out_ctx, ctrl_ctx, + added_ctxs, added_ctxs); } void xhci_cleanup_stalled_ring(struct xhci_hcd *xhci, @@ -3065,6 +3120,7 @@ int xhci_alloc_streams(struct usb_hcd *hcd, struct usb_device *udev, struct xhci_hcd *xhci; struct xhci_virt_device *vdev; struct xhci_command *config_cmd; + struct xhci_input_control_ctx *ctrl_ctx; unsigned int ep_index; unsigned int num_stream_ctxs; unsigned long flags; @@ -3086,6 +3142,13 @@ int xhci_alloc_streams(struct usb_hcd *hcd, struct usb_device *udev, xhci_dbg(xhci, "Could not allocate xHCI command structure.\n"); return -ENOMEM; } + ctrl_ctx = xhci_get_input_control_ctx(xhci, config_cmd->in_ctx); + if (!ctrl_ctx) { + xhci_warn(xhci, "%s: Could not get input context, bad type.\n", + __func__); + xhci_free_command(xhci, config_cmd); + return -ENOMEM; + } /* Check to make sure all endpoints are not already configured for * streams. While we're at it, find the maximum number of streams that @@ -3152,7 +3215,8 @@ int xhci_alloc_streams(struct usb_hcd *hcd, struct usb_device *udev, * and add the updated copy from the input context. */ xhci_setup_input_ctx_for_config_ep(xhci, config_cmd->in_ctx, - vdev->out_ctx, changed_ep_bitmask, changed_ep_bitmask); + vdev->out_ctx, ctrl_ctx, + changed_ep_bitmask, changed_ep_bitmask); /* Issue and wait for the configure endpoint command */ ret = xhci_configure_endpoint(xhci, udev, config_cmd, @@ -3210,6 +3274,7 @@ int xhci_free_streams(struct usb_hcd *hcd, struct usb_device *udev, struct xhci_hcd *xhci; struct xhci_virt_device *vdev; struct xhci_command *command; + struct xhci_input_control_ctx *ctrl_ctx; unsigned int ep_index; unsigned long flags; u32 changed_ep_bitmask; @@ -3232,6 +3297,14 @@ int xhci_free_streams(struct usb_hcd *hcd, struct usb_device *udev, */ ep_index = xhci_get_endpoint_index(&eps[0]->desc); command = vdev->eps[ep_index].stream_info->free_streams_command; + ctrl_ctx = xhci_get_input_control_ctx(xhci, command->in_ctx); + if (!ctrl_ctx) { + spin_unlock_irqrestore(&xhci->lock, flags); + xhci_warn(xhci, "%s: Could not get input context, bad type.\n", + __func__); + return -EINVAL; + } + for (i = 0; i < num_eps; i++) { struct xhci_ep_ctx *ep_ctx; @@ -3246,7 +3319,8 @@ int xhci_free_streams(struct usb_hcd *hcd, struct usb_device *udev, &vdev->eps[ep_index]); } xhci_setup_input_ctx_for_config_ep(xhci, command->in_ctx, - vdev->out_ctx, changed_ep_bitmask, changed_ep_bitmask); + vdev->out_ctx, ctrl_ctx, + changed_ep_bitmask, changed_ep_bitmask); spin_unlock_irqrestore(&xhci->lock, flags); /* Issue and wait for the configure endpoint command, @@ -3686,6 +3760,12 @@ int xhci_address_device(struct usb_hcd *hcd, struct usb_device *udev) } slot_ctx = xhci_get_slot_ctx(xhci, virt_dev->in_ctx); + ctrl_ctx = xhci_get_input_control_ctx(xhci, virt_dev->in_ctx); + if (!ctrl_ctx) { + xhci_warn(xhci, "%s: Could not get input context, bad type.\n", + __func__); + return -EINVAL; + } /* * If this is the first Set Address since device plug-in or * virt_device realloaction after a resume with an xHCI power loss, @@ -3696,7 +3776,6 @@ int xhci_address_device(struct usb_hcd *hcd, struct usb_device *udev) /* Otherwise, update the control endpoint ring enqueue pointer. */ else xhci_copy_ep0_dequeue_into_input_ctx(xhci, udev); - ctrl_ctx = xhci_get_input_control_ctx(xhci, virt_dev->in_ctx); ctrl_ctx->add_flags = cpu_to_le32(SLOT_FLAG | EP0_FLAG); ctrl_ctx->drop_flags = 0; @@ -3815,6 +3894,63 @@ int xhci_find_raw_port_number(struct usb_hcd *hcd, int port1) return raw_port; } +/* + * Issue an Evaluate Context command to change the Maximum Exit Latency in the + * slot context. If that succeeds, store the new MEL in the xhci_virt_device. + */ +static int xhci_change_max_exit_latency(struct xhci_hcd *xhci, + struct usb_device *udev, u16 max_exit_latency) +{ + struct xhci_virt_device *virt_dev; + struct xhci_command *command; + struct xhci_input_control_ctx *ctrl_ctx; + struct xhci_slot_ctx *slot_ctx; + unsigned long flags; + int ret; + + spin_lock_irqsave(&xhci->lock, flags); + if (max_exit_latency == xhci->devs[udev->slot_id]->current_mel) { + spin_unlock_irqrestore(&xhci->lock, flags); + return 0; + } + + /* Attempt to issue an Evaluate Context command to change the MEL. */ + virt_dev = xhci->devs[udev->slot_id]; + command = xhci->lpm_command; + ctrl_ctx = xhci_get_input_control_ctx(xhci, command->in_ctx); + if (!ctrl_ctx) { + spin_unlock_irqrestore(&xhci->lock, flags); + xhci_warn(xhci, "%s: Could not get input context, bad type.\n", + __func__); + return -ENOMEM; + } + + xhci_slot_copy(xhci, command->in_ctx, virt_dev->out_ctx); + spin_unlock_irqrestore(&xhci->lock, flags); + + ctrl_ctx->add_flags |= cpu_to_le32(SLOT_FLAG); + slot_ctx = xhci_get_slot_ctx(xhci, command->in_ctx); + slot_ctx->dev_info2 &= cpu_to_le32(~((u32) MAX_EXIT)); + slot_ctx->dev_info2 |= cpu_to_le32(max_exit_latency); + + xhci_dbg(xhci, "Set up evaluate context for LPM MEL change.\n"); + xhci_dbg(xhci, "Slot %u Input Context:\n", udev->slot_id); + xhci_dbg_ctx(xhci, command->in_ctx, 0); + + /* Issue and wait for the evaluate context command. */ + ret = xhci_configure_endpoint(xhci, udev, command, + true, true); + xhci_dbg(xhci, "Slot %u Output Context:\n", udev->slot_id); + xhci_dbg_ctx(xhci, virt_dev->out_ctx, 0); + + if (!ret) { + spin_lock_irqsave(&xhci->lock, flags); + virt_dev->current_mel = max_exit_latency; + spin_unlock_irqrestore(&xhci->lock, flags); + } + return ret; +} + #ifdef CONFIG_PM_RUNTIME /* BESL to HIRD Encoding array for USB2 LPM */ @@ -3856,6 +3992,28 @@ static int xhci_calculate_hird_besl(struct xhci_hcd *xhci, return besl; } +/* Calculate BESLD, L1 timeout and HIRDM for USB2 PORTHLPMC */ +static int xhci_calculate_usb2_hw_lpm_params(struct usb_device *udev) +{ + u32 field; + int l1; + int besld = 0; + int hirdm = 0; + + field = le32_to_cpu(udev->bos->ext_cap->bmAttributes); + + /* xHCI l1 is set in steps of 256us, xHCI 1.0 section 5.4.11.2 */ + l1 = udev->l1_params.timeout / 256; + + /* device has preferred BESLD */ + if (field & USB_BESL_DEEP_VALID) { + besld = USB_GET_BESL_DEEP(field); + hirdm = 1; + } + + return PORT_BESLD(besld) | PORT_L1_TIMEOUT(l1) | PORT_HIRDM(hirdm); +} + static int xhci_usb2_software_lpm_test(struct usb_hcd *hcd, struct usb_device *udev) { @@ -3911,7 +4069,7 @@ static int xhci_usb2_software_lpm_test(struct usb_hcd *hcd, * Check device's USB 2.0 extension descriptor to determine whether * HIRD or BESL shoule be used. See USB2.0 LPM errata. */ - pm_addr = port_array[port_num] + 1; + pm_addr = port_array[port_num] + PORTPMSC; hird = xhci_calculate_hird_besl(xhci, udev); temp = PORT_L1DS(udev->slot_id) | PORT_HIRD(hird); xhci_writel(xhci, temp, pm_addr); @@ -3988,11 +4146,12 @@ int xhci_set_usb2_hardware_lpm(struct usb_hcd *hcd, { struct xhci_hcd *xhci = hcd_to_xhci(hcd); __le32 __iomem **port_array; - __le32 __iomem *pm_addr; - u32 temp; + __le32 __iomem *pm_addr, *hlpm_addr; + u32 pm_val, hlpm_val, field; unsigned int port_num; unsigned long flags; - int hird; + int hird, exit_latency; + int ret; if (hcd->speed == HCD_USB3 || !xhci->hw_lpm_support || !udev->lpm_capable) @@ -4009,40 +4168,120 @@ int xhci_set_usb2_hardware_lpm(struct usb_hcd *hcd, port_array = xhci->usb2_ports; port_num = udev->portnum - 1; - pm_addr = port_array[port_num] + 1; - temp = xhci_readl(xhci, pm_addr); + pm_addr = port_array[port_num] + PORTPMSC; + pm_val = xhci_readl(xhci, pm_addr); + hlpm_addr = port_array[port_num] + PORTHLPMC; + field = le32_to_cpu(udev->bos->ext_cap->bmAttributes); xhci_dbg(xhci, "%s port %d USB2 hardware LPM\n", enable ? "enable" : "disable", port_num); - hird = xhci_calculate_hird_besl(xhci, udev); - if (enable) { - temp &= ~PORT_HIRD_MASK; - temp |= PORT_HIRD(hird) | PORT_RWE; - xhci_writel(xhci, temp, pm_addr); - temp = xhci_readl(xhci, pm_addr); - temp |= PORT_HLE; - xhci_writel(xhci, temp, pm_addr); + /* Host supports BESL timeout instead of HIRD */ + if (udev->usb2_hw_lpm_besl_capable) { + /* if device doesn't have a preferred BESL value use a + * default one which works with mixed HIRD and BESL + * systems. See XHCI_DEFAULT_BESL definition in xhci.h + */ + if ((field & USB_BESL_SUPPORT) && + (field & USB_BESL_BASELINE_VALID)) + hird = USB_GET_BESL_BASELINE(field); + else + hird = udev->l1_params.besl; + + exit_latency = xhci_besl_encoding[hird]; + spin_unlock_irqrestore(&xhci->lock, flags); + + /* USB 3.0 code dedicate one xhci->lpm_command->in_ctx + * input context for link powermanagement evaluate + * context commands. It is protected by hcd->bandwidth + * mutex and is shared by all devices. We need to set + * the max ext latency in USB 2 BESL LPM as well, so + * use the same mutex and xhci_change_max_exit_latency() + */ + mutex_lock(hcd->bandwidth_mutex); + ret = xhci_change_max_exit_latency(xhci, udev, + exit_latency); + mutex_unlock(hcd->bandwidth_mutex); + + if (ret < 0) + return ret; + spin_lock_irqsave(&xhci->lock, flags); + + hlpm_val = xhci_calculate_usb2_hw_lpm_params(udev); + xhci_writel(xhci, hlpm_val, hlpm_addr); + /* flush write */ + xhci_readl(xhci, hlpm_addr); + } else { + hird = xhci_calculate_hird_besl(xhci, udev); + } + + pm_val &= ~PORT_HIRD_MASK; + pm_val |= PORT_HIRD(hird) | PORT_RWE; + xhci_writel(xhci, pm_val, pm_addr); + pm_val = xhci_readl(xhci, pm_addr); + pm_val |= PORT_HLE; + xhci_writel(xhci, pm_val, pm_addr); + /* flush write */ + xhci_readl(xhci, pm_addr); } else { - temp &= ~(PORT_HLE | PORT_RWE | PORT_HIRD_MASK); - xhci_writel(xhci, temp, pm_addr); + pm_val &= ~(PORT_HLE | PORT_RWE | PORT_HIRD_MASK); + xhci_writel(xhci, pm_val, pm_addr); + /* flush write */ + xhci_readl(xhci, pm_addr); + if (udev->usb2_hw_lpm_besl_capable) { + spin_unlock_irqrestore(&xhci->lock, flags); + mutex_lock(hcd->bandwidth_mutex); + xhci_change_max_exit_latency(xhci, udev, 0); + mutex_unlock(hcd->bandwidth_mutex); + return 0; + } } spin_unlock_irqrestore(&xhci->lock, flags); return 0; } +/* check if a usb2 port supports a given extened capability protocol + * only USB2 ports extended protocol capability values are cached. + * Return 1 if capability is supported + */ +static int xhci_check_usb2_port_capability(struct xhci_hcd *xhci, int port, + unsigned capability) +{ + u32 port_offset, port_count; + int i; + + for (i = 0; i < xhci->num_ext_caps; i++) { + if (xhci->ext_caps[i] & capability) { + /* port offsets starts at 1 */ + port_offset = XHCI_EXT_PORT_OFF(xhci->ext_caps[i]) - 1; + port_count = XHCI_EXT_PORT_COUNT(xhci->ext_caps[i]); + if (port >= port_offset && + port < port_offset + port_count) + return 1; + } + } + return 0; +} + int xhci_update_device(struct usb_hcd *hcd, struct usb_device *udev) { struct xhci_hcd *xhci = hcd_to_xhci(hcd); int ret; + int portnum = udev->portnum - 1; ret = xhci_usb2_software_lpm_test(hcd, udev); if (!ret) { xhci_dbg(xhci, "software LPM test succeed\n"); - if (xhci->hw_lpm_support == 1) { + if (xhci->hw_lpm_support == 1 && + xhci_check_usb2_port_capability(xhci, portnum, XHCI_HLC)) { udev->usb2_hw_lpm_capable = 1; + udev->l1_params.timeout = XHCI_L1_TIMEOUT; + udev->l1_params.besl = XHCI_DEFAULT_BESL; + if (xhci_check_usb2_port_capability(xhci, portnum, + XHCI_BLC)) + udev->usb2_hw_lpm_besl_capable = 1; ret = xhci_set_usb2_hardware_lpm(hcd, udev, 1); if (!ret) udev->usb2_hw_lpm_enabled = 1; @@ -4373,56 +4612,6 @@ static u16 xhci_calculate_lpm_timeout(struct usb_hcd *hcd, return timeout; } -/* - * Issue an Evaluate Context command to change the Maximum Exit Latency in the - * slot context. If that succeeds, store the new MEL in the xhci_virt_device. - */ -static int xhci_change_max_exit_latency(struct xhci_hcd *xhci, - struct usb_device *udev, u16 max_exit_latency) -{ - struct xhci_virt_device *virt_dev; - struct xhci_command *command; - struct xhci_input_control_ctx *ctrl_ctx; - struct xhci_slot_ctx *slot_ctx; - unsigned long flags; - int ret; - - spin_lock_irqsave(&xhci->lock, flags); - if (max_exit_latency == xhci->devs[udev->slot_id]->current_mel) { - spin_unlock_irqrestore(&xhci->lock, flags); - return 0; - } - - /* Attempt to issue an Evaluate Context command to change the MEL. */ - virt_dev = xhci->devs[udev->slot_id]; - command = xhci->lpm_command; - xhci_slot_copy(xhci, command->in_ctx, virt_dev->out_ctx); - spin_unlock_irqrestore(&xhci->lock, flags); - - ctrl_ctx = xhci_get_input_control_ctx(xhci, command->in_ctx); - ctrl_ctx->add_flags |= cpu_to_le32(SLOT_FLAG); - slot_ctx = xhci_get_slot_ctx(xhci, command->in_ctx); - slot_ctx->dev_info2 &= cpu_to_le32(~((u32) MAX_EXIT)); - slot_ctx->dev_info2 |= cpu_to_le32(max_exit_latency); - - xhci_dbg(xhci, "Set up evaluate context for LPM MEL change.\n"); - xhci_dbg(xhci, "Slot %u Input Context:\n", udev->slot_id); - xhci_dbg_ctx(xhci, command->in_ctx, 0); - - /* Issue and wait for the evaluate context command. */ - ret = xhci_configure_endpoint(xhci, udev, command, - true, true); - xhci_dbg(xhci, "Slot %u Output Context:\n", udev->slot_id); - xhci_dbg_ctx(xhci, virt_dev->out_ctx, 0); - - if (!ret) { - spin_lock_irqsave(&xhci->lock, flags); - virt_dev->current_mel = max_exit_latency; - spin_unlock_irqrestore(&xhci->lock, flags); - } - return ret; -} - static int calculate_max_exit_latency(struct usb_device *udev, enum usb3_link_state state_changed, u16 hub_encoded_timeout) @@ -4564,6 +4753,13 @@ int xhci_update_hub_device(struct usb_hcd *hcd, struct usb_device *hdev, xhci_dbg(xhci, "Could not allocate xHCI command structure.\n"); return -ENOMEM; } + ctrl_ctx = xhci_get_input_control_ctx(xhci, config_cmd->in_ctx); + if (!ctrl_ctx) { + xhci_warn(xhci, "%s: Could not get input context, bad type.\n", + __func__); + xhci_free_command(xhci, config_cmd); + return -ENOMEM; + } spin_lock_irqsave(&xhci->lock, flags); if (hdev->speed == USB_SPEED_HIGH && @@ -4575,7 +4771,6 @@ int xhci_update_hub_device(struct usb_hcd *hcd, struct usb_device *hdev, } xhci_slot_copy(xhci, config_cmd->in_ctx, vdev->out_ctx); - ctrl_ctx = xhci_get_input_control_ctx(xhci, config_cmd->in_ctx); ctrl_ctx->add_flags |= cpu_to_le32(SLOT_FLAG); slot_ctx = xhci_get_slot_ctx(xhci, config_cmd->in_ctx); slot_ctx->dev_info |= cpu_to_le32(DEV_HUB); diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index 77600ce..c338741 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -132,6 +132,11 @@ struct xhci_cap_regs { /* Number of registers per port */ #define NUM_PORT_REGS 4 +#define PORTSC 0 +#define PORTPMSC 1 +#define PORTLI 2 +#define PORTHLPMC 3 + /** * struct xhci_op_regs - xHCI Host Controller Operational Registers. * @command: USBCMD - xHC command register @@ -381,6 +386,27 @@ struct xhci_op_regs { #define PORT_L1DS(p) (((p) & 0xff) << 8) #define PORT_HLE (1 << 16) + +/* USB2 Protocol PORTHLPMC */ +#define PORT_HIRDM(p)((p) & 3) +#define PORT_L1_TIMEOUT(p)(((p) & 0xff) << 2) +#define PORT_BESLD(p)(((p) & 0xf) << 10) + +/* use 512 microseconds as USB2 LPM L1 default timeout. */ +#define XHCI_L1_TIMEOUT 512 + +/* Set default HIRD/BESL value to 4 (350/400us) for USB2 L1 LPM resume latency. + * Safe to use with mixed HIRD and BESL systems (host and device) and is used + * by other operating systems. + * + * XHCI 1.0 errata 8/14/12 Table 13 notes: + * "Software should choose xHC BESL/BESLD field values that do not violate a + * device's resume latency requirements, + * e.g. not program values > '4' if BLC = '1' and a HIRD device is attached, + * or not program values < '4' if BLC = '0' and a BESL device is attached. + */ +#define XHCI_DEFAULT_BESL 4 + /** * struct xhci_intr_reg - Interrupt Register Set * @irq_pending: IMAN - Interrupt Management Register. Used to enable @@ -1532,6 +1558,9 @@ struct xhci_hcd { unsigned sw_lpm_support:1; /* support xHCI 1.0 spec USB2 hardware LPM */ unsigned hw_lpm_support:1; + /* cached usb2 extened protocol capabilites */ + u32 *ext_caps; + unsigned int num_ext_caps; /* Compliance Mode Recovery Data */ struct timer_list comp_mode_recovery_timer; u32 port_status_u0; @@ -1641,6 +1670,7 @@ int xhci_setup_addressable_virt_dev(struct xhci_hcd *xhci, struct usb_device *ud void xhci_copy_ep0_dequeue_into_input_ctx(struct xhci_hcd *xhci, struct usb_device *udev); unsigned int xhci_get_endpoint_index(struct usb_endpoint_descriptor *desc); +unsigned int xhci_get_endpoint_address(unsigned int ep_index); unsigned int xhci_get_endpoint_flag(struct usb_endpoint_descriptor *desc); unsigned int xhci_get_endpoint_flag_from_index(unsigned int ep_index); unsigned int xhci_last_valid_endpoint(u32 added_ctxs); @@ -1745,7 +1775,7 @@ int xhci_resume(struct xhci_hcd *xhci, bool hibernated); int xhci_get_frame(struct usb_hcd *hcd); irqreturn_t xhci_irq(struct usb_hcd *hcd); -irqreturn_t xhci_msi_irq(int irq, struct usb_hcd *hcd); +irqreturn_t xhci_msi_irq(int irq, void *hcd); int xhci_alloc_dev(struct usb_hcd *hcd, struct usb_device *udev); void xhci_free_dev(struct usb_hcd *hcd, struct usb_device *udev); int xhci_alloc_tt_info(struct xhci_hcd *xhci, |