diff options
Diffstat (limited to 'drivers/usb/host')
-rw-r--r-- | drivers/usb/host/Kconfig | 9 | ||||
-rw-r--r-- | drivers/usb/host/ehci-hcd.c | 5 | ||||
-rw-r--r-- | drivers/usb/host/ehci-mv.c | 391 |
3 files changed, 405 insertions, 0 deletions
diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig index 060e0e2b1ae6..a52769b5c904 100644 --- a/drivers/usb/host/Kconfig +++ b/drivers/usb/host/Kconfig | |||
@@ -194,6 +194,15 @@ config USB_EHCI_S5P | |||
194 | help | 194 | help |
195 | Enable support for the S5P SOC's on-chip EHCI controller. | 195 | Enable support for the S5P SOC's on-chip EHCI controller. |
196 | 196 | ||
197 | config USB_EHCI_MV | ||
198 | bool "EHCI support for Marvell on-chip controller" | ||
199 | depends on USB_EHCI_HCD | ||
200 | select USB_EHCI_ROOT_HUB_TT | ||
201 | ---help--- | ||
202 | Enables support for Marvell (including PXA and MMP series) on-chip | ||
203 | USB SPH and OTG controller. SPH is a single port host, and it can | ||
204 | only be EHCI host. OTG is controller that can switch to host mode. | ||
205 | |||
197 | config USB_W90X900_EHCI | 206 | config USB_W90X900_EHCI |
198 | bool "W90X900(W90P910) EHCI support" | 207 | bool "W90X900(W90P910) EHCI support" |
199 | depends on USB_EHCI_HCD && ARCH_W90X900 | 208 | depends on USB_EHCI_HCD && ARCH_W90X900 |
diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c index 3ff9f82f7263..b05e7533d08f 100644 --- a/drivers/usb/host/ehci-hcd.c +++ b/drivers/usb/host/ehci-hcd.c | |||
@@ -1329,6 +1329,11 @@ MODULE_LICENSE ("GPL"); | |||
1329 | #define PLATFORM_DRIVER ehci_xls_driver | 1329 | #define PLATFORM_DRIVER ehci_xls_driver |
1330 | #endif | 1330 | #endif |
1331 | 1331 | ||
1332 | #ifdef CONFIG_USB_EHCI_MV | ||
1333 | #include "ehci-mv.c" | ||
1334 | #define PLATFORM_DRIVER ehci_mv_driver | ||
1335 | #endif | ||
1336 | |||
1332 | #if !defined(PCI_DRIVER) && !defined(PLATFORM_DRIVER) && \ | 1337 | #if !defined(PCI_DRIVER) && !defined(PLATFORM_DRIVER) && \ |
1333 | !defined(PS3_SYSTEM_BUS_DRIVER) && !defined(OF_PLATFORM_DRIVER) && \ | 1338 | !defined(PS3_SYSTEM_BUS_DRIVER) && !defined(OF_PLATFORM_DRIVER) && \ |
1334 | !defined(XILINX_OF_PLATFORM_DRIVER) | 1339 | !defined(XILINX_OF_PLATFORM_DRIVER) |
diff --git a/drivers/usb/host/ehci-mv.c b/drivers/usb/host/ehci-mv.c new file mode 100644 index 000000000000..52a604fb9321 --- /dev/null +++ b/drivers/usb/host/ehci-mv.c | |||
@@ -0,0 +1,391 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2011 Marvell International Ltd. All rights reserved. | ||
3 | * Author: Chao Xie <chao.xie@marvell.com> | ||
4 | * Neil Zhang <zhangwm@marvell.com> | ||
5 | * | ||
6 | * This program is free software; you can redistribute it and/or modify it | ||
7 | * under the terms of the GNU General Public License as published by the | ||
8 | * Free Software Foundation; either version 2 of the License, or (at your | ||
9 | * option) any later version. | ||
10 | */ | ||
11 | |||
12 | #include <linux/kernel.h> | ||
13 | #include <linux/module.h> | ||
14 | #include <linux/platform_device.h> | ||
15 | #include <linux/clk.h> | ||
16 | #include <linux/usb/otg.h> | ||
17 | #include <linux/platform_data/mv_usb.h> | ||
18 | |||
19 | #define CAPLENGTH_MASK (0xff) | ||
20 | |||
21 | struct ehci_hcd_mv { | ||
22 | struct usb_hcd *hcd; | ||
23 | |||
24 | /* Which mode does this ehci running OTG/Host ? */ | ||
25 | int mode; | ||
26 | |||
27 | void __iomem *phy_regs; | ||
28 | void __iomem *cap_regs; | ||
29 | void __iomem *op_regs; | ||
30 | |||
31 | struct otg_transceiver *otg; | ||
32 | |||
33 | struct mv_usb_platform_data *pdata; | ||
34 | |||
35 | /* clock source and total clock number */ | ||
36 | unsigned int clknum; | ||
37 | struct clk *clk[0]; | ||
38 | }; | ||
39 | |||
40 | static void ehci_clock_enable(struct ehci_hcd_mv *ehci_mv) | ||
41 | { | ||
42 | unsigned int i; | ||
43 | |||
44 | for (i = 0; i < ehci_mv->clknum; i++) | ||
45 | clk_enable(ehci_mv->clk[i]); | ||
46 | } | ||
47 | |||
48 | static void ehci_clock_disable(struct ehci_hcd_mv *ehci_mv) | ||
49 | { | ||
50 | unsigned int i; | ||
51 | |||
52 | for (i = 0; i < ehci_mv->clknum; i++) | ||
53 | clk_disable(ehci_mv->clk[i]); | ||
54 | } | ||
55 | |||
56 | static int mv_ehci_enable(struct ehci_hcd_mv *ehci_mv) | ||
57 | { | ||
58 | int retval; | ||
59 | |||
60 | ehci_clock_enable(ehci_mv); | ||
61 | if (ehci_mv->pdata->phy_init) { | ||
62 | retval = ehci_mv->pdata->phy_init(ehci_mv->phy_regs); | ||
63 | if (retval) | ||
64 | return retval; | ||
65 | } | ||
66 | |||
67 | return 0; | ||
68 | } | ||
69 | |||
70 | static void mv_ehci_disable(struct ehci_hcd_mv *ehci_mv) | ||
71 | { | ||
72 | if (ehci_mv->pdata->phy_deinit) | ||
73 | ehci_mv->pdata->phy_deinit(ehci_mv->phy_regs); | ||
74 | ehci_clock_disable(ehci_mv); | ||
75 | } | ||
76 | |||
77 | static int mv_ehci_reset(struct usb_hcd *hcd) | ||
78 | { | ||
79 | struct ehci_hcd *ehci = hcd_to_ehci(hcd); | ||
80 | struct device *dev = hcd->self.controller; | ||
81 | struct ehci_hcd_mv *ehci_mv = dev_get_drvdata(dev); | ||
82 | int retval; | ||
83 | |||
84 | if (ehci_mv == NULL) { | ||
85 | dev_err(dev, "Can not find private ehci data\n"); | ||
86 | return -ENODEV; | ||
87 | } | ||
88 | |||
89 | /* | ||
90 | * data structure init | ||
91 | */ | ||
92 | retval = ehci_init(hcd); | ||
93 | if (retval) { | ||
94 | dev_err(dev, "ehci_init failed %d\n", retval); | ||
95 | return retval; | ||
96 | } | ||
97 | |||
98 | hcd->has_tt = 1; | ||
99 | ehci->sbrn = 0x20; | ||
100 | |||
101 | retval = ehci_reset(ehci); | ||
102 | if (retval) { | ||
103 | dev_err(dev, "ehci_reset failed %d\n", retval); | ||
104 | return retval; | ||
105 | } | ||
106 | |||
107 | return 0; | ||
108 | } | ||
109 | |||
110 | static const struct hc_driver mv_ehci_hc_driver = { | ||
111 | .description = hcd_name, | ||
112 | .product_desc = "Marvell EHCI", | ||
113 | .hcd_priv_size = sizeof(struct ehci_hcd), | ||
114 | |||
115 | /* | ||
116 | * generic hardware linkage | ||
117 | */ | ||
118 | .irq = ehci_irq, | ||
119 | .flags = HCD_MEMORY | HCD_USB2, | ||
120 | |||
121 | /* | ||
122 | * basic lifecycle operations | ||
123 | */ | ||
124 | .reset = mv_ehci_reset, | ||
125 | .start = ehci_run, | ||
126 | .stop = ehci_stop, | ||
127 | .shutdown = ehci_shutdown, | ||
128 | |||
129 | /* | ||
130 | * managing i/o requests and associated device resources | ||
131 | */ | ||
132 | .urb_enqueue = ehci_urb_enqueue, | ||
133 | .urb_dequeue = ehci_urb_dequeue, | ||
134 | .endpoint_disable = ehci_endpoint_disable, | ||
135 | .endpoint_reset = ehci_endpoint_reset, | ||
136 | .clear_tt_buffer_complete = ehci_clear_tt_buffer_complete, | ||
137 | |||
138 | /* | ||
139 | * scheduling support | ||
140 | */ | ||
141 | .get_frame_number = ehci_get_frame, | ||
142 | |||
143 | /* | ||
144 | * root hub support | ||
145 | */ | ||
146 | .hub_status_data = ehci_hub_status_data, | ||
147 | .hub_control = ehci_hub_control, | ||
148 | .bus_suspend = ehci_bus_suspend, | ||
149 | .bus_resume = ehci_bus_resume, | ||
150 | }; | ||
151 | |||
152 | static int mv_ehci_probe(struct platform_device *pdev) | ||
153 | { | ||
154 | struct mv_usb_platform_data *pdata = pdev->dev.platform_data; | ||
155 | struct usb_hcd *hcd; | ||
156 | struct ehci_hcd *ehci; | ||
157 | struct ehci_hcd_mv *ehci_mv; | ||
158 | struct resource *r; | ||
159 | int clk_i, retval = -ENODEV; | ||
160 | u32 offset; | ||
161 | size_t size; | ||
162 | |||
163 | if (!pdata) { | ||
164 | dev_err(&pdev->dev, "missing platform_data\n"); | ||
165 | return -ENODEV; | ||
166 | } | ||
167 | |||
168 | if (usb_disabled()) | ||
169 | return -ENODEV; | ||
170 | |||
171 | hcd = usb_create_hcd(&mv_ehci_hc_driver, &pdev->dev, "mv ehci"); | ||
172 | if (!hcd) | ||
173 | return -ENOMEM; | ||
174 | |||
175 | size = sizeof(*ehci_mv) + sizeof(struct clk *) * pdata->clknum; | ||
176 | ehci_mv = kzalloc(size, GFP_KERNEL); | ||
177 | if (ehci_mv == NULL) { | ||
178 | dev_err(&pdev->dev, "cannot allocate ehci_hcd_mv\n"); | ||
179 | retval = -ENOMEM; | ||
180 | goto err_put_hcd; | ||
181 | } | ||
182 | |||
183 | platform_set_drvdata(pdev, ehci_mv); | ||
184 | ehci_mv->pdata = pdata; | ||
185 | ehci_mv->hcd = hcd; | ||
186 | |||
187 | ehci_mv->clknum = pdata->clknum; | ||
188 | for (clk_i = 0; clk_i < ehci_mv->clknum; clk_i++) { | ||
189 | ehci_mv->clk[clk_i] = | ||
190 | clk_get(&pdev->dev, pdata->clkname[clk_i]); | ||
191 | if (IS_ERR(ehci_mv->clk[clk_i])) { | ||
192 | dev_err(&pdev->dev, "error get clck \"%s\"\n", | ||
193 | pdata->clkname[clk_i]); | ||
194 | retval = PTR_ERR(ehci_mv->clk[clk_i]); | ||
195 | goto err_put_clk; | ||
196 | } | ||
197 | } | ||
198 | |||
199 | r = platform_get_resource_byname(pdev, IORESOURCE_MEM, "phyregs"); | ||
200 | if (r == NULL) { | ||
201 | dev_err(&pdev->dev, "no phy I/O memory resource defined\n"); | ||
202 | retval = -ENODEV; | ||
203 | goto err_put_clk; | ||
204 | } | ||
205 | |||
206 | ehci_mv->phy_regs = ioremap(r->start, resource_size(r)); | ||
207 | if (ehci_mv->phy_regs == 0) { | ||
208 | dev_err(&pdev->dev, "failed to map phy I/O memory\n"); | ||
209 | retval = -EFAULT; | ||
210 | goto err_put_clk; | ||
211 | } | ||
212 | |||
213 | r = platform_get_resource_byname(pdev, IORESOURCE_MEM, "capregs"); | ||
214 | if (!r) { | ||
215 | dev_err(&pdev->dev, "no I/O memory resource defined\n"); | ||
216 | retval = -ENODEV; | ||
217 | goto err_iounmap_phyreg; | ||
218 | } | ||
219 | |||
220 | ehci_mv->cap_regs = ioremap(r->start, resource_size(r)); | ||
221 | if (ehci_mv->cap_regs == NULL) { | ||
222 | dev_err(&pdev->dev, "failed to map I/O memory\n"); | ||
223 | retval = -EFAULT; | ||
224 | goto err_iounmap_phyreg; | ||
225 | } | ||
226 | |||
227 | retval = mv_ehci_enable(ehci_mv); | ||
228 | if (retval) { | ||
229 | dev_err(&pdev->dev, "init phy error %d\n", retval); | ||
230 | goto err_iounmap_capreg; | ||
231 | } | ||
232 | |||
233 | offset = readl(ehci_mv->cap_regs) & CAPLENGTH_MASK; | ||
234 | ehci_mv->op_regs = | ||
235 | (void __iomem *) ((unsigned long) ehci_mv->cap_regs + offset); | ||
236 | |||
237 | hcd->rsrc_start = r->start; | ||
238 | hcd->rsrc_len = r->end - r->start + 1; | ||
239 | hcd->regs = ehci_mv->op_regs; | ||
240 | |||
241 | hcd->irq = platform_get_irq(pdev, 0); | ||
242 | if (!hcd->irq) { | ||
243 | dev_err(&pdev->dev, "Cannot get irq."); | ||
244 | retval = -ENODEV; | ||
245 | goto err_disable_clk; | ||
246 | } | ||
247 | |||
248 | ehci = hcd_to_ehci(hcd); | ||
249 | ehci->caps = (struct ehci_caps *) ehci_mv->cap_regs; | ||
250 | ehci->regs = (struct ehci_regs *) ehci_mv->op_regs; | ||
251 | ehci->hcs_params = ehci_readl(ehci, &ehci->caps->hcs_params); | ||
252 | |||
253 | ehci_mv->mode = pdata->mode; | ||
254 | if (ehci_mv->mode == MV_USB_MODE_OTG) { | ||
255 | #ifdef CONFIG_USB_OTG_UTILS | ||
256 | ehci_mv->otg = otg_get_transceiver(); | ||
257 | if (!ehci_mv->otg) { | ||
258 | dev_err(&pdev->dev, | ||
259 | "unable to find transceiver\n"); | ||
260 | retval = -ENODEV; | ||
261 | goto err_disable_clk; | ||
262 | } | ||
263 | |||
264 | retval = otg_set_host(ehci_mv->otg, &hcd->self); | ||
265 | if (retval < 0) { | ||
266 | dev_err(&pdev->dev, | ||
267 | "unable to register with transceiver\n"); | ||
268 | retval = -ENODEV; | ||
269 | goto err_put_transceiver; | ||
270 | } | ||
271 | /* otg will enable clock before use as host */ | ||
272 | mv_ehci_disable(ehci_mv); | ||
273 | #else | ||
274 | dev_info(&pdev->dev, "MV_USB_MODE_OTG " | ||
275 | "must have CONFIG_USB_OTG_UTILS enabled\n"); | ||
276 | goto err_disable_clk; | ||
277 | #endif | ||
278 | } else { | ||
279 | if (pdata->set_vbus) | ||
280 | pdata->set_vbus(1); | ||
281 | |||
282 | retval = usb_add_hcd(hcd, hcd->irq, IRQF_SHARED); | ||
283 | if (retval) { | ||
284 | dev_err(&pdev->dev, | ||
285 | "failed to add hcd with err %d\n", retval); | ||
286 | goto err_set_vbus; | ||
287 | } | ||
288 | } | ||
289 | |||
290 | if (pdata->private_init) | ||
291 | pdata->private_init(ehci_mv->op_regs, ehci_mv->phy_regs); | ||
292 | |||
293 | dev_info(&pdev->dev, | ||
294 | "successful find EHCI device with regs 0x%p irq %d" | ||
295 | " working in %s mode\n", hcd->regs, hcd->irq, | ||
296 | ehci_mv->mode == MV_USB_MODE_OTG ? "OTG" : "Host"); | ||
297 | |||
298 | return 0; | ||
299 | |||
300 | err_set_vbus: | ||
301 | if (pdata->set_vbus) | ||
302 | pdata->set_vbus(0); | ||
303 | #ifdef CONFIG_USB_OTG_UTILS | ||
304 | err_put_transceiver: | ||
305 | if (ehci_mv->otg) | ||
306 | otg_put_transceiver(ehci_mv->otg); | ||
307 | #endif | ||
308 | err_disable_clk: | ||
309 | mv_ehci_disable(ehci_mv); | ||
310 | err_iounmap_capreg: | ||
311 | iounmap(ehci_mv->cap_regs); | ||
312 | err_iounmap_phyreg: | ||
313 | iounmap(ehci_mv->phy_regs); | ||
314 | err_put_clk: | ||
315 | for (clk_i--; clk_i >= 0; clk_i--) | ||
316 | clk_put(ehci_mv->clk[clk_i]); | ||
317 | platform_set_drvdata(pdev, NULL); | ||
318 | kfree(ehci_mv); | ||
319 | err_put_hcd: | ||
320 | usb_put_hcd(hcd); | ||
321 | |||
322 | return retval; | ||
323 | } | ||
324 | |||
325 | static int mv_ehci_remove(struct platform_device *pdev) | ||
326 | { | ||
327 | struct ehci_hcd_mv *ehci_mv = platform_get_drvdata(pdev); | ||
328 | struct usb_hcd *hcd = ehci_mv->hcd; | ||
329 | int clk_i; | ||
330 | |||
331 | if (hcd->rh_registered) | ||
332 | usb_remove_hcd(hcd); | ||
333 | |||
334 | if (ehci_mv->otg) { | ||
335 | otg_set_host(ehci_mv->otg, NULL); | ||
336 | otg_put_transceiver(ehci_mv->otg); | ||
337 | } | ||
338 | |||
339 | if (ehci_mv->mode == MV_USB_MODE_HOST) { | ||
340 | if (ehci_mv->pdata->set_vbus) | ||
341 | ehci_mv->pdata->set_vbus(0); | ||
342 | |||
343 | mv_ehci_disable(ehci_mv); | ||
344 | } | ||
345 | |||
346 | iounmap(ehci_mv->cap_regs); | ||
347 | iounmap(ehci_mv->phy_regs); | ||
348 | |||
349 | for (clk_i = 0; clk_i < ehci_mv->clknum; clk_i++) | ||
350 | clk_put(ehci_mv->clk[clk_i]); | ||
351 | |||
352 | platform_set_drvdata(pdev, NULL); | ||
353 | |||
354 | kfree(ehci_mv); | ||
355 | usb_put_hcd(hcd); | ||
356 | |||
357 | return 0; | ||
358 | } | ||
359 | |||
360 | MODULE_ALIAS("mv-ehci"); | ||
361 | |||
362 | static const struct platform_device_id ehci_id_table[] = { | ||
363 | {"pxa-u2oehci", PXA_U2OEHCI}, | ||
364 | {"pxa-sph", PXA_SPH}, | ||
365 | {"mmp3-hsic", MMP3_HSIC}, | ||
366 | {"mmp3-fsic", MMP3_FSIC}, | ||
367 | {}, | ||
368 | }; | ||
369 | |||
370 | static void mv_ehci_shutdown(struct platform_device *pdev) | ||
371 | { | ||
372 | struct ehci_hcd_mv *ehci_mv = platform_get_drvdata(pdev); | ||
373 | struct usb_hcd *hcd = ehci_mv->hcd; | ||
374 | |||
375 | if (!hcd->rh_registered) | ||
376 | return; | ||
377 | |||
378 | if (hcd->driver->shutdown) | ||
379 | hcd->driver->shutdown(hcd); | ||
380 | } | ||
381 | |||
382 | static struct platform_driver ehci_mv_driver = { | ||
383 | .probe = mv_ehci_probe, | ||
384 | .remove = mv_ehci_remove, | ||
385 | .shutdown = mv_ehci_shutdown, | ||
386 | .driver = { | ||
387 | .name = "mv-ehci", | ||
388 | .bus = &platform_bus_type, | ||
389 | }, | ||
390 | .id_table = ehci_id_table, | ||
391 | }; | ||