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