USB: UHCI: disable remote wakeup when it's not needed
authorAlan Stern <stern@rowland.harvard.edu>
Tue, 22 Apr 2008 14:49:15 +0000 (10:49 -0400)
committerGreg Kroah-Hartman <gregkh@suse.de>
Fri, 2 May 2008 17:25:57 +0000 (10:25 -0700)
This patch (as1084b) fixes the way uhci-hcd handles polling and
remote wakeups for its root hubs.  When remote wakeup is disabled,
neither interrupts nor polling should be enabled during a root-hub
suspend.  Likewise, if interrupts are enabled during suspend then
polling isn't needed.

Furthermore the EGSM (Enter Global Suspend Mode) bit shouldn't be set
in the Command register unless remote wakeup is enabled.  Apparently
some controllers will issue a remote-wakeup interrupt whenever EGSM
is on, even if Resume-Detect interrupts are supposedly disabled.

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

index d3e0d8aa398078f7c5c15313f19f656202dcafaa..3a7bfe7a8874c321990bb802910ea4a902671c9a 100644 (file)
@@ -234,7 +234,7 @@ static int resume_detect_interrupts_are_broken(struct uhci_hcd *uhci)
        return 0;
 }
 
-static int remote_wakeup_is_broken(struct uhci_hcd *uhci)
+static int global_suspend_mode_is_broken(struct uhci_hcd *uhci)
 {
        int port;
        const char *sys_info;
@@ -261,27 +261,60 @@ __releases(uhci->lock)
 __acquires(uhci->lock)
 {
        int auto_stop;
-       int int_enable, egsm_enable;
+       int int_enable, egsm_enable, wakeup_enable;
        struct usb_device *rhdev = uhci_to_hcd(uhci)->self.root_hub;
 
        auto_stop = (new_state == UHCI_RH_AUTO_STOPPED);
        dev_dbg(&rhdev->dev, "%s%s\n", __func__,
                        (auto_stop ? " (auto-stop)" : ""));
 
-       /* Enable resume-detect interrupts if they work.
-        * Then enter Global Suspend mode if _it_ works, still configured.
+       /* Start off by assuming Resume-Detect interrupts and EGSM work
+        * and that remote wakeups should be enabled.
         */
        egsm_enable = USBCMD_EGSM;
-       uhci->working_RD = 1;
+       uhci->RD_enable = 1;
        int_enable = USBINTR_RESUME;
-       if (remote_wakeup_is_broken(uhci))
-               egsm_enable = 0;
-       if (resume_detect_interrupts_are_broken(uhci) || !egsm_enable ||
+       wakeup_enable = 1;
+
+       /* In auto-stop mode wakeups must always be detected, but
+        * Resume-Detect interrupts may be prohibited.  (In the absence
+        * of CONFIG_PM, they are always disallowed.)
+        */
+       if (auto_stop) {
+               if (!device_may_wakeup(&rhdev->dev))
+                       int_enable = 0;
+
+       /* In bus-suspend mode wakeups may be disabled, but if they are
+        * allowed then so are Resume-Detect interrupts.
+        */
+       } else {
 #ifdef CONFIG_PM
-                       (!auto_stop && !rhdev->do_remote_wakeup) ||
+               if (!rhdev->do_remote_wakeup)
+                       wakeup_enable = 0;
 #endif
-                       (auto_stop && !device_may_wakeup(&rhdev->dev)))
-               uhci->working_RD = int_enable = 0;
+       }
+
+       /* EGSM causes the root hub to echo a 'K' signal (resume) out any
+        * port which requests a remote wakeup.  According to the USB spec,
+        * every hub is supposed to do this.  But if we are ignoring
+        * remote-wakeup requests anyway then there's no point to it.
+        * We also shouldn't enable EGSM if it's broken.
+        */
+       if (!wakeup_enable || global_suspend_mode_is_broken(uhci))
+               egsm_enable = 0;
+
+       /* If we're ignoring wakeup events then there's no reason to
+        * enable Resume-Detect interrupts.  We also shouldn't enable
+        * them if they are broken or disallowed.
+        *
+        * This logic may lead us to enabling RD but not EGSM.  The UHCI
+        * spec foolishly says that RD works only when EGSM is on, but
+        * there's no harm in enabling it anyway -- perhaps some chips
+        * will implement it!
+        */
+       if (!wakeup_enable || resume_detect_interrupts_are_broken(uhci) ||
+                       !int_enable)
+               uhci->RD_enable = int_enable = 0;
 
        outw(int_enable, uhci->io_addr + USBINTR);
        outw(egsm_enable | USBCMD_CF, uhci->io_addr + USBCMD);
@@ -308,7 +341,11 @@ __acquires(uhci->lock)
 
        uhci->rh_state = new_state;
        uhci->is_stopped = UHCI_IS_STOPPED;
-       uhci_to_hcd(uhci)->poll_rh = !int_enable;
+
+       /* If interrupts don't work and remote wakeup is enabled then
+        * the suspended root hub needs to be polled.
+        */
+       uhci_to_hcd(uhci)->poll_rh = (!int_enable && wakeup_enable);
 
        uhci_scan_schedule(uhci);
        uhci_fsbr_off(uhci);
@@ -344,9 +381,12 @@ __acquires(uhci->lock)
         * for 20 ms.
         */
        if (uhci->rh_state == UHCI_RH_SUSPENDED) {
+               unsigned egsm;
+
+               /* Keep EGSM on if it was set before */
+               egsm = inw(uhci->io_addr + USBCMD) & USBCMD_EGSM;
                uhci->rh_state = UHCI_RH_RESUMING;
-               outw(USBCMD_FGR | USBCMD_EGSM | USBCMD_CF,
-                               uhci->io_addr + USBCMD);
+               outw(USBCMD_FGR | USBCMD_CF | egsm, uhci->io_addr + USBCMD);
                spin_unlock_irq(&uhci->lock);
                msleep(20);
                spin_lock_irq(&uhci->lock);
@@ -801,8 +841,10 @@ static int uhci_pci_resume(struct usb_hcd *hcd)
 
        spin_unlock_irq(&uhci->lock);
 
-       if (!uhci->working_RD) {
-               /* Suspended root hub needs to be polled */
+       /* If interrupts don't work and remote wakeup is enabled then
+        * the suspended root hub needs to be polled.
+        */
+       if (!uhci->RD_enable && hcd->self.root_hub->do_remote_wakeup) {
                hcd->poll_rh = 1;
                usb_hcd_poll_rh_status(hcd);
        }
index 340d6ed3e6e92006c858e80ee8bdcd5f0ef0b179..7d01c5677f92ced1517c73dc562e853c2e6990fa 100644 (file)
@@ -400,8 +400,9 @@ struct uhci_hcd {
        unsigned int scan_in_progress:1;        /* Schedule scan is running */
        unsigned int need_rescan:1;             /* Redo the schedule scan */
        unsigned int dead:1;                    /* Controller has died */
-       unsigned int working_RD:1;              /* Suspended root hub doesn't
-                                                  need to be polled */
+       unsigned int RD_enable:1;               /* Suspended root hub with
+                                                  Resume-Detect interrupts
+                                                  enabled */
        unsigned int is_initialized:1;          /* Data structure is usable */
        unsigned int fsbr_is_on:1;              /* FSBR is turned on */
        unsigned int fsbr_is_wanted:1;          /* Does any URB want FSBR? */