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