]>
Commit | Line | Data |
---|---|---|
efe7daf2 SS |
1 | /* |
2 | * OHCI HCD (Host Controller Driver) for USB. | |
3 | * | |
4 | * TI DA8xx (OMAP-L1x) Bus Glue | |
5 | * | |
6 | * Derived from: ohci-omap.c and ohci-s3c2410.c | |
7 | * Copyright (C) 2008-2009 MontaVista Software, Inc. <source@mvista.com> | |
8 | * | |
9 | * This file is licensed under the terms of the GNU General Public License | |
10 | * version 2. This program is licensed "as is" without any warranty of any | |
11 | * kind, whether express or implied. | |
12 | */ | |
13 | ||
14 | #include <linux/interrupt.h> | |
15 | #include <linux/jiffies.h> | |
16 | #include <linux/platform_device.h> | |
17 | #include <linux/clk.h> | |
18 | ||
19 | #include <mach/da8xx.h> | |
20 | #include <mach/usb.h> | |
21 | ||
22 | #ifndef CONFIG_ARCH_DAVINCI_DA8XX | |
23 | #error "This file is DA8xx bus glue. Define CONFIG_ARCH_DAVINCI_DA8XX." | |
24 | #endif | |
25 | ||
9600cbb2 | 26 | #define CFGCHIP2 DA8XX_SYSCFG0_VIRT(DA8XX_CFGCHIP2_REG) |
efe7daf2 SS |
27 | |
28 | static struct clk *usb11_clk; | |
29 | static struct clk *usb20_clk; | |
30 | ||
31 | /* Over-current indicator change bitmask */ | |
32 | static volatile u16 ocic_mask; | |
33 | ||
34 | static void ohci_da8xx_clock(int on) | |
35 | { | |
36 | u32 cfgchip2; | |
37 | ||
38 | cfgchip2 = __raw_readl(CFGCHIP2); | |
39 | if (on) { | |
40 | clk_enable(usb11_clk); | |
41 | ||
42 | /* | |
43 | * If USB 1.1 reference clock is sourced from USB 2.0 PHY, we | |
44 | * need to enable the USB 2.0 module clocking, start its PHY, | |
45 | * and not allow it to stop the clock during USB 2.0 suspend. | |
46 | */ | |
47 | if (!(cfgchip2 & CFGCHIP2_USB1PHYCLKMUX)) { | |
48 | clk_enable(usb20_clk); | |
49 | ||
50 | cfgchip2 &= ~(CFGCHIP2_RESET | CFGCHIP2_PHYPWRDN); | |
51 | cfgchip2 |= CFGCHIP2_PHY_PLLON; | |
52 | __raw_writel(cfgchip2, CFGCHIP2); | |
53 | ||
54 | pr_info("Waiting for USB PHY clock good...\n"); | |
55 | while (!(__raw_readl(CFGCHIP2) & CFGCHIP2_PHYCLKGD)) | |
56 | cpu_relax(); | |
57 | } | |
58 | ||
59 | /* Enable USB 1.1 PHY */ | |
60 | cfgchip2 |= CFGCHIP2_USB1SUSPENDM; | |
61 | } else { | |
62 | clk_disable(usb11_clk); | |
63 | if (!(cfgchip2 & CFGCHIP2_USB1PHYCLKMUX)) | |
64 | clk_disable(usb20_clk); | |
65 | ||
66 | /* Disable USB 1.1 PHY */ | |
67 | cfgchip2 &= ~CFGCHIP2_USB1SUSPENDM; | |
68 | } | |
69 | __raw_writel(cfgchip2, CFGCHIP2); | |
70 | } | |
71 | ||
72 | /* | |
73 | * Handle the port over-current indicator change. | |
74 | */ | |
75 | static void ohci_da8xx_ocic_handler(struct da8xx_ohci_root_hub *hub, | |
76 | unsigned port) | |
77 | { | |
78 | ocic_mask |= 1 << port; | |
79 | ||
80 | /* Once over-current is detected, the port needs to be powered down */ | |
81 | if (hub->get_oci(port) > 0) | |
82 | hub->set_power(port, 0); | |
83 | } | |
84 | ||
85 | static int ohci_da8xx_init(struct usb_hcd *hcd) | |
86 | { | |
87 | struct device *dev = hcd->self.controller; | |
88 | struct da8xx_ohci_root_hub *hub = dev->platform_data; | |
89 | struct ohci_hcd *ohci = hcd_to_ohci(hcd); | |
90 | int result; | |
91 | u32 rh_a; | |
92 | ||
93 | dev_dbg(dev, "starting USB controller\n"); | |
94 | ||
95 | ohci_da8xx_clock(1); | |
96 | ||
97 | /* | |
98 | * DA8xx only have 1 port connected to the pins but the HC root hub | |
99 | * register A reports 2 ports, thus we'll have to override it... | |
100 | */ | |
101 | ohci->num_ports = 1; | |
102 | ||
103 | result = ohci_init(ohci); | |
104 | if (result < 0) | |
105 | return result; | |
106 | ||
107 | /* | |
108 | * Since we're providing a board-specific root hub port power control | |
109 | * and over-current reporting, we have to override the HC root hub A | |
110 | * register's default value, so that ohci_hub_control() could return | |
111 | * the correct hub descriptor... | |
112 | */ | |
113 | rh_a = ohci_readl(ohci, &ohci->regs->roothub.a); | |
114 | if (hub->set_power) { | |
115 | rh_a &= ~RH_A_NPS; | |
116 | rh_a |= RH_A_PSM; | |
117 | } | |
118 | if (hub->get_oci) { | |
119 | rh_a &= ~RH_A_NOCP; | |
120 | rh_a |= RH_A_OCPM; | |
121 | } | |
122 | rh_a &= ~RH_A_POTPGT; | |
123 | rh_a |= hub->potpgt << 24; | |
124 | ohci_writel(ohci, rh_a, &ohci->regs->roothub.a); | |
125 | ||
126 | return result; | |
127 | } | |
128 | ||
129 | static void ohci_da8xx_stop(struct usb_hcd *hcd) | |
130 | { | |
131 | ohci_stop(hcd); | |
132 | ohci_da8xx_clock(0); | |
133 | } | |
134 | ||
135 | static int ohci_da8xx_start(struct usb_hcd *hcd) | |
136 | { | |
137 | struct ohci_hcd *ohci = hcd_to_ohci(hcd); | |
138 | int result; | |
139 | ||
140 | result = ohci_run(ohci); | |
141 | if (result < 0) | |
142 | ohci_da8xx_stop(hcd); | |
143 | ||
144 | return result; | |
145 | } | |
146 | ||
147 | /* | |
148 | * Update the status data from the hub with the over-current indicator change. | |
149 | */ | |
150 | static int ohci_da8xx_hub_status_data(struct usb_hcd *hcd, char *buf) | |
151 | { | |
152 | int length = ohci_hub_status_data(hcd, buf); | |
153 | ||
154 | /* See if we have OCIC bit set on port 1 */ | |
155 | if (ocic_mask & (1 << 1)) { | |
156 | dev_dbg(hcd->self.controller, "over-current indicator change " | |
157 | "on port 1\n"); | |
158 | ||
159 | if (!length) | |
160 | length = 1; | |
161 | ||
162 | buf[0] |= 1 << 1; | |
163 | } | |
164 | return length; | |
165 | } | |
166 | ||
167 | /* | |
168 | * Look at the control requests to the root hub and see if we need to override. | |
169 | */ | |
170 | static int ohci_da8xx_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, | |
171 | u16 wIndex, char *buf, u16 wLength) | |
172 | { | |
173 | struct device *dev = hcd->self.controller; | |
174 | struct da8xx_ohci_root_hub *hub = dev->platform_data; | |
175 | int temp; | |
176 | ||
177 | switch (typeReq) { | |
178 | case GetPortStatus: | |
179 | /* Check the port number */ | |
180 | if (wIndex != 1) | |
181 | break; | |
182 | ||
183 | dev_dbg(dev, "GetPortStatus(%u)\n", wIndex); | |
184 | ||
185 | temp = roothub_portstatus(hcd_to_ohci(hcd), wIndex - 1); | |
186 | ||
187 | /* The port power status (PPS) bit defaults to 1 */ | |
188 | if (hub->get_power && hub->get_power(wIndex) == 0) | |
189 | temp &= ~RH_PS_PPS; | |
190 | ||
191 | /* The port over-current indicator (POCI) bit is always 0 */ | |
192 | if (hub->get_oci && hub->get_oci(wIndex) > 0) | |
193 | temp |= RH_PS_POCI; | |
194 | ||
195 | /* The over-current indicator change (OCIC) bit is 0 too */ | |
196 | if (ocic_mask & (1 << wIndex)) | |
197 | temp |= RH_PS_OCIC; | |
198 | ||
199 | put_unaligned(cpu_to_le32(temp), (__le32 *)buf); | |
200 | return 0; | |
201 | case SetPortFeature: | |
202 | temp = 1; | |
203 | goto check_port; | |
204 | case ClearPortFeature: | |
205 | temp = 0; | |
206 | ||
207 | check_port: | |
208 | /* Check the port number */ | |
209 | if (wIndex != 1) | |
210 | break; | |
211 | ||
212 | switch (wValue) { | |
213 | case USB_PORT_FEAT_POWER: | |
214 | dev_dbg(dev, "%sPortFeature(%u): %s\n", | |
215 | temp ? "Set" : "Clear", wIndex, "POWER"); | |
216 | ||
217 | if (!hub->set_power) | |
218 | return -EPIPE; | |
219 | ||
220 | return hub->set_power(wIndex, temp) ? -EPIPE : 0; | |
221 | case USB_PORT_FEAT_C_OVER_CURRENT: | |
222 | dev_dbg(dev, "%sPortFeature(%u): %s\n", | |
223 | temp ? "Set" : "Clear", wIndex, | |
224 | "C_OVER_CURRENT"); | |
225 | ||
226 | if (temp) | |
227 | ocic_mask |= 1 << wIndex; | |
228 | else | |
229 | ocic_mask &= ~(1 << wIndex); | |
230 | return 0; | |
231 | } | |
232 | } | |
233 | ||
234 | return ohci_hub_control(hcd, typeReq, wValue, wIndex, buf, wLength); | |
235 | } | |
236 | ||
237 | static const struct hc_driver ohci_da8xx_hc_driver = { | |
238 | .description = hcd_name, | |
239 | .product_desc = "DA8xx OHCI", | |
240 | .hcd_priv_size = sizeof(struct ohci_hcd), | |
241 | ||
242 | /* | |
243 | * generic hardware linkage | |
244 | */ | |
245 | .irq = ohci_irq, | |
246 | .flags = HCD_USB11 | HCD_MEMORY, | |
247 | ||
248 | /* | |
249 | * basic lifecycle operations | |
250 | */ | |
251 | .reset = ohci_da8xx_init, | |
252 | .start = ohci_da8xx_start, | |
253 | .stop = ohci_da8xx_stop, | |
254 | .shutdown = ohci_shutdown, | |
255 | ||
256 | /* | |
257 | * managing i/o requests and associated device resources | |
258 | */ | |
259 | .urb_enqueue = ohci_urb_enqueue, | |
260 | .urb_dequeue = ohci_urb_dequeue, | |
261 | .endpoint_disable = ohci_endpoint_disable, | |
262 | ||
263 | /* | |
264 | * scheduling support | |
265 | */ | |
266 | .get_frame_number = ohci_get_frame, | |
267 | ||
268 | /* | |
269 | * root hub support | |
270 | */ | |
271 | .hub_status_data = ohci_da8xx_hub_status_data, | |
272 | .hub_control = ohci_da8xx_hub_control, | |
273 | ||
274 | #ifdef CONFIG_PM | |
275 | .bus_suspend = ohci_bus_suspend, | |
276 | .bus_resume = ohci_bus_resume, | |
277 | #endif | |
278 | .start_port_reset = ohci_start_port_reset, | |
279 | }; | |
280 | ||
281 | /*-------------------------------------------------------------------------*/ | |
282 | ||
283 | ||
284 | /** | |
285 | * usb_hcd_da8xx_probe - initialize DA8xx-based HCDs | |
286 | * Context: !in_interrupt() | |
287 | * | |
288 | * Allocates basic resources for this USB host controller, and | |
289 | * then invokes the start() method for the HCD associated with it | |
290 | * through the hotplug entry's driver_data. | |
291 | */ | |
292 | static int usb_hcd_da8xx_probe(const struct hc_driver *driver, | |
293 | struct platform_device *pdev) | |
294 | { | |
295 | struct da8xx_ohci_root_hub *hub = pdev->dev.platform_data; | |
296 | struct usb_hcd *hcd; | |
297 | struct resource *mem; | |
298 | int error, irq; | |
299 | ||
300 | if (hub == NULL) | |
301 | return -ENODEV; | |
302 | ||
303 | usb11_clk = clk_get(&pdev->dev, "usb11"); | |
304 | if (IS_ERR(usb11_clk)) | |
305 | return PTR_ERR(usb11_clk); | |
306 | ||
307 | usb20_clk = clk_get(&pdev->dev, "usb20"); | |
308 | if (IS_ERR(usb20_clk)) { | |
309 | error = PTR_ERR(usb20_clk); | |
310 | goto err0; | |
311 | } | |
312 | ||
313 | hcd = usb_create_hcd(driver, &pdev->dev, dev_name(&pdev->dev)); | |
314 | if (!hcd) { | |
315 | error = -ENOMEM; | |
316 | goto err1; | |
317 | } | |
318 | ||
319 | mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); | |
320 | if (!mem) { | |
321 | error = -ENODEV; | |
322 | goto err2; | |
323 | } | |
324 | hcd->rsrc_start = mem->start; | |
28f65c11 | 325 | hcd->rsrc_len = resource_size(mem); |
efe7daf2 SS |
326 | |
327 | if (!request_mem_region(hcd->rsrc_start, hcd->rsrc_len, hcd_name)) { | |
328 | dev_dbg(&pdev->dev, "request_mem_region failed\n"); | |
329 | error = -EBUSY; | |
330 | goto err2; | |
331 | } | |
332 | ||
333 | hcd->regs = ioremap(hcd->rsrc_start, hcd->rsrc_len); | |
334 | if (!hcd->regs) { | |
335 | dev_err(&pdev->dev, "ioremap failed\n"); | |
336 | error = -ENOMEM; | |
337 | goto err3; | |
338 | } | |
339 | ||
340 | ohci_hcd_init(hcd_to_ohci(hcd)); | |
341 | ||
342 | irq = platform_get_irq(pdev, 0); | |
343 | if (irq < 0) { | |
344 | error = -ENODEV; | |
345 | goto err4; | |
346 | } | |
b5dd18d8 | 347 | error = usb_add_hcd(hcd, irq, 0); |
efe7daf2 SS |
348 | if (error) |
349 | goto err4; | |
350 | ||
351 | if (hub->ocic_notify) { | |
352 | error = hub->ocic_notify(ohci_da8xx_ocic_handler); | |
353 | if (!error) | |
354 | return 0; | |
355 | } | |
356 | ||
357 | usb_remove_hcd(hcd); | |
358 | err4: | |
359 | iounmap(hcd->regs); | |
360 | err3: | |
361 | release_mem_region(hcd->rsrc_start, hcd->rsrc_len); | |
362 | err2: | |
363 | usb_put_hcd(hcd); | |
364 | err1: | |
365 | clk_put(usb20_clk); | |
366 | err0: | |
367 | clk_put(usb11_clk); | |
368 | return error; | |
369 | } | |
370 | ||
371 | /** | |
372 | * usb_hcd_da8xx_remove - shutdown processing for DA8xx-based HCDs | |
373 | * @dev: USB Host Controller being removed | |
374 | * Context: !in_interrupt() | |
375 | * | |
376 | * Reverses the effect of usb_hcd_da8xx_probe(), first invoking | |
377 | * the HCD's stop() method. It is always called from a thread | |
378 | * context, normally "rmmod", "apmd", or something similar. | |
379 | */ | |
380 | static inline void | |
381 | usb_hcd_da8xx_remove(struct usb_hcd *hcd, struct platform_device *pdev) | |
382 | { | |
383 | struct da8xx_ohci_root_hub *hub = pdev->dev.platform_data; | |
384 | ||
385 | hub->ocic_notify(NULL); | |
386 | usb_remove_hcd(hcd); | |
387 | iounmap(hcd->regs); | |
388 | release_mem_region(hcd->rsrc_start, hcd->rsrc_len); | |
389 | usb_put_hcd(hcd); | |
390 | clk_put(usb20_clk); | |
391 | clk_put(usb11_clk); | |
392 | } | |
393 | ||
394 | static int ohci_hcd_da8xx_drv_probe(struct platform_device *dev) | |
395 | { | |
396 | return usb_hcd_da8xx_probe(&ohci_da8xx_hc_driver, dev); | |
397 | } | |
398 | ||
399 | static int ohci_hcd_da8xx_drv_remove(struct platform_device *dev) | |
400 | { | |
401 | struct usb_hcd *hcd = platform_get_drvdata(dev); | |
402 | ||
403 | usb_hcd_da8xx_remove(hcd, dev); | |
404 | platform_set_drvdata(dev, NULL); | |
405 | ||
406 | return 0; | |
407 | } | |
408 | ||
409 | #ifdef CONFIG_PM | |
410 | static int ohci_da8xx_suspend(struct platform_device *dev, pm_message_t message) | |
411 | { | |
412 | struct usb_hcd *hcd = platform_get_drvdata(dev); | |
413 | struct ohci_hcd *ohci = hcd_to_ohci(hcd); | |
414 | ||
415 | if (time_before(jiffies, ohci->next_statechange)) | |
416 | msleep(5); | |
417 | ohci->next_statechange = jiffies; | |
418 | ||
419 | ohci_da8xx_clock(0); | |
420 | hcd->state = HC_STATE_SUSPENDED; | |
421 | dev->dev.power.power_state = PMSG_SUSPEND; | |
422 | return 0; | |
423 | } | |
424 | ||
425 | static int ohci_da8xx_resume(struct platform_device *dev) | |
426 | { | |
427 | struct usb_hcd *hcd = platform_get_drvdata(dev); | |
428 | struct ohci_hcd *ohci = hcd_to_ohci(hcd); | |
429 | ||
430 | if (time_before(jiffies, ohci->next_statechange)) | |
431 | msleep(5); | |
432 | ohci->next_statechange = jiffies; | |
433 | ||
434 | ohci_da8xx_clock(1); | |
435 | dev->dev.power.power_state = PMSG_ON; | |
436 | usb_hcd_resume_root_hub(hcd); | |
437 | return 0; | |
438 | } | |
439 | #endif | |
440 | ||
441 | /* | |
442 | * Driver definition to register with platform structure. | |
443 | */ | |
444 | static struct platform_driver ohci_hcd_da8xx_driver = { | |
445 | .probe = ohci_hcd_da8xx_drv_probe, | |
446 | .remove = ohci_hcd_da8xx_drv_remove, | |
447 | .shutdown = usb_hcd_platform_shutdown, | |
448 | #ifdef CONFIG_PM | |
449 | .suspend = ohci_da8xx_suspend, | |
450 | .resume = ohci_da8xx_resume, | |
451 | #endif | |
452 | .driver = { | |
453 | .owner = THIS_MODULE, | |
454 | .name = "ohci", | |
455 | }, | |
456 | }; | |
ab59ac01 JL |
457 | |
458 | MODULE_ALIAS("platform:ohci"); |