162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/* Copyright (c) 2020, Broadcom */
362306a36Sopenharmony_ci
462306a36Sopenharmony_ci#include <linux/clk.h>
562306a36Sopenharmony_ci#include <linux/dma-mapping.h>
662306a36Sopenharmony_ci#include <linux/err.h>
762306a36Sopenharmony_ci#include <linux/kernel.h>
862306a36Sopenharmony_ci#include <linux/io.h>
962306a36Sopenharmony_ci#include <linux/module.h>
1062306a36Sopenharmony_ci#include <linux/platform_device.h>
1162306a36Sopenharmony_ci#include <linux/usb.h>
1262306a36Sopenharmony_ci#include <linux/usb/hcd.h>
1362306a36Sopenharmony_ci#include <linux/iopoll.h>
1462306a36Sopenharmony_ci
1562306a36Sopenharmony_ci#include "ehci.h"
1662306a36Sopenharmony_ci
1762306a36Sopenharmony_ci#define hcd_to_ehci_priv(h) ((struct brcm_priv *)hcd_to_ehci(h)->priv)
1862306a36Sopenharmony_ci
1962306a36Sopenharmony_cistruct brcm_priv {
2062306a36Sopenharmony_ci	struct clk *clk;
2162306a36Sopenharmony_ci};
2262306a36Sopenharmony_ci
2362306a36Sopenharmony_ci/*
2462306a36Sopenharmony_ci * ehci_brcm_wait_for_sof
2562306a36Sopenharmony_ci * Wait for start of next microframe, then wait extra delay microseconds
2662306a36Sopenharmony_ci */
2762306a36Sopenharmony_cistatic inline void ehci_brcm_wait_for_sof(struct ehci_hcd *ehci, u32 delay)
2862306a36Sopenharmony_ci{
2962306a36Sopenharmony_ci	u32 frame_idx = ehci_readl(ehci, &ehci->regs->frame_index);
3062306a36Sopenharmony_ci	u32 val;
3162306a36Sopenharmony_ci	int res;
3262306a36Sopenharmony_ci
3362306a36Sopenharmony_ci	/* Wait for next microframe (every 125 usecs) */
3462306a36Sopenharmony_ci	res = readl_relaxed_poll_timeout(&ehci->regs->frame_index, val,
3562306a36Sopenharmony_ci					 val != frame_idx, 1, 130);
3662306a36Sopenharmony_ci	if (res)
3762306a36Sopenharmony_ci		ehci_err(ehci, "Error waiting for SOF\n");
3862306a36Sopenharmony_ci	udelay(delay);
3962306a36Sopenharmony_ci}
4062306a36Sopenharmony_ci
4162306a36Sopenharmony_ci/*
4262306a36Sopenharmony_ci * ehci_brcm_hub_control
4362306a36Sopenharmony_ci * The EHCI controller has a bug where it can violate the SOF
4462306a36Sopenharmony_ci * interval between the first two SOF's transmitted after resume
4562306a36Sopenharmony_ci * if the resume occurs near the end of the microframe. This causees
4662306a36Sopenharmony_ci * the controller to detect babble on the suspended port and
4762306a36Sopenharmony_ci * will eventually cause the controller to reset the port.
4862306a36Sopenharmony_ci * The fix is to Intercept the echi-hcd request to complete RESUME and
4962306a36Sopenharmony_ci * align it to the start of the next microframe.
5062306a36Sopenharmony_ci * See SWLINUX-1909 for more details
5162306a36Sopenharmony_ci */
5262306a36Sopenharmony_cistatic int ehci_brcm_hub_control(
5362306a36Sopenharmony_ci	struct usb_hcd	*hcd,
5462306a36Sopenharmony_ci	u16		typeReq,
5562306a36Sopenharmony_ci	u16		wValue,
5662306a36Sopenharmony_ci	u16		wIndex,
5762306a36Sopenharmony_ci	char		*buf,
5862306a36Sopenharmony_ci	u16		wLength)
5962306a36Sopenharmony_ci{
6062306a36Sopenharmony_ci	struct ehci_hcd	*ehci = hcd_to_ehci(hcd);
6162306a36Sopenharmony_ci	int		ports = HCS_N_PORTS(ehci->hcs_params);
6262306a36Sopenharmony_ci	u32 __iomem	*status_reg;
6362306a36Sopenharmony_ci	unsigned long flags;
6462306a36Sopenharmony_ci	int retval, irq_disabled = 0;
6562306a36Sopenharmony_ci	u32 temp;
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_ci	temp = (wIndex & 0xff) - 1;
6862306a36Sopenharmony_ci	if (temp >= HCS_N_PORTS_MAX)	/* Avoid index-out-of-bounds warning */
6962306a36Sopenharmony_ci		temp = 0;
7062306a36Sopenharmony_ci	status_reg = &ehci->regs->port_status[temp];
7162306a36Sopenharmony_ci
7262306a36Sopenharmony_ci	/*
7362306a36Sopenharmony_ci	 * RESUME is cleared when GetPortStatus() is called 20ms after start
7462306a36Sopenharmony_ci	 * of RESUME
7562306a36Sopenharmony_ci	 */
7662306a36Sopenharmony_ci	if ((typeReq == GetPortStatus) &&
7762306a36Sopenharmony_ci	    (wIndex && wIndex <= ports) &&
7862306a36Sopenharmony_ci	    ehci->reset_done[wIndex-1] &&
7962306a36Sopenharmony_ci	    time_after_eq(jiffies, ehci->reset_done[wIndex-1]) &&
8062306a36Sopenharmony_ci	    (ehci_readl(ehci, status_reg) & PORT_RESUME)) {
8162306a36Sopenharmony_ci
8262306a36Sopenharmony_ci		/*
8362306a36Sopenharmony_ci		 * to make sure we are not interrupted until RESUME bit
8462306a36Sopenharmony_ci		 * is cleared, disable interrupts on current CPU
8562306a36Sopenharmony_ci		 */
8662306a36Sopenharmony_ci		ehci_dbg(ehci, "SOF alignment workaround\n");
8762306a36Sopenharmony_ci		irq_disabled = 1;
8862306a36Sopenharmony_ci		local_irq_save(flags);
8962306a36Sopenharmony_ci		ehci_brcm_wait_for_sof(ehci, 5);
9062306a36Sopenharmony_ci	}
9162306a36Sopenharmony_ci	retval = ehci_hub_control(hcd, typeReq, wValue, wIndex, buf, wLength);
9262306a36Sopenharmony_ci	if (irq_disabled)
9362306a36Sopenharmony_ci		local_irq_restore(flags);
9462306a36Sopenharmony_ci	return retval;
9562306a36Sopenharmony_ci}
9662306a36Sopenharmony_ci
9762306a36Sopenharmony_cistatic int ehci_brcm_reset(struct usb_hcd *hcd)
9862306a36Sopenharmony_ci{
9962306a36Sopenharmony_ci	struct ehci_hcd *ehci = hcd_to_ehci(hcd);
10062306a36Sopenharmony_ci	int len;
10162306a36Sopenharmony_ci
10262306a36Sopenharmony_ci	ehci->big_endian_mmio = 1;
10362306a36Sopenharmony_ci
10462306a36Sopenharmony_ci	ehci->caps = (void __iomem *)hcd->regs;
10562306a36Sopenharmony_ci	len = HC_LENGTH(ehci, ehci_readl(ehci, &ehci->caps->hc_capbase));
10662306a36Sopenharmony_ci	ehci->regs = (void __iomem *)(hcd->regs + len);
10762306a36Sopenharmony_ci
10862306a36Sopenharmony_ci	/* This fixes the lockup during reboot due to prior interrupts */
10962306a36Sopenharmony_ci	ehci_writel(ehci, CMD_RESET, &ehci->regs->command);
11062306a36Sopenharmony_ci	mdelay(10);
11162306a36Sopenharmony_ci
11262306a36Sopenharmony_ci	/*
11362306a36Sopenharmony_ci	 * SWLINUX-1705: Avoid OUT packet underflows during high memory
11462306a36Sopenharmony_ci	 *   bus usage
11562306a36Sopenharmony_ci	 */
11662306a36Sopenharmony_ci	ehci_writel(ehci, 0x00800040, &ehci->regs->brcm_insnreg[1]);
11762306a36Sopenharmony_ci	ehci_writel(ehci, 0x00000001, &ehci->regs->brcm_insnreg[3]);
11862306a36Sopenharmony_ci
11962306a36Sopenharmony_ci	return ehci_setup(hcd);
12062306a36Sopenharmony_ci}
12162306a36Sopenharmony_ci
12262306a36Sopenharmony_cistatic struct hc_driver __read_mostly ehci_brcm_hc_driver;
12362306a36Sopenharmony_ci
12462306a36Sopenharmony_cistatic const struct ehci_driver_overrides brcm_overrides __initconst = {
12562306a36Sopenharmony_ci	.reset = ehci_brcm_reset,
12662306a36Sopenharmony_ci	.extra_priv_size = sizeof(struct brcm_priv),
12762306a36Sopenharmony_ci};
12862306a36Sopenharmony_ci
12962306a36Sopenharmony_cistatic int ehci_brcm_probe(struct platform_device *pdev)
13062306a36Sopenharmony_ci{
13162306a36Sopenharmony_ci	struct device *dev = &pdev->dev;
13262306a36Sopenharmony_ci	struct resource *res_mem;
13362306a36Sopenharmony_ci	struct brcm_priv *priv;
13462306a36Sopenharmony_ci	struct usb_hcd *hcd;
13562306a36Sopenharmony_ci	int irq;
13662306a36Sopenharmony_ci	int err;
13762306a36Sopenharmony_ci
13862306a36Sopenharmony_ci	err = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32));
13962306a36Sopenharmony_ci	if (err)
14062306a36Sopenharmony_ci		return err;
14162306a36Sopenharmony_ci
14262306a36Sopenharmony_ci	irq = platform_get_irq(pdev, 0);
14362306a36Sopenharmony_ci	if (irq < 0)
14462306a36Sopenharmony_ci		return irq;
14562306a36Sopenharmony_ci
14662306a36Sopenharmony_ci	/* Hook the hub control routine to work around a bug */
14762306a36Sopenharmony_ci	ehci_brcm_hc_driver.hub_control = ehci_brcm_hub_control;
14862306a36Sopenharmony_ci
14962306a36Sopenharmony_ci	/* initialize hcd */
15062306a36Sopenharmony_ci	hcd = usb_create_hcd(&ehci_brcm_hc_driver, dev, dev_name(dev));
15162306a36Sopenharmony_ci	if (!hcd)
15262306a36Sopenharmony_ci		return -ENOMEM;
15362306a36Sopenharmony_ci
15462306a36Sopenharmony_ci	platform_set_drvdata(pdev, hcd);
15562306a36Sopenharmony_ci	priv = hcd_to_ehci_priv(hcd);
15662306a36Sopenharmony_ci
15762306a36Sopenharmony_ci	priv->clk = devm_clk_get_optional(dev, NULL);
15862306a36Sopenharmony_ci	if (IS_ERR(priv->clk)) {
15962306a36Sopenharmony_ci		err = PTR_ERR(priv->clk);
16062306a36Sopenharmony_ci		goto err_hcd;
16162306a36Sopenharmony_ci	}
16262306a36Sopenharmony_ci
16362306a36Sopenharmony_ci	err = clk_prepare_enable(priv->clk);
16462306a36Sopenharmony_ci	if (err)
16562306a36Sopenharmony_ci		goto err_hcd;
16662306a36Sopenharmony_ci
16762306a36Sopenharmony_ci	hcd->regs = devm_platform_get_and_ioremap_resource(pdev, 0, &res_mem);
16862306a36Sopenharmony_ci	if (IS_ERR(hcd->regs)) {
16962306a36Sopenharmony_ci		err = PTR_ERR(hcd->regs);
17062306a36Sopenharmony_ci		goto err_clk;
17162306a36Sopenharmony_ci	}
17262306a36Sopenharmony_ci	hcd->rsrc_start = res_mem->start;
17362306a36Sopenharmony_ci	hcd->rsrc_len = resource_size(res_mem);
17462306a36Sopenharmony_ci	err = usb_add_hcd(hcd, irq, IRQF_SHARED);
17562306a36Sopenharmony_ci	if (err)
17662306a36Sopenharmony_ci		goto err_clk;
17762306a36Sopenharmony_ci
17862306a36Sopenharmony_ci	device_wakeup_enable(hcd->self.controller);
17962306a36Sopenharmony_ci	device_enable_async_suspend(hcd->self.controller);
18062306a36Sopenharmony_ci
18162306a36Sopenharmony_ci	return 0;
18262306a36Sopenharmony_ci
18362306a36Sopenharmony_cierr_clk:
18462306a36Sopenharmony_ci	clk_disable_unprepare(priv->clk);
18562306a36Sopenharmony_cierr_hcd:
18662306a36Sopenharmony_ci	usb_put_hcd(hcd);
18762306a36Sopenharmony_ci
18862306a36Sopenharmony_ci	return err;
18962306a36Sopenharmony_ci}
19062306a36Sopenharmony_ci
19162306a36Sopenharmony_cistatic void ehci_brcm_remove(struct platform_device *dev)
19262306a36Sopenharmony_ci{
19362306a36Sopenharmony_ci	struct usb_hcd *hcd = platform_get_drvdata(dev);
19462306a36Sopenharmony_ci	struct brcm_priv *priv = hcd_to_ehci_priv(hcd);
19562306a36Sopenharmony_ci
19662306a36Sopenharmony_ci	usb_remove_hcd(hcd);
19762306a36Sopenharmony_ci	clk_disable_unprepare(priv->clk);
19862306a36Sopenharmony_ci	usb_put_hcd(hcd);
19962306a36Sopenharmony_ci}
20062306a36Sopenharmony_ci
20162306a36Sopenharmony_cistatic int __maybe_unused ehci_brcm_suspend(struct device *dev)
20262306a36Sopenharmony_ci{
20362306a36Sopenharmony_ci	int ret;
20462306a36Sopenharmony_ci	struct usb_hcd *hcd = dev_get_drvdata(dev);
20562306a36Sopenharmony_ci	struct brcm_priv *priv = hcd_to_ehci_priv(hcd);
20662306a36Sopenharmony_ci	bool do_wakeup = device_may_wakeup(dev);
20762306a36Sopenharmony_ci
20862306a36Sopenharmony_ci	ret = ehci_suspend(hcd, do_wakeup);
20962306a36Sopenharmony_ci	if (ret)
21062306a36Sopenharmony_ci		return ret;
21162306a36Sopenharmony_ci	clk_disable_unprepare(priv->clk);
21262306a36Sopenharmony_ci	return 0;
21362306a36Sopenharmony_ci}
21462306a36Sopenharmony_ci
21562306a36Sopenharmony_cistatic int __maybe_unused ehci_brcm_resume(struct device *dev)
21662306a36Sopenharmony_ci{
21762306a36Sopenharmony_ci	struct usb_hcd *hcd = dev_get_drvdata(dev);
21862306a36Sopenharmony_ci	struct ehci_hcd *ehci = hcd_to_ehci(hcd);
21962306a36Sopenharmony_ci	struct brcm_priv *priv = hcd_to_ehci_priv(hcd);
22062306a36Sopenharmony_ci	int err;
22162306a36Sopenharmony_ci
22262306a36Sopenharmony_ci	err = clk_prepare_enable(priv->clk);
22362306a36Sopenharmony_ci	if (err)
22462306a36Sopenharmony_ci		return err;
22562306a36Sopenharmony_ci	/*
22662306a36Sopenharmony_ci	 * SWLINUX-1705: Avoid OUT packet underflows during high memory
22762306a36Sopenharmony_ci	 *   bus usage
22862306a36Sopenharmony_ci	 */
22962306a36Sopenharmony_ci	ehci_writel(ehci, 0x00800040, &ehci->regs->brcm_insnreg[1]);
23062306a36Sopenharmony_ci	ehci_writel(ehci, 0x00000001, &ehci->regs->brcm_insnreg[3]);
23162306a36Sopenharmony_ci
23262306a36Sopenharmony_ci	ehci_resume(hcd, false);
23362306a36Sopenharmony_ci
23462306a36Sopenharmony_ci	pm_runtime_disable(dev);
23562306a36Sopenharmony_ci	pm_runtime_set_active(dev);
23662306a36Sopenharmony_ci	pm_runtime_enable(dev);
23762306a36Sopenharmony_ci
23862306a36Sopenharmony_ci	return 0;
23962306a36Sopenharmony_ci}
24062306a36Sopenharmony_ci
24162306a36Sopenharmony_cistatic SIMPLE_DEV_PM_OPS(ehci_brcm_pm_ops, ehci_brcm_suspend,
24262306a36Sopenharmony_ci		ehci_brcm_resume);
24362306a36Sopenharmony_ci
24462306a36Sopenharmony_cistatic const struct of_device_id brcm_ehci_of_match[] = {
24562306a36Sopenharmony_ci	{ .compatible = "brcm,ehci-brcm-v2", },
24662306a36Sopenharmony_ci	{ .compatible = "brcm,bcm7445-ehci", },
24762306a36Sopenharmony_ci	{}
24862306a36Sopenharmony_ci};
24962306a36Sopenharmony_ci
25062306a36Sopenharmony_cistatic struct platform_driver ehci_brcm_driver = {
25162306a36Sopenharmony_ci	.probe		= ehci_brcm_probe,
25262306a36Sopenharmony_ci	.remove_new	= ehci_brcm_remove,
25362306a36Sopenharmony_ci	.shutdown	= usb_hcd_platform_shutdown,
25462306a36Sopenharmony_ci	.driver		= {
25562306a36Sopenharmony_ci		.name	= "ehci-brcm",
25662306a36Sopenharmony_ci		.pm	= &ehci_brcm_pm_ops,
25762306a36Sopenharmony_ci		.of_match_table = brcm_ehci_of_match,
25862306a36Sopenharmony_ci	}
25962306a36Sopenharmony_ci};
26062306a36Sopenharmony_ci
26162306a36Sopenharmony_cistatic int __init ehci_brcm_init(void)
26262306a36Sopenharmony_ci{
26362306a36Sopenharmony_ci	if (usb_disabled())
26462306a36Sopenharmony_ci		return -ENODEV;
26562306a36Sopenharmony_ci
26662306a36Sopenharmony_ci	ehci_init_driver(&ehci_brcm_hc_driver, &brcm_overrides);
26762306a36Sopenharmony_ci	return platform_driver_register(&ehci_brcm_driver);
26862306a36Sopenharmony_ci}
26962306a36Sopenharmony_cimodule_init(ehci_brcm_init);
27062306a36Sopenharmony_ci
27162306a36Sopenharmony_cistatic void __exit ehci_brcm_exit(void)
27262306a36Sopenharmony_ci{
27362306a36Sopenharmony_ci	platform_driver_unregister(&ehci_brcm_driver);
27462306a36Sopenharmony_ci}
27562306a36Sopenharmony_cimodule_exit(ehci_brcm_exit);
27662306a36Sopenharmony_ci
27762306a36Sopenharmony_ciMODULE_ALIAS("platform:ehci-brcm");
27862306a36Sopenharmony_ciMODULE_DESCRIPTION("EHCI Broadcom STB driver");
27962306a36Sopenharmony_ciMODULE_AUTHOR("Al Cooper");
28062306a36Sopenharmony_ciMODULE_LICENSE("GPL");
281