Commit | Line | Data |
---|---|---|
62194244 JH |
1 | /* |
2 | * SAMSUNG EXYNOS USB HOST OHCI Controller | |
3 | * | |
4 | * Copyright (C) 2011 Samsung Electronics Co.Ltd | |
5 | * Author: Jingoo Han <jg1.han@samsung.com> | |
6 | * | |
7 | * This program is free software; you can redistribute it and/or modify it | |
8 | * under the terms of the GNU General Public License as published by the | |
9 | * Free Software Foundation; either version 2 of the License, or (at your | |
10 | * option) any later version. | |
11 | * | |
12 | */ | |
13 | ||
14 | #include <linux/clk.h> | |
50a97e05 MG |
15 | #include <linux/dma-mapping.h> |
16 | #include <linux/io.h> | |
17 | #include <linux/kernel.h> | |
18 | #include <linux/module.h> | |
d5138930 | 19 | #include <linux/of.h> |
62194244 | 20 | #include <linux/platform_device.h> |
7d28e54b | 21 | #include <linux/phy/phy.h> |
50a97e05 MG |
22 | #include <linux/usb.h> |
23 | #include <linux/usb/hcd.h> | |
50a97e05 MG |
24 | |
25 | #include "ohci.h" | |
26 | ||
27 | #define DRIVER_DESC "OHCI EXYNOS driver" | |
28 | ||
29 | static const char hcd_name[] = "ohci-exynos"; | |
30 | static struct hc_driver __read_mostly exynos_ohci_hc_driver; | |
31 | ||
32 | #define to_exynos_ohci(hcd) (struct exynos_ohci_hcd *)(hcd_to_ohci(hcd)->priv) | |
62194244 | 33 | |
7d28e54b VG |
34 | #define PHY_NUMBER 3 |
35 | ||
62194244 | 36 | struct exynos_ohci_hcd { |
62194244 | 37 | struct clk *clk; |
2db94162 | 38 | struct phy *phy[PHY_NUMBER]; |
62194244 JH |
39 | }; |
40 | ||
7d28e54b VG |
41 | static int exynos_ohci_get_phy(struct device *dev, |
42 | struct exynos_ohci_hcd *exynos_ohci) | |
43 | { | |
44 | struct device_node *child; | |
45 | struct phy *phy; | |
46 | int phy_number; | |
2db94162 | 47 | int ret; |
7d28e54b | 48 | |
2db94162 | 49 | /* Get PHYs for the controller */ |
7d28e54b VG |
50 | for_each_available_child_of_node(dev->of_node, child) { |
51 | ret = of_property_read_u32(child, "reg", &phy_number); | |
52 | if (ret) { | |
53 | dev_err(dev, "Failed to parse device tree\n"); | |
54 | of_node_put(child); | |
55 | return ret; | |
56 | } | |
57 | ||
58 | if (phy_number >= PHY_NUMBER) { | |
59 | dev_err(dev, "Invalid number of PHYs\n"); | |
60 | of_node_put(child); | |
61 | return -EINVAL; | |
62 | } | |
63 | ||
473e92e6 | 64 | phy = devm_of_phy_get(dev, child, NULL); |
2db94162 | 65 | exynos_ohci->phy[phy_number] = phy; |
2db94162 VG |
66 | if (IS_ERR(phy)) { |
67 | ret = PTR_ERR(phy); | |
68 | if (ret == -EPROBE_DEFER) { | |
69 | return ret; | |
70 | } else if (ret != -ENOSYS && ret != -ENODEV) { | |
71 | dev_err(dev, | |
72 | "Error retrieving usb2 phy: %d\n", ret); | |
73 | return ret; | |
74 | } | |
2f7f41c7 | 75 | } |
7d28e54b VG |
76 | } |
77 | ||
2db94162 | 78 | return 0; |
7d28e54b VG |
79 | } |
80 | ||
81 | static int exynos_ohci_phy_enable(struct device *dev) | |
ed993bf1 | 82 | { |
54969ed6 | 83 | struct usb_hcd *hcd = dev_get_drvdata(dev); |
50a97e05 | 84 | struct exynos_ohci_hcd *exynos_ohci = to_exynos_ohci(hcd); |
7d28e54b VG |
85 | int i; |
86 | int ret = 0; | |
87 | ||
7d28e54b | 88 | for (i = 0; ret == 0 && i < PHY_NUMBER; i++) |
2db94162 VG |
89 | if (!IS_ERR(exynos_ohci->phy[i])) |
90 | ret = phy_power_on(exynos_ohci->phy[i]); | |
7d28e54b VG |
91 | if (ret) |
92 | for (i--; i >= 0; i--) | |
2db94162 VG |
93 | if (!IS_ERR(exynos_ohci->phy[i])) |
94 | phy_power_off(exynos_ohci->phy[i]); | |
7d28e54b VG |
95 | |
96 | return ret; | |
ed993bf1 VG |
97 | } |
98 | ||
54969ed6 | 99 | static void exynos_ohci_phy_disable(struct device *dev) |
ed993bf1 | 100 | { |
54969ed6 | 101 | struct usb_hcd *hcd = dev_get_drvdata(dev); |
50a97e05 | 102 | struct exynos_ohci_hcd *exynos_ohci = to_exynos_ohci(hcd); |
7d28e54b | 103 | int i; |
ed993bf1 | 104 | |
7d28e54b | 105 | for (i = 0; i < PHY_NUMBER; i++) |
2db94162 VG |
106 | if (!IS_ERR(exynos_ohci->phy[i])) |
107 | phy_power_off(exynos_ohci->phy[i]); | |
ed993bf1 VG |
108 | } |
109 | ||
41ac7b3a | 110 | static int exynos_ohci_probe(struct platform_device *pdev) |
62194244 | 111 | { |
62194244 JH |
112 | struct exynos_ohci_hcd *exynos_ohci; |
113 | struct usb_hcd *hcd; | |
62194244 JH |
114 | struct resource *res; |
115 | int irq; | |
116 | int err; | |
117 | ||
d5138930 VG |
118 | /* |
119 | * Right now device-tree probed devices don't get dma_mask set. | |
120 | * Since shared usb code relies on it, set it here for now. | |
121 | * Once we move to full device tree support this will vanish off. | |
122 | */ | |
e1fd7341 | 123 | err = dma_coerce_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32)); |
22d9d8e8 RK |
124 | if (err) |
125 | return err; | |
d5138930 | 126 | |
50a97e05 MG |
127 | hcd = usb_create_hcd(&exynos_ohci_hc_driver, |
128 | &pdev->dev, dev_name(&pdev->dev)); | |
129 | if (!hcd) { | |
130 | dev_err(&pdev->dev, "Unable to create HCD\n"); | |
62194244 | 131 | return -ENOMEM; |
50a97e05 MG |
132 | } |
133 | ||
134 | exynos_ohci = to_exynos_ohci(hcd); | |
62194244 | 135 | |
2871782a TA |
136 | if (of_device_is_compatible(pdev->dev.of_node, |
137 | "samsung,exynos5440-ohci")) | |
138 | goto skip_phy; | |
139 | ||
7d28e54b VG |
140 | err = exynos_ohci_get_phy(&pdev->dev, exynos_ohci); |
141 | if (err) | |
142 | goto fail_clk; | |
ed993bf1 | 143 | |
2871782a | 144 | skip_phy: |
60d80adb | 145 | exynos_ohci->clk = devm_clk_get(&pdev->dev, "usbhost"); |
62194244 JH |
146 | |
147 | if (IS_ERR(exynos_ohci->clk)) { | |
148 | dev_err(&pdev->dev, "Failed to get usbhost clock\n"); | |
149 | err = PTR_ERR(exynos_ohci->clk); | |
150 | goto fail_clk; | |
151 | } | |
152 | ||
c05c946c | 153 | err = clk_prepare_enable(exynos_ohci->clk); |
62194244 | 154 | if (err) |
60d80adb | 155 | goto fail_clk; |
62194244 JH |
156 | |
157 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | |
bae00c1a VG |
158 | hcd->regs = devm_ioremap_resource(&pdev->dev, res); |
159 | if (IS_ERR(hcd->regs)) { | |
160 | err = PTR_ERR(hcd->regs); | |
62194244 JH |
161 | goto fail_io; |
162 | } | |
b87a9c52 VB |
163 | hcd->rsrc_start = res->start; |
164 | hcd->rsrc_len = resource_size(res); | |
62194244 JH |
165 | |
166 | irq = platform_get_irq(pdev, 0); | |
167 | if (!irq) { | |
168 | dev_err(&pdev->dev, "Failed to get IRQ\n"); | |
169 | err = -ENODEV; | |
390a0a78 | 170 | goto fail_io; |
62194244 JH |
171 | } |
172 | ||
50a97e05 | 173 | platform_set_drvdata(pdev, hcd); |
62194244 | 174 | |
7d28e54b VG |
175 | err = exynos_ohci_phy_enable(&pdev->dev); |
176 | if (err) { | |
177 | dev_err(&pdev->dev, "Failed to enable USB phy\n"); | |
178 | goto fail_io; | |
179 | } | |
62194244 JH |
180 | |
181 | err = usb_add_hcd(hcd, irq, IRQF_SHARED); | |
182 | if (err) { | |
183 | dev_err(&pdev->dev, "Failed to add USB HCD\n"); | |
ed993bf1 | 184 | goto fail_add_hcd; |
62194244 | 185 | } |
3c9740a1 | 186 | device_wakeup_enable(hcd->self.controller); |
62194244 JH |
187 | return 0; |
188 | ||
ed993bf1 | 189 | fail_add_hcd: |
54969ed6 | 190 | exynos_ohci_phy_disable(&pdev->dev); |
62194244 | 191 | fail_io: |
c05c946c | 192 | clk_disable_unprepare(exynos_ohci->clk); |
62194244 JH |
193 | fail_clk: |
194 | usb_put_hcd(hcd); | |
62194244 JH |
195 | return err; |
196 | } | |
197 | ||
fb4e98ab | 198 | static int exynos_ohci_remove(struct platform_device *pdev) |
62194244 | 199 | { |
50a97e05 MG |
200 | struct usb_hcd *hcd = platform_get_drvdata(pdev); |
201 | struct exynos_ohci_hcd *exynos_ohci = to_exynos_ohci(hcd); | |
62194244 JH |
202 | |
203 | usb_remove_hcd(hcd); | |
204 | ||
54969ed6 | 205 | exynos_ohci_phy_disable(&pdev->dev); |
62194244 | 206 | |
c05c946c | 207 | clk_disable_unprepare(exynos_ohci->clk); |
62194244 JH |
208 | |
209 | usb_put_hcd(hcd); | |
62194244 JH |
210 | |
211 | return 0; | |
212 | } | |
213 | ||
214 | static void exynos_ohci_shutdown(struct platform_device *pdev) | |
215 | { | |
50a97e05 | 216 | struct usb_hcd *hcd = platform_get_drvdata(pdev); |
62194244 JH |
217 | |
218 | if (hcd->driver->shutdown) | |
219 | hcd->driver->shutdown(hcd); | |
220 | } | |
221 | ||
222 | #ifdef CONFIG_PM | |
223 | static int exynos_ohci_suspend(struct device *dev) | |
224 | { | |
50a97e05 MG |
225 | struct usb_hcd *hcd = dev_get_drvdata(dev); |
226 | struct exynos_ohci_hcd *exynos_ohci = to_exynos_ohci(hcd); | |
14982e31 | 227 | bool do_wakeup = device_may_wakeup(dev); |
14982e31 | 228 | int rc = ohci_suspend(hcd, do_wakeup); |
62194244 | 229 | |
14982e31 MG |
230 | if (rc) |
231 | return rc; | |
066ba8e0 | 232 | |
54969ed6 | 233 | exynos_ohci_phy_disable(dev); |
e864abed | 234 | |
c05c946c | 235 | clk_disable_unprepare(exynos_ohci->clk); |
e864abed | 236 | |
14982e31 | 237 | return 0; |
62194244 JH |
238 | } |
239 | ||
240 | static int exynos_ohci_resume(struct device *dev) | |
241 | { | |
50a97e05 MG |
242 | struct usb_hcd *hcd = dev_get_drvdata(dev); |
243 | struct exynos_ohci_hcd *exynos_ohci = to_exynos_ohci(hcd); | |
7d28e54b | 244 | int ret; |
62194244 | 245 | |
c05c946c | 246 | clk_prepare_enable(exynos_ohci->clk); |
e864abed | 247 | |
7d28e54b VG |
248 | ret = exynos_ohci_phy_enable(dev); |
249 | if (ret) { | |
250 | dev_err(dev, "Failed to enable USB phy\n"); | |
251 | clk_disable_unprepare(exynos_ohci->clk); | |
252 | return ret; | |
253 | } | |
62194244 | 254 | |
cfa49b4b | 255 | ohci_resume(hcd, false); |
62194244 JH |
256 | |
257 | return 0; | |
258 | } | |
259 | #else | |
260 | #define exynos_ohci_suspend NULL | |
261 | #define exynos_ohci_resume NULL | |
262 | #endif | |
263 | ||
50a97e05 MG |
264 | static const struct ohci_driver_overrides exynos_overrides __initconst = { |
265 | .extra_priv_size = sizeof(struct exynos_ohci_hcd), | |
266 | }; | |
267 | ||
62194244 JH |
268 | static const struct dev_pm_ops exynos_ohci_pm_ops = { |
269 | .suspend = exynos_ohci_suspend, | |
270 | .resume = exynos_ohci_resume, | |
271 | }; | |
272 | ||
d5138930 VG |
273 | #ifdef CONFIG_OF |
274 | static const struct of_device_id exynos_ohci_match[] = { | |
6e247777 | 275 | { .compatible = "samsung,exynos4210-ohci" }, |
2871782a | 276 | { .compatible = "samsung,exynos5440-ohci" }, |
d5138930 VG |
277 | {}, |
278 | }; | |
279 | MODULE_DEVICE_TABLE(of, exynos_ohci_match); | |
280 | #endif | |
281 | ||
62194244 JH |
282 | static struct platform_driver exynos_ohci_driver = { |
283 | .probe = exynos_ohci_probe, | |
7690417d | 284 | .remove = exynos_ohci_remove, |
62194244 JH |
285 | .shutdown = exynos_ohci_shutdown, |
286 | .driver = { | |
287 | .name = "exynos-ohci", | |
62194244 | 288 | .pm = &exynos_ohci_pm_ops, |
d5138930 | 289 | .of_match_table = of_match_ptr(exynos_ohci_match), |
62194244 JH |
290 | } |
291 | }; | |
50a97e05 MG |
292 | static int __init ohci_exynos_init(void) |
293 | { | |
294 | if (usb_disabled()) | |
295 | return -ENODEV; | |
296 | ||
297 | pr_info("%s: " DRIVER_DESC "\n", hcd_name); | |
298 | ohci_init_driver(&exynos_ohci_hc_driver, &exynos_overrides); | |
299 | return platform_driver_register(&exynos_ohci_driver); | |
300 | } | |
301 | module_init(ohci_exynos_init); | |
302 | ||
303 | static void __exit ohci_exynos_cleanup(void) | |
304 | { | |
305 | platform_driver_unregister(&exynos_ohci_driver); | |
306 | } | |
307 | module_exit(ohci_exynos_cleanup); | |
62194244 JH |
308 | |
309 | MODULE_ALIAS("platform:exynos-ohci"); | |
310 | MODULE_AUTHOR("Jingoo Han <jg1.han@samsung.com>"); | |
50a97e05 | 311 | MODULE_LICENSE("GPL v2"); |