aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/usb/host/ehci-mv.c
diff options
context:
space:
mode:
authorNeil Zhang <zhangwm@marvell.com>2011-12-20 00:20:23 -0500
committerFelipe Balbi <balbi@ti.com>2011-12-20 05:58:13 -0500
commit3a082ec9b2f544a81e977cfa259e3f990a995dc8 (patch)
tree62afa27c120afb53eef6a762c3a7ab430e986f01 /drivers/usb/host/ehci-mv.c
parent277164f03f466b7a1ea0d0c3dac8b8a0599ce0dc (diff)
USB: EHCI: Add Marvell Host Controller driver
This patch adds support for EHCI compliant HSUSB Host controller found on Marvell Socs. It fits both OTG and SPH controller on marvell Socs, including PXA9xx/MMP2/MMP3/MGx. Signed-off-by: Neil Zhang <zhangwm@marvell.com> Signed-off-by: Felipe Balbi <balbi@ti.com>
Diffstat (limited to 'drivers/usb/host/ehci-mv.c')
-rw-r--r--drivers/usb/host/ehci-mv.c391
1 files changed, 391 insertions, 0 deletions
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
21struct 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
40static 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
48static 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
56static 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
70static 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
77static 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
110static 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
152static 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
300err_set_vbus:
301 if (pdata->set_vbus)
302 pdata->set_vbus(0);
303#ifdef CONFIG_USB_OTG_UTILS
304err_put_transceiver:
305 if (ehci_mv->otg)
306 otg_put_transceiver(ehci_mv->otg);
307#endif
308err_disable_clk:
309 mv_ehci_disable(ehci_mv);
310err_iounmap_capreg:
311 iounmap(ehci_mv->cap_regs);
312err_iounmap_phyreg:
313 iounmap(ehci_mv->phy_regs);
314err_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);
319err_put_hcd:
320 usb_put_hcd(hcd);
321
322 return retval;
323}
324
325static 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
360MODULE_ALIAS("mv-ehci");
361
362static 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
370static 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
382static 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};