Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0
0002 /* Copyright (c) 2020, Broadcom */
0003 
0004 #include <linux/clk.h>
0005 #include <linux/dma-mapping.h>
0006 #include <linux/err.h>
0007 #include <linux/kernel.h>
0008 #include <linux/io.h>
0009 #include <linux/module.h>
0010 #include <linux/platform_device.h>
0011 #include <linux/usb.h>
0012 #include <linux/usb/hcd.h>
0013 #include <linux/iopoll.h>
0014 
0015 #include "ehci.h"
0016 
0017 #define hcd_to_ehci_priv(h) ((struct brcm_priv *)hcd_to_ehci(h)->priv)
0018 
0019 struct brcm_priv {
0020     struct clk *clk;
0021 };
0022 
0023 /*
0024  * ehci_brcm_wait_for_sof
0025  * Wait for start of next microframe, then wait extra delay microseconds
0026  */
0027 static inline void ehci_brcm_wait_for_sof(struct ehci_hcd *ehci, u32 delay)
0028 {
0029     u32 frame_idx = ehci_readl(ehci, &ehci->regs->frame_index);
0030     u32 val;
0031     int res;
0032 
0033     /* Wait for next microframe (every 125 usecs) */
0034     res = readl_relaxed_poll_timeout(&ehci->regs->frame_index, val,
0035                      val != frame_idx, 1, 130);
0036     if (res)
0037         ehci_err(ehci, "Error waiting for SOF\n");
0038     udelay(delay);
0039 }
0040 
0041 /*
0042  * ehci_brcm_hub_control
0043  * The EHCI controller has a bug where it can violate the SOF
0044  * interval between the first two SOF's transmitted after resume
0045  * if the resume occurs near the end of the microframe. This causees
0046  * the controller to detect babble on the suspended port and
0047  * will eventually cause the controller to reset the port.
0048  * The fix is to Intercept the echi-hcd request to complete RESUME and
0049  * align it to the start of the next microframe.
0050  * See SWLINUX-1909 for more details
0051  */
0052 static int ehci_brcm_hub_control(
0053     struct usb_hcd  *hcd,
0054     u16     typeReq,
0055     u16     wValue,
0056     u16     wIndex,
0057     char        *buf,
0058     u16     wLength)
0059 {
0060     struct ehci_hcd *ehci = hcd_to_ehci(hcd);
0061     int     ports = HCS_N_PORTS(ehci->hcs_params);
0062     u32 __iomem *status_reg;
0063     unsigned long flags;
0064     int retval, irq_disabled = 0;
0065     u32 temp;
0066 
0067     temp = (wIndex & 0xff) - 1;
0068     if (temp >= HCS_N_PORTS_MAX)    /* Avoid index-out-of-bounds warning */
0069         temp = 0;
0070     status_reg = &ehci->regs->port_status[temp];
0071 
0072     /*
0073      * RESUME is cleared when GetPortStatus() is called 20ms after start
0074      * of RESUME
0075      */
0076     if ((typeReq == GetPortStatus) &&
0077         (wIndex && wIndex <= ports) &&
0078         ehci->reset_done[wIndex-1] &&
0079         time_after_eq(jiffies, ehci->reset_done[wIndex-1]) &&
0080         (ehci_readl(ehci, status_reg) & PORT_RESUME)) {
0081 
0082         /*
0083          * to make sure we are not interrupted until RESUME bit
0084          * is cleared, disable interrupts on current CPU
0085          */
0086         ehci_dbg(ehci, "SOF alignment workaround\n");
0087         irq_disabled = 1;
0088         local_irq_save(flags);
0089         ehci_brcm_wait_for_sof(ehci, 5);
0090     }
0091     retval = ehci_hub_control(hcd, typeReq, wValue, wIndex, buf, wLength);
0092     if (irq_disabled)
0093         local_irq_restore(flags);
0094     return retval;
0095 }
0096 
0097 static int ehci_brcm_reset(struct usb_hcd *hcd)
0098 {
0099     struct ehci_hcd *ehci = hcd_to_ehci(hcd);
0100     int len;
0101 
0102     ehci->big_endian_mmio = 1;
0103 
0104     ehci->caps = (void __iomem *)hcd->regs;
0105     len = HC_LENGTH(ehci, ehci_readl(ehci, &ehci->caps->hc_capbase));
0106     ehci->regs = (void __iomem *)(hcd->regs + len);
0107 
0108     /* This fixes the lockup during reboot due to prior interrupts */
0109     ehci_writel(ehci, CMD_RESET, &ehci->regs->command);
0110     mdelay(10);
0111 
0112     /*
0113      * SWLINUX-1705: Avoid OUT packet underflows during high memory
0114      *   bus usage
0115      */
0116     ehci_writel(ehci, 0x00800040, &ehci->regs->brcm_insnreg[1]);
0117     ehci_writel(ehci, 0x00000001, &ehci->regs->brcm_insnreg[3]);
0118 
0119     return ehci_setup(hcd);
0120 }
0121 
0122 static struct hc_driver __read_mostly ehci_brcm_hc_driver;
0123 
0124 static const struct ehci_driver_overrides brcm_overrides __initconst = {
0125     .reset = ehci_brcm_reset,
0126     .extra_priv_size = sizeof(struct brcm_priv),
0127 };
0128 
0129 static int ehci_brcm_probe(struct platform_device *pdev)
0130 {
0131     struct device *dev = &pdev->dev;
0132     struct resource *res_mem;
0133     struct brcm_priv *priv;
0134     struct usb_hcd *hcd;
0135     int irq;
0136     int err;
0137 
0138     err = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32));
0139     if (err)
0140         return err;
0141 
0142     irq = platform_get_irq(pdev, 0);
0143     if (irq <= 0)
0144         return irq ? irq : -EINVAL;
0145 
0146     /* Hook the hub control routine to work around a bug */
0147     ehci_brcm_hc_driver.hub_control = ehci_brcm_hub_control;
0148 
0149     /* initialize hcd */
0150     hcd = usb_create_hcd(&ehci_brcm_hc_driver, dev, dev_name(dev));
0151     if (!hcd)
0152         return -ENOMEM;
0153 
0154     platform_set_drvdata(pdev, hcd);
0155     priv = hcd_to_ehci_priv(hcd);
0156 
0157     priv->clk = devm_clk_get_optional(dev, NULL);
0158     if (IS_ERR(priv->clk)) {
0159         err = PTR_ERR(priv->clk);
0160         goto err_hcd;
0161     }
0162 
0163     err = clk_prepare_enable(priv->clk);
0164     if (err)
0165         goto err_hcd;
0166 
0167     hcd->regs = devm_platform_get_and_ioremap_resource(pdev, 0, &res_mem);
0168     if (IS_ERR(hcd->regs)) {
0169         err = PTR_ERR(hcd->regs);
0170         goto err_clk;
0171     }
0172     hcd->rsrc_start = res_mem->start;
0173     hcd->rsrc_len = resource_size(res_mem);
0174     err = usb_add_hcd(hcd, irq, IRQF_SHARED);
0175     if (err)
0176         goto err_clk;
0177 
0178     device_wakeup_enable(hcd->self.controller);
0179     device_enable_async_suspend(hcd->self.controller);
0180 
0181     return 0;
0182 
0183 err_clk:
0184     clk_disable_unprepare(priv->clk);
0185 err_hcd:
0186     usb_put_hcd(hcd);
0187 
0188     return err;
0189 }
0190 
0191 static int ehci_brcm_remove(struct platform_device *dev)
0192 {
0193     struct usb_hcd *hcd = platform_get_drvdata(dev);
0194     struct brcm_priv *priv = hcd_to_ehci_priv(hcd);
0195 
0196     usb_remove_hcd(hcd);
0197     clk_disable_unprepare(priv->clk);
0198     usb_put_hcd(hcd);
0199     return 0;
0200 }
0201 
0202 static int __maybe_unused ehci_brcm_suspend(struct device *dev)
0203 {
0204     int ret;
0205     struct usb_hcd *hcd = dev_get_drvdata(dev);
0206     struct brcm_priv *priv = hcd_to_ehci_priv(hcd);
0207     bool do_wakeup = device_may_wakeup(dev);
0208 
0209     ret = ehci_suspend(hcd, do_wakeup);
0210     if (ret)
0211         return ret;
0212     clk_disable_unprepare(priv->clk);
0213     return 0;
0214 }
0215 
0216 static int __maybe_unused ehci_brcm_resume(struct device *dev)
0217 {
0218     struct usb_hcd *hcd = dev_get_drvdata(dev);
0219     struct ehci_hcd *ehci = hcd_to_ehci(hcd);
0220     struct brcm_priv *priv = hcd_to_ehci_priv(hcd);
0221     int err;
0222 
0223     err = clk_prepare_enable(priv->clk);
0224     if (err)
0225         return err;
0226     /*
0227      * SWLINUX-1705: Avoid OUT packet underflows during high memory
0228      *   bus usage
0229      */
0230     ehci_writel(ehci, 0x00800040, &ehci->regs->brcm_insnreg[1]);
0231     ehci_writel(ehci, 0x00000001, &ehci->regs->brcm_insnreg[3]);
0232 
0233     ehci_resume(hcd, false);
0234 
0235     pm_runtime_disable(dev);
0236     pm_runtime_set_active(dev);
0237     pm_runtime_enable(dev);
0238 
0239     return 0;
0240 }
0241 
0242 static SIMPLE_DEV_PM_OPS(ehci_brcm_pm_ops, ehci_brcm_suspend,
0243         ehci_brcm_resume);
0244 
0245 static const struct of_device_id brcm_ehci_of_match[] = {
0246     { .compatible = "brcm,ehci-brcm-v2", },
0247     { .compatible = "brcm,bcm7445-ehci", },
0248     {}
0249 };
0250 
0251 static struct platform_driver ehci_brcm_driver = {
0252     .probe      = ehci_brcm_probe,
0253     .remove     = ehci_brcm_remove,
0254     .shutdown   = usb_hcd_platform_shutdown,
0255     .driver     = {
0256         .name   = "ehci-brcm",
0257         .pm = &ehci_brcm_pm_ops,
0258         .of_match_table = brcm_ehci_of_match,
0259     }
0260 };
0261 
0262 static int __init ehci_brcm_init(void)
0263 {
0264     if (usb_disabled())
0265         return -ENODEV;
0266 
0267     ehci_init_driver(&ehci_brcm_hc_driver, &brcm_overrides);
0268     return platform_driver_register(&ehci_brcm_driver);
0269 }
0270 module_init(ehci_brcm_init);
0271 
0272 static void __exit ehci_brcm_exit(void)
0273 {
0274     platform_driver_unregister(&ehci_brcm_driver);
0275 }
0276 module_exit(ehci_brcm_exit);
0277 
0278 MODULE_ALIAS("platform:ehci-brcm");
0279 MODULE_DESCRIPTION("EHCI Broadcom STB driver");
0280 MODULE_AUTHOR("Al Cooper");
0281 MODULE_LICENSE("GPL");