aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/usb
diff options
context:
space:
mode:
authorThomas Petazzoni <thomas.petazzoni@free-electrons.com>2011-07-13 05:29:17 -0400
committerArnd Bergmann <arnd@arndb.de>2011-09-10 17:03:13 -0400
commitaa6e52a35d388e730f4df0ec2ec48294590cc459 (patch)
tree6286c7b93ea25e69bd242e7c4875ce6886a45a7e /drivers/usb
parente7da859e424ccc30d2ef87dbabf655ad3d59f291 (diff)
at91: at91-ohci: support overcurrent notification
Several USB power switches (AIC1526 or MIC2026) have a digital output that is used to notify that an overcurrent situation is taking place. This digital outputs are typically connected to GPIO inputs of the processor and can be used to be notified of those overcurrent situations. Therefore, we add a new overcurrent_pin[] array in the at91_usbh_data structure so that boards can tell the AT91 OHCI driver which pins are used for the overcurrent notification, and an overcurrent_supported boolean to tell the driver whether overcurrent is supported or not. The code has been largely borrowed from ohci-da8xx.c and ohci-s3c2410.c. Signed-off-by: Thomas Petazzoni <thomas.petazzoni@free-electrons.com> Signed-off-by: Nicolas Ferre <nicolas.ferre@atmel.com>
Diffstat (limited to 'drivers/usb')
-rw-r--r--drivers/usb/host/ohci-at91.c224
1 files changed, 215 insertions, 9 deletions
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
221static 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
229static 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 */
240static 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 */
260static 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
223static const struct hc_driver ohci_at91_hc_driver = { 373static 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
417static 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
267static int ohci_hcd_at91_drv_probe(struct platform_device *pdev) 455static 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);