diff mbox

[2/3] at91-ohci: support overcurrent notification

Message ID 1310549358-13330-3-git-send-email-thomas.petazzoni@free-electrons.com (mailing list archive)
State New, archived
Headers show

Commit Message

Thomas Petazzoni July 13, 2011, 9:29 a.m. UTC
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>
Cc: Andrew Victor <linux@maxim.org.za>
Cc: Nicolas Ferre <nicolas.ferre@atmel.com>
Cc: Jean-Christophe Plagniol-Villard <plagnioj@jcrosoft.com>
Cc: Matthieu CASTET <matthieu.castet@parrot.com>
---
 arch/arm/mach-at91/include/mach/board.h |    4 +
 drivers/usb/host/ohci-at91.c            |  224 +++++++++++++++++++++++++++++--
 2 files changed, 219 insertions(+), 9 deletions(-)

Comments

Thomas Petazzoni July 13, 2011, 2:28 p.m. UTC | #1
Hello,

Le Wed, 13 Jul 2011 11:29:17 +0200,
Thomas Petazzoni <thomas.petazzoni@free-electrons.com> a écrit :

> 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.

Now, I have a question about the behavior I observe with this code in
place. In order to easily trigger the over-current situation, I have
told in my board code that a button is the GPIO for the overcurrent_pin.

When I push the button, I enter the over-current situation, when I
release the button, I exit the over-current situation.

With the code above in place, when I push the button, the power is shut
off on the corresponding USB port and the device goes away. But
immediately after that, the power is switched on at the USB port by the
USB core, and the device is detected again. Is this normal behaviour ?
I would have expected the USB core to wait for the over-current
situation to disappear (i.e from me releasing the button). Maybe I am
misunderstanding how over-current management works ?

Apparently, the behaviour I observe is implemented by the following
piece of code in drivers/usb/core/hub.c :

                        if (portchange & USB_PORT_STAT_C_OVERCURRENT) {
                                u16 status = 0;
                                u16 unused;

                                dev_dbg(hub_dev, "over-current change on port "
                                        "%d\n", i);
                                clear_port_feature(hdev, i,
                                        USB_PORT_FEAT_C_OVER_CURRENT);
                                msleep(100);    /* Cool down */
                                hub_power_on(hub, true);
                                hub_port_status(hub, i, &status, &unused);
                                if (status & USB_PORT_STAT_OVERCURRENT)
                                        dev_err(hub_dev, "over-current "
                                                "condition on port %d\n", i);
                        }

So I don't see where it would wait for the over-current situation to be
cleared. However, the USB 2.0 specification says, in section 11.12.5 :

"""
Host recovery actions for an over-current event should include the
following:
1. Host gets change notification from hub with over-current
   event.
2. Host extracts appropriate hub or port change information
   (depending on the information in the change bitmap).
3. Host waits for over-current status bit to be cleared to 0.
4. Host cycles power on to all of the necessary ports (e.g., issues a
   SetPortFeature(PORT_POWER) request for each port).
5. Host re-enumerates all affected ports
"""

So, according to step 3), I would have expected the USB core to wait
for the over-currrent status to be cleared, that is, wait until I
release the button.

Below is the log of what happens between the moment I press the button
(message "overcurrent situation notified" from the GPIO interrupt
handler) and the moment I release the button (message "overcurrent
situation exited" from the GPIO interrupt handler).

[   41.750000] at91_ohci at91_ohci: overcurrent situation notified
[   41.790000] hub 1-0:1.0: state 7 ports 2 chg 0000 evt 0006
[   41.790000] at91_ohci at91_ohci: ohci_at91_hub_control(c79ccc00,0xa300,0x0000,0x0001,c7861e48,0004)
[   41.790000] at91_ohci at91_ohci: GetPortStatus(1)
[   41.790000] hub 1-0:1.0: over-current change on port 1
[   41.790000] at91_ohci at91_ohci: ohci_at91_hub_control(c79ccc00,0x2301,0x0013,0x0001,c7861e70,0000)
[   41.790000] at91_ohci at91_ohci: ClearPortFeature: C_OVER_CURRENT
[   41.790000] at91_ohci at91_ohci: CTRL: TypeReq=0x2301 val=0x13 idx=0x1 len=0 ==> -22
[   41.900000] hub 1-0:1.0: enabling power on all ports
[   41.900000] at91_ohci at91_ohci: ohci_at91_hub_control(c79ccc00,0x2303,0x0008,0x0001,c7861e58,0000)
[   41.900000] at91_ohci at91_ohci: SetPortFeat: POWER
[   41.900000] at91_ohci at91_ohci: CTRL: TypeReq=0x2303 val=0x8 idx=0x1 len=0 ==> -22
[   41.900000] at91_ohci at91_ohci: ohci_at91_hub_control(c79ccc00,0x2303,0x0008,0x0002,c7861e58,0000)
[   41.900000] at91_ohci at91_ohci: SetPortFeat: POWER
[   41.900000] at91_ohci at91_ohci: CTRL: TypeReq=0x2303 val=0x8 idx=0x2 len=0 ==> -22
[   42.010000] at91_ohci at91_ohci: ohci_at91_hub_control(c79ccc00,0xa300,0x0000,0x0001,c7861e48,0004)
[   42.010000] at91_ohci at91_ohci: GetPortStatus(1)
[   42.010000] at91_ohci at91_ohci: ohci_at91_hub_control(c79ccc00,0xa300,0x0000,0x0002,c7861e48,0004)
[   42.010000] at91_ohci at91_ohci: GetStatus roothub.portstatus [1] = 0x00030301 PESC CSC LSDA PPS CCS
[   42.010000] at91_ohci at91_ohci: GetPortStatus(2)
[   42.010000] at91_ohci at91_ohci: ohci_at91_hub_control(c79ccc00,0x2301,0x0010,0x0002,c7861e70,0000)
[   42.010000] at91_ohci at91_ohci: ohci_at91_hub_control(c79ccc00,0x2301,0x0011,0x0002,c7861e70,0000)
[   42.010000] hub 1-0:1.0: port 2, status 0301, change 0003, 1.5 Mb/s
[   42.010000] usb 1-2: USB disconnect, device number 2
[   42.010000] usb 1-2: unregistering device
[   42.010000] usb 1-2: unregistering interface 1-2:1.0
[   42.010000] usb 1-2: usb_disable_device nuking all URBs
[   42.010000] at91_ohci at91_ohci: ohci_at91_hub_control(c79ccc00,0xa300,0x0000,0x0002,c7861e48,0004)
[   42.010000] at91_ohci at91_ohci: GetPortStatus(2)
[   42.050000] at91_ohci at91_ohci: ohci_at91_hub_control(c79ccc00,0xa300,0x0000,0x0002,c7861e48,0004)
[   42.050000] at91_ohci at91_ohci: GetPortStatus(2)
[   42.090000] at91_ohci at91_ohci: ohci_at91_hub_control(c79ccc00,0xa300,0x0000,0x0002,c7861e48,0004)
[   42.090000] at91_ohci at91_ohci: GetPortStatus(2)
[   42.130000] at91_ohci at91_ohci: ohci_at91_hub_control(c79ccc00,0xa300,0x0000,0x0002,c7861e48,0004)
[   42.130000] at91_ohci at91_ohci: GetPortStatus(2)
[   42.170000] at91_ohci at91_ohci: ohci_at91_hub_control(c79ccc00,0xa300,0x0000,0x0002,c7861e48,0004)
[   42.170000] at91_ohci at91_ohci: GetPortStatus(2)
[   42.170000] hub 1-0:1.0: debounce: port 2: total 100ms stable 100ms status 0x301
[   42.170000] at91_ohci at91_ohci: ohci_at91_hub_control(c79ccc00,0x2303,0x0004,0x0002,c7861de0,0000)
[   42.290000] at91_ohci at91_ohci: ohci_at91_hub_control(c79ccc00,0xa300,0x0000,0x0002,c7861db8,0004)
[   42.290000] at91_ohci at91_ohci: GetStatus roothub.portstatus [1] = 0x00100303 PRSC LSDA PPS PES CCS
[   42.290000] at91_ohci at91_ohci: GetPortStatus(2)
[   42.350000] at91_ohci at91_ohci: ohci_at91_hub_control(c79ccc00,0x2301,0x0014,0x0002,c7861de0,0000)
[   42.350000] usb 1-2: new low speed USB device number 3 using at91_ohci
[   42.350000] at91_ohci at91_ohci: ohci_at91_hub_control(c79ccc00,0x2303,0x0004,0x0002,c7861de0,0000)
[   42.470000] at91_ohci at91_ohci: ohci_at91_hub_control(c79ccc00,0xa300,0x0000,0x0002,c7861db8,0004)
[   42.470000] at91_ohci at91_ohci: GetStatus roothub.portstatus [1] = 0x00100303 PRSC LSDA PPS PES CCS
[   42.470000] at91_ohci at91_ohci: GetPortStatus(2)
[   42.530000] at91_ohci at91_ohci: ohci_at91_hub_control(c79ccc00,0x2301,0x0014,0x0002,c7861de0,0000)
[   42.560000] usb 1-2: skipped 1 descriptor after interface
[   42.560000] usb 1-2: default language 0x0409
[   42.570000] usb 1-2: udev 3, busnum 1, minor = 2
[   42.570000] usb 1-2: New USB device found, idVendor=0a81, idProduct=0701
[   42.570000] usb 1-2: New USB device strings: Mfr=1, Product=2, SerialNumber=0
[   42.580000] usb 1-2: Product: USB Missile Launcher v1.0
[   42.590000] usb 1-2: Manufacturer: Dream Link
[   42.590000] usb 1-2: usb_probe_device
[   42.590000] usb 1-2: configuration #1 chosen from 1 choice
[   42.590000] usb 1-2: adding 1-2:1.0 (config #1, interface 0)
[   42.600000] usbhid 1-2:1.0: usb_probe_interface
[   42.600000] usbhid 1-2:1.0: usb_probe_interface - got id
[   42.610000] generic-usb 0003:0A81:0701.0002: claimed by neither input, hiddev nor hidraw
[   42.610000] /home/thomas/projets/linux-2.6/drivers/usb/core/inode.c: creating file '003'
[   42.610000] hub 1-0:1.0: state 7 ports 2 chg 0000 evt 0004
[   42.610000] at91_ohci at91_ohci: ohci_at91_hub_control(c79ccc00,0xa300,0x0000,0x0002,c7861e48,0004)
[   42.610000] at91_ohci at91_ohci: GetPortStatus(2)
[   43.850000] at91_ohci at91_ohci: overcurrent situation exited

Could you enlighten me on how this over-current mechanism is supposed
to work ?

Thanks,

Thomas
Alan Stern July 13, 2011, 3:54 p.m. UTC | #2
On Wed, 13 Jul 2011, Thomas Petazzoni wrote:

> Now, I have a question about the behavior I observe with this code in
> place. In order to easily trigger the over-current situation, I have
> told in my board code that a button is the GPIO for the overcurrent_pin.
> 
> When I push the button, I enter the over-current situation, when I
> release the button, I exit the over-current situation.
> 
> With the code above in place, when I push the button, the power is shut
> off on the corresponding USB port and the device goes away. But
> immediately after that, the power is switched on at the USB port by the
> USB core, and the device is detected again. Is this normal behaviour ?
> I would have expected the USB core to wait for the over-current
> situation to disappear (i.e from me releasing the button). Maybe I am
> misunderstanding how over-current management works ?
> 
> Apparently, the behaviour I observe is implemented by the following
> piece of code in drivers/usb/core/hub.c :
> 
>                         if (portchange & USB_PORT_STAT_C_OVERCURRENT) {
>                                 u16 status = 0;
>                                 u16 unused;
> 
>                                 dev_dbg(hub_dev, "over-current change on port "
>                                         "%d\n", i);
>                                 clear_port_feature(hdev, i,
>                                         USB_PORT_FEAT_C_OVER_CURRENT);
>                                 msleep(100);    /* Cool down */
>                                 hub_power_on(hub, true);
>                                 hub_port_status(hub, i, &status, &unused);
>                                 if (status & USB_PORT_STAT_OVERCURRENT)
>                                         dev_err(hub_dev, "over-current "
>                                                 "condition on port %d\n", i);
>                         }
> 
> So I don't see where it would wait for the over-current situation to be
> cleared.

That's what the "msleep(100)" is for.

>  However, the USB 2.0 specification says, in section 11.12.5 :
> 
> """
> Host recovery actions for an over-current event should include the
> following:
> 1. Host gets change notification from hub with over-current
>    event.
> 2. Host extracts appropriate hub or port change information
>    (depending on the information in the change bitmap).
> 3. Host waits for over-current status bit to be cleared to 0.
> 4. Host cycles power on to all of the necessary ports (e.g., issues a
>    SetPortFeature(PORT_POWER) request for each port).
> 5. Host re-enumerates all affected ports
> """
> 
> So, according to step 3), I would have expected the USB core to wait
> for the over-currrent status to be cleared, that is, wait until I
> release the button.

If there is no power to the port, the attached device can't draw any
current.  Right?  Therefore the port's over-current status isn't
meaningful until the power is restored.

> Below is the log of what happens between the moment I press the button
> (message "overcurrent situation notified" from the GPIO interrupt
> handler) and the moment I release the button (message "overcurrent
> situation exited" from the GPIO interrupt handler).
> 
> [   41.750000] at91_ohci at91_ohci: overcurrent situation notified
> [   41.790000] hub 1-0:1.0: state 7 ports 2 chg 0000 evt 0006
> [   41.790000] at91_ohci at91_ohci: ohci_at91_hub_control(c79ccc00,0xa300,0x0000,0x0001,c7861e48,0004)
> [   41.790000] at91_ohci at91_ohci: GetPortStatus(1)
> [   41.790000] hub 1-0:1.0: over-current change on port 1
> [   41.790000] at91_ohci at91_ohci: ohci_at91_hub_control(c79ccc00,0x2301,0x0013,0x0001,c7861e70,0000)
> [   41.790000] at91_ohci at91_ohci: ClearPortFeature: C_OVER_CURRENT
> [   41.790000] at91_ohci at91_ohci: CTRL: TypeReq=0x2301 val=0x13 idx=0x1 len=0 ==> -22
> [   41.900000] hub 1-0:1.0: enabling power on all ports
> [   41.900000] at91_ohci at91_ohci: ohci_at91_hub_control(c79ccc00,0x2303,0x0008,0x0001,c7861e58,0000)
> [   41.900000] at91_ohci at91_ohci: SetPortFeat: POWER
> [   41.900000] at91_ohci at91_ohci: CTRL: TypeReq=0x2303 val=0x8 idx=0x1 len=0 ==> -22
> [   41.900000] at91_ohci at91_ohci: ohci_at91_hub_control(c79ccc00,0x2303,0x0008,0x0002,c7861e58,0000)
> [   41.900000] at91_ohci at91_ohci: SetPortFeat: POWER
> [   41.900000] at91_ohci at91_ohci: CTRL: TypeReq=0x2303 val=0x8 idx=0x2 len=0 ==> -22
> [   42.010000] at91_ohci at91_ohci: ohci_at91_hub_control(c79ccc00,0xa300,0x0000,0x0001,c7861e48,0004)
> [   42.010000] at91_ohci at91_ohci: GetPortStatus(1)
> [   42.010000] at91_ohci at91_ohci: ohci_at91_hub_control(c79ccc00,0xa300,0x0000,0x0002,c7861e48,0004)
> [   42.010000] at91_ohci at91_ohci: GetStatus roothub.portstatus [1] = 0x00030301 PESC CSC LSDA PPS CCS

At this point the "over-current condition on port 1" error message
should have appeared, and the power to port 1 should have been turned
off again by the hardware.

> [   42.010000] at91_ohci at91_ohci: GetPortStatus(2)
> [   42.010000] at91_ohci at91_ohci: ohci_at91_hub_control(c79ccc00,0x2301,0x0010,0x0002,c7861e70,0000)
> [   42.010000] at91_ohci at91_ohci: ohci_at91_hub_control(c79ccc00,0x2301,0x0011,0x0002,c7861e70,0000)
> [   42.010000] hub 1-0:1.0: port 2, status 0301, change 0003, 1.5 Mb/s
> [   42.010000] usb 1-2: USB disconnect, device number 2
> [   42.010000] usb 1-2: unregistering device
> [   42.010000] usb 1-2: unregistering interface 1-2:1.0
> [   42.010000] usb 1-2: usb_disable_device nuking all URBs
> [   42.010000] at91_ohci at91_ohci: ohci_at91_hub_control(c79ccc00,0xa300,0x0000,0x0002,c7861e48,0004)
> [   42.010000] at91_ohci at91_ohci: GetPortStatus(2)
> [   42.050000] at91_ohci at91_ohci: ohci_at91_hub_control(c79ccc00,0xa300,0x0000,0x0002,c7861e48,0004)
> [   42.050000] at91_ohci at91_ohci: GetPortStatus(2)
> [   42.090000] at91_ohci at91_ohci: ohci_at91_hub_control(c79ccc00,0xa300,0x0000,0x0002,c7861e48,0004)
> [   42.090000] at91_ohci at91_ohci: GetPortStatus(2)
> [   42.130000] at91_ohci at91_ohci: ohci_at91_hub_control(c79ccc00,0xa300,0x0000,0x0002,c7861e48,0004)
> [   42.130000] at91_ohci at91_ohci: GetPortStatus(2)
> [   42.170000] at91_ohci at91_ohci: ohci_at91_hub_control(c79ccc00,0xa300,0x0000,0x0002,c7861e48,0004)
> [   42.170000] at91_ohci at91_ohci: GetPortStatus(2)
> [   42.170000] hub 1-0:1.0: debounce: port 2: total 100ms stable 100ms status 0x301
> [   42.170000] at91_ohci at91_ohci: ohci_at91_hub_control(c79ccc00,0x2303,0x0004,0x0002,c7861de0,0000)
> [   42.290000] at91_ohci at91_ohci: ohci_at91_hub_control(c79ccc00,0xa300,0x0000,0x0002,c7861db8,0004)
> [   42.290000] at91_ohci at91_ohci: GetStatus roothub.portstatus [1] = 0x00100303 PRSC LSDA PPS PES CCS
> [   42.290000] at91_ohci at91_ohci: GetPortStatus(2)
> [   42.350000] at91_ohci at91_ohci: ohci_at91_hub_control(c79ccc00,0x2301,0x0014,0x0002,c7861de0,0000)
> [   42.350000] usb 1-2: new low speed USB device number 3 using at91_ohci
> [   42.350000] at91_ohci at91_ohci: ohci_at91_hub_control(c79ccc00,0x2303,0x0004,0x0002,c7861de0,0000)
> [   42.470000] at91_ohci at91_ohci: ohci_at91_hub_control(c79ccc00,0xa300,0x0000,0x0002,c7861db8,0004)
> [   42.470000] at91_ohci at91_ohci: GetStatus roothub.portstatus [1] = 0x00100303 PRSC LSDA PPS PES CCS
> [   42.470000] at91_ohci at91_ohci: GetPortStatus(2)
> [   42.530000] at91_ohci at91_ohci: ohci_at91_hub_control(c79ccc00,0x2301,0x0014,0x0002,c7861de0,0000)
> [   42.560000] usb 1-2: skipped 1 descriptor after interface
> [   42.560000] usb 1-2: default language 0x0409
> [   42.570000] usb 1-2: udev 3, busnum 1, minor = 2
> [   42.570000] usb 1-2: New USB device found, idVendor=0a81, idProduct=0701
> [   42.570000] usb 1-2: New USB device strings: Mfr=1, Product=2, SerialNumber=0
> [   42.580000] usb 1-2: Product: USB Missile Launcher v1.0
> [   42.590000] usb 1-2: Manufacturer: Dream Link
> [   42.590000] usb 1-2: usb_probe_device
> [   42.590000] usb 1-2: configuration #1 chosen from 1 choice
> [   42.590000] usb 1-2: adding 1-2:1.0 (config #1, interface 0)
> [   42.600000] usbhid 1-2:1.0: usb_probe_interface
> [   42.600000] usbhid 1-2:1.0: usb_probe_interface - got id
> [   42.610000] generic-usb 0003:0A81:0701.0002: claimed by neither input, hiddev nor hidraw
> [   42.610000] /home/thomas/projets/linux-2.6/drivers/usb/core/inode.c: creating file '003'
> [   42.610000] hub 1-0:1.0: state 7 ports 2 chg 0000 evt 0004
> [   42.610000] at91_ohci at91_ohci: ohci_at91_hub_control(c79ccc00,0xa300,0x0000,0x0002,c7861e48,0004)
> [   42.610000] at91_ohci at91_ohci: GetPortStatus(2)
> [   43.850000] at91_ohci at91_ohci: overcurrent situation exited
> 
> Could you enlighten me on how this over-current mechanism is supposed
> to work ?

We don't have any good way of waiting for the over-current status to
clear, because the hub driver is single-threaded.  It wouldn't be able
to respond to any other USB events while waiting.

If the port is still over-current when the power is turned back on, the 
hub is expected to turn the port power off again and signal another 
over-current status change.

Alan Stern
Thomas Petazzoni July 13, 2011, 4:43 p.m. UTC | #3
Le Wed, 13 Jul 2011 11:54:15 -0400 (EDT),
Alan Stern <stern@rowland.harvard.edu> a écrit :

> > So, according to step 3), I would have expected the USB core to wait
> > for the over-currrent status to be cleared, that is, wait until I
> > release the button.
> 
> If there is no power to the port, the attached device can't draw any
> current.  Right?  Therefore the port's over-current status isn't
> meaningful until the power is restored.

Right, but as you highlighted below, as soon as you power on the port
again, if the same device is still plugged into the port, then you might
end up in the same over-current situation, and this will loop forever.

> > Below is the log of what happens between the moment I press the button
> > (message "overcurrent situation notified" from the GPIO interrupt
> > handler) and the moment I release the button (message "overcurrent
> > situation exited" from the GPIO interrupt handler).
> > 
> > [   41.750000] at91_ohci at91_ohci: overcurrent situation notified
> > [   41.790000] hub 1-0:1.0: state 7 ports 2 chg 0000 evt 0006
> > [   41.790000] at91_ohci at91_ohci: ohci_at91_hub_control(c79ccc00,0xa300,0x0000,0x0001,c7861e48,0004)
> > [   41.790000] at91_ohci at91_ohci: GetPortStatus(1)
> > [   41.790000] hub 1-0:1.0: over-current change on port 1
> > [   41.790000] at91_ohci at91_ohci: ohci_at91_hub_control(c79ccc00,0x2301,0x0013,0x0001,c7861e70,0000)
> > [   41.790000] at91_ohci at91_ohci: ClearPortFeature: C_OVER_CURRENT
> > [   41.790000] at91_ohci at91_ohci: CTRL: TypeReq=0x2301 val=0x13 idx=0x1 len=0 ==> -22
> > [   41.900000] hub 1-0:1.0: enabling power on all ports
> > [   41.900000] at91_ohci at91_ohci: ohci_at91_hub_control(c79ccc00,0x2303,0x0008,0x0001,c7861e58,0000)
> > [   41.900000] at91_ohci at91_ohci: SetPortFeat: POWER
> > [   41.900000] at91_ohci at91_ohci: CTRL: TypeReq=0x2303 val=0x8 idx=0x1 len=0 ==> -22
> > [   41.900000] at91_ohci at91_ohci: ohci_at91_hub_control(c79ccc00,0x2303,0x0008,0x0002,c7861e58,0000)
> > [   41.900000] at91_ohci at91_ohci: SetPortFeat: POWER
> > [   41.900000] at91_ohci at91_ohci: CTRL: TypeReq=0x2303 val=0x8 idx=0x2 len=0 ==> -22
> > [   42.010000] at91_ohci at91_ohci: ohci_at91_hub_control(c79ccc00,0xa300,0x0000,0x0001,c7861e48,0004)
> > [   42.010000] at91_ohci at91_ohci: GetPortStatus(1)
> > [   42.010000] at91_ohci at91_ohci: ohci_at91_hub_control(c79ccc00,0xa300,0x0000,0x0002,c7861e48,0004)
> > [   42.010000] at91_ohci at91_ohci: GetStatus roothub.portstatus [1] = 0x00030301 PESC CSC LSDA PPS CCS
> 
> At this point the "over-current condition on port 1" error message
> should have appeared, and the power to port 1 should have been turned
> off again by the hardware.

I'm not sure to follow you here. If the message did not appear, it
indicates that the USB_PORT_STAT_OVERCURRENT flag wasn't set, for some
reason.

Regarding the power being turned off, it's already done by the GPIO
interrupt handler that notifies of the beginning of an over-current
situation :

static irqreturn_t ohci_hcd_at91_overcurrent_irq(int irq, void *data)
{
[...]
        val = gpio_get_value(gpio);

        /* When notified of an over-current situation, disable power
           on the corresponding port, and mark this port in
           over-current. */
        if (! val) {
                ohci_at91_usb_set_power(pdata, port, 0);
                pdata->overcurrent_status[port]  = 1;
                pdata->overcurrent_changed[port] = 1;
        }
[...]

Isn't this correct ?

> > Could you enlighten me on how this over-current mechanism is supposed
> > to work ?
> 
> We don't have any good way of waiting for the over-current status to
> clear, because the hub driver is single-threaded.  It wouldn't be able
> to respond to any other USB events while waiting.

Ok. I guess it would be possible to regularly poll for the over-current
flag and see whether things are improving (and between the polls,
handle all other USB events). But as you said above, as soon as you
power off the port, the over-current situation should have disappeared.
What worries me is that the over-current situation will likely take
place again as soon as you power on the port again.

In the end, is the behavior that I see the normal behavior of
over-current management as supported currently by the kernel?

Regards,

Thomas
Alan Stern July 13, 2011, 5:17 p.m. UTC | #4
On Wed, 13 Jul 2011, Thomas Petazzoni wrote:

> Le Wed, 13 Jul 2011 11:54:15 -0400 (EDT),
> Alan Stern <stern@rowland.harvard.edu> a écrit :
> 
> > > So, according to step 3), I would have expected the USB core to wait
> > > for the over-currrent status to be cleared, that is, wait until I
> > > release the button.
> > 
> > If there is no power to the port, the attached device can't draw any
> > current.  Right?  Therefore the port's over-current status isn't
> > meaningful until the power is restored.
> 
> Right, but as you highlighted below, as soon as you power on the port
> again, if the same device is still plugged into the port, then you might
> end up in the same over-current situation, and this will loop forever.

Potentially yes, that could happen.  Nobody has ever complained about 
seeing it, though.

In general our handling of this situation probably is not adequate.  
However it has never received proper testing.  You can review the git
history of the hub.c file; there have been some recent changes to this
code.

> > > Below is the log of what happens between the moment I press the button
> > > (message "overcurrent situation notified" from the GPIO interrupt
> > > handler) and the moment I release the button (message "overcurrent
> > > situation exited" from the GPIO interrupt handler).
> > > 
> > > [   41.750000] at91_ohci at91_ohci: overcurrent situation notified
> > > [   41.790000] hub 1-0:1.0: state 7 ports 2 chg 0000 evt 0006
> > > [   41.790000] at91_ohci at91_ohci: ohci_at91_hub_control(c79ccc00,0xa300,0x0000,0x0001,c7861e48,0004)
> > > [   41.790000] at91_ohci at91_ohci: GetPortStatus(1)
> > > [   41.790000] hub 1-0:1.0: over-current change on port 1
> > > [   41.790000] at91_ohci at91_ohci: ohci_at91_hub_control(c79ccc00,0x2301,0x0013,0x0001,c7861e70,0000)
> > > [   41.790000] at91_ohci at91_ohci: ClearPortFeature: C_OVER_CURRENT
> > > [   41.790000] at91_ohci at91_ohci: CTRL: TypeReq=0x2301 val=0x13 idx=0x1 len=0 ==> -22
> > > [   41.900000] hub 1-0:1.0: enabling power on all ports
> > > [   41.900000] at91_ohci at91_ohci: ohci_at91_hub_control(c79ccc00,0x2303,0x0008,0x0001,c7861e58,0000)
> > > [   41.900000] at91_ohci at91_ohci: SetPortFeat: POWER
> > > [   41.900000] at91_ohci at91_ohci: CTRL: TypeReq=0x2303 val=0x8 idx=0x1 len=0 ==> -22
> > > [   41.900000] at91_ohci at91_ohci: ohci_at91_hub_control(c79ccc00,0x2303,0x0008,0x0002,c7861e58,0000)
> > > [   41.900000] at91_ohci at91_ohci: SetPortFeat: POWER
> > > [   41.900000] at91_ohci at91_ohci: CTRL: TypeReq=0x2303 val=0x8 idx=0x2 len=0 ==> -22
> > > [   42.010000] at91_ohci at91_ohci: ohci_at91_hub_control(c79ccc00,0xa300,0x0000,0x0001,c7861e48,0004)
> > > [   42.010000] at91_ohci at91_ohci: GetPortStatus(1)
> > > [   42.010000] at91_ohci at91_ohci: ohci_at91_hub_control(c79ccc00,0xa300,0x0000,0x0002,c7861e48,0004)
> > > [   42.010000] at91_ohci at91_ohci: GetStatus roothub.portstatus [1] = 0x00030301 PESC CSC LSDA PPS CCS
> > 
> > At this point the "over-current condition on port 1" error message
> > should have appeared, and the power to port 1 should have been turned
> > off again by the hardware.
> 
> I'm not sure to follow you here. If the message did not appear, it
> indicates that the USB_PORT_STAT_OVERCURRENT flag wasn't set, for some
> reason.

Right.  Why wasn't the flag set?  Was it because the port power had
already been turned back off?  That's not what the log indicates.

> Regarding the power being turned off, it's already done by the GPIO
> interrupt handler that notifies of the beginning of an over-current
> situation :
> 
> static irqreturn_t ohci_hcd_at91_overcurrent_irq(int irq, void *data)
> {
> [...]
>         val = gpio_get_value(gpio);
> 
>         /* When notified of an over-current situation, disable power
>            on the corresponding port, and mark this port in
>            over-current. */
>         if (! val) {
>                 ohci_at91_usb_set_power(pdata, port, 0);
>                 pdata->overcurrent_status[port]  = 1;
>                 pdata->overcurrent_changed[port] = 1;
>         }
> [...]
> 
> Isn't this correct ?

I have no idea; I don't know how the AT91 works.  When does the 
overcurrent_status value get cleared?  Under what conditions does that 
IRQ fire?

> > > Could you enlighten me on how this over-current mechanism is supposed
> > > to work ?
> > 
> > We don't have any good way of waiting for the over-current status to
> > clear, because the hub driver is single-threaded.  It wouldn't be able
> > to respond to any other USB events while waiting.
> 
> Ok. I guess it would be possible to regularly poll for the over-current
> flag and see whether things are improving (and between the polls,
> handle all other USB events). But as you said above, as soon as you
> power off the port, the over-current situation should have disappeared.
> What worries me is that the over-current situation will likely take
> place again as soon as you power on the port again.
> 
> In the end, is the behavior that I see the normal behavior of
> over-current management as supported currently by the kernel?

More or less, yes.  We are always willing to consider suggestions for
improvements, especially when accompanied by patches.

Alan Stern
Nicolas Ferre Sept. 7, 2011, 10:47 a.m. UTC | #5
Le 13/07/2011 11:29, Thomas Petazzoni :
> 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>
> Cc: Andrew Victor <linux@maxim.org.za>
> Cc: Nicolas Ferre <nicolas.ferre@atmel.com>

Signed-off-by: Nicolas Ferre <nicolas.ferre@atmel.com>

> Cc: Jean-Christophe Plagniol-Villard <plagnioj@jcrosoft.com>
> Cc: Matthieu CASTET <matthieu.castet@parrot.com>
> ---
>  arch/arm/mach-at91/include/mach/board.h |    4 +
>  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 61d52dc..d07767f 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 {
>  	u8		ports;		/* number of ports on root hub */
>  	u8		vbus_pin[2];	/* port power-control pin */
>  	u8              vbus_pin_inverted;
> +	u8              overcurrent_supported;
> +	u8              overcurrent_pin[2];
> +	u8              overcurrent_status[2];
> +	u8              overcurrent_changed[2];
>  };
>  extern void __init at91_add_device_usbh(struct at91_usbh_data *data);
>  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 3612ccd..331909f 100644
> --- a/drivers/usb/host/ohci-at91.c
> +++ b/drivers/usb/host/ohci-at91.c
> @@ -223,6 +223,156 @@ ohci_at91_start (struct usb_hcd *hcd)
>  	return 0;
>  }
>  
> +static void ohci_at91_usb_set_power(struct at91_usbh_data *pdata, int port, int enable)
> +{
> +	if (port < 0 || port >= 2)
> +		return;
> +
> +	gpio_set_value(pdata->vbus_pin[port], !pdata->vbus_pin_inverted ^ enable);
> +}
> +
> +static int ohci_at91_usb_get_power(struct at91_usbh_data *pdata, int port)
> +{
> +	if (port < 0 || port >= 2)
> +		return -EINVAL;
> +
> +	return gpio_get_value(pdata->vbus_pin[port]) ^ !pdata->vbus_pin_inverted;
> +}
> +
> +/*
> + * Update the status data from the hub with the over-current indicator change.
> + */
> +static int ohci_at91_hub_status_data(struct usb_hcd *hcd, char *buf)
> +{
> +	struct at91_usbh_data *pdata = hcd->self.controller->platform_data;
> +	int length = ohci_hub_status_data(hcd, buf);
> +	int port;
> +
> +	for (port = 0; port < ARRAY_SIZE(pdata->overcurrent_pin); port++) {
> +		if (pdata->overcurrent_changed[port]) {
> +			if (! length)
> +				length = 1;
> +			buf[0] |= 1 << (port + 1);
> +		}
> +	}
> +
> +	return length;
> +}
> +
> +/*
> + * Look at the control requests to the root hub and see if we need to override.
> + */
> +static int ohci_at91_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
> +				 u16 wIndex, char *buf, u16 wLength)
> +{
> +	struct at91_usbh_data *pdata = hcd->self.controller->platform_data;
> +	struct usb_hub_descriptor *desc;
> +	int ret = -EINVAL;
> +	u32 *data = (u32 *)buf;
> +
> +	dev_dbg(hcd->self.controller,
> +		"ohci_at91_hub_control(%p,0x%04x,0x%04x,0x%04x,%p,%04x)\n",
> +		hcd, typeReq, wValue, wIndex, buf, wLength);
> +
> +	switch (typeReq) {
> +	case SetPortFeature:
> +		if (wValue == USB_PORT_FEAT_POWER) {
> +			dev_dbg(hcd->self.controller, "SetPortFeat: POWER\n");
> +			ohci_at91_usb_set_power(pdata, wIndex - 1, 1);
> +			goto out;
> +		}
> +		break;
> +
> +	case ClearPortFeature:
> +		switch (wValue) {
> +		case USB_PORT_FEAT_C_OVER_CURRENT:
> +			dev_dbg(hcd->self.controller,
> +				"ClearPortFeature: C_OVER_CURRENT\n");
> +
> +			if (wIndex == 1 || wIndex == 2) {
> +				pdata->overcurrent_changed[wIndex-1] = 0;
> +				pdata->overcurrent_status[wIndex-1] = 0;
> +			}
> +
> +			goto out;
> +
> +		case USB_PORT_FEAT_OVER_CURRENT:
> +			dev_dbg(hcd->self.controller,
> +				"ClearPortFeature: OVER_CURRENT\n");
> +
> +			if (wIndex == 1 || wIndex == 2) {
> +				pdata->overcurrent_status[wIndex-1] = 0;
> +			}
> +
> +			goto out;
> +
> +		case USB_PORT_FEAT_POWER:
> +			dev_dbg(hcd->self.controller,
> +				"ClearPortFeature: POWER\n");
> +
> +			if (wIndex == 1 || wIndex == 2) {
> +				ohci_at91_usb_set_power(pdata, wIndex - 1, 0);
> +				return 0;
> +			}
> +		}
> +		break;
> +	}
> +
> +	ret = ohci_hub_control(hcd, typeReq, wValue, wIndex, buf, wLength);
> +	if (ret)
> +		goto out;
> +
> +	switch (typeReq) {
> +	case GetHubDescriptor:
> +
> +		/* update the hub's descriptor */
> +
> +		desc = (struct usb_hub_descriptor *)buf;
> +
> +		dev_dbg(hcd->self.controller, "wHubCharacteristics 0x%04x\n",
> +			desc->wHubCharacteristics);
> +
> +		/* remove the old configurations for power-switching, and
> +		 * over-current protection, and insert our new configuration
> +		 */
> +
> +		desc->wHubCharacteristics &= ~cpu_to_le16(HUB_CHAR_LPSM);
> +		desc->wHubCharacteristics |= cpu_to_le16(0x0001);
> +
> +		if (pdata->overcurrent_supported) {
> +			desc->wHubCharacteristics &= ~cpu_to_le16(HUB_CHAR_OCPM);
> +			desc->wHubCharacteristics |=  cpu_to_le16(0x0008|0x0001);
> +		}
> +
> +		dev_dbg(hcd->self.controller, "wHubCharacteristics after 0x%04x\n",
> +			desc->wHubCharacteristics);
> +
> +		return ret;
> +
> +	case GetPortStatus:
> +		/* check port status */
> +
> +		dev_dbg(hcd->self.controller, "GetPortStatus(%d)\n", wIndex);
> +
> +		if (wIndex == 1 || wIndex == 2) {
> +			if (! ohci_at91_usb_get_power(pdata, wIndex-1)) {
> +				*data &= ~cpu_to_le32(RH_PS_PPS);
> +			}
> +
> +			if (pdata->overcurrent_changed[wIndex-1]) {
> +				*data |= cpu_to_le32(RH_PS_OCIC);
> +			}
> +
> +			if (pdata->overcurrent_status[wIndex-1]) {
> +				*data |= cpu_to_le32(RH_PS_POCI);
> +			}
> +		}
> +	}
> +
> + out:
> +	return ret;
> +}
> +
>  /*-------------------------------------------------------------------------*/
>  
>  static const struct hc_driver ohci_at91_hc_driver = {
> @@ -258,8 +408,8 @@ static const struct hc_driver ohci_at91_hc_driver = {
>  	/*
>  	 * root hub support
>  	 */
> -	.hub_status_data =	ohci_hub_status_data,
> -	.hub_control =		ohci_hub_control,
> +	.hub_status_data =	ohci_at91_hub_status_data,
> +	.hub_control =		ohci_at91_hub_control,
>  #ifdef CONFIG_PM
>  	.bus_suspend =		ohci_bus_suspend,
>  	.bus_resume =		ohci_bus_resume,
> @@ -269,22 +419,71 @@ static const struct hc_driver ohci_at91_hc_driver = {
>  
>  /*-------------------------------------------------------------------------*/
>  
> +static irqreturn_t ohci_hcd_at91_overcurrent_irq(int irq, void *data)
> +{
> +	struct platform_device *pdev = data;
> +	struct at91_usbh_data *pdata = pdev->dev.platform_data;
> +	int val, gpio, port;
> +
> +	/* From the GPIO notifying the over-current situation, find
> +	 * out the corresponding port */
> +	gpio = irq_to_gpio(irq);
> +	for (port = 0; port < ARRAY_SIZE(pdata->overcurrent_pin); port++) {
> +		if (pdata->overcurrent_pin[port] == gpio)
> +			break;
> +	}
> +
> +	if (port == ARRAY_SIZE(pdata->overcurrent_pin)) {
> +		dev_err(& pdev->dev, "overcurrent interrupt from unknown GPIO\n");
> +		return IRQ_HANDLED;
> +	}
> +
> +	val = gpio_get_value(gpio);
> +
> +	/* When notified of an over-current situation, disable power
> +	   on the corresponding port, and mark this port in
> +	   over-current. */
> +	if (! val) {
> +		ohci_at91_usb_set_power(pdata, port, 0);
> +		pdata->overcurrent_status[port]  = 1;
> +		pdata->overcurrent_changed[port] = 1;
> +	}
> +
> +	dev_dbg(& pdev->dev, "overcurrent situation %s\n",
> +		val ? "exited" : "notified");
> +
> +	return IRQ_HANDLED;
> +}
> +
> +/*-------------------------------------------------------------------------*/
> +
>  static int ohci_hcd_at91_drv_probe(struct platform_device *pdev)
>  {
>  	struct at91_usbh_data	*pdata = pdev->dev.platform_data;
>  	int			i;
>  
>  	if (pdata) {
> -		/* REVISIT make the driver support per-port power switching,
> -		 * and also overcurrent detection.  Here we assume the ports
> -		 * are always powered while this driver is active, and use
> -		 * active-low power switches.
> -		 */
>  		for (i = 0; i < ARRAY_SIZE(pdata->vbus_pin); i++) {
>  			if (pdata->vbus_pin[i] <= 0)
>  				continue;
>  			gpio_request(pdata->vbus_pin[i], "ohci_vbus");
> -			gpio_direction_output(pdata->vbus_pin[i], pdata->vbus_pin_inverted);
> +			ohci_at91_usb_set_power(pdata, i, 1);
> +		}
> +
> +		for (i = 0; i < ARRAY_SIZE(pdata->overcurrent_pin); i++) {
> +			int ret;
> +
> +			if (pdata->overcurrent_pin[i] <= 0)
> +				continue;
> +			gpio_request(pdata->overcurrent_pin[i], "ohci_overcurrent");
> +
> +			ret = request_irq(gpio_to_irq(pdata->overcurrent_pin[i]),
> +					  ohci_hcd_at91_overcurrent_irq,
> +					  IRQF_SHARED, "ohci_overcurrent", pdev);
> +			if (ret) {
> +				gpio_free(pdata->overcurrent_pin[i]);
> +				dev_warn(& pdev->dev, "cannot get GPIO IRQ for overcurrent\n");
> +			}
>  		}
>  	}
>  
> @@ -301,9 +500,16 @@ static int ohci_hcd_at91_drv_remove(struct platform_device *pdev)
>  		for (i = 0; i < ARRAY_SIZE(pdata->vbus_pin); i++) {
>  			if (pdata->vbus_pin[i] <= 0)
>  				continue;
> -			gpio_direction_output(pdata->vbus_pin[i], !pdata->vbus_pin_inverted);
> +			ohci_at91_usb_set_power(pdata, i, 0);
>  			gpio_free(pdata->vbus_pin[i]);
>  		}
> +
> +		for (i = 0; i < ARRAY_SIZE(pdata->overcurrent_pin); i++) {
> +			if (pdata->overcurrent_pin[i] <= 0)
> +				continue;
> +			free_irq(gpio_to_irq(pdata->overcurrent_pin[i]), pdev);
> +			gpio_free(pdata->overcurrent_pin[i]);
> +		}
>  	}
>  
>  	device_init_wakeup(&pdev->dev, 0);
diff mbox

Patch

diff --git a/arch/arm/mach-at91/include/mach/board.h b/arch/arm/mach-at91/include/mach/board.h
index 61d52dc..d07767f 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 {
 	u8		ports;		/* number of ports on root hub */
 	u8		vbus_pin[2];	/* port power-control pin */
 	u8              vbus_pin_inverted;
+	u8              overcurrent_supported;
+	u8              overcurrent_pin[2];
+	u8              overcurrent_status[2];
+	u8              overcurrent_changed[2];
 };
 extern void __init at91_add_device_usbh(struct at91_usbh_data *data);
 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 3612ccd..331909f 100644
--- a/drivers/usb/host/ohci-at91.c
+++ b/drivers/usb/host/ohci-at91.c
@@ -223,6 +223,156 @@  ohci_at91_start (struct usb_hcd *hcd)
 	return 0;
 }
 
+static void ohci_at91_usb_set_power(struct at91_usbh_data *pdata, int port, int enable)
+{
+	if (port < 0 || port >= 2)
+		return;
+
+	gpio_set_value(pdata->vbus_pin[port], !pdata->vbus_pin_inverted ^ enable);
+}
+
+static int ohci_at91_usb_get_power(struct at91_usbh_data *pdata, int port)
+{
+	if (port < 0 || port >= 2)
+		return -EINVAL;
+
+	return gpio_get_value(pdata->vbus_pin[port]) ^ !pdata->vbus_pin_inverted;
+}
+
+/*
+ * Update the status data from the hub with the over-current indicator change.
+ */
+static int ohci_at91_hub_status_data(struct usb_hcd *hcd, char *buf)
+{
+	struct at91_usbh_data *pdata = hcd->self.controller->platform_data;
+	int length = ohci_hub_status_data(hcd, buf);
+	int port;
+
+	for (port = 0; port < ARRAY_SIZE(pdata->overcurrent_pin); port++) {
+		if (pdata->overcurrent_changed[port]) {
+			if (! length)
+				length = 1;
+			buf[0] |= 1 << (port + 1);
+		}
+	}
+
+	return length;
+}
+
+/*
+ * Look at the control requests to the root hub and see if we need to override.
+ */
+static int ohci_at91_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
+				 u16 wIndex, char *buf, u16 wLength)
+{
+	struct at91_usbh_data *pdata = hcd->self.controller->platform_data;
+	struct usb_hub_descriptor *desc;
+	int ret = -EINVAL;
+	u32 *data = (u32 *)buf;
+
+	dev_dbg(hcd->self.controller,
+		"ohci_at91_hub_control(%p,0x%04x,0x%04x,0x%04x,%p,%04x)\n",
+		hcd, typeReq, wValue, wIndex, buf, wLength);
+
+	switch (typeReq) {
+	case SetPortFeature:
+		if (wValue == USB_PORT_FEAT_POWER) {
+			dev_dbg(hcd->self.controller, "SetPortFeat: POWER\n");
+			ohci_at91_usb_set_power(pdata, wIndex - 1, 1);
+			goto out;
+		}
+		break;
+
+	case ClearPortFeature:
+		switch (wValue) {
+		case USB_PORT_FEAT_C_OVER_CURRENT:
+			dev_dbg(hcd->self.controller,
+				"ClearPortFeature: C_OVER_CURRENT\n");
+
+			if (wIndex == 1 || wIndex == 2) {
+				pdata->overcurrent_changed[wIndex-1] = 0;
+				pdata->overcurrent_status[wIndex-1] = 0;
+			}
+
+			goto out;
+
+		case USB_PORT_FEAT_OVER_CURRENT:
+			dev_dbg(hcd->self.controller,
+				"ClearPortFeature: OVER_CURRENT\n");
+
+			if (wIndex == 1 || wIndex == 2) {
+				pdata->overcurrent_status[wIndex-1] = 0;
+			}
+
+			goto out;
+
+		case USB_PORT_FEAT_POWER:
+			dev_dbg(hcd->self.controller,
+				"ClearPortFeature: POWER\n");
+
+			if (wIndex == 1 || wIndex == 2) {
+				ohci_at91_usb_set_power(pdata, wIndex - 1, 0);
+				return 0;
+			}
+		}
+		break;
+	}
+
+	ret = ohci_hub_control(hcd, typeReq, wValue, wIndex, buf, wLength);
+	if (ret)
+		goto out;
+
+	switch (typeReq) {
+	case GetHubDescriptor:
+
+		/* update the hub's descriptor */
+
+		desc = (struct usb_hub_descriptor *)buf;
+
+		dev_dbg(hcd->self.controller, "wHubCharacteristics 0x%04x\n",
+			desc->wHubCharacteristics);
+
+		/* remove the old configurations for power-switching, and
+		 * over-current protection, and insert our new configuration
+		 */
+
+		desc->wHubCharacteristics &= ~cpu_to_le16(HUB_CHAR_LPSM);
+		desc->wHubCharacteristics |= cpu_to_le16(0x0001);
+
+		if (pdata->overcurrent_supported) {
+			desc->wHubCharacteristics &= ~cpu_to_le16(HUB_CHAR_OCPM);
+			desc->wHubCharacteristics |=  cpu_to_le16(0x0008|0x0001);
+		}
+
+		dev_dbg(hcd->self.controller, "wHubCharacteristics after 0x%04x\n",
+			desc->wHubCharacteristics);
+
+		return ret;
+
+	case GetPortStatus:
+		/* check port status */
+
+		dev_dbg(hcd->self.controller, "GetPortStatus(%d)\n", wIndex);
+
+		if (wIndex == 1 || wIndex == 2) {
+			if (! ohci_at91_usb_get_power(pdata, wIndex-1)) {
+				*data &= ~cpu_to_le32(RH_PS_PPS);
+			}
+
+			if (pdata->overcurrent_changed[wIndex-1]) {
+				*data |= cpu_to_le32(RH_PS_OCIC);
+			}
+
+			if (pdata->overcurrent_status[wIndex-1]) {
+				*data |= cpu_to_le32(RH_PS_POCI);
+			}
+		}
+	}
+
+ out:
+	return ret;
+}
+
 /*-------------------------------------------------------------------------*/
 
 static const struct hc_driver ohci_at91_hc_driver = {
@@ -258,8 +408,8 @@  static const struct hc_driver ohci_at91_hc_driver = {
 	/*
 	 * root hub support
 	 */
-	.hub_status_data =	ohci_hub_status_data,
-	.hub_control =		ohci_hub_control,
+	.hub_status_data =	ohci_at91_hub_status_data,
+	.hub_control =		ohci_at91_hub_control,
 #ifdef CONFIG_PM
 	.bus_suspend =		ohci_bus_suspend,
 	.bus_resume =		ohci_bus_resume,
@@ -269,22 +419,71 @@  static const struct hc_driver ohci_at91_hc_driver = {
 
 /*-------------------------------------------------------------------------*/
 
+static irqreturn_t ohci_hcd_at91_overcurrent_irq(int irq, void *data)
+{
+	struct platform_device *pdev = data;
+	struct at91_usbh_data *pdata = pdev->dev.platform_data;
+	int val, gpio, port;
+
+	/* From the GPIO notifying the over-current situation, find
+	 * out the corresponding port */
+	gpio = irq_to_gpio(irq);
+	for (port = 0; port < ARRAY_SIZE(pdata->overcurrent_pin); port++) {
+		if (pdata->overcurrent_pin[port] == gpio)
+			break;
+	}
+
+	if (port == ARRAY_SIZE(pdata->overcurrent_pin)) {
+		dev_err(& pdev->dev, "overcurrent interrupt from unknown GPIO\n");
+		return IRQ_HANDLED;
+	}
+
+	val = gpio_get_value(gpio);
+
+	/* When notified of an over-current situation, disable power
+	   on the corresponding port, and mark this port in
+	   over-current. */
+	if (! val) {
+		ohci_at91_usb_set_power(pdata, port, 0);
+		pdata->overcurrent_status[port]  = 1;
+		pdata->overcurrent_changed[port] = 1;
+	}
+
+	dev_dbg(& pdev->dev, "overcurrent situation %s\n",
+		val ? "exited" : "notified");
+
+	return IRQ_HANDLED;
+}
+
+/*-------------------------------------------------------------------------*/
+
 static int ohci_hcd_at91_drv_probe(struct platform_device *pdev)
 {
 	struct at91_usbh_data	*pdata = pdev->dev.platform_data;
 	int			i;
 
 	if (pdata) {
-		/* REVISIT make the driver support per-port power switching,
-		 * and also overcurrent detection.  Here we assume the ports
-		 * are always powered while this driver is active, and use
-		 * active-low power switches.
-		 */
 		for (i = 0; i < ARRAY_SIZE(pdata->vbus_pin); i++) {
 			if (pdata->vbus_pin[i] <= 0)
 				continue;
 			gpio_request(pdata->vbus_pin[i], "ohci_vbus");
-			gpio_direction_output(pdata->vbus_pin[i], pdata->vbus_pin_inverted);
+			ohci_at91_usb_set_power(pdata, i, 1);
+		}
+
+		for (i = 0; i < ARRAY_SIZE(pdata->overcurrent_pin); i++) {
+			int ret;
+
+			if (pdata->overcurrent_pin[i] <= 0)
+				continue;
+			gpio_request(pdata->overcurrent_pin[i], "ohci_overcurrent");
+
+			ret = request_irq(gpio_to_irq(pdata->overcurrent_pin[i]),
+					  ohci_hcd_at91_overcurrent_irq,
+					  IRQF_SHARED, "ohci_overcurrent", pdev);
+			if (ret) {
+				gpio_free(pdata->overcurrent_pin[i]);
+				dev_warn(& pdev->dev, "cannot get GPIO IRQ for overcurrent\n");
+			}
 		}
 	}
 
@@ -301,9 +500,16 @@  static int ohci_hcd_at91_drv_remove(struct platform_device *pdev)
 		for (i = 0; i < ARRAY_SIZE(pdata->vbus_pin); i++) {
 			if (pdata->vbus_pin[i] <= 0)
 				continue;
-			gpio_direction_output(pdata->vbus_pin[i], !pdata->vbus_pin_inverted);
+			ohci_at91_usb_set_power(pdata, i, 0);
 			gpio_free(pdata->vbus_pin[i]);
 		}
+
+		for (i = 0; i < ARRAY_SIZE(pdata->overcurrent_pin); i++) {
+			if (pdata->overcurrent_pin[i] <= 0)
+				continue;
+			free_irq(gpio_to_irq(pdata->overcurrent_pin[i]), pdev);
+			gpio_free(pdata->overcurrent_pin[i]);
+		}
 	}
 
 	device_init_wakeup(&pdev->dev, 0);