diff options
author | Peter Griffin <peter.griffin@linaro.org> | 2014-09-08 08:04:44 -0400 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2014-09-24 00:35:50 -0400 |
commit | e47c5a0906f9a5792988786c8a186e9f5880f622 (patch) | |
tree | 623fe221c7fe17e1fe2f55a4a1a07cec073ab403 | |
parent | ae7c798d6b482682d1fa05b42ad02e3bdade07aa (diff) |
usb: host: ehci-st: Add EHCI support for ST STB devices
This patch adds the glue code required to ensure the on-chip EHCI
controller works on STi consumer electronics SoC's from STMicroelectronics.
It mainly manages the setting and enabling of the relevant clocks and manages
the reset / power signals to the IP block.
Signed-off-by: Peter Griffin <peter.griffin@linaro.org>
Reviewed-by: Arnd Bergmann <arnd@arndb.de>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
-rw-r--r-- | drivers/usb/host/ehci-st.c | 375 |
1 files changed, 375 insertions, 0 deletions
diff --git a/drivers/usb/host/ehci-st.c b/drivers/usb/host/ehci-st.c new file mode 100644 index 000000000000..7e4bd39cf757 --- /dev/null +++ b/drivers/usb/host/ehci-st.c | |||
@@ -0,0 +1,375 @@ | |||
1 | /* | ||
2 | * ST EHCI driver | ||
3 | * | ||
4 | * Copyright (C) 2014 STMicroelectronics – All Rights Reserved | ||
5 | * | ||
6 | * Author: Peter Griffin <peter.griffin@linaro.org> | ||
7 | * | ||
8 | * Derived from ehci-platform.c | ||
9 | * | ||
10 | * This program is free software; you can redistribute it and/or modify | ||
11 | * it under the terms of the GNU General Public License version 2 as | ||
12 | * published by the Free Software Foundation. | ||
13 | */ | ||
14 | |||
15 | #include <linux/clk.h> | ||
16 | #include <linux/dma-mapping.h> | ||
17 | #include <linux/err.h> | ||
18 | #include <linux/kernel.h> | ||
19 | #include <linux/hrtimer.h> | ||
20 | #include <linux/io.h> | ||
21 | #include <linux/module.h> | ||
22 | #include <linux/of.h> | ||
23 | #include <linux/phy/phy.h> | ||
24 | #include <linux/platform_device.h> | ||
25 | #include <linux/reset.h> | ||
26 | #include <linux/usb.h> | ||
27 | #include <linux/usb/hcd.h> | ||
28 | #include <linux/usb/ehci_pdriver.h> | ||
29 | |||
30 | #include "ehci.h" | ||
31 | |||
32 | #define USB_MAX_CLKS 3 | ||
33 | |||
34 | struct st_ehci_platform_priv { | ||
35 | struct clk *clks[USB_MAX_CLKS]; | ||
36 | struct clk *clk48; | ||
37 | struct reset_control *rst; | ||
38 | struct reset_control *pwr; | ||
39 | struct phy *phy; | ||
40 | }; | ||
41 | |||
42 | #define DRIVER_DESC "EHCI STMicroelectronics driver" | ||
43 | |||
44 | #define hcd_to_ehci_priv(h) \ | ||
45 | ((struct st_ehci_platform_priv *)hcd_to_ehci(h)->priv) | ||
46 | |||
47 | static const char hcd_name[] = "ehci-st"; | ||
48 | |||
49 | #define EHCI_CAPS_SIZE 0x10 | ||
50 | #define AHB2STBUS_INSREG01 (EHCI_CAPS_SIZE + 0x84) | ||
51 | |||
52 | static int st_ehci_platform_reset(struct usb_hcd *hcd) | ||
53 | { | ||
54 | struct platform_device *pdev = to_platform_device(hcd->self.controller); | ||
55 | struct usb_ehci_pdata *pdata = dev_get_platdata(&pdev->dev); | ||
56 | struct ehci_hcd *ehci = hcd_to_ehci(hcd); | ||
57 | int retval; | ||
58 | u32 threshold; | ||
59 | |||
60 | /* Set EHCI packet buffer IN/OUT threshold to 128 bytes */ | ||
61 | threshold = 128 | (128 << 16); | ||
62 | writel(threshold, hcd->regs + AHB2STBUS_INSREG01); | ||
63 | |||
64 | ehci->caps = hcd->regs + pdata->caps_offset; | ||
65 | retval = ehci_setup(hcd); | ||
66 | if (retval) | ||
67 | return retval; | ||
68 | |||
69 | return 0; | ||
70 | } | ||
71 | |||
72 | static int st_ehci_platform_power_on(struct platform_device *dev) | ||
73 | { | ||
74 | struct usb_hcd *hcd = platform_get_drvdata(dev); | ||
75 | struct st_ehci_platform_priv *priv = hcd_to_ehci_priv(hcd); | ||
76 | int clk, ret; | ||
77 | |||
78 | ret = reset_control_deassert(priv->pwr); | ||
79 | if (ret) | ||
80 | return ret; | ||
81 | |||
82 | ret = reset_control_deassert(priv->rst); | ||
83 | if (ret) | ||
84 | goto err_assert_power; | ||
85 | |||
86 | /* some SoCs don't have a dedicated 48Mhz clock, but those that do | ||
87 | need the rate to be explicitly set */ | ||
88 | if (priv->clk48) { | ||
89 | ret = clk_set_rate(priv->clk48, 48000000); | ||
90 | if (ret) | ||
91 | goto err_assert_reset; | ||
92 | } | ||
93 | |||
94 | for (clk = 0; clk < USB_MAX_CLKS && priv->clks[clk]; clk++) { | ||
95 | ret = clk_prepare_enable(priv->clks[clk]); | ||
96 | if (ret) | ||
97 | goto err_disable_clks; | ||
98 | } | ||
99 | |||
100 | ret = phy_init(priv->phy); | ||
101 | if (ret) | ||
102 | goto err_disable_clks; | ||
103 | |||
104 | ret = phy_power_on(priv->phy); | ||
105 | if (ret) | ||
106 | goto err_exit_phy; | ||
107 | |||
108 | return 0; | ||
109 | |||
110 | err_exit_phy: | ||
111 | phy_exit(priv->phy); | ||
112 | err_disable_clks: | ||
113 | while (--clk >= 0) | ||
114 | clk_disable_unprepare(priv->clks[clk]); | ||
115 | err_assert_reset: | ||
116 | reset_control_assert(priv->rst); | ||
117 | err_assert_power: | ||
118 | reset_control_assert(priv->pwr); | ||
119 | |||
120 | return ret; | ||
121 | } | ||
122 | |||
123 | static void st_ehci_platform_power_off(struct platform_device *dev) | ||
124 | { | ||
125 | struct usb_hcd *hcd = platform_get_drvdata(dev); | ||
126 | struct st_ehci_platform_priv *priv = hcd_to_ehci_priv(hcd); | ||
127 | int clk; | ||
128 | |||
129 | reset_control_assert(priv->pwr); | ||
130 | |||
131 | reset_control_assert(priv->rst); | ||
132 | |||
133 | phy_power_off(priv->phy); | ||
134 | |||
135 | phy_exit(priv->phy); | ||
136 | |||
137 | for (clk = USB_MAX_CLKS - 1; clk >= 0; clk--) | ||
138 | if (priv->clks[clk]) | ||
139 | clk_disable_unprepare(priv->clks[clk]); | ||
140 | |||
141 | } | ||
142 | |||
143 | static struct hc_driver __read_mostly ehci_platform_hc_driver; | ||
144 | |||
145 | static const struct ehci_driver_overrides platform_overrides __initconst = { | ||
146 | .reset = st_ehci_platform_reset, | ||
147 | .extra_priv_size = sizeof(struct st_ehci_platform_priv), | ||
148 | }; | ||
149 | |||
150 | static struct usb_ehci_pdata ehci_platform_defaults = { | ||
151 | .power_on = st_ehci_platform_power_on, | ||
152 | .power_suspend = st_ehci_platform_power_off, | ||
153 | .power_off = st_ehci_platform_power_off, | ||
154 | }; | ||
155 | |||
156 | static int st_ehci_platform_probe(struct platform_device *dev) | ||
157 | { | ||
158 | struct usb_hcd *hcd; | ||
159 | struct resource *res_mem; | ||
160 | struct usb_ehci_pdata *pdata = &ehci_platform_defaults; | ||
161 | struct st_ehci_platform_priv *priv; | ||
162 | struct ehci_hcd *ehci; | ||
163 | int err, irq, clk = 0; | ||
164 | |||
165 | if (usb_disabled()) | ||
166 | return -ENODEV; | ||
167 | |||
168 | irq = platform_get_irq(dev, 0); | ||
169 | if (irq < 0) { | ||
170 | dev_err(&dev->dev, "no irq provided"); | ||
171 | return irq; | ||
172 | } | ||
173 | res_mem = platform_get_resource(dev, IORESOURCE_MEM, 0); | ||
174 | if (!res_mem) { | ||
175 | dev_err(&dev->dev, "no memory resource provided"); | ||
176 | return -ENXIO; | ||
177 | } | ||
178 | |||
179 | hcd = usb_create_hcd(&ehci_platform_hc_driver, &dev->dev, | ||
180 | dev_name(&dev->dev)); | ||
181 | if (!hcd) | ||
182 | return -ENOMEM; | ||
183 | |||
184 | platform_set_drvdata(dev, hcd); | ||
185 | dev->dev.platform_data = pdata; | ||
186 | priv = hcd_to_ehci_priv(hcd); | ||
187 | ehci = hcd_to_ehci(hcd); | ||
188 | |||
189 | priv->phy = devm_phy_get(&dev->dev, "usb"); | ||
190 | if (IS_ERR(priv->phy)) { | ||
191 | err = PTR_ERR(priv->phy); | ||
192 | goto err_put_hcd; | ||
193 | } | ||
194 | |||
195 | for (clk = 0; clk < USB_MAX_CLKS; clk++) { | ||
196 | priv->clks[clk] = of_clk_get(dev->dev.of_node, clk); | ||
197 | if (IS_ERR(priv->clks[clk])) { | ||
198 | err = PTR_ERR(priv->clks[clk]); | ||
199 | if (err == -EPROBE_DEFER) | ||
200 | goto err_put_clks; | ||
201 | priv->clks[clk] = NULL; | ||
202 | break; | ||
203 | } | ||
204 | } | ||
205 | |||
206 | /* some SoCs don't have a dedicated 48Mhz clock, but those that | ||
207 | do need the rate to be explicitly set */ | ||
208 | priv->clk48 = devm_clk_get(&dev->dev, "clk48"); | ||
209 | if (IS_ERR(priv->clk48)) { | ||
210 | dev_info(&dev->dev, "48MHz clk not found\n"); | ||
211 | priv->clk48 = NULL; | ||
212 | } | ||
213 | |||
214 | priv->pwr = devm_reset_control_get_optional(&dev->dev, "power"); | ||
215 | if (IS_ERR(priv->pwr)) { | ||
216 | err = PTR_ERR(priv->pwr); | ||
217 | if (err == -EPROBE_DEFER) | ||
218 | goto err_put_clks; | ||
219 | priv->pwr = NULL; | ||
220 | } | ||
221 | |||
222 | priv->rst = devm_reset_control_get_optional(&dev->dev, "softreset"); | ||
223 | if (IS_ERR(priv->rst)) { | ||
224 | err = PTR_ERR(priv->rst); | ||
225 | if (err == -EPROBE_DEFER) | ||
226 | goto err_put_clks; | ||
227 | priv->rst = NULL; | ||
228 | } | ||
229 | |||
230 | if (pdata->power_on) { | ||
231 | err = pdata->power_on(dev); | ||
232 | if (err < 0) | ||
233 | goto err_put_clks; | ||
234 | } | ||
235 | |||
236 | hcd->rsrc_start = res_mem->start; | ||
237 | hcd->rsrc_len = resource_size(res_mem); | ||
238 | |||
239 | hcd->regs = devm_ioremap_resource(&dev->dev, res_mem); | ||
240 | if (IS_ERR(hcd->regs)) { | ||
241 | err = PTR_ERR(hcd->regs); | ||
242 | goto err_put_clks; | ||
243 | } | ||
244 | |||
245 | err = usb_add_hcd(hcd, irq, IRQF_SHARED); | ||
246 | if (err) | ||
247 | goto err_put_clks; | ||
248 | |||
249 | device_wakeup_enable(hcd->self.controller); | ||
250 | platform_set_drvdata(dev, hcd); | ||
251 | |||
252 | return err; | ||
253 | |||
254 | err_put_clks: | ||
255 | while (--clk >= 0) | ||
256 | clk_put(priv->clks[clk]); | ||
257 | err_put_hcd: | ||
258 | if (pdata == &ehci_platform_defaults) | ||
259 | dev->dev.platform_data = NULL; | ||
260 | |||
261 | usb_put_hcd(hcd); | ||
262 | |||
263 | return err; | ||
264 | } | ||
265 | |||
266 | static int st_ehci_platform_remove(struct platform_device *dev) | ||
267 | { | ||
268 | struct usb_hcd *hcd = platform_get_drvdata(dev); | ||
269 | struct usb_ehci_pdata *pdata = dev_get_platdata(&dev->dev); | ||
270 | struct st_ehci_platform_priv *priv = hcd_to_ehci_priv(hcd); | ||
271 | int clk; | ||
272 | |||
273 | usb_remove_hcd(hcd); | ||
274 | |||
275 | if (pdata->power_off) | ||
276 | pdata->power_off(dev); | ||
277 | |||
278 | for (clk = 0; clk < USB_MAX_CLKS && priv->clks[clk]; clk++) | ||
279 | clk_put(priv->clks[clk]); | ||
280 | |||
281 | usb_put_hcd(hcd); | ||
282 | |||
283 | if (pdata == &ehci_platform_defaults) | ||
284 | dev->dev.platform_data = NULL; | ||
285 | |||
286 | return 0; | ||
287 | } | ||
288 | |||
289 | #ifdef CONFIG_PM_SLEEP | ||
290 | |||
291 | static int st_ehci_suspend(struct device *dev) | ||
292 | { | ||
293 | struct usb_hcd *hcd = dev_get_drvdata(dev); | ||
294 | struct usb_ehci_pdata *pdata = dev_get_platdata(dev); | ||
295 | struct platform_device *pdev = | ||
296 | container_of(dev, struct platform_device, dev); | ||
297 | bool do_wakeup = device_may_wakeup(dev); | ||
298 | int ret; | ||
299 | |||
300 | ret = ehci_suspend(hcd, do_wakeup); | ||
301 | if (ret) | ||
302 | return ret; | ||
303 | |||
304 | if (pdata->power_suspend) | ||
305 | pdata->power_suspend(pdev); | ||
306 | |||
307 | pinctrl_pm_select_sleep_state(dev); | ||
308 | |||
309 | return ret; | ||
310 | } | ||
311 | |||
312 | static int st_ehci_resume(struct device *dev) | ||
313 | { | ||
314 | struct usb_hcd *hcd = dev_get_drvdata(dev); | ||
315 | struct usb_ehci_pdata *pdata = dev_get_platdata(dev); | ||
316 | struct platform_device *pdev = | ||
317 | container_of(dev, struct platform_device, dev); | ||
318 | int err; | ||
319 | |||
320 | pinctrl_pm_select_default_state(dev); | ||
321 | |||
322 | if (pdata->power_on) { | ||
323 | err = pdata->power_on(pdev); | ||
324 | if (err < 0) | ||
325 | return err; | ||
326 | } | ||
327 | |||
328 | ehci_resume(hcd, false); | ||
329 | return 0; | ||
330 | } | ||
331 | |||
332 | static SIMPLE_DEV_PM_OPS(st_ehci_pm_ops, st_ehci_suspend, st_ehci_resume); | ||
333 | |||
334 | #endif /* CONFIG_PM_SLEEP */ | ||
335 | |||
336 | static const struct of_device_id st_ehci_ids[] = { | ||
337 | { .compatible = "st,st-ehci-300x", }, | ||
338 | { /* sentinel */ } | ||
339 | }; | ||
340 | MODULE_DEVICE_TABLE(of, st_ehci_ids); | ||
341 | |||
342 | static struct platform_driver ehci_platform_driver = { | ||
343 | .probe = st_ehci_platform_probe, | ||
344 | .remove = st_ehci_platform_remove, | ||
345 | .shutdown = usb_hcd_platform_shutdown, | ||
346 | .driver = { | ||
347 | .name = "st-ehci", | ||
348 | #ifdef CONFIG_PM_SLEEP | ||
349 | .pm = &st_ehci_pm_ops, | ||
350 | #endif | ||
351 | .of_match_table = st_ehci_ids, | ||
352 | } | ||
353 | }; | ||
354 | |||
355 | static int __init ehci_platform_init(void) | ||
356 | { | ||
357 | if (usb_disabled()) | ||
358 | return -ENODEV; | ||
359 | |||
360 | pr_info("%s: " DRIVER_DESC "\n", hcd_name); | ||
361 | |||
362 | ehci_init_driver(&ehci_platform_hc_driver, &platform_overrides); | ||
363 | return platform_driver_register(&ehci_platform_driver); | ||
364 | } | ||
365 | module_init(ehci_platform_init); | ||
366 | |||
367 | static void __exit ehci_platform_cleanup(void) | ||
368 | { | ||
369 | platform_driver_unregister(&ehci_platform_driver); | ||
370 | } | ||
371 | module_exit(ehci_platform_cleanup); | ||
372 | |||
373 | MODULE_DESCRIPTION(DRIVER_DESC); | ||
374 | MODULE_AUTHOR("Peter Griffin <peter.griffin@linaro.org>"); | ||
375 | MODULE_LICENSE("GPL"); | ||