]>
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> | |
ec2a0833 | 20 | #include <linux/platform_data/usb-davinci.h> |
efe7daf2 SS |
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; | |
d4f09e28 | 88 | struct da8xx_ohci_root_hub *hub = dev_get_platdata(dev); |
efe7daf2 SS |
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; | |
d4f09e28 | 174 | struct da8xx_ohci_root_hub *hub = dev_get_platdata(dev); |
efe7daf2 SS |
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 | { | |
d4f09e28 | 295 | struct da8xx_ohci_root_hub *hub = dev_get_platdata(&pdev->dev); |
efe7daf2 SS |
296 | struct usb_hcd *hcd; |
297 | struct resource *mem; | |
298 | int error, irq; | |
299 | ||
300 | if (hub == NULL) | |
301 | return -ENODEV; | |
302 | ||
644db166 | 303 | usb11_clk = devm_clk_get(&pdev->dev, "usb11"); |
efe7daf2 SS |
304 | if (IS_ERR(usb11_clk)) |
305 | return PTR_ERR(usb11_clk); | |
306 | ||
644db166 JH |
307 | usb20_clk = devm_clk_get(&pdev->dev, "usb20"); |
308 | if (IS_ERR(usb20_clk)) | |
309 | return PTR_ERR(usb20_clk); | |
efe7daf2 SS |
310 | |
311 | hcd = usb_create_hcd(driver, &pdev->dev, dev_name(&pdev->dev)); | |
644db166 JH |
312 | if (!hcd) |
313 | return -ENOMEM; | |
efe7daf2 SS |
314 | |
315 | mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); | |
644db166 JH |
316 | if (!mem) |
317 | return -ENODEV; | |
efe7daf2 | 318 | hcd->rsrc_start = mem->start; |
28f65c11 | 319 | hcd->rsrc_len = resource_size(mem); |
efe7daf2 | 320 | |
644db166 JH |
321 | hcd->regs = devm_ioremap_resource(&pdev->dev, mem); |
322 | if (IS_ERR(hcd->regs)) { | |
323 | error = PTR_ERR(hcd->regs); | |
324 | goto err; | |
efe7daf2 SS |
325 | } |
326 | ||
327 | ohci_hcd_init(hcd_to_ohci(hcd)); | |
328 | ||
329 | irq = platform_get_irq(pdev, 0); | |
330 | if (irq < 0) { | |
331 | error = -ENODEV; | |
644db166 | 332 | goto err; |
efe7daf2 | 333 | } |
b5dd18d8 | 334 | error = usb_add_hcd(hcd, irq, 0); |
efe7daf2 | 335 | if (error) |
644db166 | 336 | goto err; |
efe7daf2 | 337 | |
3c9740a1 PC |
338 | device_wakeup_enable(hcd->self.controller); |
339 | ||
efe7daf2 SS |
340 | if (hub->ocic_notify) { |
341 | error = hub->ocic_notify(ohci_da8xx_ocic_handler); | |
342 | if (!error) | |
343 | return 0; | |
344 | } | |
345 | ||
346 | usb_remove_hcd(hcd); | |
644db166 | 347 | err: |
efe7daf2 | 348 | usb_put_hcd(hcd); |
efe7daf2 SS |
349 | return error; |
350 | } | |
351 | ||
352 | /** | |
353 | * usb_hcd_da8xx_remove - shutdown processing for DA8xx-based HCDs | |
354 | * @dev: USB Host Controller being removed | |
355 | * Context: !in_interrupt() | |
356 | * | |
357 | * Reverses the effect of usb_hcd_da8xx_probe(), first invoking | |
358 | * the HCD's stop() method. It is always called from a thread | |
359 | * context, normally "rmmod", "apmd", or something similar. | |
360 | */ | |
361 | static inline void | |
362 | usb_hcd_da8xx_remove(struct usb_hcd *hcd, struct platform_device *pdev) | |
363 | { | |
d4f09e28 | 364 | struct da8xx_ohci_root_hub *hub = dev_get_platdata(&pdev->dev); |
efe7daf2 SS |
365 | |
366 | hub->ocic_notify(NULL); | |
367 | usb_remove_hcd(hcd); | |
efe7daf2 | 368 | usb_put_hcd(hcd); |
efe7daf2 SS |
369 | } |
370 | ||
371 | static int ohci_hcd_da8xx_drv_probe(struct platform_device *dev) | |
372 | { | |
373 | return usb_hcd_da8xx_probe(&ohci_da8xx_hc_driver, dev); | |
374 | } | |
375 | ||
376 | static int ohci_hcd_da8xx_drv_remove(struct platform_device *dev) | |
377 | { | |
378 | struct usb_hcd *hcd = platform_get_drvdata(dev); | |
379 | ||
380 | usb_hcd_da8xx_remove(hcd, dev); | |
efe7daf2 SS |
381 | |
382 | return 0; | |
383 | } | |
384 | ||
385 | #ifdef CONFIG_PM | |
933bb1f0 MG |
386 | static int ohci_da8xx_suspend(struct platform_device *pdev, |
387 | pm_message_t message) | |
efe7daf2 | 388 | { |
933bb1f0 | 389 | struct usb_hcd *hcd = platform_get_drvdata(pdev); |
efe7daf2 | 390 | struct ohci_hcd *ohci = hcd_to_ohci(hcd); |
933bb1f0 MG |
391 | bool do_wakeup = device_may_wakeup(&pdev->dev); |
392 | int ret; | |
393 | ||
efe7daf2 SS |
394 | |
395 | if (time_before(jiffies, ohci->next_statechange)) | |
396 | msleep(5); | |
397 | ohci->next_statechange = jiffies; | |
398 | ||
933bb1f0 MG |
399 | ret = ohci_suspend(hcd, do_wakeup); |
400 | if (ret) | |
401 | return ret; | |
402 | ||
efe7daf2 SS |
403 | ohci_da8xx_clock(0); |
404 | hcd->state = HC_STATE_SUSPENDED; | |
933bb1f0 MG |
405 | |
406 | return ret; | |
efe7daf2 SS |
407 | } |
408 | ||
409 | static int ohci_da8xx_resume(struct platform_device *dev) | |
410 | { | |
411 | struct usb_hcd *hcd = platform_get_drvdata(dev); | |
412 | struct ohci_hcd *ohci = hcd_to_ohci(hcd); | |
413 | ||
414 | if (time_before(jiffies, ohci->next_statechange)) | |
415 | msleep(5); | |
416 | ohci->next_statechange = jiffies; | |
417 | ||
418 | ohci_da8xx_clock(1); | |
419 | dev->dev.power.power_state = PMSG_ON; | |
420 | usb_hcd_resume_root_hub(hcd); | |
421 | return 0; | |
422 | } | |
423 | #endif | |
424 | ||
425 | /* | |
426 | * Driver definition to register with platform structure. | |
427 | */ | |
428 | static struct platform_driver ohci_hcd_da8xx_driver = { | |
429 | .probe = ohci_hcd_da8xx_drv_probe, | |
430 | .remove = ohci_hcd_da8xx_drv_remove, | |
431 | .shutdown = usb_hcd_platform_shutdown, | |
432 | #ifdef CONFIG_PM | |
433 | .suspend = ohci_da8xx_suspend, | |
434 | .resume = ohci_da8xx_resume, | |
435 | #endif | |
436 | .driver = { | |
437 | .owner = THIS_MODULE, | |
438 | .name = "ohci", | |
439 | }, | |
440 | }; | |
ab59ac01 JL |
441 | |
442 | MODULE_ALIAS("platform:ohci"); |