ChangeSet 1.1500.2.171, 2004/02/06 14:19:24-08:00, david-b@pacbell.net

[PATCH] USB: USB misc OHCI updates

Here are three minor OHCI changes:

* Turn off periodic dma transfers until they're needed.  Extra DMAs
  consume power, and can trigger problems on marginal systems.

* New module param "power_switching" (default false).  Many boards
  will have no problems with this mode.  It makes OHCI act more like
  most external hubs and like EHCI.

* Minor SMP cleanup affecting display-only usbfs statistics.

On one system, turning off the periodic DMAs made two of the four
active OHCI controllers work in cases that previously triggered
"Unrecoverable Error" IRQs.


 drivers/usb/host/ohci-hcd.c |   35 ++++++++++++++++++++++++++---------
 drivers/usb/host/ohci-hub.c |    2 ++
 drivers/usb/host/ohci-pci.c |    3 +++
 drivers/usb/host/ohci-q.c   |   35 +++++++++++++++++++++++++----------
 4 files changed, 56 insertions(+), 19 deletions(-)


diff -Nru a/drivers/usb/host/ohci-hcd.c b/drivers/usb/host/ohci-hcd.c
--- a/drivers/usb/host/ohci-hcd.c	Mon Feb  9 14:37:09 2004
+++ b/drivers/usb/host/ohci-hcd.c	Mon Feb  9 14:37:09 2004
@@ -81,6 +81,7 @@
 #endif
 
 #include <linux/module.h>
+#include <linux/moduleparam.h>
 #include <linux/pci.h>
 #include <linux/kernel.h>
 #include <linux/delay.h>
@@ -103,7 +104,7 @@
 #include <asm/byteorder.h>
 
 
-#define DRIVER_VERSION "2003 Oct 13"
+#define DRIVER_VERSION "2004 Feb 02"
 #define DRIVER_AUTHOR "Roman Weissgaerber, David Brownell"
 #define DRIVER_DESC "USB 1.1 'Open' Host Controller (OHCI) Driver"
 
@@ -112,8 +113,7 @@
 // #define OHCI_VERBOSE_DEBUG	/* not always helpful */
 
 /* For initializing controller (mask in an HCFS mode too) */
-#define	OHCI_CONTROL_INIT \
-	 (OHCI_CTRL_CBSR & 0x3) | OHCI_CTRL_IE | OHCI_CTRL_PLE
+#define	OHCI_CONTROL_INIT 	OHCI_CTRL_CBSR
 
 #define OHCI_UNLINK_TIMEOUT	 (HZ / 10)
 
@@ -133,6 +133,12 @@
 #include "ohci-mem.c"
 #include "ohci-q.c"
 
+
+/* Some boards don't support per-port power switching */
+static int power_switching = 0;
+module_param (power_switching, bool, 0);
+MODULE_PARM_DESC (power_switching, "true (not default) to switch port power");
+
 /*-------------------------------------------------------------------------*/
 
 /*
@@ -288,11 +294,8 @@
 		 * with HC dead, we won't respect hc queue pointers
 		 * any more ... just clean up every urb's memory.
 		 */
-		if (urb->hcpriv) {
-			spin_unlock (&ohci->lock);
+		if (urb->hcpriv)
 			finish_urb (ohci, urb, NULL);
-			spin_lock (&ohci->lock);
-		}
 	}
 	spin_unlock_irqrestore (&ohci->lock, flags);
 	return 0;
@@ -413,6 +416,14 @@
 	ohci->hc_control = readl (&ohci->regs->control);
 	ohci->hc_control &= OHCI_CTRL_RWC;	/* hcfs 0 = RESET */
 	writel (ohci->hc_control, &ohci->regs->control);
+	if (power_switching) {
+		unsigned ports = roothub_a (ohci) & RH_A_NDP; 
+
+		/* power down each port */
+		for (temp = 0; temp < ports; temp++)
+			writel (RH_PS_LSDA,
+				&ohci->regs->roothub.portstatus [temp]);
+	}
 	// flush those pci writes
 	(void) readl (&ohci->regs->control);
 	wait_ms (50);
@@ -502,15 +513,21 @@
 		/* NSC 87560 and maybe others */
 		tmp |= RH_A_NOCP;
 		tmp &= ~(RH_A_POTPGT | RH_A_NPS);
+	} else if (power_switching) {
+		/* act like most external hubs:  use per-port power
+		 * switching and overcurrent reporting.
+		 */
+		tmp &= ~(RH_A_NPS | RH_A_NOCP);
+		tmp |= RH_A_PSM | RH_A_OCPM;
 	} else {
 		/* hub power always on; required for AMD-756 and some
-		 * Mac platforms, use this mode everywhere by default
+		 * Mac platforms.  ganged overcurrent reporting, if any.
 		 */
 		tmp |= RH_A_NPS;
 	}
 	writel (tmp, &ohci->regs->roothub.a);
 	writel (RH_HS_LPSC, &ohci->regs->roothub.status);
-	writel (0, &ohci->regs->roothub.b);
+	writel (power_switching ? RH_B_PPCM : 0, &ohci->regs->roothub.b);
 	// flush those pci writes
 	(void) readl (&ohci->regs->control);
 
diff -Nru a/drivers/usb/host/ohci-hub.c b/drivers/usb/host/ohci-hub.c
--- a/drivers/usb/host/ohci-hub.c	Mon Feb  9 14:37:09 2004
+++ b/drivers/usb/host/ohci-hub.c	Mon Feb  9 14:37:09 2004
@@ -128,6 +128,8 @@
 	desc->bDescLength = 7 + 2 * temp;
 
 	temp = 0;
+	if (rh & RH_A_NPS)		/* no power switching? */
+	    temp |= 0x0002;
 	if (rh & RH_A_PSM) 		/* per-port power switching? */
 	    temp |= 0x0001;
 	if (rh & RH_A_NOCP)		/* no overcurrent reporting? */
diff -Nru a/drivers/usb/host/ohci-pci.c b/drivers/usb/host/ohci-pci.c
--- a/drivers/usb/host/ohci-pci.c	Mon Feb  9 14:37:09 2004
+++ b/drivers/usb/host/ohci-pci.c	Mon Feb  9 14:37:09 2004
@@ -266,6 +266,9 @@
 			if (ohci->ed_bulktail)
 				ohci->hc_control |= OHCI_CTRL_BLE;
 		}
+		if (hcd_to_bus (&ohci->hcd)->bandwidth_isoc_reqs
+				|| hcd_to_bus (&ohci->hcd)->bandwidth_int_reqs)
+			ohci->hc_control |= OHCI_CTRL_PLE|OHCI_CTRL_IE;
 		hcd->state = USB_STATE_RUNNING;
 		writel (ohci->hc_control, &ohci->regs->control);
 
diff -Nru a/drivers/usb/host/ohci-q.c b/drivers/usb/host/ohci-q.c
--- a/drivers/usb/host/ohci-q.c	Mon Feb  9 14:37:09 2004
+++ b/drivers/usb/host/ohci-q.c	Mon Feb  9 14:37:09 2004
@@ -30,7 +30,7 @@
 /*
  * URB goes back to driver, and isn't reissued.
  * It's completely gone from HC data structures.
- * PRECONDITION:  no locks held, irqs blocked  (Giveback can call into HCD.)
+ * PRECONDITION:  ohci lock held, irqs blocked.
  */
 static void
 finish_urb (struct ohci_hcd *ohci, struct urb *urb, struct pt_regs *regs)
@@ -55,7 +55,6 @@
 	}
 	spin_unlock (&urb->lock);
 
-	// what lock protects these?
 	switch (usb_pipetype (urb->pipe)) {
 	case PIPE_ISOCHRONOUS:
 		hcd_to_bus (&ohci->hcd)->bandwidth_isoc_reqs--;
@@ -68,7 +67,18 @@
 #ifdef OHCI_VERBOSE_DEBUG
 	urb_print (urb, "RET", usb_pipeout (urb->pipe));
 #endif
+
+	/* urb->complete() can reenter this HCD */
+	spin_unlock (&ohci->lock);
 	usb_hcd_giveback_urb (&ohci->hcd, urb, regs);
+	spin_lock (&ohci->lock);
+
+	/* stop periodic dma if it's not needed */
+	if (hcd_to_bus (&ohci->hcd)->bandwidth_isoc_reqs == 0
+			&& hcd_to_bus (&ohci->hcd)->bandwidth_int_reqs == 0) {
+		ohci->hc_control &= ~(OHCI_CTRL_PLE|OHCI_CTRL_IE);
+		writel (ohci->hc_control, &ohci->regs->control);
+	}
 }
 
 
@@ -549,6 +559,7 @@
 	int		cnt = 0;
 	u32		info = 0;
 	int		is_out = usb_pipeout (urb->pipe);
+	int		periodic = 0;
 
 	/* OHCI handles the bulk/interrupt data toggles itself.  We just
 	 * use the device toggle bits for resetting, and rely on the fact
@@ -578,7 +589,8 @@
 	 */
 	case PIPE_INTERRUPT:
 		/* ... and periodic urbs have extra accounting */
-		hcd_to_bus (&ohci->hcd)->bandwidth_int_reqs++;
+		periodic = hcd_to_bus (&ohci->hcd)->bandwidth_int_reqs++ == 0
+			&& hcd_to_bus (&ohci->hcd)->bandwidth_isoc_reqs == 0;
 		/* FALLTHROUGH */
 	case PIPE_BULK:
 		info = is_out
@@ -646,9 +658,17 @@
 				data + urb->iso_frame_desc [cnt].offset,
 				urb->iso_frame_desc [cnt].length, urb, cnt);
 		}
-		hcd_to_bus (&ohci->hcd)->bandwidth_isoc_reqs++;
+		periodic = hcd_to_bus (&ohci->hcd)->bandwidth_isoc_reqs++ == 0
+			&& hcd_to_bus (&ohci->hcd)->bandwidth_int_reqs == 0;
 		break;
 	}
+
+	/* start periodic dma if needed */
+	if (periodic) {
+		ohci->hc_control |= OHCI_CTRL_PLE|OHCI_CTRL_IE;
+		writel (ohci->hc_control, &ohci->regs->control);
+	}
+
 	// ASSERT (urb_priv->length == cnt);
 }
 
@@ -949,9 +969,7 @@
 			/* if URB is done, clean up */
 			if (urb_priv->td_cnt == urb_priv->length) {
 				modified = completed = 1;
-				spin_unlock (&ohci->lock);
 				finish_urb (ohci, urb, regs);
-				spin_lock (&ohci->lock);
 			}
 		}
 		if (completed && !list_empty (&ed->td_list))
@@ -1030,11 +1048,8 @@
   		urb_priv->td_cnt++;
 
 		/* If all this urb's TDs are done, call complete() */
-  		if (urb_priv->td_cnt == urb_priv->length) {
-     			spin_unlock (&ohci->lock);
+  		if (urb_priv->td_cnt == urb_priv->length)
   			finish_urb (ohci, urb, regs);
-  			spin_lock (&ohci->lock);
-  		}
 
 		/* clean schedule:  unlink EDs that are no longer busy */
 		if (list_empty (&ed->td_list))
