# This is a BitKeeper generated patch for the following project:
# Project Name: Linux kernel tree
# This patch format is intended for GNU patch command version 2.5 or higher.
# This patch includes the following deltas:
#	           ChangeSet	1.655   -> 1.656  
#	drivers/usb/host/ehci-q.c	1.25    -> 1.26   
#	drivers/usb/host/ehci-hcd.c	1.27    -> 1.28   
#	drivers/usb/host/ehci.h	1.10    -> 1.11   
#
# The following is the BitKeeper ChangeSet Log
# --------------------------------------------
# 02/09/11	david-b@pacbell.net	1.656
# [PATCH] ehci, async idle timout
# 
# One more patch:  this turns off async schedule processing
# if there are no control or bulk transactions for a while
# (currently HZ/3).  Consequence:  no PCI accesses unless
# there's work to do.  (And a FIXME comment is gone!)
# --------------------------------------------
#
diff -Nru a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c
--- a/drivers/usb/host/ehci-hcd.c	Thu Sep 12 10:25:42 2002
+++ b/drivers/usb/host/ehci-hcd.c	Thu Sep 12 10:25:42 2002
@@ -111,6 +111,7 @@
 #define	EHCI_TUNE_MULT_TT	1
 
 #define EHCI_WATCHDOG_JIFFIES	(HZ/100)	/* arbitrary; ~10 msec */
+#define EHCI_ASYNC_JIFFIES	(HZ/3)		/* async idle timeout */
 
 /* Initial IRQ latency:  lower than default */
 static int log2_irq_thresh = 0;		// 0 to 6
@@ -247,9 +248,14 @@
 	struct ehci_hcd		*ehci = (struct ehci_hcd *) param;
 	unsigned long		flags;
 
-	/* guard against lost IAA, which wedges everything */
 	spin_lock_irqsave (&ehci->lock, flags);
+	/* guard against lost IAA, which wedges everything */
 	ehci_irq (&ehci->hcd);
+ 	/* unlink the last qh after it's idled a while */
+ 	if (ehci->async_idle) {
+ 		start_unlink_async (ehci, ehci->async);
+ 		ehci->async_idle = 0;
+	}
 	spin_unlock_irqrestore (&ehci->lock, flags);
 }
 
diff -Nru a/drivers/usb/host/ehci-q.c b/drivers/usb/host/ehci-q.c
--- a/drivers/usb/host/ehci-q.c	Thu Sep 12 10:25:42 2002
+++ b/drivers/usb/host/ehci-q.c	Thu Sep 12 10:25:42 2002
@@ -736,6 +736,8 @@
 	}
 	qh->qh_state = QH_STATE_LINKED;
 	/* qtd completions reported later by interrupt */
+
+	ehci->async_idle = 0;
 }
 
 /*-------------------------------------------------------------------------*/
@@ -1004,16 +1006,18 @@
 			}
 
 			/* unlink idle entries, reducing HC PCI usage as
-			 * well as HCD schedule-scanning costs
+			 * well as HCD schedule-scanning costs.  removing
+			 * the last qh is deferred, since it's costly.
 			 */
 			if (list_empty (&qh->qtd_list) && !ehci->reclaim) {
 				if (qh->qh_next.qh != qh) {
 					// dbg ("irq/empty");
 					start_unlink_async (ehci, qh);
-				} else {
-					// FIXME:  arrange to stop
-					// after it's been idle a while.
-					// stop/restart isn't free...
+				} else if (!timer_pending (&ehci->watchdog)) {
+					/* can't use IAA for last entry */
+					ehci->async_idle = 1;
+					mod_timer (&ehci->watchdog,
+						jiffies + EHCI_ASYNC_JIFFIES);
 				}
 			}
 			qh = qh->qh_next.qh;
diff -Nru a/drivers/usb/host/ehci.h b/drivers/usb/host/ehci.h
--- a/drivers/usb/host/ehci.h	Thu Sep 12 10:25:42 2002
+++ b/drivers/usb/host/ehci.h	Thu Sep 12 10:25:42 2002
@@ -39,7 +39,8 @@
 	/* async schedule support */
 	struct ehci_qh		*async;
 	struct ehci_qh		*reclaim;
-	int			reclaim_ready;
+	int			reclaim_ready : 1,
+				async_idle : 1;
 
 	/* periodic schedule support */
 #define	DEFAULT_I_TDPS		1024		/* some HCs can do less */
