1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * SAMSUNG EXYNOS USB HOST OHCI Controller |
4 | * |
5 | * Copyright (C) 2011 Samsung Electronics Co.Ltd |
6 | * Author: Jingoo Han <jg1.han@samsung.com> |
7 | */ |
8 | |
9 | #include <linux/clk.h> |
10 | #include <linux/dma-mapping.h> |
11 | #include <linux/io.h> |
12 | #include <linux/kernel.h> |
13 | #include <linux/module.h> |
14 | #include <linux/of.h> |
15 | #include <linux/platform_device.h> |
16 | #include <linux/phy/phy.h> |
17 | #include <linux/usb.h> |
18 | #include <linux/usb/hcd.h> |
19 | |
20 | #include "ohci.h" |
21 | |
22 | #define DRIVER_DESC "OHCI Exynos driver" |
23 | |
24 | static struct hc_driver __read_mostly exynos_ohci_hc_driver; |
25 | |
26 | #define to_exynos_ohci(hcd) (struct exynos_ohci_hcd *)(hcd_to_ohci(hcd)->priv) |
27 | |
28 | #define PHY_NUMBER 3 |
29 | |
30 | struct exynos_ohci_hcd { |
31 | struct clk *clk; |
32 | struct device_node *of_node; |
33 | struct phy *phy[PHY_NUMBER]; |
34 | bool legacy_phy; |
35 | }; |
36 | |
37 | static int exynos_ohci_get_phy(struct device *dev, |
38 | struct exynos_ohci_hcd *exynos_ohci) |
39 | { |
40 | struct device_node *child; |
41 | struct phy *phy; |
42 | int phy_number, num_phys; |
43 | int ret; |
44 | |
45 | /* Get PHYs for the controller */ |
46 | num_phys = of_count_phandle_with_args(np: dev->of_node, list_name: "phys" , |
47 | cells_name: "#phy-cells" ); |
48 | for (phy_number = 0; phy_number < num_phys; phy_number++) { |
49 | phy = devm_of_phy_get_by_index(dev, np: dev->of_node, index: phy_number); |
50 | if (IS_ERR(ptr: phy)) |
51 | return PTR_ERR(ptr: phy); |
52 | exynos_ohci->phy[phy_number] = phy; |
53 | } |
54 | if (num_phys > 0) |
55 | return 0; |
56 | |
57 | /* Get PHYs using legacy bindings */ |
58 | for_each_available_child_of_node(dev->of_node, child) { |
59 | ret = of_property_read_u32(np: child, propname: "reg" , out_value: &phy_number); |
60 | if (ret) { |
61 | dev_err(dev, "Failed to parse device tree\n" ); |
62 | of_node_put(node: child); |
63 | return ret; |
64 | } |
65 | |
66 | if (phy_number >= PHY_NUMBER) { |
67 | dev_err(dev, "Invalid number of PHYs\n" ); |
68 | of_node_put(node: child); |
69 | return -EINVAL; |
70 | } |
71 | |
72 | phy = devm_of_phy_optional_get(dev, np: child, NULL); |
73 | exynos_ohci->phy[phy_number] = phy; |
74 | if (IS_ERR(ptr: phy)) { |
75 | of_node_put(node: child); |
76 | return PTR_ERR(ptr: phy); |
77 | } |
78 | } |
79 | |
80 | exynos_ohci->legacy_phy = true; |
81 | return 0; |
82 | } |
83 | |
84 | static int exynos_ohci_phy_enable(struct device *dev) |
85 | { |
86 | struct usb_hcd *hcd = dev_get_drvdata(dev); |
87 | struct exynos_ohci_hcd *exynos_ohci = to_exynos_ohci(hcd); |
88 | int i; |
89 | int ret = 0; |
90 | |
91 | for (i = 0; ret == 0 && i < PHY_NUMBER; i++) |
92 | ret = phy_power_on(phy: exynos_ohci->phy[i]); |
93 | if (ret) |
94 | for (i--; i >= 0; i--) |
95 | phy_power_off(phy: exynos_ohci->phy[i]); |
96 | |
97 | return ret; |
98 | } |
99 | |
100 | static void exynos_ohci_phy_disable(struct device *dev) |
101 | { |
102 | struct usb_hcd *hcd = dev_get_drvdata(dev); |
103 | struct exynos_ohci_hcd *exynos_ohci = to_exynos_ohci(hcd); |
104 | int i; |
105 | |
106 | for (i = 0; i < PHY_NUMBER; i++) |
107 | phy_power_off(phy: exynos_ohci->phy[i]); |
108 | } |
109 | |
110 | static int exynos_ohci_probe(struct platform_device *pdev) |
111 | { |
112 | struct exynos_ohci_hcd *exynos_ohci; |
113 | struct usb_hcd *hcd; |
114 | struct resource *res; |
115 | int irq; |
116 | int err; |
117 | |
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 | */ |
123 | err = dma_coerce_mask_and_coherent(dev: &pdev->dev, DMA_BIT_MASK(32)); |
124 | if (err) |
125 | return err; |
126 | |
127 | hcd = usb_create_hcd(driver: &exynos_ohci_hc_driver, |
128 | dev: &pdev->dev, bus_name: dev_name(dev: &pdev->dev)); |
129 | if (!hcd) { |
130 | dev_err(&pdev->dev, "Unable to create HCD\n" ); |
131 | return -ENOMEM; |
132 | } |
133 | |
134 | exynos_ohci = to_exynos_ohci(hcd); |
135 | |
136 | err = exynos_ohci_get_phy(dev: &pdev->dev, exynos_ohci); |
137 | if (err) |
138 | goto fail_clk; |
139 | |
140 | exynos_ohci->clk = devm_clk_get(dev: &pdev->dev, id: "usbhost" ); |
141 | |
142 | if (IS_ERR(ptr: exynos_ohci->clk)) { |
143 | dev_err(&pdev->dev, "Failed to get usbhost clock\n" ); |
144 | err = PTR_ERR(ptr: exynos_ohci->clk); |
145 | goto fail_clk; |
146 | } |
147 | |
148 | err = clk_prepare_enable(clk: exynos_ohci->clk); |
149 | if (err) |
150 | goto fail_clk; |
151 | |
152 | hcd->regs = devm_platform_get_and_ioremap_resource(pdev, index: 0, res: &res); |
153 | if (IS_ERR(ptr: hcd->regs)) { |
154 | err = PTR_ERR(ptr: hcd->regs); |
155 | goto fail_io; |
156 | } |
157 | hcd->rsrc_start = res->start; |
158 | hcd->rsrc_len = resource_size(res); |
159 | |
160 | irq = platform_get_irq(pdev, 0); |
161 | if (irq < 0) { |
162 | err = irq; |
163 | goto fail_io; |
164 | } |
165 | |
166 | platform_set_drvdata(pdev, data: hcd); |
167 | |
168 | err = exynos_ohci_phy_enable(dev: &pdev->dev); |
169 | if (err) { |
170 | dev_err(&pdev->dev, "Failed to enable USB phy\n" ); |
171 | goto fail_io; |
172 | } |
173 | |
174 | /* |
175 | * Workaround: reset of_node pointer to avoid conflict between legacy |
176 | * Exynos OHCI port subnodes and generic USB device bindings |
177 | */ |
178 | exynos_ohci->of_node = pdev->dev.of_node; |
179 | if (exynos_ohci->legacy_phy) |
180 | pdev->dev.of_node = NULL; |
181 | |
182 | err = usb_add_hcd(hcd, irqnum: irq, IRQF_SHARED); |
183 | if (err) { |
184 | dev_err(&pdev->dev, "Failed to add USB HCD\n" ); |
185 | goto fail_add_hcd; |
186 | } |
187 | device_wakeup_enable(dev: hcd->self.controller); |
188 | return 0; |
189 | |
190 | fail_add_hcd: |
191 | exynos_ohci_phy_disable(dev: &pdev->dev); |
192 | pdev->dev.of_node = exynos_ohci->of_node; |
193 | fail_io: |
194 | clk_disable_unprepare(clk: exynos_ohci->clk); |
195 | fail_clk: |
196 | usb_put_hcd(hcd); |
197 | return err; |
198 | } |
199 | |
200 | static void exynos_ohci_remove(struct platform_device *pdev) |
201 | { |
202 | struct usb_hcd *hcd = platform_get_drvdata(pdev); |
203 | struct exynos_ohci_hcd *exynos_ohci = to_exynos_ohci(hcd); |
204 | |
205 | pdev->dev.of_node = exynos_ohci->of_node; |
206 | |
207 | usb_remove_hcd(hcd); |
208 | |
209 | exynos_ohci_phy_disable(dev: &pdev->dev); |
210 | |
211 | clk_disable_unprepare(clk: exynos_ohci->clk); |
212 | |
213 | usb_put_hcd(hcd); |
214 | } |
215 | |
216 | static void exynos_ohci_shutdown(struct platform_device *pdev) |
217 | { |
218 | struct usb_hcd *hcd = platform_get_drvdata(pdev); |
219 | |
220 | if (hcd->driver->shutdown) |
221 | hcd->driver->shutdown(hcd); |
222 | } |
223 | |
224 | #ifdef CONFIG_PM |
225 | static int exynos_ohci_suspend(struct device *dev) |
226 | { |
227 | struct usb_hcd *hcd = dev_get_drvdata(dev); |
228 | struct exynos_ohci_hcd *exynos_ohci = to_exynos_ohci(hcd); |
229 | bool do_wakeup = device_may_wakeup(dev); |
230 | int rc = ohci_suspend(hcd, do_wakeup); |
231 | |
232 | if (rc) |
233 | return rc; |
234 | |
235 | exynos_ohci_phy_disable(dev); |
236 | |
237 | clk_disable_unprepare(clk: exynos_ohci->clk); |
238 | |
239 | return 0; |
240 | } |
241 | |
242 | static int exynos_ohci_resume(struct device *dev) |
243 | { |
244 | struct usb_hcd *hcd = dev_get_drvdata(dev); |
245 | struct exynos_ohci_hcd *exynos_ohci = to_exynos_ohci(hcd); |
246 | int ret; |
247 | |
248 | clk_prepare_enable(clk: exynos_ohci->clk); |
249 | |
250 | ret = exynos_ohci_phy_enable(dev); |
251 | if (ret) { |
252 | dev_err(dev, "Failed to enable USB phy\n" ); |
253 | clk_disable_unprepare(clk: exynos_ohci->clk); |
254 | return ret; |
255 | } |
256 | |
257 | ohci_resume(hcd, hibernated: false); |
258 | |
259 | return 0; |
260 | } |
261 | #else |
262 | #define exynos_ohci_suspend NULL |
263 | #define exynos_ohci_resume NULL |
264 | #endif |
265 | |
266 | static const struct ohci_driver_overrides exynos_overrides __initconst = { |
267 | .extra_priv_size = sizeof(struct exynos_ohci_hcd), |
268 | }; |
269 | |
270 | static const struct dev_pm_ops exynos_ohci_pm_ops = { |
271 | .suspend = exynos_ohci_suspend, |
272 | .resume = exynos_ohci_resume, |
273 | }; |
274 | |
275 | #ifdef CONFIG_OF |
276 | static const struct of_device_id exynos_ohci_match[] = { |
277 | { .compatible = "samsung,exynos4210-ohci" }, |
278 | {}, |
279 | }; |
280 | MODULE_DEVICE_TABLE(of, exynos_ohci_match); |
281 | #endif |
282 | |
283 | static struct platform_driver exynos_ohci_driver = { |
284 | .probe = exynos_ohci_probe, |
285 | .remove_new = exynos_ohci_remove, |
286 | .shutdown = exynos_ohci_shutdown, |
287 | .driver = { |
288 | .name = "exynos-ohci" , |
289 | .pm = &exynos_ohci_pm_ops, |
290 | .of_match_table = of_match_ptr(exynos_ohci_match), |
291 | } |
292 | }; |
293 | static int __init ohci_exynos_init(void) |
294 | { |
295 | if (usb_disabled()) |
296 | return -ENODEV; |
297 | |
298 | ohci_init_driver(drv: &exynos_ohci_hc_driver, over: &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); |
308 | |
309 | MODULE_ALIAS("platform:exynos-ohci" ); |
310 | MODULE_AUTHOR("Jingoo Han <jg1.han@samsung.com>" ); |
311 | MODULE_LICENSE("GPL v2" ); |
312 | |