Re: [PATCH 4/5] usb: host: Add XHCI driver for Broadcom STB SoCs

From: Chunfeng Yun
Date: Thu Sep 27 2018 - 01:52:03 EST


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");