diff options
-rw-r--r-- | arch/arm/mach-at91/include/mach/board.h | 4 | ||||
-rw-r--r-- | drivers/usb/host/ohci-at91.c | 224 |
2 files changed, 219 insertions, 9 deletions
diff --git a/arch/arm/mach-at91/include/mach/board.h b/arch/arm/mach-at91/include/mach/board.h index 61d52dc05051..d07767f4052e 100644 --- a/arch/arm/mach-at91/include/mach/board.h +++ b/arch/arm/mach-at91/include/mach/board.h | |||
@@ -99,6 +99,10 @@ struct at91_usbh_data { | |||
99 | u8 ports; /* number of ports on root hub */ | 99 | u8 ports; /* number of ports on root hub */ |
100 | u8 vbus_pin[2]; /* port power-control pin */ | 100 | u8 vbus_pin[2]; /* port power-control pin */ |
101 | u8 vbus_pin_inverted; | 101 | u8 vbus_pin_inverted; |
102 | u8 overcurrent_supported; | ||
103 | u8 overcurrent_pin[2]; | ||
104 | u8 overcurrent_status[2]; | ||
105 | u8 overcurrent_changed[2]; | ||
102 | }; | 106 | }; |
103 | extern void __init at91_add_device_usbh(struct at91_usbh_data *data); | 107 | extern void __init at91_add_device_usbh(struct at91_usbh_data *data); |
104 | extern void __init at91_add_device_usbh_ohci(struct at91_usbh_data *data); | 108 | extern void __init at91_add_device_usbh_ohci(struct at91_usbh_data *data); |
diff --git a/drivers/usb/host/ohci-at91.c b/drivers/usb/host/ohci-at91.c index 5dd381fc2ddb..ba3a46b78b75 100644 --- a/drivers/usb/host/ohci-at91.c +++ b/drivers/usb/host/ohci-at91.c | |||
@@ -218,6 +218,156 @@ ohci_at91_start (struct usb_hcd *hcd) | |||
218 | return 0; | 218 | return 0; |
219 | } | 219 | } |
220 | 220 | ||
221 | static void ohci_at91_usb_set_power(struct at91_usbh_data *pdata, int port, int enable) | ||
222 | { | ||
223 | if (port < 0 || port >= 2) | ||
224 | return; | ||
225 | |||
226 | gpio_set_value(pdata->vbus_pin[port], !pdata->vbus_pin_inverted ^ enable); | ||
227 | } | ||
228 | |||
229 | static int ohci_at91_usb_get_power(struct at91_usbh_data *pdata, int port) | ||
230 | { | ||
231 | if (port < 0 || port >= 2) | ||
232 | return -EINVAL; | ||
233 | |||
234 | return gpio_get_value(pdata->vbus_pin[port]) ^ !pdata->vbus_pin_inverted; | ||
235 | } | ||
236 | |||
237 | /* | ||
238 | * Update the status data from the hub with the over-current indicator change. | ||
239 | */ | ||
240 | static int ohci_at91_hub_status_data(struct usb_hcd *hcd, char *buf) | ||
241 | { | ||
242 | struct at91_usbh_data *pdata = hcd->self.controller->platform_data; | ||
243 | int length = ohci_hub_status_data(hcd, buf); | ||
244 | int port; | ||
245 | |||
246 | for (port = 0; port < ARRAY_SIZE(pdata->overcurrent_pin); port++) { | ||
247 | if (pdata->overcurrent_changed[port]) { | ||
248 | if (! length) | ||
249 | length = 1; | ||
250 | buf[0] |= 1 << (port + 1); | ||
251 | } | ||
252 | } | ||
253 | |||
254 | return length; | ||
255 | } | ||
256 | |||
257 | /* | ||
258 | * Look at the control requests to the root hub and see if we need to override. | ||
259 | */ | ||
260 | static int ohci_at91_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, | ||
261 | u16 wIndex, char *buf, u16 wLength) | ||
262 | { | ||
263 | struct at91_usbh_data *pdata = hcd->self.controller->platform_data; | ||
264 | struct usb_hub_descriptor *desc; | ||
265 | int ret = -EINVAL; | ||
266 | u32 *data = (u32 *)buf; | ||
267 | |||
268 | dev_dbg(hcd->self.controller, | ||
269 | "ohci_at91_hub_control(%p,0x%04x,0x%04x,0x%04x,%p,%04x)\n", | ||
270 | hcd, typeReq, wValue, wIndex, buf, wLength); | ||
271 | |||
272 | switch (typeReq) { | ||
273 | case SetPortFeature: | ||
274 | if (wValue == USB_PORT_FEAT_POWER) { | ||
275 | dev_dbg(hcd->self.controller, "SetPortFeat: POWER\n"); | ||
276 | ohci_at91_usb_set_power(pdata, wIndex - 1, 1); | ||
277 | goto out; | ||
278 | } | ||
279 | break; | ||
280 | |||
281 | case ClearPortFeature: | ||
282 | switch (wValue) { | ||
283 | case USB_PORT_FEAT_C_OVER_CURRENT: | ||
284 | dev_dbg(hcd->self.controller, | ||
285 | "ClearPortFeature: C_OVER_CURRENT\n"); | ||
286 | |||
287 | if (wIndex == 1 || wIndex == 2) { | ||
288 | pdata->overcurrent_changed[wIndex-1] = 0; | ||
289 | pdata->overcurrent_status[wIndex-1] = 0; | ||
290 | } | ||
291 | |||
292 | goto out; | ||
293 | |||
294 | case USB_PORT_FEAT_OVER_CURRENT: | ||
295 | dev_dbg(hcd->self.controller, | ||
296 | "ClearPortFeature: OVER_CURRENT\n"); | ||
297 | |||
298 | if (wIndex == 1 || wIndex == 2) { | ||
299 | pdata->overcurrent_status[wIndex-1] = 0; | ||
300 | } | ||
301 | |||
302 | goto out; | ||
303 | |||
304 | case USB_PORT_FEAT_POWER: | ||
305 | dev_dbg(hcd->self.controller, | ||
306 | "ClearPortFeature: POWER\n"); | ||
307 | |||
308 | if (wIndex == 1 || wIndex == 2) { | ||
309 | ohci_at91_usb_set_power(pdata, wIndex - 1, 0); | ||
310 | return 0; | ||
311 | } | ||
312 | } | ||
313 | break; | ||
314 | } | ||
315 | |||
316 | ret = ohci_hub_control(hcd, typeReq, wValue, wIndex, buf, wLength); | ||
317 | if (ret) | ||
318 | goto out; | ||
319 | |||
320 | switch (typeReq) { | ||
321 | case GetHubDescriptor: | ||
322 | |||
323 | /* update the hub's descriptor */ | ||
324 | |||
325 | desc = (struct usb_hub_descriptor *)buf; | ||
326 | |||
327 | dev_dbg(hcd->self.controller, "wHubCharacteristics 0x%04x\n", | ||
328 | desc->wHubCharacteristics); | ||
329 | |||
330 | /* remove the old configurations for power-switching, and | ||
331 | * over-current protection, and insert our new configuration | ||
332 | */ | ||
333 | |||
334 | desc->wHubCharacteristics &= ~cpu_to_le16(HUB_CHAR_LPSM); | ||
335 | desc->wHubCharacteristics |= cpu_to_le16(0x0001); | ||
336 | |||
337 | if (pdata->overcurrent_supported) { | ||
338 | desc->wHubCharacteristics &= ~cpu_to_le16(HUB_CHAR_OCPM); | ||
339 | desc->wHubCharacteristics |= cpu_to_le16(0x0008|0x0001); | ||
340 | } | ||
341 | |||
342 | dev_dbg(hcd->self.controller, "wHubCharacteristics after 0x%04x\n", | ||
343 | desc->wHubCharacteristics); | ||
344 | |||
345 | return ret; | ||
346 | |||
347 | case GetPortStatus: | ||
348 | /* check port status */ | ||
349 | |||
350 | dev_dbg(hcd->self.controller, "GetPortStatus(%d)\n", wIndex); | ||
351 | |||
352 | if (wIndex == 1 || wIndex == 2) { | ||
353 | if (! ohci_at91_usb_get_power(pdata, wIndex-1)) { | ||
354 | *data &= ~cpu_to_le32(RH_PS_PPS); | ||
355 | } | ||
356 | |||
357 | if (pdata->overcurrent_changed[wIndex-1]) { | ||
358 | *data |= cpu_to_le32(RH_PS_OCIC); | ||
359 | } | ||
360 | |||
361 | if (pdata->overcurrent_status[wIndex-1]) { | ||
362 | *data |= cpu_to_le32(RH_PS_POCI); | ||
363 | } | ||
364 | } | ||
365 | } | ||
366 | |||
367 | out: | ||
368 | return ret; | ||
369 | } | ||
370 | |||
221 | /*-------------------------------------------------------------------------*/ | 371 | /*-------------------------------------------------------------------------*/ |
222 | 372 | ||
223 | static const struct hc_driver ohci_at91_hc_driver = { | 373 | static const struct hc_driver ohci_at91_hc_driver = { |
@@ -253,8 +403,8 @@ static const struct hc_driver ohci_at91_hc_driver = { | |||
253 | /* | 403 | /* |
254 | * root hub support | 404 | * root hub support |
255 | */ | 405 | */ |
256 | .hub_status_data = ohci_hub_status_data, | 406 | .hub_status_data = ohci_at91_hub_status_data, |
257 | .hub_control = ohci_hub_control, | 407 | .hub_control = ohci_at91_hub_control, |
258 | #ifdef CONFIG_PM | 408 | #ifdef CONFIG_PM |
259 | .bus_suspend = ohci_bus_suspend, | 409 | .bus_suspend = ohci_bus_suspend, |
260 | .bus_resume = ohci_bus_resume, | 410 | .bus_resume = ohci_bus_resume, |
@@ -264,22 +414,71 @@ static const struct hc_driver ohci_at91_hc_driver = { | |||
264 | 414 | ||
265 | /*-------------------------------------------------------------------------*/ | 415 | /*-------------------------------------------------------------------------*/ |
266 | 416 | ||
417 | static irqreturn_t ohci_hcd_at91_overcurrent_irq(int irq, void *data) | ||
418 | { | ||
419 | struct platform_device *pdev = data; | ||
420 | struct at91_usbh_data *pdata = pdev->dev.platform_data; | ||
421 | int val, gpio, port; | ||
422 | |||
423 | /* From the GPIO notifying the over-current situation, find | ||
424 | * out the corresponding port */ | ||
425 | gpio = irq_to_gpio(irq); | ||
426 | for (port = 0; port < ARRAY_SIZE(pdata->overcurrent_pin); port++) { | ||
427 | if (pdata->overcurrent_pin[port] == gpio) | ||
428 | break; | ||
429 | } | ||
430 | |||
431 | if (port == ARRAY_SIZE(pdata->overcurrent_pin)) { | ||
432 | dev_err(& pdev->dev, "overcurrent interrupt from unknown GPIO\n"); | ||
433 | return IRQ_HANDLED; | ||
434 | } | ||
435 | |||
436 | val = gpio_get_value(gpio); | ||
437 | |||
438 | /* When notified of an over-current situation, disable power | ||
439 | on the corresponding port, and mark this port in | ||
440 | over-current. */ | ||
441 | if (! val) { | ||
442 | ohci_at91_usb_set_power(pdata, port, 0); | ||
443 | pdata->overcurrent_status[port] = 1; | ||
444 | pdata->overcurrent_changed[port] = 1; | ||
445 | } | ||
446 | |||
447 | dev_dbg(& pdev->dev, "overcurrent situation %s\n", | ||
448 | val ? "exited" : "notified"); | ||
449 | |||
450 | return IRQ_HANDLED; | ||
451 | } | ||
452 | |||
453 | /*-------------------------------------------------------------------------*/ | ||
454 | |||
267 | static int ohci_hcd_at91_drv_probe(struct platform_device *pdev) | 455 | static int ohci_hcd_at91_drv_probe(struct platform_device *pdev) |
268 | { | 456 | { |
269 | struct at91_usbh_data *pdata = pdev->dev.platform_data; | 457 | struct at91_usbh_data *pdata = pdev->dev.platform_data; |
270 | int i; | 458 | int i; |
271 | 459 | ||
272 | if (pdata) { | 460 | if (pdata) { |
273 | /* REVISIT make the driver support per-port power switching, | ||
274 | * and also overcurrent detection. Here we assume the ports | ||
275 | * are always powered while this driver is active, and use | ||
276 | * active-low power switches. | ||
277 | */ | ||
278 | for (i = 0; i < ARRAY_SIZE(pdata->vbus_pin); i++) { | 461 | for (i = 0; i < ARRAY_SIZE(pdata->vbus_pin); i++) { |
279 | if (pdata->vbus_pin[i] <= 0) | 462 | if (pdata->vbus_pin[i] <= 0) |
280 | continue; | 463 | continue; |
281 | gpio_request(pdata->vbus_pin[i], "ohci_vbus"); | 464 | gpio_request(pdata->vbus_pin[i], "ohci_vbus"); |
282 | gpio_direction_output(pdata->vbus_pin[i], pdata->vbus_pin_inverted); | 465 | ohci_at91_usb_set_power(pdata, i, 1); |
466 | } | ||
467 | |||
468 | for (i = 0; i < ARRAY_SIZE(pdata->overcurrent_pin); i++) { | ||
469 | int ret; | ||
470 | |||
471 | if (pdata->overcurrent_pin[i] <= 0) | ||
472 | continue; | ||
473 | gpio_request(pdata->overcurrent_pin[i], "ohci_overcurrent"); | ||
474 | |||
475 | ret = request_irq(gpio_to_irq(pdata->overcurrent_pin[i]), | ||
476 | ohci_hcd_at91_overcurrent_irq, | ||
477 | IRQF_SHARED, "ohci_overcurrent", pdev); | ||
478 | if (ret) { | ||
479 | gpio_free(pdata->overcurrent_pin[i]); | ||
480 | dev_warn(& pdev->dev, "cannot get GPIO IRQ for overcurrent\n"); | ||
481 | } | ||
283 | } | 482 | } |
284 | } | 483 | } |
285 | 484 | ||
@@ -296,9 +495,16 @@ static int ohci_hcd_at91_drv_remove(struct platform_device *pdev) | |||
296 | for (i = 0; i < ARRAY_SIZE(pdata->vbus_pin); i++) { | 495 | for (i = 0; i < ARRAY_SIZE(pdata->vbus_pin); i++) { |
297 | if (pdata->vbus_pin[i] <= 0) | 496 | if (pdata->vbus_pin[i] <= 0) |
298 | continue; | 497 | continue; |
299 | gpio_direction_output(pdata->vbus_pin[i], !pdata->vbus_pin_inverted); | 498 | ohci_at91_usb_set_power(pdata, i, 0); |
300 | gpio_free(pdata->vbus_pin[i]); | 499 | gpio_free(pdata->vbus_pin[i]); |
301 | } | 500 | } |
501 | |||
502 | for (i = 0; i < ARRAY_SIZE(pdata->overcurrent_pin); i++) { | ||
503 | if (pdata->overcurrent_pin[i] <= 0) | ||
504 | continue; | ||
505 | free_irq(gpio_to_irq(pdata->overcurrent_pin[i]), pdev); | ||
506 | gpio_free(pdata->overcurrent_pin[i]); | ||
507 | } | ||
302 | } | 508 | } |
303 | 509 | ||
304 | device_init_wakeup(&pdev->dev, 0); | 510 | device_init_wakeup(&pdev->dev, 0); |