Hi, On Wed, 2018-09-26 at 18:20 -0400, Al Cooper wrote: > This driver enables USB XHCI on Broadcom ARM STB SoCs. > The drivers depend on a matching "brcm,brcmstb-usb-phy" > Broadcom STB USB Phy driver. > > The standard platform driver can't be used because of differences > in PHY and Clock handling. The standard PHY handling in hcd.c will > do a phy_exit/phy_init on suspend/resume and this will end up > shutting down the PHYs to the point that the host controller > registers are no longer accessible and will cause suspend to crash. You can avoid hcd.c to do a phy_exit/phy_init on suspend/resume by device_init_wakeup(dev, true); > The clocks specified in device tree for these drivers are not > available in mainline so instead of returning EPROBE_DEFER when > the specified clock is not found and eventually failing probe, > the clock pointer is set to NULL which disables all clock handling. Try to use a fixed-clock as dummy clock if the clock is optional? > > Signed-off-by: Al Cooper <alcooperx@xxxxxxxxx> > --- > drivers/usb/host/xhci-brcm.c | 294 +++++++++++++++++++++++++++++++++++++++++++ > 1 file changed, 294 insertions(+) > create mode 100644 drivers/usb/host/xhci-brcm.c > > diff --git a/drivers/usb/host/xhci-brcm.c b/drivers/usb/host/xhci-brcm.c > new file mode 100644 > index 000000000000..1a7578b8ef6a > --- /dev/null > +++ b/drivers/usb/host/xhci-brcm.c > @@ -0,0 +1,294 @@ > +// SPDX-License-Identifier: GPL-2.0 > +/* Copyright (c) 2018, Broadcom */ > + > +#include <linux/platform_device.h> > +#include <linux/module.h> > +#include <linux/of.h> > +#include <linux/dma-mapping.h> > +#include <linux/clk.h> > +#include <linux/phy/phy.h> > + > +#include "xhci.h" > + > +static struct hc_driver __read_mostly xhci_brcm_hc_driver; > + > +#define BRCM_DRIVER_DESC "xHCI Broadcom STB driver" > +#define BRCM_DRIVER_NAME "xhci-brcm" > + > +#define hcd_to_xhci_priv(h) ((struct brcm_priv *)hcd_to_xhci(h)->priv) > + > +struct brcm_priv { > + struct phy *phy; > +}; > + > +static void xhci_brcm_quirks(struct device *dev, struct xhci_hcd *xhci) > +{ > + /* > + * As of now platform drivers don't provide MSI support so we ensure > + * here that the generic code does not try to make a pci_dev from our > + * dev struct in order to setup MSI > + */ > + xhci->quirks |= XHCI_PLAT; > + > + /* > + * The Broadcom XHCI core does not support save/restore state > + * so we need to reset on resume. > + */ > + xhci->quirks |= XHCI_RESET_ON_RESUME; > +} > + > +/* called during probe() after chip reset completes */ > +static int xhci_brcm_setup(struct usb_hcd *hcd) > +{ > + return xhci_gen_setup(hcd, xhci_brcm_quirks); > +} > + > +static const struct xhci_driver_overrides brcm_overrides __initconst = { > + > + .extra_priv_size = sizeof(struct brcm_priv), > + .reset = xhci_brcm_setup, > +}; > + > +static int xhci_brcm_probe(struct platform_device *pdev) > +{ > + const struct hc_driver *driver; > + struct brcm_priv *priv; > + struct xhci_hcd *xhci; > + struct resource *res; > + struct usb_hcd *hcd; > + int ret; > + int irq; > + > + if (usb_disabled()) > + return -ENODEV; > + > + driver = &xhci_brcm_hc_driver; > + > + irq = platform_get_irq(pdev, 0); > + if (irq < 0) > + return -ENODEV; > + > + /* Try to set 64-bit DMA first */ > + if (WARN_ON(!pdev->dev.dma_mask)) > + /* Platform did not initialize dma_mask */ > + ret = dma_coerce_mask_and_coherent(&pdev->dev, > + DMA_BIT_MASK(64)); > + else > + ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64)); > + > + /* If seting 64-bit DMA mask fails, fall back to 32-bit DMA mask */ > + if (ret) { > + ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32)); > + if (ret) > + return ret; > + } > + > + pm_runtime_set_active(&pdev->dev); > + pm_runtime_enable(&pdev->dev); > + pm_runtime_get_noresume(&pdev->dev); > + > + hcd = __usb_create_hcd(driver, &pdev->dev, &pdev->dev, > + dev_name(&pdev->dev), NULL); > + if (!hcd) { > + return -ENOMEM; > + goto disable_runtime; > + } > + > + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); > + hcd->regs = devm_ioremap_resource(&pdev->dev, res); > + if (IS_ERR(hcd->regs)) { > + ret = PTR_ERR(hcd->regs); > + goto put_hcd; > + } > + > + hcd->rsrc_start = res->start; > + hcd->rsrc_len = resource_size(res); > + > + /* > + * Not all platforms have a clk so it is not an error if the > + * clock does not exists. > + */ > + xhci = hcd_to_xhci(hcd); > + xhci->clk = devm_clk_get(&pdev->dev, NULL); > + if (IS_ERR(xhci->clk)) { > + dev_err(&pdev->dev, "Clock not found in Device Tree\n"); > + xhci->clk = NULL; > + } > + device_wakeup_enable(hcd->self.controller); > + > + xhci->main_hcd = hcd; > + xhci->shared_hcd = __usb_create_hcd(driver, &pdev->dev, &pdev->dev, > + dev_name(&pdev->dev), hcd); > + if (!xhci->shared_hcd) { > + ret = -ENOMEM; > + goto disable_clk; > + } > + > + if (device_property_read_bool(&pdev->dev, "usb3-lpm-capable")) > + xhci->quirks |= XHCI_LPM_SUPPORT; > + > + priv = hcd_to_xhci_priv(hcd); > + priv->phy = devm_of_phy_get_by_index(&pdev->dev, pdev->dev.of_node, 0); > + if (IS_ERR(priv->phy)) { > + dev_err(&pdev->dev, "USB Phy not found.\n"); > + ret = PTR_ERR(priv->phy); > + goto put_usb3_hcd; > + } > + ret = phy_init(priv->phy); > + if (ret) > + goto put_usb3_hcd; > + > + hcd->skip_phy_initialization = 1; > + ret = usb_add_hcd(hcd, irq, IRQF_SHARED); > + if (ret) > + goto disable_usb_phy; > + > + if (HCC_MAX_PSA(xhci->hcc_params) >= 4) > + xhci->shared_hcd->can_do_streams = 1; > + > + ret = usb_add_hcd(xhci->shared_hcd, irq, IRQF_SHARED); > + if (ret) > + goto dealloc_usb2_hcd; > + > + device_enable_async_suspend(&pdev->dev); > + pm_runtime_put_noidle(&pdev->dev); > + > + /* > + * Prevent runtime pm from being on as default, users should enable > + * runtime pm using power/control in sysfs. > + */ > + pm_runtime_forbid(&pdev->dev); > + > + return 0; > + > +dealloc_usb2_hcd: > + usb_remove_hcd(hcd); > + > +disable_usb_phy: > + phy_exit(priv->phy); > + > +put_usb3_hcd: > + usb_put_hcd(xhci->shared_hcd); > + > +disable_clk: > + if (!IS_ERR(xhci->clk)) > + clk_disable_unprepare(xhci->clk); > + > +put_hcd: > + usb_put_hcd(hcd); > + > +disable_runtime: > + pm_runtime_put_noidle(&pdev->dev); > + pm_runtime_disable(&pdev->dev); > + > + return ret; > +} > + > +static int xhci_brcm_remove(struct platform_device *dev) > +{ > + struct usb_hcd *hcd = platform_get_drvdata(dev); > + struct xhci_hcd *xhci = hcd_to_xhci(hcd); > + struct brcm_priv *priv = hcd_to_xhci_priv(hcd); > + > + xhci->xhc_state |= XHCI_STATE_REMOVING; > + > + usb_remove_hcd(xhci->shared_hcd); > + usb_remove_hcd(hcd); > + usb_put_hcd(xhci->shared_hcd); > + phy_exit(priv->phy); > + clk_disable_unprepare(xhci->clk); > + usb_put_hcd(hcd); > + > + pm_runtime_set_suspended(&dev->dev); > + pm_runtime_disable(&dev->dev); > + > + return 0; > +} > + > +#ifdef CONFIG_PM_SLEEP > +static int xhci_brcm_suspend(struct device *dev) > +{ > + int ret; > + struct usb_hcd *hcd = dev_get_drvdata(dev); > + struct xhci_hcd *xhci = hcd_to_xhci(hcd); > + > + ret = xhci_suspend(xhci, device_may_wakeup(dev)); > + clk_disable_unprepare(xhci->clk); > + return ret; > +} > + > +static int xhci_brcm_resume(struct device *dev) > +{ > + struct usb_hcd *hcd = dev_get_drvdata(dev); > + struct xhci_hcd *xhci = hcd_to_xhci(hcd); > + int err; > + > + err = clk_prepare_enable(xhci->clk); > + if (err) > + return err; > + return xhci_resume(xhci, 0); > +} > + > +static int xhci_brcm_runtime_suspend(struct device *dev) > +{ > + struct usb_hcd *hcd = dev_get_drvdata(dev); > + struct xhci_hcd *xhci = hcd_to_xhci(hcd); > + > + return xhci_suspend(xhci, true); > +} > + > +static int xhci_brcm_runtime_resume(struct device *dev) > +{ > + struct usb_hcd *hcd = dev_get_drvdata(dev); > + struct xhci_hcd *xhci = hcd_to_xhci(hcd); > + > + return xhci_resume(xhci, 0); > +} > + > +#endif /* CONFIG_PM_SLEEP */ > + > + > +static const struct dev_pm_ops xhci_brcm_pm_ops = { > + SET_SYSTEM_SLEEP_PM_OPS(xhci_brcm_suspend, xhci_brcm_resume) > + > + SET_RUNTIME_PM_OPS(xhci_brcm_runtime_suspend, > + xhci_brcm_runtime_resume, > + NULL) > +}; > + > +#ifdef CONFIG_OF > +static const struct of_device_id brcm_xhci_of_match[] = { > + { .compatible = "brcm,xhci-brcm-v2" }, > + { }, > +}; > +MODULE_DEVICE_TABLE(of, brcm_xhci_of_match); > +#endif > + > +static struct platform_driver xhci_brcm_driver = { > + .probe = xhci_brcm_probe, > + .remove = xhci_brcm_remove, > + .driver = { > + .name = BRCM_DRIVER_NAME, > + .pm = &xhci_brcm_pm_ops, > + .of_match_table = of_match_ptr(brcm_xhci_of_match), > + }, > +}; > + > +static int __init xhci_brcm_init(void) > +{ > + pr_info("%s: " BRCM_DRIVER_DESC "\n", BRCM_DRIVER_NAME); > + xhci_init_driver(&xhci_brcm_hc_driver, &brcm_overrides); > + return platform_driver_register(&xhci_brcm_driver); > +} > +module_init(xhci_brcm_init); > + > +static void __exit xhci_brcm_exit(void) > +{ > + platform_driver_unregister(&xhci_brcm_driver); > +} > +module_exit(xhci_brcm_exit); > + > +MODULE_ALIAS("platform:xhci-brcm"); > +MODULE_DESCRIPTION(BRCM_DRIVER_DESC); > +MODULE_AUTHOR("Al Cooper"); > +MODULE_LICENSE("GPL");