aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/usb/host/ehci-mxc.c
diff options
context:
space:
mode:
authorDaniel Mack <daniel@caiaq.de>2009-10-27 20:14:59 -0400
committerGreg Kroah-Hartman <gregkh@suse.de>2009-12-11 14:55:18 -0500
commit7e8d5cd93fac4d3720d8f780b350c9421e8997d4 (patch)
treefe7bf118d39c3c5d85e2270bc2cf34f56ea62455 /drivers/usb/host/ehci-mxc.c
parented1db3ada189c9af592c4d2971b22b482b68aafe (diff)
USB: Add EHCI support for MX27 and MX31 based boards
The Freescale MX27 and MX31 SoCs have a EHCI controller onboard. The controller is capable of USB on the go. This patch adds a driver to support all three of them. Users have to pass details about serial interface configuration in the platform data. The USB OTG core used here is the ARC core, so the driver should be renamed and probably be merged with ehci-fsl.c eventually. Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de> Signed-off-by: Daniel Mack <daniel@caiaq.de> Cc: David Brownell <dbrownell@users.sourceforge.net> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
Diffstat (limited to 'drivers/usb/host/ehci-mxc.c')
-rw-r--r--drivers/usb/host/ehci-mxc.c296
1 files changed, 296 insertions, 0 deletions
diff --git a/drivers/usb/host/ehci-mxc.c b/drivers/usb/host/ehci-mxc.c
new file mode 100644
index 000000000000..35c56f40bdbb
--- /dev/null
+++ b/drivers/usb/host/ehci-mxc.c
@@ -0,0 +1,296 @@
1/*
2 * Copyright (c) 2008 Sascha Hauer <s.hauer@pengutronix.de>, Pengutronix
3 * Copyright (c) 2009 Daniel Mack <daniel@caiaq.de>
4 *
5 * This program is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License as published by the
7 * Free Software Foundation; either version 2 of the License, or (at your
8 * option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
13 * for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software Foundation,
17 * Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18 */
19
20#include <linux/platform_device.h>
21#include <linux/clk.h>
22#include <linux/delay.h>
23#include <linux/usb/otg.h>
24
25#include <mach/mxc_ehci.h>
26
27#define ULPI_VIEWPORT_OFFSET 0x170
28#define PORTSC_OFFSET 0x184
29#define USBMODE_OFFSET 0x1a8
30#define USBMODE_CM_HOST 3
31
32struct ehci_mxc_priv {
33 struct clk *usbclk, *ahbclk;
34 struct usb_hcd *hcd;
35};
36
37/* called during probe() after chip reset completes */
38static int ehci_mxc_setup(struct usb_hcd *hcd)
39{
40 struct ehci_hcd *ehci = hcd_to_ehci(hcd);
41 int retval;
42
43 /* EHCI registers start at offset 0x100 */
44 ehci->caps = hcd->regs + 0x100;
45 ehci->regs = hcd->regs + 0x100 +
46 HC_LENGTH(ehci_readl(ehci, &ehci->caps->hc_capbase));
47 dbg_hcs_params(ehci, "reset");
48 dbg_hcc_params(ehci, "reset");
49
50 /* cache this readonly data; minimize chip reads */
51 ehci->hcs_params = ehci_readl(ehci, &ehci->caps->hcs_params);
52
53 retval = ehci_halt(ehci);
54 if (retval)
55 return retval;
56
57 /* data structure init */
58 retval = ehci_init(hcd);
59 if (retval)
60 return retval;
61
62 hcd->has_tt = 1;
63
64 ehci->sbrn = 0x20;
65
66 ehci_reset(ehci);
67
68 ehci_port_power(ehci, 0);
69 return 0;
70}
71
72static const struct hc_driver ehci_mxc_hc_driver = {
73 .description = hcd_name,
74 .product_desc = "Freescale On-Chip EHCI Host Controller",
75 .hcd_priv_size = sizeof(struct ehci_hcd),
76
77 /*
78 * generic hardware linkage
79 */
80 .irq = ehci_irq,
81 .flags = HCD_USB2 | HCD_MEMORY,
82
83 /*
84 * basic lifecycle operations
85 */
86 .reset = ehci_mxc_setup,
87 .start = ehci_run,
88 .stop = ehci_stop,
89 .shutdown = ehci_shutdown,
90
91 /*
92 * managing i/o requests and associated device resources
93 */
94 .urb_enqueue = ehci_urb_enqueue,
95 .urb_dequeue = ehci_urb_dequeue,
96 .endpoint_disable = ehci_endpoint_disable,
97
98 /*
99 * scheduling support
100 */
101 .get_frame_number = ehci_get_frame,
102
103 /*
104 * root hub support
105 */
106 .hub_status_data = ehci_hub_status_data,
107 .hub_control = ehci_hub_control,
108 .bus_suspend = ehci_bus_suspend,
109 .bus_resume = ehci_bus_resume,
110 .relinquish_port = ehci_relinquish_port,
111 .port_handed_over = ehci_port_handed_over,
112};
113
114static int ehci_mxc_drv_probe(struct platform_device *pdev)
115{
116 struct mxc_usbh_platform_data *pdata = pdev->dev.platform_data;
117 struct usb_hcd *hcd;
118 struct resource *res;
119 int irq, ret, temp;
120 struct ehci_mxc_priv *priv;
121 struct device *dev = &pdev->dev;
122
123 dev_info(&pdev->dev, "initializing i.MX USB Controller\n");
124
125 if (!pdata) {
126 dev_err(dev, "No platform data given, bailing out.\n");
127 return -EINVAL;
128 }
129
130 irq = platform_get_irq(pdev, 0);
131
132 hcd = usb_create_hcd(&ehci_mxc_hc_driver, dev, dev_name(dev));
133 if (!hcd)
134 return -ENOMEM;
135
136 priv = kzalloc(sizeof(*priv), GFP_KERNEL);
137 if (!priv) {
138 ret = -ENOMEM;
139 goto err_alloc;
140 }
141
142 res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
143 if (!res) {
144 dev_err(dev, "Found HC with no register addr. Check setup!\n");
145 ret = -ENODEV;
146 goto err_get_resource;
147 }
148
149 hcd->rsrc_start = res->start;
150 hcd->rsrc_len = resource_size(res);
151
152 if (!request_mem_region(hcd->rsrc_start, hcd->rsrc_len, hcd_name)) {
153 dev_dbg(dev, "controller already in use\n");
154 ret = -EBUSY;
155 goto err_request_mem;
156 }
157
158 hcd->regs = ioremap(hcd->rsrc_start, hcd->rsrc_len);
159 if (!hcd->regs) {
160 dev_err(dev, "error mapping memory\n");
161 ret = -EFAULT;
162 goto err_ioremap;
163 }
164
165 /* enable clocks */
166 priv->usbclk = clk_get(dev, "usb");
167 if (IS_ERR(priv->usbclk)) {
168 ret = PTR_ERR(priv->usbclk);
169 goto err_clk;
170 }
171 clk_enable(priv->usbclk);
172
173 if (!cpu_is_mx35()) {
174 priv->ahbclk = clk_get(dev, "usb_ahb");
175 if (IS_ERR(priv->ahbclk)) {
176 ret = PTR_ERR(priv->ahbclk);
177 goto err_clk_ahb;
178 }
179 clk_enable(priv->ahbclk);
180 }
181
182 /* set USBMODE to host mode */
183 temp = readl(hcd->regs + USBMODE_OFFSET);
184 writel(temp | USBMODE_CM_HOST, hcd->regs + USBMODE_OFFSET);
185
186 /* set up the PORTSCx register */
187 writel(pdata->portsc, hcd->regs + PORTSC_OFFSET);
188 mdelay(10);
189
190 /* setup USBCONTROL. */
191 ret = mxc_set_usbcontrol(pdev->id, pdata->flags);
192 if (ret < 0)
193 goto err_init;
194
195 /* call platform specific init function */
196 if (pdata->init) {
197 ret = pdata->init(pdev);
198 if (ret) {
199 dev_err(dev, "platform init failed\n");
200 goto err_init;
201 }
202 }
203
204 /* most platforms need some time to settle changed IO settings */
205 mdelay(10);
206
207 /* Initialize the transceiver */
208 if (pdata->otg) {
209 pdata->otg->io_priv = hcd->regs + ULPI_VIEWPORT_OFFSET;
210 if (otg_init(pdata->otg) != 0)
211 dev_err(dev, "unable to init transceiver\n");
212 else if (otg_set_vbus(pdata->otg, 1) != 0)
213 dev_err(dev, "unable to enable vbus on transceiver\n");
214 }
215
216 priv->hcd = hcd;
217 platform_set_drvdata(pdev, priv);
218
219 ret = usb_add_hcd(hcd, irq, IRQF_DISABLED | IRQF_SHARED);
220 if (ret)
221 goto err_add;
222
223 return 0;
224
225err_add:
226 if (pdata && pdata->exit)
227 pdata->exit(pdev);
228err_init:
229 if (priv->ahbclk) {
230 clk_disable(priv->ahbclk);
231 clk_put(priv->ahbclk);
232 }
233err_clk_ahb:
234 clk_disable(priv->usbclk);
235 clk_put(priv->usbclk);
236err_clk:
237 iounmap(hcd->regs);
238err_ioremap:
239 release_mem_region(hcd->rsrc_start, hcd->rsrc_len);
240err_request_mem:
241err_get_resource:
242 kfree(priv);
243err_alloc:
244 usb_put_hcd(hcd);
245 return ret;
246}
247
248static int __exit ehci_mxc_drv_remove(struct platform_device *pdev)
249{
250 struct mxc_usbh_platform_data *pdata = pdev->dev.platform_data;
251 struct ehci_mxc_priv *priv = platform_get_drvdata(pdev);
252 struct usb_hcd *hcd = priv->hcd;
253
254 if (pdata && pdata->exit)
255 pdata->exit(pdev);
256
257 if (pdata->otg)
258 otg_shutdown(pdata->otg);
259
260 usb_remove_hcd(hcd);
261 iounmap(hcd->regs);
262 release_mem_region(hcd->rsrc_start, hcd->rsrc_len);
263 usb_put_hcd(hcd);
264 platform_set_drvdata(pdev, NULL);
265
266 clk_disable(priv->usbclk);
267 clk_put(priv->usbclk);
268 if (priv->ahbclk) {
269 clk_disable(priv->ahbclk);
270 clk_put(priv->ahbclk);
271 }
272
273 kfree(priv);
274
275 return 0;
276}
277
278static void ehci_mxc_drv_shutdown(struct platform_device *pdev)
279{
280 struct ehci_mxc_priv *priv = platform_get_drvdata(pdev);
281 struct usb_hcd *hcd = priv->hcd;
282
283 if (hcd->driver->shutdown)
284 hcd->driver->shutdown(hcd);
285}
286
287MODULE_ALIAS("platform:mxc-ehci");
288
289static struct platform_driver ehci_mxc_driver = {
290 .probe = ehci_mxc_drv_probe,
291 .remove = __exit_p(ehci_mxc_drv_remove),
292 .shutdown = ehci_mxc_drv_shutdown,
293 .driver = {
294 .name = "mxc-ehci",
295 },
296};