[PATCH] USB UHCI: Use root-hub IRQs while suspended

This patch, which has as478b as a prerequisite, enables the uhci-hcd
driver to take advantage of root-hub IRQs rather than polling during the
time it is suspended.  (Unfortunately the hardware doesn't support
port-change interrupts while the controller is running.)  It also turns
off the driver's private timer while the controller is suspended, as it
isn't needed then.  The combined elimination of polling interrupts and
timer interrupts ought to be enough to allow some systems to save a
noticeable amount of power while they are otherwise idle.

Signed-off-by: Alan Stern <stern@rowland.harvard.edu>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
diff --git a/drivers/usb/host/uhci-hcd.c b/drivers/usb/host/uhci-hcd.c
index 82e608a..25a718e 100644
--- a/drivers/usb/host/uhci-hcd.c
+++ b/drivers/usb/host/uhci-hcd.c
@@ -84,6 +84,8 @@
 
 static kmem_cache_t *uhci_up_cachep;	/* urb_priv */
 
+static void suspend_rh(struct uhci_hcd *uhci, enum uhci_rh_state new_state);
+static void wakeup_rh(struct uhci_hcd *uhci);
 static void uhci_get_current_frame_number(struct uhci_hcd *uhci);
 
 /* If a transfer is still active after this much time, turn off FSBR */
@@ -133,12 +135,12 @@
 	outw(0, uhci->io_addr + USBINTR);
 	outw(0, uhci->io_addr + USBCMD);
 
-	uhci->resume_detect = 0;
 	uhci->port_c_suspend = uhci->suspended_ports =
 			uhci->resuming_ports = 0;
 	uhci->rh_state = UHCI_RH_RESET;
 	uhci->is_stopped = UHCI_IS_STOPPED;
 	uhci_to_hcd(uhci)->state = HC_STATE_HALT;
+	uhci_to_hcd(uhci)->poll_rh = 0;
 }
 
 /*
@@ -148,6 +150,7 @@
 {
 	reset_hc(uhci);
 	uhci->hc_inaccessible = 1;
+	del_timer(&uhci->stall_timer);
 }
 
 /*
@@ -302,14 +305,14 @@
 
 	uhci->rh_state = new_state;
 	uhci->is_stopped = UHCI_IS_STOPPED;
-	uhci->resume_detect = 0;
+	del_timer(&uhci->stall_timer);
+	uhci_to_hcd(uhci)->poll_rh = !int_enable;
 
 	uhci_scan_schedule(uhci, NULL);
 }
 
 static void start_rh(struct uhci_hcd *uhci)
 {
-	uhci->rh_state = UHCI_RH_RUNNING;
 	uhci->is_stopped = 0;
 	smp_wmb();
 
@@ -320,6 +323,9 @@
 	outw(USBINTR_TIMEOUT | USBINTR_RESUME | USBINTR_IOC | USBINTR_SP,
 			uhci->io_addr + USBINTR);
 	mb();
+	uhci->rh_state = UHCI_RH_RUNNING;
+	uhci_to_hcd(uhci)->poll_rh = 1;
+	restart_timer(uhci);
 }
 
 static void wakeup_rh(struct uhci_hcd *uhci)
@@ -353,36 +359,9 @@
 	}
 
 	start_rh(uhci);
-}
 
-static void rh_state_transitions(struct uhci_hcd *uhci)
-{
-	switch (uhci->rh_state) {
-	    case UHCI_RH_RUNNING:
-		/* are any devices attached? */
-		if (!any_ports_active(uhci)) {
-			uhci->rh_state = UHCI_RH_RUNNING_NODEVS;
-			uhci->auto_stop_time = jiffies + HZ;
-		}
-		break;
-
-	    case UHCI_RH_RUNNING_NODEVS:
-		/* auto-stop if nothing connected for 1 second */
-		if (any_ports_active(uhci))
-			uhci->rh_state = UHCI_RH_RUNNING;
-		else if (time_after_eq(jiffies, uhci->auto_stop_time))
-			suspend_rh(uhci, UHCI_RH_AUTO_STOPPED);
-		break;
-
-	    case UHCI_RH_AUTO_STOPPED:
-		/* wakeup if requested by a device */
-		if (uhci->resume_detect)
-			wakeup_rh(uhci);
-		break;
-
-	    default:
-		break;
-	}
+	/* Restart root hub polling */
+	mod_timer(&uhci_to_hcd(uhci)->rh_timer, jiffies);
 }
 
 static void stall_callback(unsigned long _uhci)
@@ -394,14 +373,8 @@
 	uhci_scan_schedule(uhci, NULL);
 	check_fsbr(uhci);
 
-	/* Poll for and perform state transitions */
-	if (!uhci->hc_inaccessible) {
-		rh_state_transitions(uhci);
-		if (uhci->suspended_ports)
-			uhci_check_ports(uhci);
-	}
-
-	restart_timer(uhci);
+	if (!uhci->is_stopped)
+		restart_timer(uhci);
 	spin_unlock_irqrestore(&uhci->lock, flags);
 }
 
@@ -443,7 +416,7 @@
 	}
 
 	if (status & USBSTS_RD)
-		uhci->resume_detect = 1;
+		usb_hcd_poll_rh_status(hcd);
 
 	spin_lock_irqsave(&uhci->lock, flags);
 	uhci_scan_schedule(uhci, regs);
@@ -542,6 +515,7 @@
 	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# */
 
@@ -714,8 +688,6 @@
 	configure_hc(uhci);
 	start_rh(uhci);
 
-	restart_timer(uhci);
-
 	udev->speed = USB_SPEED_FULL;
 
 	if (usb_hcd_register_root_hub(udev, hcd) != 0) {
@@ -730,8 +702,8 @@
  * error exits:
  */
 err_start_root_hub:
-	del_timer_sync(&uhci->stall_timer);
 	reset_hc(uhci);
+	del_timer_sync(&uhci->stall_timer);
 
 err_alloc_skelqh:
 	for (i = 0; i < UHCI_NUM_SKELQH; i++)
@@ -771,13 +743,12 @@
 {
 	struct uhci_hcd *uhci = hcd_to_uhci(hcd);
 
-	del_timer_sync(&uhci->stall_timer);
-
 	spin_lock_irq(&uhci->lock);
 	reset_hc(uhci);
 	uhci_scan_schedule(uhci, NULL);
 	spin_unlock_irq(&uhci->lock);
-	
+
+	del_timer_sync(&uhci->stall_timer);
 	release_uhci(uhci);
 }
 
@@ -844,6 +815,8 @@
 
 done:
 	spin_unlock_irq(&uhci->lock);
+	if (rc == 0)
+		del_timer_sync(&hcd->rh_timer);
 	return rc;
 }
 
@@ -875,6 +848,9 @@
 		suspend_rh(uhci, UHCI_RH_SUSPENDED);
 
 	spin_unlock_irq(&uhci->lock);
+
+	if (hcd->poll_rh)
+		usb_hcd_poll_rh_status(hcd);
 	return 0;
 }
 #endif