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 +#include #include #include #include @@ -103,7 +104,7 @@ #include -#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))