[PATCH] USB UHCI: improved reset handling
authorAlan Stern <stern@rowland.harvard.edu>
Fri, 22 Apr 2005 18:39:12 +0000 (14:39 -0400)
committerGreg Kroah-Hartman <gregkh@suse.de>
Mon, 27 Jun 2005 21:43:44 +0000 (14:43 -0700)
This patch improves the strategy uhci-hcd uses for performing controller
resets and checking whether they are needed.

The HCRESET command doesn't affect the Suspend, Resume,
or Reset bits in the port status & control registers, so
the driver must clear them by itself.  This means the
code to figure out how many ports there are has to be moved
to an earlier spot in the driver.

The R/WC bits in the USBLEGSUP register can be set by the
hardware even in the absence of BIOS meddling with legacy
support features.  Hence it's not a good idea to check them
while trying to determine whether the BIOS has altered the
controller's state.

Signed-off-by: Alan Stern <stern@rowland.harvard.edu>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
drivers/usb/host/uhci-hcd.c

index 25a718eb1d0fffd07f100079ac5239e4629b5e41..cec070fa8c83e0b5b4fbe7a52d4a3518702a9d2a 100644 (file)
@@ -112,6 +112,8 @@ static inline void restart_timer(struct uhci_hcd *uhci)
  */
 static void reset_hc(struct uhci_hcd *uhci)
 {
+       int port;
+
        /* Turn off PIRQ enable and SMI enable.  (This also turns off the
         * BIOS's USB Legacy Support.)  Turn off all the R/WC bits too.
         */
@@ -135,6 +137,13 @@ static void reset_hc(struct uhci_hcd *uhci)
        outw(0, uhci->io_addr + USBINTR);
        outw(0, uhci->io_addr + USBCMD);
 
+       /* HCRESET doesn't affect the Suspend, Reset, and Resume Detect
+        * bits in the port status and control registers.
+        * We have to clear them by hand.
+        */
+       for (port = 0; port < uhci->rh_numports; ++port)
+               outw(0, uhci->io_addr + USBPORTSC1 + (port * 2));
+
        uhci->port_c_suspend = uhci->suspended_ports =
                        uhci->resuming_ports = 0;
        uhci->rh_state = UHCI_RH_RESET;
@@ -166,14 +175,14 @@ static void check_and_reset_hc(struct uhci_hcd *uhci)
         * When restarting a suspended controller, we expect all the
         * settings to be the same as we left them:
         *
-        *      PIRQ and SMI disabled, no R/WC bits set in USBLEGSUP;
+        *      PIRQ and SMI disabled, no R/W bits set in USBLEGSUP;
         *      Controller is stopped and configured with EGSM set;
         *      No interrupts enabled except possibly Resume Detect.
         *
         * If any of these conditions are violated we do a complete reset.
         */
        pci_read_config_word(to_pci_dev(uhci_dev(uhci)), USBLEGSUP, &legsup);
-       if (legsup & ~USBLEGSUP_RO) {
+       if (legsup & ~(USBLEGSUP_RO | USBLEGSUP_RWC)) {
                dev_dbg(uhci_dev(uhci), "%s: legsup = 0x%04x\n",
                                __FUNCTION__, legsup);
                goto reset_needed;
@@ -478,9 +487,37 @@ static void release_uhci(struct uhci_hcd *uhci)
 static int uhci_reset(struct usb_hcd *hcd)
 {
        struct uhci_hcd *uhci = hcd_to_uhci(hcd);
+       unsigned io_size = (unsigned) hcd->rsrc_len;
+       int port;
 
        uhci->io_addr = (unsigned long) hcd->rsrc_start;
 
+       /* The UHCI spec says devices must have 2 ports, and goes on to say
+        * they may have more but gives no way to determine how many there
+        * are.  However, according to the UHCI spec, Bit 7 of the port
+        * status and control register is always set to 1.  So we try to
+        * use this to our advantage.
+        */
+       for (port = 0; port < (io_size - USBPORTSC1) / 2; port++) {
+               unsigned int portstatus;
+
+               portstatus = inw(uhci->io_addr + USBPORTSC1 + (port * 2));
+               if (!(portstatus & 0x0080))
+                       break;
+       }
+       if (debug)
+               dev_info(uhci_dev(uhci), "detected %d ports\n", port);
+
+       /* Anything less than 2 or greater than 7 is weird,
+        * so we'll ignore it.
+        */
+       if (port < 2 || port > UHCI_RH_MAXCHILD) {
+               dev_info(uhci_dev(uhci), "port count misdetected? "
+                               "forcing to 2 ports\n");
+               port = 2;
+       }
+       uhci->rh_numports = port;
+
        /* Kick BIOS off this hardware and reset if the controller
         * isn't already safely quiescent.
         */
@@ -508,13 +545,11 @@ static int uhci_start(struct usb_hcd *hcd)
 {
        struct uhci_hcd *uhci = hcd_to_uhci(hcd);
        int retval = -EBUSY;
-       int i, port;
-       unsigned io_size;
+       int i;
        dma_addr_t dma_handle;
        struct usb_device *udev;
        struct dentry *dentry;
 
-       io_size = (unsigned) hcd->rsrc_len;
        hcd->uses_new_polling = 1;
        if (pci_find_capability(to_pci_dev(uhci_dev(uhci)), PCI_CAP_ID_PM))
                hcd->can_wakeup = 1;            /* Assume it supports PME# */
@@ -578,30 +613,6 @@ static int uhci_start(struct usb_hcd *hcd)
 
        /* Initialize the root hub */
 
-       /* UHCI specs says devices must have 2 ports, but goes on to say */
-       /*  they may have more but give no way to determine how many they */
-       /*  have. However, according to the UHCI spec, Bit 7 is always set */
-       /*  to 1. So we try to use this to our advantage */
-       for (port = 0; port < (io_size - 0x10) / 2; port++) {
-               unsigned int portstatus;
-
-               portstatus = inw(uhci->io_addr + 0x10 + (port * 2));
-               if (!(portstatus & 0x0080))
-                       break;
-       }
-       if (debug)
-               dev_info(uhci_dev(uhci), "detected %d ports\n", port);
-
-       /* This is experimental so anything less than 2 or greater than 8 is */
-       /*  something weird and we'll ignore it */
-       if (port < 2 || port > UHCI_RH_MAXCHILD) {
-               dev_info(uhci_dev(uhci), "port count misdetected? "
-                               "forcing to 2 ports\n");
-               port = 2;
-       }
-
-       uhci->rh_numports = port;
-
        udev = usb_alloc_dev(NULL, &hcd->self, 0);
        if (!udev) {
                dev_err(uhci_dev(uhci), "unable to allocate root hub\n");