diff options
Diffstat (limited to 'arch/arm/mach-pxa/pxa3xx-ulpi.c')
-rw-r--r-- | arch/arm/mach-pxa/pxa3xx-ulpi.c | 400 |
1 files changed, 400 insertions, 0 deletions
diff --git a/arch/arm/mach-pxa/pxa3xx-ulpi.c b/arch/arm/mach-pxa/pxa3xx-ulpi.c new file mode 100644 index 000000000000..ce7168b233e2 --- /dev/null +++ b/arch/arm/mach-pxa/pxa3xx-ulpi.c | |||
@@ -0,0 +1,400 @@ | |||
1 | /* | ||
2 | * linux/arch/arm/mach-pxa/pxa3xx-ulpi.c | ||
3 | * | ||
4 | * code specific to pxa3xx aka Monahans | ||
5 | * | ||
6 | * Copyright (C) 2010 CompuLab Ltd. | ||
7 | * | ||
8 | * 2010-13-07: Igor Grinberg <grinberg@compulab.co.il> | ||
9 | * initial version: pxa310 USB Host mode support | ||
10 | * | ||
11 | * This program is free software; you can redistribute it and/or modify | ||
12 | * it under the terms of the GNU General Public License version 2 as | ||
13 | * published by the Free Software Foundation. | ||
14 | */ | ||
15 | |||
16 | #include <linux/module.h> | ||
17 | #include <linux/kernel.h> | ||
18 | #include <linux/slab.h> | ||
19 | #include <linux/device.h> | ||
20 | #include <linux/platform_device.h> | ||
21 | #include <linux/err.h> | ||
22 | #include <linux/io.h> | ||
23 | #include <linux/delay.h> | ||
24 | #include <linux/clk.h> | ||
25 | #include <linux/usb.h> | ||
26 | #include <linux/usb/otg.h> | ||
27 | |||
28 | #include <mach/hardware.h> | ||
29 | #include <mach/regs-u2d.h> | ||
30 | #include <mach/pxa3xx-u2d.h> | ||
31 | |||
32 | struct pxa3xx_u2d_ulpi { | ||
33 | struct clk *clk; | ||
34 | void __iomem *mmio_base; | ||
35 | |||
36 | struct otg_transceiver *otg; | ||
37 | unsigned int ulpi_mode; | ||
38 | }; | ||
39 | |||
40 | static struct pxa3xx_u2d_ulpi *u2d; | ||
41 | |||
42 | static inline u32 u2d_readl(u32 reg) | ||
43 | { | ||
44 | return __raw_readl(u2d->mmio_base + reg); | ||
45 | } | ||
46 | |||
47 | static inline void u2d_writel(u32 reg, u32 val) | ||
48 | { | ||
49 | __raw_writel(val, u2d->mmio_base + reg); | ||
50 | } | ||
51 | |||
52 | #if defined(CONFIG_PXA310_ULPI) | ||
53 | enum u2d_ulpi_phy_mode { | ||
54 | SYNCH = 0, | ||
55 | CARKIT = (1 << 0), | ||
56 | SER_3PIN = (1 << 1), | ||
57 | SER_6PIN = (1 << 2), | ||
58 | LOWPOWER = (1 << 3), | ||
59 | }; | ||
60 | |||
61 | static inline enum u2d_ulpi_phy_mode pxa310_ulpi_get_phymode(void) | ||
62 | { | ||
63 | return (u2d_readl(U2DOTGUSR) >> 28) & 0xF; | ||
64 | } | ||
65 | |||
66 | static int pxa310_ulpi_poll(void) | ||
67 | { | ||
68 | int timeout = 50000; | ||
69 | |||
70 | while (timeout--) { | ||
71 | if (!(u2d_readl(U2DOTGUCR) & U2DOTGUCR_RUN)) | ||
72 | return 0; | ||
73 | |||
74 | cpu_relax(); | ||
75 | } | ||
76 | |||
77 | pr_warning("%s: ULPI access timed out!\n", __func__); | ||
78 | |||
79 | return -ETIMEDOUT; | ||
80 | } | ||
81 | |||
82 | static int pxa310_ulpi_read(struct otg_transceiver *otg, u32 reg) | ||
83 | { | ||
84 | int err; | ||
85 | |||
86 | if (pxa310_ulpi_get_phymode() != SYNCH) { | ||
87 | pr_warning("%s: PHY is not in SYNCH mode!\n", __func__); | ||
88 | return -EBUSY; | ||
89 | } | ||
90 | |||
91 | u2d_writel(U2DOTGUCR, U2DOTGUCR_RUN | U2DOTGUCR_RNW | (reg << 16)); | ||
92 | msleep(5); | ||
93 | |||
94 | err = pxa310_ulpi_poll(); | ||
95 | if (err) | ||
96 | return err; | ||
97 | |||
98 | return u2d_readl(U2DOTGUCR) & U2DOTGUCR_RDATA; | ||
99 | } | ||
100 | |||
101 | static int pxa310_ulpi_write(struct otg_transceiver *otg, u32 val, u32 reg) | ||
102 | { | ||
103 | if (pxa310_ulpi_get_phymode() != SYNCH) { | ||
104 | pr_warning("%s: PHY is not in SYNCH mode!\n", __func__); | ||
105 | return -EBUSY; | ||
106 | } | ||
107 | |||
108 | u2d_writel(U2DOTGUCR, U2DOTGUCR_RUN | (reg << 16) | (val << 8)); | ||
109 | msleep(5); | ||
110 | |||
111 | return pxa310_ulpi_poll(); | ||
112 | } | ||
113 | |||
114 | struct otg_io_access_ops pxa310_ulpi_access_ops = { | ||
115 | .read = pxa310_ulpi_read, | ||
116 | .write = pxa310_ulpi_write, | ||
117 | }; | ||
118 | |||
119 | static void pxa310_otg_transceiver_rtsm(void) | ||
120 | { | ||
121 | u32 u2dotgcr; | ||
122 | |||
123 | /* put PHY to sync mode */ | ||
124 | u2dotgcr = u2d_readl(U2DOTGCR); | ||
125 | u2dotgcr |= U2DOTGCR_RTSM | U2DOTGCR_UTMID; | ||
126 | u2d_writel(U2DOTGCR, u2dotgcr); | ||
127 | msleep(10); | ||
128 | |||
129 | /* setup OTG sync mode */ | ||
130 | u2dotgcr = u2d_readl(U2DOTGCR); | ||
131 | u2dotgcr |= U2DOTGCR_ULAF; | ||
132 | u2dotgcr &= ~(U2DOTGCR_SMAF | U2DOTGCR_CKAF); | ||
133 | u2d_writel(U2DOTGCR, u2dotgcr); | ||
134 | } | ||
135 | |||
136 | static int pxa310_start_otg_host_transcvr(struct usb_bus *host) | ||
137 | { | ||
138 | int err; | ||
139 | |||
140 | pxa310_otg_transceiver_rtsm(); | ||
141 | |||
142 | err = otg_init(u2d->otg); | ||
143 | if (err) { | ||
144 | pr_err("OTG transceiver init failed"); | ||
145 | return err; | ||
146 | } | ||
147 | |||
148 | err = otg_set_vbus(u2d->otg, 1); | ||
149 | if (err) { | ||
150 | pr_err("OTG transceiver VBUS set failed"); | ||
151 | return err; | ||
152 | } | ||
153 | |||
154 | err = otg_set_host(u2d->otg, host); | ||
155 | if (err) | ||
156 | pr_err("OTG transceiver Host mode set failed"); | ||
157 | |||
158 | return err; | ||
159 | } | ||
160 | |||
161 | static int pxa310_start_otg_hc(struct usb_bus *host) | ||
162 | { | ||
163 | u32 u2dotgcr; | ||
164 | int err; | ||
165 | |||
166 | /* disable USB device controller */ | ||
167 | u2d_writel(U2DCR, u2d_readl(U2DCR) & ~U2DCR_UDE); | ||
168 | u2d_writel(U2DOTGCR, u2d_readl(U2DOTGCR) | U2DOTGCR_UTMID); | ||
169 | u2d_writel(U2DOTGICR, u2d_readl(U2DOTGICR) & ~0x37F7F); | ||
170 | |||
171 | err = pxa310_start_otg_host_transcvr(host); | ||
172 | if (err) | ||
173 | return err; | ||
174 | |||
175 | /* set xceiver mode */ | ||
176 | if (u2d->ulpi_mode & ULPI_IC_6PIN_SERIAL) | ||
177 | u2d_writel(U2DP3CR, u2d_readl(U2DP3CR) & ~U2DP3CR_P2SS); | ||
178 | else if (u2d->ulpi_mode & ULPI_IC_3PIN_SERIAL) | ||
179 | u2d_writel(U2DP3CR, u2d_readl(U2DP3CR) | U2DP3CR_P2SS); | ||
180 | |||
181 | /* start OTG host controller */ | ||
182 | u2dotgcr = u2d_readl(U2DOTGCR) | U2DOTGCR_SMAF; | ||
183 | u2d_writel(U2DOTGCR, u2dotgcr & ~(U2DOTGCR_ULAF | U2DOTGCR_CKAF)); | ||
184 | |||
185 | return 0; | ||
186 | } | ||
187 | |||
188 | static void pxa310_stop_otg_hc(void) | ||
189 | { | ||
190 | pxa310_otg_transceiver_rtsm(); | ||
191 | |||
192 | otg_set_host(u2d->otg, NULL); | ||
193 | otg_set_vbus(u2d->otg, 0); | ||
194 | otg_shutdown(u2d->otg); | ||
195 | } | ||
196 | |||
197 | static void pxa310_u2d_setup_otg_hc(void) | ||
198 | { | ||
199 | u32 u2dotgcr; | ||
200 | |||
201 | u2dotgcr = u2d_readl(U2DOTGCR); | ||
202 | u2dotgcr |= U2DOTGCR_ULAF | U2DOTGCR_UTMID; | ||
203 | u2dotgcr &= ~(U2DOTGCR_SMAF | U2DOTGCR_CKAF); | ||
204 | u2d_writel(U2DOTGCR, u2dotgcr); | ||
205 | msleep(5); | ||
206 | u2d_writel(U2DOTGCR, u2dotgcr | U2DOTGCR_ULE); | ||
207 | msleep(5); | ||
208 | u2d_writel(U2DOTGICR, u2d_readl(U2DOTGICR) & ~0x37F7F); | ||
209 | } | ||
210 | |||
211 | static int pxa310_otg_init(struct pxa3xx_u2d_platform_data *pdata) | ||
212 | { | ||
213 | unsigned int ulpi_mode = ULPI_OTG_DRVVBUS; | ||
214 | |||
215 | if (pdata) { | ||
216 | if (pdata->ulpi_mode & ULPI_SER_6PIN) | ||
217 | ulpi_mode |= ULPI_IC_6PIN_SERIAL; | ||
218 | else if (pdata->ulpi_mode & ULPI_SER_3PIN) | ||
219 | ulpi_mode |= ULPI_IC_3PIN_SERIAL; | ||
220 | } | ||
221 | |||
222 | u2d->ulpi_mode = ulpi_mode; | ||
223 | |||
224 | u2d->otg = otg_ulpi_create(&pxa310_ulpi_access_ops, ulpi_mode); | ||
225 | if (!u2d->otg) | ||
226 | return -ENOMEM; | ||
227 | |||
228 | u2d->otg->io_priv = u2d->mmio_base; | ||
229 | |||
230 | return 0; | ||
231 | } | ||
232 | |||
233 | static void pxa310_otg_exit(void) | ||
234 | { | ||
235 | kfree(u2d->otg); | ||
236 | } | ||
237 | #else | ||
238 | static inline void pxa310_u2d_setup_otg_hc(void) {} | ||
239 | static inline int pxa310_start_otg_hc(struct usb_bus *host) | ||
240 | { | ||
241 | return 0; | ||
242 | } | ||
243 | static inline void pxa310_stop_otg_hc(void) {} | ||
244 | static inline int pxa310_otg_init(struct pxa3xx_u2d_platform_data *pdata) | ||
245 | { | ||
246 | return 0; | ||
247 | } | ||
248 | static inline void pxa310_otg_exit(void) {} | ||
249 | #endif /* CONFIG_PXA310_ULPI */ | ||
250 | |||
251 | int pxa3xx_u2d_start_hc(struct usb_bus *host) | ||
252 | { | ||
253 | int err = 0; | ||
254 | |||
255 | /* In case the PXA3xx ULPI isn't used, do nothing. */ | ||
256 | if (!u2d) | ||
257 | return 0; | ||
258 | |||
259 | clk_enable(u2d->clk); | ||
260 | |||
261 | if (cpu_is_pxa310()) { | ||
262 | pxa310_u2d_setup_otg_hc(); | ||
263 | err = pxa310_start_otg_hc(host); | ||
264 | } | ||
265 | |||
266 | return err; | ||
267 | } | ||
268 | |||
269 | void pxa3xx_u2d_stop_hc(struct usb_bus *host) | ||
270 | { | ||
271 | /* In case the PXA3xx ULPI isn't used, do nothing. */ | ||
272 | if (!u2d) | ||
273 | return; | ||
274 | |||
275 | if (cpu_is_pxa310()) | ||
276 | pxa310_stop_otg_hc(); | ||
277 | |||
278 | clk_disable(u2d->clk); | ||
279 | } | ||
280 | |||
281 | static int pxa3xx_u2d_probe(struct platform_device *pdev) | ||
282 | { | ||
283 | struct pxa3xx_u2d_platform_data *pdata = pdev->dev.platform_data; | ||
284 | struct resource *r; | ||
285 | int err; | ||
286 | |||
287 | u2d = kzalloc(sizeof(struct pxa3xx_u2d_ulpi), GFP_KERNEL); | ||
288 | if (!u2d) { | ||
289 | dev_err(&pdev->dev, "failed to allocate memory\n"); | ||
290 | return -ENOMEM; | ||
291 | } | ||
292 | |||
293 | u2d->clk = clk_get(&pdev->dev, NULL); | ||
294 | if (IS_ERR(u2d->clk)) { | ||
295 | dev_err(&pdev->dev, "failed to get u2d clock\n"); | ||
296 | err = PTR_ERR(u2d->clk); | ||
297 | goto err_free_mem; | ||
298 | } | ||
299 | |||
300 | r = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
301 | if (!r) { | ||
302 | dev_err(&pdev->dev, "no IO memory resource defined\n"); | ||
303 | err = -ENODEV; | ||
304 | goto err_put_clk; | ||
305 | } | ||
306 | |||
307 | r = request_mem_region(r->start, resource_size(r), pdev->name); | ||
308 | if (!r) { | ||
309 | dev_err(&pdev->dev, "failed to request memory resource\n"); | ||
310 | err = -EBUSY; | ||
311 | goto err_put_clk; | ||
312 | } | ||
313 | |||
314 | u2d->mmio_base = ioremap(r->start, resource_size(r)); | ||
315 | if (!u2d->mmio_base) { | ||
316 | dev_err(&pdev->dev, "ioremap() failed\n"); | ||
317 | err = -ENODEV; | ||
318 | goto err_free_res; | ||
319 | } | ||
320 | |||
321 | if (pdata->init) { | ||
322 | err = pdata->init(&pdev->dev); | ||
323 | if (err) | ||
324 | goto err_free_io; | ||
325 | } | ||
326 | |||
327 | /* Only PXA310 U2D has OTG functionality */ | ||
328 | if (cpu_is_pxa310()) { | ||
329 | err = pxa310_otg_init(pdata); | ||
330 | if (err) | ||
331 | goto err_free_plat; | ||
332 | } | ||
333 | |||
334 | platform_set_drvdata(pdev, &u2d); | ||
335 | |||
336 | return 0; | ||
337 | |||
338 | err_free_plat: | ||
339 | if (pdata->exit) | ||
340 | pdata->exit(&pdev->dev); | ||
341 | err_free_io: | ||
342 | iounmap(u2d->mmio_base); | ||
343 | err_free_res: | ||
344 | release_mem_region(r->start, resource_size(r)); | ||
345 | err_put_clk: | ||
346 | clk_put(u2d->clk); | ||
347 | err_free_mem: | ||
348 | kfree(u2d); | ||
349 | return err; | ||
350 | } | ||
351 | |||
352 | static int pxa3xx_u2d_remove(struct platform_device *pdev) | ||
353 | { | ||
354 | struct pxa3xx_u2d_platform_data *pdata = pdev->dev.platform_data; | ||
355 | struct resource *r; | ||
356 | |||
357 | if (cpu_is_pxa310()) { | ||
358 | pxa310_stop_otg_hc(); | ||
359 | pxa310_otg_exit(); | ||
360 | } | ||
361 | |||
362 | if (pdata->exit) | ||
363 | pdata->exit(&pdev->dev); | ||
364 | |||
365 | platform_set_drvdata(pdev, NULL); | ||
366 | iounmap(u2d->mmio_base); | ||
367 | r = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
368 | release_mem_region(r->start, resource_size(r)); | ||
369 | |||
370 | clk_put(u2d->clk); | ||
371 | |||
372 | kfree(u2d); | ||
373 | |||
374 | return 0; | ||
375 | } | ||
376 | |||
377 | static struct platform_driver pxa3xx_u2d_ulpi_driver = { | ||
378 | .driver = { | ||
379 | .name = "pxa3xx-u2d", | ||
380 | .owner = THIS_MODULE, | ||
381 | }, | ||
382 | .probe = pxa3xx_u2d_probe, | ||
383 | .remove = pxa3xx_u2d_remove, | ||
384 | }; | ||
385 | |||
386 | static int pxa3xx_u2d_ulpi_init(void) | ||
387 | { | ||
388 | return platform_driver_register(&pxa3xx_u2d_ulpi_driver); | ||
389 | } | ||
390 | module_init(pxa3xx_u2d_ulpi_init); | ||
391 | |||
392 | static void __exit pxa3xx_u2d_ulpi_exit(void) | ||
393 | { | ||
394 | platform_driver_unregister(&pxa3xx_u2d_ulpi_driver); | ||
395 | } | ||
396 | module_exit(pxa3xx_u2d_ulpi_exit); | ||
397 | |||
398 | MODULE_DESCRIPTION("PXA3xx U2D ULPI driver"); | ||
399 | MODULE_AUTHOR("Igor Grinberg"); | ||
400 | MODULE_LICENSE("GPL v2"); | ||