1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* Copyright (c) 2020, Broadcom */ |
3 | |
4 | #include <linux/clk.h> |
5 | #include <linux/dma-mapping.h> |
6 | #include <linux/err.h> |
7 | #include <linux/kernel.h> |
8 | #include <linux/io.h> |
9 | #include <linux/module.h> |
10 | #include <linux/platform_device.h> |
11 | #include <linux/usb.h> |
12 | #include <linux/usb/hcd.h> |
13 | #include <linux/iopoll.h> |
14 | |
15 | #include "ehci.h" |
16 | |
17 | #define hcd_to_ehci_priv(h) ((struct brcm_priv *)hcd_to_ehci(h)->priv) |
18 | |
19 | struct brcm_priv { |
20 | struct clk *clk; |
21 | }; |
22 | |
23 | /* |
24 | * ehci_brcm_wait_for_sof |
25 | * Wait for start of next microframe, then wait extra delay microseconds |
26 | */ |
27 | static inline void ehci_brcm_wait_for_sof(struct ehci_hcd *ehci, u32 delay) |
28 | { |
29 | u32 frame_idx = ehci_readl(ehci, regs: &ehci->regs->frame_index); |
30 | u32 val; |
31 | int res; |
32 | |
33 | /* Wait for next microframe (every 125 usecs) */ |
34 | res = readl_relaxed_poll_timeout(&ehci->regs->frame_index, val, |
35 | val != frame_idx, 1, 130); |
36 | if (res) |
37 | ehci_err(ehci, "Error waiting for SOF\n" ); |
38 | udelay(delay); |
39 | } |
40 | |
41 | /* |
42 | * ehci_brcm_hub_control |
43 | * The EHCI controller has a bug where it can violate the SOF |
44 | * interval between the first two SOF's transmitted after resume |
45 | * if the resume occurs near the end of the microframe. This causees |
46 | * the controller to detect babble on the suspended port and |
47 | * will eventually cause the controller to reset the port. |
48 | * The fix is to Intercept the echi-hcd request to complete RESUME and |
49 | * align it to the start of the next microframe. |
50 | * See SWLINUX-1909 for more details |
51 | */ |
52 | static int ehci_brcm_hub_control( |
53 | struct usb_hcd *hcd, |
54 | u16 typeReq, |
55 | u16 wValue, |
56 | u16 wIndex, |
57 | char *buf, |
58 | u16 wLength) |
59 | { |
60 | struct ehci_hcd *ehci = hcd_to_ehci(hcd); |
61 | int ports = HCS_N_PORTS(ehci->hcs_params); |
62 | u32 __iomem *status_reg; |
63 | unsigned long flags; |
64 | int retval, irq_disabled = 0; |
65 | u32 temp; |
66 | |
67 | temp = (wIndex & 0xff) - 1; |
68 | if (temp >= HCS_N_PORTS_MAX) /* Avoid index-out-of-bounds warning */ |
69 | temp = 0; |
70 | status_reg = &ehci->regs->port_status[temp]; |
71 | |
72 | /* |
73 | * RESUME is cleared when GetPortStatus() is called 20ms after start |
74 | * of RESUME |
75 | */ |
76 | if ((typeReq == GetPortStatus) && |
77 | (wIndex && wIndex <= ports) && |
78 | ehci->reset_done[wIndex-1] && |
79 | time_after_eq(jiffies, ehci->reset_done[wIndex-1]) && |
80 | (ehci_readl(ehci, regs: status_reg) & PORT_RESUME)) { |
81 | |
82 | /* |
83 | * to make sure we are not interrupted until RESUME bit |
84 | * is cleared, disable interrupts on current CPU |
85 | */ |
86 | ehci_dbg(ehci, "SOF alignment workaround\n" ); |
87 | irq_disabled = 1; |
88 | local_irq_save(flags); |
89 | ehci_brcm_wait_for_sof(ehci, delay: 5); |
90 | } |
91 | retval = ehci_hub_control(hcd, typeReq, wValue, wIndex, buf, wLength); |
92 | if (irq_disabled) |
93 | local_irq_restore(flags); |
94 | return retval; |
95 | } |
96 | |
97 | static int ehci_brcm_reset(struct usb_hcd *hcd) |
98 | { |
99 | struct ehci_hcd *ehci = hcd_to_ehci(hcd); |
100 | int len; |
101 | |
102 | ehci->big_endian_mmio = 1; |
103 | |
104 | ehci->caps = (void __iomem *)hcd->regs; |
105 | len = HC_LENGTH(ehci, ehci_readl(ehci, &ehci->caps->hc_capbase)); |
106 | ehci->regs = (void __iomem *)(hcd->regs + len); |
107 | |
108 | /* This fixes the lockup during reboot due to prior interrupts */ |
109 | ehci_writel(ehci, CMD_RESET, regs: &ehci->regs->command); |
110 | mdelay(10); |
111 | |
112 | /* |
113 | * SWLINUX-1705: Avoid OUT packet underflows during high memory |
114 | * bus usage |
115 | */ |
116 | ehci_writel(ehci, val: 0x00800040, regs: &ehci->regs->brcm_insnreg[1]); |
117 | ehci_writel(ehci, val: 0x00000001, regs: &ehci->regs->brcm_insnreg[3]); |
118 | |
119 | return ehci_setup(hcd); |
120 | } |
121 | |
122 | static struct hc_driver __read_mostly ehci_brcm_hc_driver; |
123 | |
124 | static const struct ehci_driver_overrides brcm_overrides __initconst = { |
125 | .reset = ehci_brcm_reset, |
126 | .extra_priv_size = sizeof(struct brcm_priv), |
127 | }; |
128 | |
129 | static int ehci_brcm_probe(struct platform_device *pdev) |
130 | { |
131 | struct device *dev = &pdev->dev; |
132 | struct resource *res_mem; |
133 | struct brcm_priv *priv; |
134 | struct usb_hcd *hcd; |
135 | int irq; |
136 | int err; |
137 | |
138 | err = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32)); |
139 | if (err) |
140 | return err; |
141 | |
142 | irq = platform_get_irq(pdev, 0); |
143 | if (irq < 0) |
144 | return irq; |
145 | |
146 | /* Hook the hub control routine to work around a bug */ |
147 | ehci_brcm_hc_driver.hub_control = ehci_brcm_hub_control; |
148 | |
149 | /* initialize hcd */ |
150 | hcd = usb_create_hcd(driver: &ehci_brcm_hc_driver, dev, bus_name: dev_name(dev)); |
151 | if (!hcd) |
152 | return -ENOMEM; |
153 | |
154 | platform_set_drvdata(pdev, data: hcd); |
155 | priv = hcd_to_ehci_priv(hcd); |
156 | |
157 | priv->clk = devm_clk_get_optional(dev, NULL); |
158 | if (IS_ERR(ptr: priv->clk)) { |
159 | err = PTR_ERR(ptr: priv->clk); |
160 | goto err_hcd; |
161 | } |
162 | |
163 | err = clk_prepare_enable(clk: priv->clk); |
164 | if (err) |
165 | goto err_hcd; |
166 | |
167 | hcd->regs = devm_platform_get_and_ioremap_resource(pdev, index: 0, res: &res_mem); |
168 | if (IS_ERR(ptr: hcd->regs)) { |
169 | err = PTR_ERR(ptr: hcd->regs); |
170 | goto err_clk; |
171 | } |
172 | hcd->rsrc_start = res_mem->start; |
173 | hcd->rsrc_len = resource_size(res: res_mem); |
174 | err = usb_add_hcd(hcd, irqnum: irq, IRQF_SHARED); |
175 | if (err) |
176 | goto err_clk; |
177 | |
178 | device_wakeup_enable(dev: hcd->self.controller); |
179 | device_enable_async_suspend(dev: hcd->self.controller); |
180 | |
181 | return 0; |
182 | |
183 | err_clk: |
184 | clk_disable_unprepare(clk: priv->clk); |
185 | err_hcd: |
186 | usb_put_hcd(hcd); |
187 | |
188 | return err; |
189 | } |
190 | |
191 | static void ehci_brcm_remove(struct platform_device *dev) |
192 | { |
193 | struct usb_hcd *hcd = platform_get_drvdata(pdev: dev); |
194 | struct brcm_priv *priv = hcd_to_ehci_priv(hcd); |
195 | |
196 | usb_remove_hcd(hcd); |
197 | clk_disable_unprepare(clk: priv->clk); |
198 | usb_put_hcd(hcd); |
199 | } |
200 | |
201 | static int __maybe_unused ehci_brcm_suspend(struct device *dev) |
202 | { |
203 | int ret; |
204 | struct usb_hcd *hcd = dev_get_drvdata(dev); |
205 | struct brcm_priv *priv = hcd_to_ehci_priv(hcd); |
206 | bool do_wakeup = device_may_wakeup(dev); |
207 | |
208 | ret = ehci_suspend(hcd, do_wakeup); |
209 | if (ret) |
210 | return ret; |
211 | clk_disable_unprepare(clk: priv->clk); |
212 | return 0; |
213 | } |
214 | |
215 | static int __maybe_unused ehci_brcm_resume(struct device *dev) |
216 | { |
217 | struct usb_hcd *hcd = dev_get_drvdata(dev); |
218 | struct ehci_hcd *ehci = hcd_to_ehci(hcd); |
219 | struct brcm_priv *priv = hcd_to_ehci_priv(hcd); |
220 | int err; |
221 | |
222 | err = clk_prepare_enable(clk: priv->clk); |
223 | if (err) |
224 | return err; |
225 | /* |
226 | * SWLINUX-1705: Avoid OUT packet underflows during high memory |
227 | * bus usage |
228 | */ |
229 | ehci_writel(ehci, val: 0x00800040, regs: &ehci->regs->brcm_insnreg[1]); |
230 | ehci_writel(ehci, val: 0x00000001, regs: &ehci->regs->brcm_insnreg[3]); |
231 | |
232 | ehci_resume(hcd, force_reset: false); |
233 | |
234 | pm_runtime_disable(dev); |
235 | pm_runtime_set_active(dev); |
236 | pm_runtime_enable(dev); |
237 | |
238 | return 0; |
239 | } |
240 | |
241 | static SIMPLE_DEV_PM_OPS(ehci_brcm_pm_ops, ehci_brcm_suspend, |
242 | ehci_brcm_resume); |
243 | |
244 | static const struct of_device_id brcm_ehci_of_match[] = { |
245 | { .compatible = "brcm,ehci-brcm-v2" , }, |
246 | { .compatible = "brcm,bcm7445-ehci" , }, |
247 | {} |
248 | }; |
249 | |
250 | static struct platform_driver ehci_brcm_driver = { |
251 | .probe = ehci_brcm_probe, |
252 | .remove_new = ehci_brcm_remove, |
253 | .shutdown = usb_hcd_platform_shutdown, |
254 | .driver = { |
255 | .name = "ehci-brcm" , |
256 | .pm = &ehci_brcm_pm_ops, |
257 | .of_match_table = brcm_ehci_of_match, |
258 | } |
259 | }; |
260 | |
261 | static int __init ehci_brcm_init(void) |
262 | { |
263 | if (usb_disabled()) |
264 | return -ENODEV; |
265 | |
266 | ehci_init_driver(drv: &ehci_brcm_hc_driver, over: &brcm_overrides); |
267 | return platform_driver_register(&ehci_brcm_driver); |
268 | } |
269 | module_init(ehci_brcm_init); |
270 | |
271 | static void __exit ehci_brcm_exit(void) |
272 | { |
273 | platform_driver_unregister(&ehci_brcm_driver); |
274 | } |
275 | module_exit(ehci_brcm_exit); |
276 | |
277 | MODULE_ALIAS("platform:ehci-brcm" ); |
278 | MODULE_DESCRIPTION("EHCI Broadcom STB driver" ); |
279 | MODULE_AUTHOR("Al Cooper" ); |
280 | MODULE_LICENSE("GPL" ); |
281 | |