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> | |
d5138930 | 15 | #include <linux/of.h> |
62194244 JH |
16 | #include <linux/platform_device.h> |
17 | #include <mach/ohci.h> | |
18 | #include <plat/usb-phy.h> | |
19 | ||
20 | struct exynos_ohci_hcd { | |
21 | struct device *dev; | |
22 | struct usb_hcd *hcd; | |
23 | struct clk *clk; | |
24 | }; | |
25 | ||
26 | static int ohci_exynos_start(struct usb_hcd *hcd) | |
27 | { | |
28 | struct ohci_hcd *ohci = hcd_to_ohci(hcd); | |
29 | int ret; | |
30 | ||
31 | ohci_dbg(ohci, "ohci_exynos_start, ohci:%p", ohci); | |
32 | ||
33 | ret = ohci_init(ohci); | |
34 | if (ret < 0) | |
35 | return ret; | |
36 | ||
37 | ret = ohci_run(ohci); | |
38 | if (ret < 0) { | |
5e415245 GKH |
39 | dev_err(hcd->self.controller, "can't start %s\n", |
40 | hcd->self.bus_name); | |
62194244 JH |
41 | ohci_stop(hcd); |
42 | return ret; | |
43 | } | |
44 | ||
45 | return 0; | |
46 | } | |
47 | ||
48 | static const struct hc_driver exynos_ohci_hc_driver = { | |
49 | .description = hcd_name, | |
50 | .product_desc = "EXYNOS OHCI Host Controller", | |
51 | .hcd_priv_size = sizeof(struct ohci_hcd), | |
52 | ||
53 | .irq = ohci_irq, | |
54 | .flags = HCD_MEMORY|HCD_USB11, | |
55 | ||
56 | .start = ohci_exynos_start, | |
57 | .stop = ohci_stop, | |
58 | .shutdown = ohci_shutdown, | |
59 | ||
60 | .get_frame_number = ohci_get_frame, | |
61 | ||
62 | .urb_enqueue = ohci_urb_enqueue, | |
63 | .urb_dequeue = ohci_urb_dequeue, | |
64 | .endpoint_disable = ohci_endpoint_disable, | |
65 | ||
66 | .hub_status_data = ohci_hub_status_data, | |
67 | .hub_control = ohci_hub_control, | |
68 | #ifdef CONFIG_PM | |
69 | .bus_suspend = ohci_bus_suspend, | |
70 | .bus_resume = ohci_bus_resume, | |
71 | #endif | |
72 | .start_port_reset = ohci_start_port_reset, | |
73 | }; | |
74 | ||
d5138930 VG |
75 | static u64 ohci_exynos_dma_mask = DMA_BIT_MASK(32); |
76 | ||
62194244 JH |
77 | static int __devinit exynos_ohci_probe(struct platform_device *pdev) |
78 | { | |
79 | struct exynos4_ohci_platdata *pdata; | |
80 | struct exynos_ohci_hcd *exynos_ohci; | |
81 | struct usb_hcd *hcd; | |
82 | struct ohci_hcd *ohci; | |
83 | struct resource *res; | |
84 | int irq; | |
85 | int err; | |
86 | ||
87 | pdata = pdev->dev.platform_data; | |
88 | if (!pdata) { | |
89 | dev_err(&pdev->dev, "No platform data defined\n"); | |
90 | return -EINVAL; | |
91 | } | |
92 | ||
d5138930 VG |
93 | /* |
94 | * Right now device-tree probed devices don't get dma_mask set. | |
95 | * Since shared usb code relies on it, set it here for now. | |
96 | * Once we move to full device tree support this will vanish off. | |
97 | */ | |
98 | if (!pdev->dev.dma_mask) | |
99 | pdev->dev.dma_mask = &ohci_exynos_dma_mask; | |
100 | if (!pdev->dev.coherent_dma_mask) | |
101 | pdev->dev.coherent_dma_mask = DMA_BIT_MASK(32); | |
102 | ||
390a0a78 JH |
103 | exynos_ohci = devm_kzalloc(&pdev->dev, sizeof(struct exynos_ohci_hcd), |
104 | GFP_KERNEL); | |
62194244 JH |
105 | if (!exynos_ohci) |
106 | return -ENOMEM; | |
107 | ||
108 | exynos_ohci->dev = &pdev->dev; | |
109 | ||
110 | hcd = usb_create_hcd(&exynos_ohci_hc_driver, &pdev->dev, | |
111 | dev_name(&pdev->dev)); | |
112 | if (!hcd) { | |
113 | dev_err(&pdev->dev, "Unable to create HCD\n"); | |
390a0a78 | 114 | return -ENOMEM; |
62194244 JH |
115 | } |
116 | ||
117 | exynos_ohci->hcd = hcd; | |
118 | exynos_ohci->clk = clk_get(&pdev->dev, "usbhost"); | |
119 | ||
120 | if (IS_ERR(exynos_ohci->clk)) { | |
121 | dev_err(&pdev->dev, "Failed to get usbhost clock\n"); | |
122 | err = PTR_ERR(exynos_ohci->clk); | |
123 | goto fail_clk; | |
124 | } | |
125 | ||
126 | err = clk_enable(exynos_ohci->clk); | |
127 | if (err) | |
128 | goto fail_clken; | |
129 | ||
130 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | |
131 | if (!res) { | |
132 | dev_err(&pdev->dev, "Failed to get I/O memory\n"); | |
133 | err = -ENXIO; | |
134 | goto fail_io; | |
135 | } | |
136 | ||
137 | hcd->rsrc_start = res->start; | |
138 | hcd->rsrc_len = resource_size(res); | |
390a0a78 | 139 | hcd->regs = devm_ioremap(&pdev->dev, res->start, hcd->rsrc_len); |
62194244 JH |
140 | if (!hcd->regs) { |
141 | dev_err(&pdev->dev, "Failed to remap I/O memory\n"); | |
142 | err = -ENOMEM; | |
143 | goto fail_io; | |
144 | } | |
145 | ||
146 | irq = platform_get_irq(pdev, 0); | |
147 | if (!irq) { | |
148 | dev_err(&pdev->dev, "Failed to get IRQ\n"); | |
149 | err = -ENODEV; | |
390a0a78 | 150 | goto fail_io; |
62194244 JH |
151 | } |
152 | ||
153 | if (pdata->phy_init) | |
154 | pdata->phy_init(pdev, S5P_USB_PHY_HOST); | |
155 | ||
156 | ohci = hcd_to_ohci(hcd); | |
157 | ohci_hcd_init(ohci); | |
158 | ||
159 | err = usb_add_hcd(hcd, irq, IRQF_SHARED); | |
160 | if (err) { | |
161 | dev_err(&pdev->dev, "Failed to add USB HCD\n"); | |
390a0a78 | 162 | goto fail_io; |
62194244 JH |
163 | } |
164 | ||
165 | platform_set_drvdata(pdev, exynos_ohci); | |
166 | ||
167 | return 0; | |
168 | ||
62194244 JH |
169 | fail_io: |
170 | clk_disable(exynos_ohci->clk); | |
171 | fail_clken: | |
172 | clk_put(exynos_ohci->clk); | |
173 | fail_clk: | |
174 | usb_put_hcd(hcd); | |
62194244 JH |
175 | return err; |
176 | } | |
177 | ||
178 | static int __devexit exynos_ohci_remove(struct platform_device *pdev) | |
179 | { | |
180 | struct exynos4_ohci_platdata *pdata = pdev->dev.platform_data; | |
181 | struct exynos_ohci_hcd *exynos_ohci = platform_get_drvdata(pdev); | |
182 | struct usb_hcd *hcd = exynos_ohci->hcd; | |
183 | ||
184 | usb_remove_hcd(hcd); | |
185 | ||
186 | if (pdata && pdata->phy_exit) | |
187 | pdata->phy_exit(pdev, S5P_USB_PHY_HOST); | |
188 | ||
62194244 JH |
189 | clk_disable(exynos_ohci->clk); |
190 | clk_put(exynos_ohci->clk); | |
191 | ||
192 | usb_put_hcd(hcd); | |
62194244 JH |
193 | |
194 | return 0; | |
195 | } | |
196 | ||
197 | static void exynos_ohci_shutdown(struct platform_device *pdev) | |
198 | { | |
199 | struct exynos_ohci_hcd *exynos_ohci = platform_get_drvdata(pdev); | |
200 | struct usb_hcd *hcd = exynos_ohci->hcd; | |
201 | ||
202 | if (hcd->driver->shutdown) | |
203 | hcd->driver->shutdown(hcd); | |
204 | } | |
205 | ||
206 | #ifdef CONFIG_PM | |
207 | static int exynos_ohci_suspend(struct device *dev) | |
208 | { | |
209 | struct exynos_ohci_hcd *exynos_ohci = dev_get_drvdata(dev); | |
210 | struct usb_hcd *hcd = exynos_ohci->hcd; | |
211 | struct ohci_hcd *ohci = hcd_to_ohci(hcd); | |
212 | struct platform_device *pdev = to_platform_device(dev); | |
213 | struct exynos4_ohci_platdata *pdata = pdev->dev.platform_data; | |
214 | unsigned long flags; | |
215 | int rc = 0; | |
216 | ||
217 | /* | |
218 | * Root hub was already suspended. Disable irq emission and | |
219 | * mark HW unaccessible, bail out if RH has been resumed. Use | |
220 | * the spinlock to properly synchronize with possible pending | |
221 | * RH suspend or resume activity. | |
62194244 JH |
222 | */ |
223 | spin_lock_irqsave(&ohci->lock, flags); | |
2b4ffe31 JH |
224 | if (ohci->rh_state != OHCI_RH_SUSPENDED && |
225 | ohci->rh_state != OHCI_RH_HALTED) { | |
62194244 JH |
226 | rc = -EINVAL; |
227 | goto fail; | |
228 | } | |
229 | ||
230 | clear_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); | |
231 | ||
232 | if (pdata && pdata->phy_exit) | |
233 | pdata->phy_exit(pdev, S5P_USB_PHY_HOST); | |
e864abed JH |
234 | |
235 | clk_disable(exynos_ohci->clk); | |
236 | ||
62194244 JH |
237 | fail: |
238 | spin_unlock_irqrestore(&ohci->lock, flags); | |
239 | ||
240 | return rc; | |
241 | } | |
242 | ||
243 | static int exynos_ohci_resume(struct device *dev) | |
244 | { | |
245 | struct exynos_ohci_hcd *exynos_ohci = dev_get_drvdata(dev); | |
246 | struct usb_hcd *hcd = exynos_ohci->hcd; | |
247 | struct platform_device *pdev = to_platform_device(dev); | |
248 | struct exynos4_ohci_platdata *pdata = pdev->dev.platform_data; | |
249 | ||
e864abed JH |
250 | clk_enable(exynos_ohci->clk); |
251 | ||
62194244 JH |
252 | if (pdata && pdata->phy_init) |
253 | pdata->phy_init(pdev, S5P_USB_PHY_HOST); | |
254 | ||
255 | /* Mark hardware accessible again as we are out of D3 state by now */ | |
256 | set_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); | |
257 | ||
258 | ohci_finish_controller_resume(hcd); | |
259 | ||
260 | return 0; | |
261 | } | |
262 | #else | |
263 | #define exynos_ohci_suspend NULL | |
264 | #define exynos_ohci_resume NULL | |
265 | #endif | |
266 | ||
267 | static const struct dev_pm_ops exynos_ohci_pm_ops = { | |
268 | .suspend = exynos_ohci_suspend, | |
269 | .resume = exynos_ohci_resume, | |
270 | }; | |
271 | ||
d5138930 VG |
272 | #ifdef CONFIG_OF |
273 | static const struct of_device_id exynos_ohci_match[] = { | |
274 | { .compatible = "samsung,exynos-ohci" }, | |
275 | {}, | |
276 | }; | |
277 | MODULE_DEVICE_TABLE(of, exynos_ohci_match); | |
278 | #endif | |
279 | ||
62194244 JH |
280 | static struct platform_driver exynos_ohci_driver = { |
281 | .probe = exynos_ohci_probe, | |
282 | .remove = __devexit_p(exynos_ohci_remove), | |
283 | .shutdown = exynos_ohci_shutdown, | |
284 | .driver = { | |
285 | .name = "exynos-ohci", | |
286 | .owner = THIS_MODULE, | |
287 | .pm = &exynos_ohci_pm_ops, | |
d5138930 | 288 | .of_match_table = of_match_ptr(exynos_ohci_match), |
62194244 JH |
289 | } |
290 | }; | |
291 | ||
292 | MODULE_ALIAS("platform:exynos-ohci"); | |
293 | MODULE_AUTHOR("Jingoo Han <jg1.han@samsung.com>"); |