diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2020-06-07 09:42:16 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2020-06-07 09:42:16 -0700 |
commit | e611c0fe318c6d6827ee2bba660fbc23cf73f7dc (patch) | |
tree | 00854551e810b32cceab84157882eab147f2a98c /drivers/usb/host | |
parent | 3b69e8b4571125bec1f77f886174fe6cab6b9d75 (diff) | |
parent | 347052e3bf1b62a25c11f7a673acfbaf554d67a1 (diff) | |
download | linux-stable-e611c0fe318c6d6827ee2bba660fbc23cf73f7dc.tar.gz linux-stable-e611c0fe318c6d6827ee2bba660fbc23cf73f7dc.tar.bz2 linux-stable-e611c0fe318c6d6827ee2bba660fbc23cf73f7dc.zip |
Merge tag 'usb-5.8-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/usb
Pull USB/PHY driver updates from Greg KH:
"Here are the large set of USB and PHY driver updates for 5.8-rc1.
Nothing huge, just lots of little things:
- USB gadget fixes and additions all over the place
- new PHY drivers
- PHY driver fixes and updates
- XHCI driver updates
- musb driver updates
- more USB-serial driver ids added
- various USB quirks added
- thunderbolt minor updates and fixes
- typec updates and additions
All of these have been in linux-next for a while with no reported
issues"
* tag 'usb-5.8-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/usb: (245 commits)
usb: dwc3: meson-g12a: fix USB2 PHY initialization on G12A and A1 SoCs
usb: dwc3: meson-g12a: fix error path when fetching the reset line fails
Revert "dt-bindings: usb: qcom,dwc3: Convert USB DWC3 bindings"
Revert "dt-bindings: usb: qcom,dwc3: Add compatible for SC7180"
Revert "dt-bindings: usb: qcom,dwc3: Introduce interconnect properties for Qualcomm DWC3 driver"
USB: serial: ch341: fix lockup of devices with limited prescaler
USB: serial: ch341: add basis for quirk detection
CDC-ACM: heed quirk also in error handling
USB: serial: option: add Telit LE910C1-EUX compositions
usb: musb: Fix runtime PM imbalance on error
usb: musb: jz4740: Prevent lockup when CONFIG_SMP is set
usb: musb: mediatek: add reset FADDR to zero in reset interrupt handle
usb: musb: use true for 'use_dma'
usb: musb: start session in resume for host port
usb: musb: return -ESHUTDOWN in urb when three-strikes error happened
USB: serial: qcserial: add DW5816e QDL support
thunderbolt: Add trivial .shutdown
usb: dwc3: keystone: Turn on USB3 PHY before controller
dt-bindings: usb: ti,keystone-dwc3.yaml: Add USB3.0 PHY property
dt-bindings: usb: convert keystone-usb.txt to YAML
...
Diffstat (limited to 'drivers/usb/host')
32 files changed, 1099 insertions, 66 deletions
diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig index 55bdfdf11e4c..62c348062e48 100644 --- a/drivers/usb/host/Kconfig +++ b/drivers/usb/host/Kconfig @@ -40,8 +40,17 @@ config USB_XHCI_DBGCAP config USB_XHCI_PCI tristate depends on USB_PCI + depends on USB_XHCI_PCI_RENESAS || !USB_XHCI_PCI_RENESAS default y +config USB_XHCI_PCI_RENESAS + tristate "Support for additional Renesas xHCI controller with firwmare" + ---help--- + Say 'Y' to enable the support for the Renesas xHCI controller with + firwmare. Make sure you have the firwmare for the device and + installed on your system for this device to work. + If unsure, say 'N'. + config USB_XHCI_PLATFORM tristate "Generic xHCI driver for a platform device" select USB_XHCI_RCAR if ARCH_RENESAS @@ -97,6 +106,26 @@ config USB_XHCI_TEGRA endif # USB_XHCI_HCD +config USB_EHCI_BRCMSTB + tristate + +config USB_BRCMSTB + tristate "Broadcom STB USB support" + depends on (ARCH_BRCMSTB && PHY_BRCM_USB) || COMPILE_TEST + select USB_OHCI_HCD_PLATFORM if USB_OHCI_HCD + select USB_EHCI_BRCMSTB if USB_EHCI_HCD + select USB_XHCI_PLATFORM if USB_XHCI_HCD + help + Enables support for XHCI, EHCI and OHCI host controllers + found in Broadcom STB SoC's. + + To compile these drivers as modules, choose M here: the + modules will be called ohci-platform.ko, ehci-brcm.ko and + xhci-plat-hcd.ko + + Disabling this will keep the controllers and corresponding + PHYs powered down. + config USB_EHCI_HCD tristate "EHCI HCD (USB 2.0) support" depends on HAS_DMA && HAS_IOMEM diff --git a/drivers/usb/host/Makefile b/drivers/usb/host/Makefile index b191361257cc..bc731332fed9 100644 --- a/drivers/usb/host/Makefile +++ b/drivers/usb/host/Makefile @@ -49,6 +49,7 @@ obj-$(CONFIG_USB_EHCI_HCD_STI) += ehci-st.o obj-$(CONFIG_USB_EHCI_EXYNOS) += ehci-exynos.o obj-$(CONFIG_USB_EHCI_HCD_AT91) += ehci-atmel.o obj-$(CONFIG_USB_EHCI_TEGRA) += ehci-tegra.o +obj-$(CONFIG_USB_EHCI_BRCMSTB) += ehci-brcm.o obj-$(CONFIG_USB_OXU210HP_HCD) += oxu210hp-hcd.o obj-$(CONFIG_USB_ISP116X_HCD) += isp116x-hcd.o @@ -71,6 +72,7 @@ obj-$(CONFIG_USB_UHCI_HCD) += uhci-hcd.o obj-$(CONFIG_USB_FHCI_HCD) += fhci.o obj-$(CONFIG_USB_XHCI_HCD) += xhci-hcd.o obj-$(CONFIG_USB_XHCI_PCI) += xhci-pci.o +obj-$(CONFIG_USB_XHCI_PCI_RENESAS) += xhci-pci-renesas.o obj-$(CONFIG_USB_XHCI_PLATFORM) += xhci-plat-hcd.o obj-$(CONFIG_USB_XHCI_HISTB) += xhci-histb.o obj-$(CONFIG_USB_XHCI_MTK) += xhci-mtk.o diff --git a/drivers/usb/host/ehci-brcm.c b/drivers/usb/host/ehci-brcm.c new file mode 100644 index 000000000000..3e0ebe8cc649 --- /dev/null +++ b/drivers/usb/host/ehci-brcm.c @@ -0,0 +1,280 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2020, Broadcom */ + +#include <linux/clk.h> +#include <linux/dma-mapping.h> +#include <linux/err.h> +#include <linux/kernel.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/usb.h> +#include <linux/usb/hcd.h> +#include <linux/iopoll.h> + +#include "ehci.h" + +#define hcd_to_ehci_priv(h) ((struct brcm_priv *)hcd_to_ehci(h)->priv) + +struct brcm_priv { + struct clk *clk; +}; + +/* + * ehci_brcm_wait_for_sof + * Wait for start of next microframe, then wait extra delay microseconds + */ +static inline void ehci_brcm_wait_for_sof(struct ehci_hcd *ehci, u32 delay) +{ + u32 frame_idx = ehci_readl(ehci, &ehci->regs->frame_index); + u32 val; + int res; + + /* Wait for next microframe (every 125 usecs) */ + res = readl_relaxed_poll_timeout(&ehci->regs->frame_index, val, + val != frame_idx, 1, 130); + if (res) + ehci_err(ehci, "Error waiting for SOF\n"); + udelay(delay); +} + +/* + * ehci_brcm_hub_control + * The EHCI controller has a bug where it can violate the SOF + * interval between the first two SOF's transmitted after resume + * if the resume occurs near the end of the microframe. This causees + * the controller to detect babble on the suspended port and + * will eventually cause the controller to reset the port. + * The fix is to Intercept the echi-hcd request to complete RESUME and + * align it to the start of the next microframe. + * See SWLINUX-1909 for more details + */ +static int ehci_brcm_hub_control( + struct usb_hcd *hcd, + u16 typeReq, + u16 wValue, + u16 wIndex, + char *buf, + u16 wLength) +{ + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + int ports = HCS_N_PORTS(ehci->hcs_params); + u32 __iomem *status_reg; + unsigned long flags; + int retval, irq_disabled = 0; + + status_reg = &ehci->regs->port_status[(wIndex & 0xff) - 1]; + + /* + * RESUME is cleared when GetPortStatus() is called 20ms after start + * of RESUME + */ + if ((typeReq == GetPortStatus) && + (wIndex && wIndex <= ports) && + ehci->reset_done[wIndex-1] && + time_after_eq(jiffies, ehci->reset_done[wIndex-1]) && + (ehci_readl(ehci, status_reg) & PORT_RESUME)) { + + /* + * to make sure we are not interrupted until RESUME bit + * is cleared, disable interrupts on current CPU + */ + ehci_dbg(ehci, "SOF alignment workaround\n"); + irq_disabled = 1; + local_irq_save(flags); + ehci_brcm_wait_for_sof(ehci, 5); + } + retval = ehci_hub_control(hcd, typeReq, wValue, wIndex, buf, wLength); + if (irq_disabled) + local_irq_restore(flags); + return retval; +} + +static int ehci_brcm_reset(struct usb_hcd *hcd) +{ + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + int len; + + ehci->big_endian_mmio = 1; + + ehci->caps = (void __iomem *)hcd->regs; + len = HC_LENGTH(ehci, ehci_readl(ehci, &ehci->caps->hc_capbase)); + ehci->regs = (void __iomem *)(hcd->regs + len); + + /* This fixes the lockup during reboot due to prior interrupts */ + ehci_writel(ehci, CMD_RESET, &ehci->regs->command); + mdelay(10); + + /* + * SWLINUX-1705: Avoid OUT packet underflows during high memory + * bus usage + * port_status[0x0f] = Broadcom-proprietary USB_EHCI_INSNREG00 @ 0x90 + */ + ehci_writel(ehci, 0x00800040, &ehci->regs->port_status[0x10]); + ehci_writel(ehci, 0x00000001, &ehci->regs->port_status[0x12]); + + return ehci_setup(hcd); +} + +static struct hc_driver __read_mostly ehci_brcm_hc_driver; + +static const struct ehci_driver_overrides brcm_overrides __initconst = { + .reset = ehci_brcm_reset, + .extra_priv_size = sizeof(struct brcm_priv), +}; + +static int ehci_brcm_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct resource *res_mem; + struct brcm_priv *priv; + struct usb_hcd *hcd; + int irq; + int err; + + err = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32)); + if (err) + return err; + + irq = platform_get_irq(pdev, 0); + if (irq <= 0) + return irq ? irq : -EINVAL; + + /* Hook the hub control routine to work around a bug */ + ehci_brcm_hc_driver.hub_control = ehci_brcm_hub_control; + + /* initialize hcd */ + hcd = usb_create_hcd(&ehci_brcm_hc_driver, dev, dev_name(dev)); + if (!hcd) + return -ENOMEM; + + platform_set_drvdata(pdev, hcd); + priv = hcd_to_ehci_priv(hcd); + + priv->clk = devm_clk_get_optional(dev, NULL); + if (IS_ERR(priv->clk)) { + err = PTR_ERR(priv->clk); + goto err_hcd; + } + + err = clk_prepare_enable(priv->clk); + if (err) + goto err_hcd; + + hcd->regs = devm_platform_get_and_ioremap_resource(pdev, 0, &res_mem); + if (IS_ERR(hcd->regs)) { + err = PTR_ERR(hcd->regs); + goto err_clk; + } + hcd->rsrc_start = res_mem->start; + hcd->rsrc_len = resource_size(res_mem); + err = usb_add_hcd(hcd, irq, IRQF_SHARED); + if (err) + goto err_clk; + + device_wakeup_enable(hcd->self.controller); + device_enable_async_suspend(hcd->self.controller); + + return 0; + +err_clk: + clk_disable_unprepare(priv->clk); +err_hcd: + usb_put_hcd(hcd); + + return err; +} + +static int ehci_brcm_remove(struct platform_device *dev) +{ + struct usb_hcd *hcd = platform_get_drvdata(dev); + struct brcm_priv *priv = hcd_to_ehci_priv(hcd); + + usb_remove_hcd(hcd); + clk_disable_unprepare(priv->clk); + usb_put_hcd(hcd); + return 0; +} + +static int __maybe_unused ehci_brcm_suspend(struct device *dev) +{ + int ret; + struct usb_hcd *hcd = dev_get_drvdata(dev); + struct brcm_priv *priv = hcd_to_ehci_priv(hcd); + bool do_wakeup = device_may_wakeup(dev); + + ret = ehci_suspend(hcd, do_wakeup); + if (ret) + return ret; + clk_disable_unprepare(priv->clk); + return 0; +} + +static int __maybe_unused ehci_brcm_resume(struct device *dev) +{ + struct usb_hcd *hcd = dev_get_drvdata(dev); + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + struct brcm_priv *priv = hcd_to_ehci_priv(hcd); + int err; + + err = clk_prepare_enable(priv->clk); + if (err) + return err; + /* + * SWLINUX-1705: Avoid OUT packet underflows during high memory + * bus usage + * port_status[0x0f] = Broadcom-proprietary USB_EHCI_INSNREG00 + * @ 0x90 + */ + ehci_writel(ehci, 0x00800040, &ehci->regs->port_status[0x10]); + ehci_writel(ehci, 0x00000001, &ehci->regs->port_status[0x12]); + + ehci_resume(hcd, false); + + pm_runtime_disable(dev); + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + + return 0; +} + +static SIMPLE_DEV_PM_OPS(ehci_brcm_pm_ops, ehci_brcm_suspend, + ehci_brcm_resume); + +static const struct of_device_id brcm_ehci_of_match[] = { + { .compatible = "brcm,ehci-brcm-v2", }, + { .compatible = "brcm,bcm7445-ehci", }, + {} +}; + +static struct platform_driver ehci_brcm_driver = { + .probe = ehci_brcm_probe, + .remove = ehci_brcm_remove, + .shutdown = usb_hcd_platform_shutdown, + .driver = { + .name = "ehci-brcm", + .pm = &ehci_brcm_pm_ops, + .of_match_table = brcm_ehci_of_match, + } +}; + +static int __init ehci_brcm_init(void) +{ + if (usb_disabled()) + return -ENODEV; + + ehci_init_driver(&ehci_brcm_hc_driver, &brcm_overrides); + return platform_driver_register(&ehci_brcm_driver); +} +module_init(ehci_brcm_init); + +static void __exit ehci_brcm_exit(void) +{ + platform_driver_unregister(&ehci_brcm_driver); +} +module_exit(ehci_brcm_exit); + +MODULE_ALIAS("platform:ehci-brcm"); +MODULE_DESCRIPTION("EHCI Broadcom STB driver"); +MODULE_AUTHOR("Al Cooper"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/host/ehci-fsl.h b/drivers/usb/host/ehci-fsl.h index 9d18c6e6ab27..c95341d472f4 100644 --- a/drivers/usb/host/ehci-fsl.h +++ b/drivers/usb/host/ehci-fsl.h @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-2.0+ +/* SPDX-License-Identifier: GPL-2.0+ */ /* Copyright (C) 2005-2010,2012 Freescale Semiconductor, Inc. * Copyright (c) 2005 MontaVista Software */ diff --git a/drivers/usb/host/ehci-mv.c b/drivers/usb/host/ehci-mv.c index 1300c457d9ed..cffdc8d01b2a 100644 --- a/drivers/usb/host/ehci-mv.c +++ b/drivers/usb/host/ehci-mv.c @@ -108,7 +108,7 @@ static int mv_ehci_probe(struct platform_device *pdev) struct ehci_hcd *ehci; struct ehci_hcd_mv *ehci_mv; struct resource *r; - int retval = -ENODEV; + int retval; u32 offset; u32 status; @@ -143,8 +143,6 @@ static int mv_ehci_probe(struct platform_device *pdev) goto err_put_hcd; } - - r = platform_get_resource(pdev, IORESOURCE_MEM, 0); ehci_mv->base = devm_ioremap_resource(&pdev->dev, r); if (IS_ERR(ehci_mv->base)) { @@ -168,12 +166,10 @@ static int mv_ehci_probe(struct platform_device *pdev) hcd->rsrc_len = resource_size(r); hcd->regs = ehci_mv->op_regs; - hcd->irq = platform_get_irq(pdev, 0); - if (!hcd->irq) { - dev_err(&pdev->dev, "Cannot get irq."); - retval = -ENODEV; + retval = platform_get_irq(pdev, 0); + if (retval < 0) goto err_disable_clk; - } + hcd->irq = retval; ehci = hcd_to_ehci(hcd); ehci->caps = (struct ehci_caps __iomem *) ehci_mv->cap_regs; diff --git a/drivers/usb/host/ehci-mxc.c b/drivers/usb/host/ehci-mxc.c index c9f91e6c72b6..dc2676320527 100644 --- a/drivers/usb/host/ehci-mxc.c +++ b/drivers/usb/host/ehci-mxc.c @@ -36,12 +36,12 @@ static const struct ehci_driver_overrides ehci_mxc_overrides __initconst = { static int ehci_mxc_drv_probe(struct platform_device *pdev) { - struct mxc_usbh_platform_data *pdata = dev_get_platdata(&pdev->dev); + struct device *dev = &pdev->dev; + struct mxc_usbh_platform_data *pdata = dev_get_platdata(dev); struct usb_hcd *hcd; struct resource *res; int irq, ret; struct ehci_mxc_priv *priv; - struct device *dev = &pdev->dev; struct ehci_hcd *ehci; if (!pdata) { @@ -50,13 +50,15 @@ static int ehci_mxc_drv_probe(struct platform_device *pdev) } irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; hcd = usb_create_hcd(&ehci_mxc_hc_driver, dev, dev_name(dev)); if (!hcd) return -ENOMEM; res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - hcd->regs = devm_ioremap_resource(&pdev->dev, res); + hcd->regs = devm_ioremap_resource(dev, res); if (IS_ERR(hcd->regs)) { ret = PTR_ERR(hcd->regs); goto err_alloc; @@ -69,14 +71,14 @@ static int ehci_mxc_drv_probe(struct platform_device *pdev) priv = (struct ehci_mxc_priv *) ehci->priv; /* enable clocks */ - priv->usbclk = devm_clk_get(&pdev->dev, "ipg"); + priv->usbclk = devm_clk_get(dev, "ipg"); if (IS_ERR(priv->usbclk)) { ret = PTR_ERR(priv->usbclk); goto err_alloc; } clk_prepare_enable(priv->usbclk); - priv->ahbclk = devm_clk_get(&pdev->dev, "ahb"); + priv->ahbclk = devm_clk_get(dev, "ahb"); if (IS_ERR(priv->ahbclk)) { ret = PTR_ERR(priv->ahbclk); goto err_clk_ahb; @@ -84,13 +86,12 @@ static int ehci_mxc_drv_probe(struct platform_device *pdev) clk_prepare_enable(priv->ahbclk); /* "dr" device has its own clock on i.MX51 */ - priv->phyclk = devm_clk_get(&pdev->dev, "phy"); + priv->phyclk = devm_clk_get(dev, "phy"); if (IS_ERR(priv->phyclk)) priv->phyclk = NULL; if (priv->phyclk) clk_prepare_enable(priv->phyclk); - /* call platform specific init function */ if (pdata->init) { ret = pdata->init(pdev); diff --git a/drivers/usb/host/ehci-pci.c b/drivers/usb/host/ehci-pci.c index 1a48ab1bd3b2..3c3820ad9092 100644 --- a/drivers/usb/host/ehci-pci.c +++ b/drivers/usb/host/ehci-pci.c @@ -360,23 +360,21 @@ static int ehci_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) { if (is_bypassed_id(pdev)) return -ENODEV; - return usb_hcd_pci_probe(pdev, id); + return usb_hcd_pci_probe(pdev, id, &ehci_pci_hc_driver); } static void ehci_pci_remove(struct pci_dev *pdev) { pci_clear_mwi(pdev); - usb_hcd_pci_remove(pdev); + usb_hcd_pci_remove(pdev); } /* PCI driver selection metadata; PCI hotplugging uses this */ static const struct pci_device_id pci_ids [] = { { /* handle any USB 2.0 EHCI controller */ PCI_DEVICE_CLASS(PCI_CLASS_SERIAL_USB_EHCI, ~0), - .driver_data = (unsigned long) &ehci_pci_hc_driver, }, { PCI_VDEVICE(STMICRO, PCI_DEVICE_ID_STMICRO_USB_HOST), - .driver_data = (unsigned long) &ehci_pci_hc_driver, }, { /* end: all zeroes */ } }; diff --git a/drivers/usb/host/ehci-platform.c b/drivers/usb/host/ehci-platform.c index e4fc3f66d43b..e9a49007cce4 100644 --- a/drivers/usb/host/ehci-platform.c +++ b/drivers/usb/host/ehci-platform.c @@ -455,6 +455,10 @@ static int ehci_platform_resume(struct device *dev) ehci_resume(hcd, priv->reset_on_resume); + pm_runtime_disable(dev); + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + if (priv->quirk_poll) quirk_poll_init(priv); diff --git a/drivers/usb/host/ehci-tegra.c b/drivers/usb/host/ehci-tegra.c index 10d51daa6a1b..e077b2ca53c5 100644 --- a/drivers/usb/host/ehci-tegra.c +++ b/drivers/usb/host/ehci-tegra.c @@ -480,7 +480,6 @@ static int tegra_ehci_probe(struct platform_device *pdev) irq = platform_get_irq(pdev, 0); if (!irq) { - dev_err(&pdev->dev, "Failed to get IRQ\n"); err = -ENODEV; goto cleanup_phy; } diff --git a/drivers/usb/host/ehci.h b/drivers/usb/host/ehci.h index 229b3de319e6..eabf22a78eae 100644 --- a/drivers/usb/host/ehci.h +++ b/drivers/usb/host/ehci.h @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-2.0+ +/* SPDX-License-Identifier: GPL-2.0+ */ /* * Copyright (c) 2001-2002 by David Brownell */ diff --git a/drivers/usb/host/fhci.h b/drivers/usb/host/fhci.h index 2ce5031d866d..81fbc019a9b3 100644 --- a/drivers/usb/host/fhci.h +++ b/drivers/usb/host/fhci.h @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-2.0+ +/* SPDX-License-Identifier: GPL-2.0+ */ /* * Freescale QUICC Engine USB Host Controller Driver * diff --git a/drivers/usb/host/imx21-hcd.h b/drivers/usb/host/imx21-hcd.h index 7b9cf0a38d6e..96d16752a73e 100644 --- a/drivers/usb/host/imx21-hcd.h +++ b/drivers/usb/host/imx21-hcd.h @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-2.0+ +/* SPDX-License-Identifier: GPL-2.0+ */ /* * Macros and prototypes for i.MX21 * diff --git a/drivers/usb/host/ohci-pci.c b/drivers/usb/host/ohci-pci.c index 22117a6aeb4a..585222af24ff 100644 --- a/drivers/usb/host/ohci-pci.c +++ b/drivers/usb/host/ohci-pci.c @@ -277,21 +277,24 @@ static const struct ohci_driver_overrides pci_overrides __initconst = { static const struct pci_device_id pci_ids[] = { { /* handle any USB OHCI controller */ PCI_DEVICE_CLASS(PCI_CLASS_SERIAL_USB_OHCI, ~0), - .driver_data = (unsigned long) &ohci_pci_hc_driver, }, { /* The device in the ConneXT I/O hub has no class reg */ PCI_VDEVICE(STMICRO, PCI_DEVICE_ID_STMICRO_USB_OHCI), - .driver_data = (unsigned long) &ohci_pci_hc_driver, }, { /* end: all zeroes */ } }; MODULE_DEVICE_TABLE (pci, pci_ids); +static int ohci_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) +{ + return usb_hcd_pci_probe(dev, id, &ohci_pci_hc_driver); +} + /* pci driver glue; this is a "new style" PCI driver module */ static struct pci_driver ohci_pci_driver = { .name = hcd_name, .id_table = pci_ids, - .probe = usb_hcd_pci_probe, + .probe = ohci_pci_probe, .remove = usb_hcd_pci_remove, .shutdown = usb_hcd_pci_shutdown, diff --git a/drivers/usb/host/ohci-platform.c b/drivers/usb/host/ohci-platform.c index 7addfc2cbadc..4a8456f12a73 100644 --- a/drivers/usb/host/ohci-platform.c +++ b/drivers/usb/host/ohci-platform.c @@ -299,6 +299,11 @@ static int ohci_platform_resume(struct device *dev) } ohci_resume(hcd, false); + + pm_runtime_disable(dev); + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + return 0; } #endif /* CONFIG_PM_SLEEP */ diff --git a/drivers/usb/host/ohci-sm501.c b/drivers/usb/host/ohci-sm501.c index c158cda9e4b9..cff965240327 100644 --- a/drivers/usb/host/ohci-sm501.c +++ b/drivers/usb/host/ohci-sm501.c @@ -157,9 +157,10 @@ static int ohci_hcd_sm501_drv_probe(struct platform_device *pdev) * the call to usb_hcd_setup_local_mem() below does just that. */ - if (usb_hcd_setup_local_mem(hcd, mem->start, - mem->start - mem->parent->start, - resource_size(mem)) < 0) + retval = usb_hcd_setup_local_mem(hcd, mem->start, + mem->start - mem->parent->start, + resource_size(mem)); + if (retval < 0) goto err5; retval = usb_add_hcd(hcd, irq, IRQF_SHARED); if (retval) diff --git a/drivers/usb/host/ohci.h b/drivers/usb/host/ohci.h index 27c26ca10bfd..b85a39588f9d 100644 --- a/drivers/usb/host/ohci.h +++ b/drivers/usb/host/ohci.h @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-1.0+ +/* SPDX-License-Identifier: GPL-1.0+ */ /* * OHCI HCD (Host Controller Driver) for USB. * diff --git a/drivers/usb/host/pci-quirks.c b/drivers/usb/host/pci-quirks.c index 0dc34668bb2a..0b949acfa258 100644 --- a/drivers/usb/host/pci-quirks.c +++ b/drivers/usb/host/pci-quirks.c @@ -208,7 +208,7 @@ static void usb_amd_find_chipset_info(void) { unsigned long flags; struct amd_chipset_info info; - info.need_pll_quirk = 0; + info.need_pll_quirk = false; spin_lock_irqsave(&amd_lock, flags); @@ -232,10 +232,10 @@ static void usb_amd_find_chipset_info(void) case AMD_CHIPSET_SB800: case AMD_CHIPSET_HUDSON2: case AMD_CHIPSET_BOLTON: - info.need_pll_quirk = 1; + info.need_pll_quirk = true; break; default: - info.need_pll_quirk = 0; + info.need_pll_quirk = false; break; } @@ -532,7 +532,7 @@ void usb_amd_dev_put(void) amd_chipset.nb_type = 0; memset(&amd_chipset.sb_type, 0, sizeof(amd_chipset.sb_type)); amd_chipset.isoc_reqs = 0; - amd_chipset.need_pll_quirk = 0; + amd_chipset.need_pll_quirk = false; spin_unlock_irqrestore(&amd_lock, flags); diff --git a/drivers/usb/host/r8a66597.h b/drivers/usb/host/r8a66597.h index 51973a923526..ab081475c113 100644 --- a/drivers/usb/host/r8a66597.h +++ b/drivers/usb/host/r8a66597.h @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-2.0 +/* SPDX-License-Identifier: GPL-2.0 */ /* * R8A66597 HCD (Host Controller Driver) * diff --git a/drivers/usb/host/u132-hcd.c b/drivers/usb/host/u132-hcd.c index e9209e3e6248..995bc52d2d22 100644 --- a/drivers/usb/host/u132-hcd.c +++ b/drivers/usb/host/u132-hcd.c @@ -81,7 +81,6 @@ static DECLARE_WAIT_QUEUE_HEAD(u132_hcd_wait); static struct mutex u132_module_lock; static int u132_exiting; static int u132_instances; -static struct list_head u132_static_list; /* * end of the global variables protected by u132_module_lock */ @@ -177,7 +176,6 @@ struct u132_ring { }; struct u132 { struct kref kref; - struct list_head u132_list; struct mutex sw_lock; struct mutex scheduler_lock; struct u132_platform_data *board; @@ -254,7 +252,6 @@ static void u132_hcd_delete(struct kref *kref) struct usb_hcd *hcd = u132_to_hcd(u132); u132->going += 1; mutex_lock(&u132_module_lock); - list_del_init(&u132->u132_list); u132_instances -= 1; mutex_unlock(&u132_module_lock); dev_warn(&u132->platform_dev->dev, "FREEING the hcd=%p and thus the u13" @@ -3089,7 +3086,6 @@ static int u132_probe(struct platform_device *pdev) retval = 0; hcd->rsrc_start = 0; mutex_lock(&u132_module_lock); - list_add_tail(&u132->u132_list, &u132_static_list); u132->sequence_num = ++u132_instances; mutex_unlock(&u132_module_lock); u132_u132_init_kref(u132); @@ -3192,7 +3188,6 @@ static struct platform_driver u132_platform_driver = { static int __init u132_hcd_init(void) { int retval; - INIT_LIST_HEAD(&u132_static_list); u132_instances = 0; u132_exiting = 0; mutex_init(&u132_module_lock); @@ -3213,14 +3208,9 @@ static int __init u132_hcd_init(void) module_init(u132_hcd_init); static void __exit u132_hcd_exit(void) { - struct u132 *u132; - struct u132 *temp; mutex_lock(&u132_module_lock); u132_exiting += 1; mutex_unlock(&u132_module_lock); - list_for_each_entry_safe(u132, temp, &u132_static_list, u132_list) { - platform_device_unregister(u132->platform_dev); - } platform_driver_unregister(&u132_platform_driver); printk(KERN_INFO "u132-hcd driver deregistered\n"); wait_event(u132_hcd_wait, u132_instances == 0); diff --git a/drivers/usb/host/uhci-pci.c b/drivers/usb/host/uhci-pci.c index 957c87efc746..9b88745d247f 100644 --- a/drivers/usb/host/uhci-pci.c +++ b/drivers/usb/host/uhci-pci.c @@ -287,17 +287,21 @@ static const struct hc_driver uhci_driver = { static const struct pci_device_id uhci_pci_ids[] = { { /* handle any USB UHCI controller */ PCI_DEVICE_CLASS(PCI_CLASS_SERIAL_USB_UHCI, ~0), - .driver_data = (unsigned long) &uhci_driver, }, { /* end: all zeroes */ } }; MODULE_DEVICE_TABLE(pci, uhci_pci_ids); +static int uhci_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) +{ + return usb_hcd_pci_probe(dev, id, &uhci_driver); +} + static struct pci_driver uhci_pci_driver = { .name = hcd_name, .id_table = uhci_pci_ids, - .probe = usb_hcd_pci_probe, + .probe = uhci_pci_probe, .remove = usb_hcd_pci_remove, .shutdown = uhci_shutdown, diff --git a/drivers/usb/host/xhci-debugfs.h b/drivers/usb/host/xhci-debugfs.h index f7a4e2492b00..56db635fcd6e 100644 --- a/drivers/usb/host/xhci-debugfs.h +++ b/drivers/usb/host/xhci-debugfs.h @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-2.0 +/* SPDX-License-Identifier: GPL-2.0 */ /* * xhci-debugfs.h - xHCI debugfs interface * diff --git a/drivers/usb/host/xhci-ext-caps.h b/drivers/usb/host/xhci-ext-caps.h index 268328c20681..fa59b242cd51 100644 --- a/drivers/usb/host/xhci-ext-caps.h +++ b/drivers/usb/host/xhci-ext-caps.h @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-2.0 +/* SPDX-License-Identifier: GPL-2.0 */ /* * xHCI host controller driver * diff --git a/drivers/usb/host/xhci-mtk.h b/drivers/usb/host/xhci-mtk.h index acd56517215a..a93cfe817904 100644 --- a/drivers/usb/host/xhci-mtk.h +++ b/drivers/usb/host/xhci-mtk.h @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-2.0 +/* SPDX-License-Identifier: GPL-2.0 */ /* * Copyright (c) 2015 MediaTek Inc. * Author: diff --git a/drivers/usb/host/xhci-mvebu.h b/drivers/usb/host/xhci-mvebu.h index ca0a3a5721dd..3be021793cc8 100644 --- a/drivers/usb/host/xhci-mvebu.h +++ b/drivers/usb/host/xhci-mvebu.h @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-2.0 +/* SPDX-License-Identifier: GPL-2.0 */ /* * Copyright (C) 2014 Marvell * diff --git a/drivers/usb/host/xhci-pci-renesas.c b/drivers/usb/host/xhci-pci-renesas.c new file mode 100644 index 000000000000..59b1965ad0a3 --- /dev/null +++ b/drivers/usb/host/xhci-pci-renesas.c @@ -0,0 +1,645 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (C) 2019-2020 Linaro Limited */ + +#include <linux/acpi.h> +#include <linux/firmware.h> +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/slab.h> +#include <asm/unaligned.h> + +#include "xhci.h" +#include "xhci-trace.h" +#include "xhci-pci.h" + +#define RENESAS_FW_VERSION 0x6C +#define RENESAS_ROM_CONFIG 0xF0 +#define RENESAS_FW_STATUS 0xF4 +#define RENESAS_FW_STATUS_MSB 0xF5 +#define RENESAS_ROM_STATUS 0xF6 +#define RENESAS_ROM_STATUS_MSB 0xF7 +#define RENESAS_DATA0 0xF8 +#define RENESAS_DATA1 0xFC + +#define RENESAS_FW_VERSION_FIELD GENMASK(23, 7) +#define RENESAS_FW_VERSION_OFFSET 8 + +#define RENESAS_FW_STATUS_DOWNLOAD_ENABLE BIT(0) +#define RENESAS_FW_STATUS_LOCK BIT(1) +#define RENESAS_FW_STATUS_RESULT GENMASK(6, 4) + #define RENESAS_FW_STATUS_INVALID 0 + #define RENESAS_FW_STATUS_SUCCESS BIT(4) + #define RENESAS_FW_STATUS_ERROR BIT(5) +#define RENESAS_FW_STATUS_SET_DATA0 BIT(8) +#define RENESAS_FW_STATUS_SET_DATA1 BIT(9) + +#define RENESAS_ROM_STATUS_ACCESS BIT(0) +#define RENESAS_ROM_STATUS_ERASE BIT(1) +#define RENESAS_ROM_STATUS_RELOAD BIT(2) +#define RENESAS_ROM_STATUS_RESULT GENMASK(6, 4) + #define RENESAS_ROM_STATUS_NO_RESULT 0 + #define RENESAS_ROM_STATUS_SUCCESS BIT(4) + #define RENESAS_ROM_STATUS_ERROR BIT(5) +#define RENESAS_ROM_STATUS_SET_DATA0 BIT(8) +#define RENESAS_ROM_STATUS_SET_DATA1 BIT(9) +#define RENESAS_ROM_STATUS_ROM_EXISTS BIT(15) + +#define RENESAS_ROM_ERASE_MAGIC 0x5A65726F +#define RENESAS_ROM_WRITE_MAGIC 0x53524F4D + +#define RENESAS_RETRY 10000 +#define RENESAS_DELAY 10 + +#define ROM_VALID_01 0x2013 +#define ROM_VALID_02 0x2026 + +static int renesas_verify_fw_version(struct pci_dev *pdev, u32 version) +{ + switch (version) { + case ROM_VALID_01: + case ROM_VALID_02: + return 0; + } + dev_err(&pdev->dev, "FW has invalid version :%d\n", version); + return -EINVAL; +} + +static int renesas_fw_download_image(struct pci_dev *dev, + const u32 *fw, size_t step, bool rom) +{ + size_t i; + int err; + u8 fw_status; + bool data0_or_data1; + u32 status_reg; + + if (rom) + status_reg = RENESAS_ROM_STATUS_MSB; + else + status_reg = RENESAS_FW_STATUS_MSB; + + /* + * The hardware does alternate between two 32-bit pages. + * (This is because each row of the firmware is 8 bytes). + * + * for even steps we use DATA0, for odd steps DATA1. + */ + data0_or_data1 = (step & 1) == 1; + + /* step+1. Read "Set DATAX" and confirm it is cleared. */ + for (i = 0; i < RENESAS_RETRY; i++) { + err = pci_read_config_byte(dev, status_reg, &fw_status); + if (err) { + dev_err(&dev->dev, "Read Status failed: %d\n", + pcibios_err_to_errno(err)); + return pcibios_err_to_errno(err); + } + if (!(fw_status & BIT(data0_or_data1))) + break; + + udelay(RENESAS_DELAY); + } + if (i == RENESAS_RETRY) { + dev_err(&dev->dev, "Timeout for Set DATAX step: %zd\n", step); + return -ETIMEDOUT; + } + + /* + * step+2. Write FW data to "DATAX". + * "LSB is left" => force little endian + */ + err = pci_write_config_dword(dev, data0_or_data1 ? + RENESAS_DATA1 : RENESAS_DATA0, + (__force u32)cpu_to_le32(fw[step])); + if (err) { + dev_err(&dev->dev, "Write to DATAX failed: %d\n", + pcibios_err_to_errno(err)); + return pcibios_err_to_errno(err); + } + + udelay(100); + + /* step+3. Set "Set DATAX". */ + err = pci_write_config_byte(dev, status_reg, BIT(data0_or_data1)); + if (err) { + dev_err(&dev->dev, "Write config for DATAX failed: %d\n", + pcibios_err_to_errno(err)); + return pcibios_err_to_errno(err); + } + + return 0; +} + +static int renesas_fw_verify(const void *fw_data, + size_t length) +{ + u16 fw_version_pointer; + u16 fw_version; + + /* + * The Firmware's Data Format is describe in + * "6.3 Data Format" R19UH0078EJ0500 Rev.5.00 page 124 + */ + + /* + * The bootrom chips of the big brother have sizes up to 64k, let's + * assume that's the biggest the firmware can get. + */ + if (length < 0x1000 || length >= 0x10000) { + pr_err("firmware is size %zd is not (4k - 64k).", + length); + return -EINVAL; + } + + /* The First 2 bytes are fixed value (55aa). "LSB on Left" */ + if (get_unaligned_le16(fw_data) != 0x55aa) { + pr_err("no valid firmware header found."); + return -EINVAL; + } + + /* verify the firmware version position and print it. */ + fw_version_pointer = get_unaligned_le16(fw_data + 4); + if (fw_version_pointer + 2 >= length) { + pr_err("fw ver pointer is outside of the firmware image"); + return -EINVAL; + } + + fw_version = get_unaligned_le16(fw_data + fw_version_pointer); + pr_err("got firmware version: %02x.", fw_version); + + return 0; +} + +static bool renesas_check_rom(struct pci_dev *pdev) +{ + u16 rom_status; + int retval; + + /* Check if external ROM exists */ + retval = pci_read_config_word(pdev, RENESAS_ROM_STATUS, &rom_status); + if (retval) + return false; + + rom_status &= RENESAS_ROM_STATUS_ROM_EXISTS; + if (rom_status) { + dev_dbg(&pdev->dev, "External ROM exists\n"); + return true; /* External ROM exists */ + } + + return false; +} + +static int renesas_check_rom_state(struct pci_dev *pdev) +{ + u16 rom_state; + u32 version; + int err; + + /* check FW version */ + err = pci_read_config_dword(pdev, RENESAS_FW_VERSION, &version); + if (err) + return pcibios_err_to_errno(err); + + version &= RENESAS_FW_VERSION_FIELD; + version = version >> RENESAS_FW_VERSION_OFFSET; + + err = renesas_verify_fw_version(pdev, version); + if (err) + return err; + + /* + * Test if ROM is present and loaded, if so we can skip everything + */ + err = pci_read_config_word(pdev, RENESAS_ROM_STATUS, &rom_state); + if (err) + return pcibios_err_to_errno(err); + + if (rom_state & BIT(15)) { + /* ROM exists */ + dev_dbg(&pdev->dev, "ROM exists\n"); + + /* Check the "Result Code" Bits (6:4) and act accordingly */ + switch (rom_state & RENESAS_ROM_STATUS_RESULT) { + case RENESAS_ROM_STATUS_SUCCESS: + return 0; + + case RENESAS_ROM_STATUS_NO_RESULT: /* No result yet */ + return 0; + + case RENESAS_ROM_STATUS_ERROR: /* Error State */ + default: /* All other states are marked as "Reserved states" */ + dev_err(&pdev->dev, "Invalid ROM.."); + break; + } + } + + return -EIO; +} + +static int renesas_fw_check_running(struct pci_dev *pdev) +{ + u8 fw_state; + int err; + + /* Check if device has ROM and loaded, if so skip everything */ + err = renesas_check_rom(pdev); + if (err) { /* we have rom */ + err = renesas_check_rom_state(pdev); + if (!err) + return err; + } + + /* + * Test if the device is actually needing the firmware. As most + * BIOSes will initialize the device for us. If the device is + * initialized. + */ + err = pci_read_config_byte(pdev, RENESAS_FW_STATUS, &fw_state); + if (err) + return pcibios_err_to_errno(err); + + /* + * Check if "FW Download Lock" is locked. If it is and the FW is + * ready we can simply continue. If the FW is not ready, we have + * to give up. + */ + if (fw_state & RENESAS_FW_STATUS_LOCK) { + dev_dbg(&pdev->dev, "FW Download Lock is engaged."); + + if (fw_state & RENESAS_FW_STATUS_SUCCESS) + return 0; + + dev_err(&pdev->dev, + "FW Download Lock is set and FW is not ready. Giving Up."); + return -EIO; + } + + /* + * Check if "FW Download Enable" is set. If someone (us?) tampered + * with it and it can't be reset, we have to give up too... and + * ask for a forgiveness and a reboot. + */ + if (fw_state & RENESAS_FW_STATUS_DOWNLOAD_ENABLE) { + dev_err(&pdev->dev, + "FW Download Enable is stale. Giving Up (poweroff/reboot needed)."); + return -EIO; + } + + /* Otherwise, Check the "Result Code" Bits (6:4) and act accordingly */ + switch (fw_state & RENESAS_FW_STATUS_RESULT) { + case 0: /* No result yet */ + dev_dbg(&pdev->dev, "FW is not ready/loaded yet."); + + /* tell the caller, that this device needs the firmware. */ + return 1; + + case RENESAS_FW_STATUS_SUCCESS: /* Success, device should be working. */ + dev_dbg(&pdev->dev, "FW is ready."); + return 0; + + case RENESAS_FW_STATUS_ERROR: /* Error State */ + dev_err(&pdev->dev, + "hardware is in an error state. Giving up (poweroff/reboot needed)."); + return -ENODEV; + + default: /* All other states are marked as "Reserved states" */ + dev_err(&pdev->dev, + "hardware is in an invalid state %lx. Giving up (poweroff/reboot needed).", + (fw_state & RENESAS_FW_STATUS_RESULT) >> 4); + return -EINVAL; + } +} + +static int renesas_fw_download(struct pci_dev *pdev, + const struct firmware *fw) +{ + const u32 *fw_data = (const u32 *)fw->data; + size_t i; + int err; + u8 fw_status; + + /* + * For more information and the big picture: please look at the + * "Firmware Download Sequence" in "7.1 FW Download Interface" + * of R19UH0078EJ0500 Rev.5.00 page 131 + */ + + /* + * 0. Set "FW Download Enable" bit in the + * "FW Download Control & Status Register" at 0xF4 + */ + err = pci_write_config_byte(pdev, RENESAS_FW_STATUS, + RENESAS_FW_STATUS_DOWNLOAD_ENABLE); + if (err) + return pcibios_err_to_errno(err); + + /* 1 - 10 follow one step after the other. */ + for (i = 0; i < fw->size / 4; i++) { + err = renesas_fw_download_image(pdev, fw_data, i, false); + if (err) { + dev_err(&pdev->dev, + "Firmware Download Step %zd failed at position %zd bytes with (%d).", + i, i * 4, err); + return err; + } + } + + /* + * This sequence continues until the last data is written to + * "DATA0" or "DATA1". Naturally, we wait until "SET DATA0/1" + * is cleared by the hardware beforehand. + */ + for (i = 0; i < RENESAS_RETRY; i++) { + err = pci_read_config_byte(pdev, RENESAS_FW_STATUS_MSB, + &fw_status); + if (err) + return pcibios_err_to_errno(err); + if (!(fw_status & (BIT(0) | BIT(1)))) + break; + + udelay(RENESAS_DELAY); + } + if (i == RENESAS_RETRY) + dev_warn(&pdev->dev, "Final Firmware Download step timed out."); + + /* + * 11. After finishing writing the last data of FW, the + * System Software must clear "FW Download Enable" + */ + err = pci_write_config_byte(pdev, RENESAS_FW_STATUS, 0); + if (err) + return pcibios_err_to_errno(err); + + /* 12. Read "Result Code" and confirm it is good. */ + for (i = 0; i < RENESAS_RETRY; i++) { + err = pci_read_config_byte(pdev, RENESAS_FW_STATUS, &fw_status); + if (err) + return pcibios_err_to_errno(err); + if (fw_status & RENESAS_FW_STATUS_SUCCESS) + break; + + udelay(RENESAS_DELAY); + } + if (i == RENESAS_RETRY) { + /* Timed out / Error - let's see if we can fix this */ + err = renesas_fw_check_running(pdev); + switch (err) { + case 0: /* + * we shouldn't end up here. + * maybe it took a little bit longer. + * But all should be well? + */ + break; + + case 1: /* (No result yet! */ + dev_err(&pdev->dev, "FW Load timedout"); + return -ETIMEDOUT; + + default: + return err; + } + } + + return 0; +} + +static void renesas_rom_erase(struct pci_dev *pdev) +{ + int retval, i; + u8 status; + + dev_dbg(&pdev->dev, "Performing ROM Erase...\n"); + retval = pci_write_config_dword(pdev, RENESAS_DATA0, + RENESAS_ROM_ERASE_MAGIC); + if (retval) { + dev_err(&pdev->dev, "ROM erase, magic word write failed: %d\n", + pcibios_err_to_errno(retval)); + return; + } + + retval = pci_read_config_byte(pdev, RENESAS_ROM_STATUS, &status); + if (retval) { + dev_err(&pdev->dev, "ROM status read failed: %d\n", + pcibios_err_to_errno(retval)); + return; + } + status |= RENESAS_ROM_STATUS_ERASE; + retval = pci_write_config_byte(pdev, RENESAS_ROM_STATUS, status); + if (retval) { + dev_err(&pdev->dev, "ROM erase set word write failed\n"); + return; + } + + /* sleep a bit while ROM is erased */ + msleep(20); + + for (i = 0; i < RENESAS_RETRY; i++) { + retval = pci_read_config_byte(pdev, RENESAS_ROM_STATUS, + &status); + status &= RENESAS_ROM_STATUS_ERASE; + if (!status) + break; + + mdelay(RENESAS_DELAY); + } + + if (i == RENESAS_RETRY) + dev_dbg(&pdev->dev, "Chip erase timedout: %x\n", status); + + dev_dbg(&pdev->dev, "ROM Erase... Done success\n"); +} + +static bool renesas_setup_rom(struct pci_dev *pdev, const struct firmware *fw) +{ + const u32 *fw_data = (const u32 *)fw->data; + int err, i; + u8 status; + + /* 2. Write magic word to Data0 */ + err = pci_write_config_dword(pdev, RENESAS_DATA0, + RENESAS_ROM_WRITE_MAGIC); + if (err) + return false; + + /* 3. Set External ROM access */ + err = pci_write_config_byte(pdev, RENESAS_ROM_STATUS, + RENESAS_ROM_STATUS_ACCESS); + if (err) + goto remove_bypass; + + /* 4. Check the result */ + err = pci_read_config_byte(pdev, RENESAS_ROM_STATUS, &status); + if (err) + goto remove_bypass; + status &= GENMASK(6, 4); + if (status) { + dev_err(&pdev->dev, + "setting external rom failed: %x\n", status); + goto remove_bypass; + } + + /* 5 to 16 Write FW to DATA0/1 while checking SetData0/1 */ + for (i = 0; i < fw->size / 4; i++) { + err = renesas_fw_download_image(pdev, fw_data, i, true); + if (err) { + dev_err(&pdev->dev, + "ROM Download Step %d failed at position %d bytes with (%d)\n", + i, i * 4, err); + goto remove_bypass; + } + } + + /* + * wait till DATA0/1 is cleared + */ + for (i = 0; i < RENESAS_RETRY; i++) { + err = pci_read_config_byte(pdev, RENESAS_ROM_STATUS_MSB, + &status); + if (err) + goto remove_bypass; + if (!(status & (BIT(0) | BIT(1)))) + break; + + udelay(RENESAS_DELAY); + } + if (i == RENESAS_RETRY) { + dev_err(&pdev->dev, "Final Firmware ROM Download step timed out\n"); + goto remove_bypass; + } + + /* 17. Remove bypass */ + err = pci_write_config_byte(pdev, RENESAS_ROM_STATUS, 0); + if (err) + return false; + + udelay(10); + + /* 18. check result */ + for (i = 0; i < RENESAS_RETRY; i++) { + err = pci_read_config_byte(pdev, RENESAS_ROM_STATUS, &status); + if (err) { + dev_err(&pdev->dev, "Read ROM status failed:%d\n", + pcibios_err_to_errno(err)); + return false; + } + status &= RENESAS_ROM_STATUS_RESULT; + if (status == RENESAS_ROM_STATUS_SUCCESS) { + dev_dbg(&pdev->dev, "Download ROM success\n"); + break; + } + udelay(RENESAS_DELAY); + } + if (i == RENESAS_RETRY) { /* Timed out */ + dev_err(&pdev->dev, + "Download to external ROM TO: %x\n", status); + return false; + } + + dev_dbg(&pdev->dev, "Download to external ROM succeeded\n"); + + /* Last step set Reload */ + err = pci_write_config_byte(pdev, RENESAS_ROM_STATUS, + RENESAS_ROM_STATUS_RELOAD); + if (err) { + dev_err(&pdev->dev, "Set ROM execute failed: %d\n", + pcibios_err_to_errno(err)); + return false; + } + + /* + * wait till Reload is cleared + */ + for (i = 0; i < RENESAS_RETRY; i++) { + err = pci_read_config_byte(pdev, RENESAS_ROM_STATUS, &status); + if (err) + return false; + if (!(status & RENESAS_ROM_STATUS_RELOAD)) + break; + + udelay(RENESAS_DELAY); + } + if (i == RENESAS_RETRY) { + dev_err(&pdev->dev, "ROM Exec timed out: %x\n", status); + return false; + } + + return true; + +remove_bypass: + pci_write_config_byte(pdev, RENESAS_ROM_STATUS, 0); + return false; +} + +static int renesas_load_fw(struct pci_dev *pdev, const struct firmware *fw) +{ + int err = 0; + bool rom; + + /* Check if the device has external ROM */ + rom = renesas_check_rom(pdev); + if (rom) { + /* perform chip erase first */ + renesas_rom_erase(pdev); + + /* lets try loading fw on ROM first */ + rom = renesas_setup_rom(pdev, fw); + if (!rom) { + dev_dbg(&pdev->dev, + "ROM load failed, falling back on FW load\n"); + } else { + dev_dbg(&pdev->dev, + "ROM load success\n"); + goto exit; + } + } + + err = renesas_fw_download(pdev, fw); + +exit: + if (err) + dev_err(&pdev->dev, "firmware failed to download (%d).", err); + return err; +} + +int renesas_xhci_check_request_fw(struct pci_dev *pdev, + const struct pci_device_id *id) +{ + struct xhci_driver_data *driver_data = + (struct xhci_driver_data *)id->driver_data; + const char *fw_name = driver_data->firmware; + const struct firmware *fw; + int err; + + err = renesas_fw_check_running(pdev); + /* Continue ahead, if the firmware is already running. */ + if (err == 0) + return 0; + + if (err != 1) + return err; + + pci_dev_get(pdev); + err = request_firmware(&fw, fw_name, &pdev->dev); + pci_dev_put(pdev); + if (err) { + dev_err(&pdev->dev, "request_firmware failed: %d\n", err); + return err; + } + + err = renesas_fw_verify(fw->data, fw->size); + if (err) + goto exit; + + err = renesas_load_fw(pdev, fw); +exit: + release_firmware(fw); + return err; +} +EXPORT_SYMBOL_GPL(renesas_xhci_check_request_fw); + +void renesas_xhci_pci_exit(struct pci_dev *dev) +{ +} +EXPORT_SYMBOL_GPL(renesas_xhci_pci_exit); + +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/host/xhci-pci.c b/drivers/usb/host/xhci-pci.c index 766b74723e64..ef513c2fb843 100644 --- a/drivers/usb/host/xhci-pci.c +++ b/drivers/usb/host/xhci-pci.c @@ -15,6 +15,7 @@ #include "xhci.h" #include "xhci-trace.h" +#include "xhci-pci.h" #define SSIC_PORT_NUM 2 #define SSIC_PORT_CFG2 0x880c @@ -87,7 +88,16 @@ static int xhci_pci_reinit(struct xhci_hcd *xhci, struct pci_dev *pdev) static void xhci_pci_quirks(struct device *dev, struct xhci_hcd *xhci) { - struct pci_dev *pdev = to_pci_dev(dev); + struct pci_dev *pdev = to_pci_dev(dev); + struct xhci_driver_data *driver_data; + const struct pci_device_id *id; + + id = pci_match_id(pdev->driver->id_table, pdev); + + if (id && id->driver_data) { + driver_data = (struct xhci_driver_data *)id->driver_data; + xhci->quirks |= driver_data->quirks; + } /* Look for vendor-specific quirks */ if (pdev->vendor == PCI_VENDOR_ID_FRESCO_LOGIC && @@ -327,10 +337,15 @@ static int xhci_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) { int retval; struct xhci_hcd *xhci; - struct hc_driver *driver; struct usb_hcd *hcd; + struct xhci_driver_data *driver_data; - driver = (struct hc_driver *)id->driver_data; + driver_data = (struct xhci_driver_data *)id->driver_data; + if (driver_data && driver_data->quirks & XHCI_RENESAS_FW_QUIRK) { + retval = renesas_xhci_check_request_fw(dev, id); + if (retval) + return retval; + } /* Prevent runtime suspending between USB-2 and USB-3 initialization */ pm_runtime_get_noresume(&dev->dev); @@ -341,7 +356,7 @@ static int xhci_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) * to say USB 2.0, but I'm not sure what the implications would be in * the other parts of the HCD code. */ - retval = usb_hcd_pci_probe(dev, id); + retval = usb_hcd_pci_probe(dev, id, &xhci_pci_hc_driver); if (retval) goto put_runtime_pm; @@ -349,8 +364,8 @@ static int xhci_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) /* USB 2.0 roothub is stored in the PCI device now. */ hcd = dev_get_drvdata(&dev->dev); xhci = hcd_to_xhci(hcd); - xhci->shared_hcd = usb_create_shared_hcd(driver, &dev->dev, - pci_name(dev), hcd); + xhci->shared_hcd = usb_create_shared_hcd(&xhci_pci_hc_driver, &dev->dev, + pci_name(dev), hcd); if (!xhci->shared_hcd) { retval = -ENOMEM; goto dealloc_usb2_hcd; @@ -392,6 +407,9 @@ static void xhci_pci_remove(struct pci_dev *dev) struct xhci_hcd *xhci; xhci = hcd_to_xhci(pci_get_drvdata(dev)); + if (xhci->quirks & XHCI_RENESAS_FW_QUIRK) + renesas_xhci_pci_exit(dev); + xhci->xhc_state |= XHCI_STATE_REMOVING; if (xhci->quirks & XHCI_DEFAULT_PM_RUNTIME_ALLOW) @@ -543,15 +561,26 @@ static void xhci_pci_shutdown(struct usb_hcd *hcd) /*-------------------------------------------------------------------------*/ +static const struct xhci_driver_data reneses_data = { + .quirks = XHCI_RENESAS_FW_QUIRK, + .firmware = "renesas_usb_fw.mem", +}; + /* PCI driver selection metadata; PCI hotplugging uses this */ -static const struct pci_device_id pci_ids[] = { { +static const struct pci_device_id pci_ids[] = { + { PCI_DEVICE(0x1912, 0x0014), + .driver_data = (unsigned long)&reneses_data, + }, + { PCI_DEVICE(0x1912, 0x0015), + .driver_data = (unsigned long)&reneses_data, + }, /* handle any USB 3.0 xHCI controller */ - PCI_DEVICE_CLASS(PCI_CLASS_SERIAL_USB_XHCI, ~0), - .driver_data = (unsigned long) &xhci_pci_hc_driver, + { PCI_DEVICE_CLASS(PCI_CLASS_SERIAL_USB_XHCI, ~0), }, { /* end: all zeroes */ } }; MODULE_DEVICE_TABLE(pci, pci_ids); +MODULE_FIRMWARE("renesas_usb_fw.mem"); /* pci driver glue; this is a "new style" PCI driver module */ static struct pci_driver xhci_pci_driver = { diff --git a/drivers/usb/host/xhci-pci.h b/drivers/usb/host/xhci-pci.h new file mode 100644 index 000000000000..acd7cf0a1706 --- /dev/null +++ b/drivers/usb/host/xhci-pci.h @@ -0,0 +1,28 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright (C) 2019-2020 Linaro Limited */ + +#ifndef XHCI_PCI_H +#define XHCI_PCI_H + +#if IS_ENABLED(CONFIG_USB_XHCI_PCI_RENESAS) +int renesas_xhci_check_request_fw(struct pci_dev *dev, + const struct pci_device_id *id); +void renesas_xhci_pci_exit(struct pci_dev *dev); + +#else +static int renesas_xhci_check_request_fw(struct pci_dev *dev, + const struct pci_device_id *id) +{ + return 0; +} + +static void renesas_xhci_pci_exit(struct pci_dev *dev) { }; + +#endif + +struct xhci_driver_data { + u64 quirks; + const char *firmware; +}; + +#endif diff --git a/drivers/usb/host/xhci-plat.c b/drivers/usb/host/xhci-plat.c index ea460b9682d5..f6b4089bfc4a 100644 --- a/drivers/usb/host/xhci-plat.c +++ b/drivers/usb/host/xhci-plat.c @@ -112,6 +112,10 @@ static const struct xhci_plat_priv xhci_plat_renesas_rcar_gen3 = { SET_XHCI_PLAT_PRIV_FOR_RCAR(XHCI_RCAR_FIRMWARE_NAME_V3) }; +static const struct xhci_plat_priv xhci_plat_brcm = { + .quirks = XHCI_RESET_ON_RESUME, +}; + static const struct of_device_id usb_xhci_of_match[] = { { .compatible = "generic-xhci", @@ -147,6 +151,12 @@ static const struct of_device_id usb_xhci_of_match[] = { }, { .compatible = "renesas,rcar-gen3-xhci", .data = &xhci_plat_renesas_rcar_gen3, + }, { + .compatible = "brcm,xhci-brcm-v2", + .data = &xhci_plat_brcm, + }, { + .compatible = "brcm,bcm7445-xhci", + .data = &xhci_plat_brcm, }, {}, }; @@ -409,7 +419,15 @@ static int __maybe_unused xhci_plat_resume(struct device *dev) if (ret) return ret; - return xhci_resume(xhci, 0); + ret = xhci_resume(xhci, 0); + if (ret) + return ret; + + pm_runtime_disable(dev); + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + + return 0; } static int __maybe_unused xhci_plat_runtime_suspend(struct device *dev) diff --git a/drivers/usb/host/xhci-plat.h b/drivers/usb/host/xhci-plat.h index 5681723fc9cd..b49f6447bd3a 100644 --- a/drivers/usb/host/xhci-plat.h +++ b/drivers/usb/host/xhci-plat.h @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-2.0 +/* SPDX-License-Identifier: GPL-2.0 */ /* * xhci-plat.h - xHCI host controller driver platform Bus Glue. * diff --git a/drivers/usb/host/xhci-rcar.h b/drivers/usb/host/xhci-rcar.h index 012744a63a49..048ad3b8a6c7 100644 --- a/drivers/usb/host/xhci-rcar.h +++ b/drivers/usb/host/xhci-rcar.h @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-2.0 +/* SPDX-License-Identifier: GPL-2.0 */ /* * drivers/usb/host/xhci-rcar.h * diff --git a/drivers/usb/host/xhci-trace.h b/drivers/usb/host/xhci-trace.h index b19582b2a72c..627abd236dbe 100644 --- a/drivers/usb/host/xhci-trace.h +++ b/drivers/usb/host/xhci-trace.h @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-2.0 +/* SPDX-License-Identifier: GPL-2.0 */ /* * xHCI host controller driver * diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index 86cfefdd6632..2c6c4f8d1ee1 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-2.0 +/* SPDX-License-Identifier: GPL-2.0 */ /* * xHCI host controller driver @@ -1873,6 +1873,7 @@ struct xhci_hcd { #define XHCI_DEFAULT_PM_RUNTIME_ALLOW BIT_ULL(33) #define XHCI_RESET_PLL_ON_DISCONNECT BIT_ULL(34) #define XHCI_SNPS_BROKEN_SUSPEND BIT_ULL(35) +#define XHCI_RENESAS_FW_QUIRK BIT_ULL(36) unsigned int num_active_eps; unsigned int limit_active_eps; |