* 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.
This isn't 2.6.2 material, but that UE elimination will make me want to see this before long!
- Dave
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.
--- 1.52/drivers/usb/host/ohci-hcd.c Sat Jan 3 16:18:59 2004
+++ edited/drivers/usb/host/ohci-hcd.c Sun Feb 1 14:23:15 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);
--- 1.13/drivers/usb/host/ohci-hub.c Mon Oct 13 06:24:06 2003
+++ edited/drivers/usb/host/ohci-hub.c Sun Feb 1 14:23:15 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? */
--- 1.18/drivers/usb/host/ohci-pci.c Mon Oct 13 06:24:06 2003
+++ edited/drivers/usb/host/ohci-pci.c Sun Feb 1 14:23:15 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);
--- 1.46/drivers/usb/host/ohci-q.c Mon Dec 29 16:10:11 2003
+++ edited/drivers/usb/host/ohci-q.c Sun Feb 1 14:23:15 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))
