Commit c1bcf4ad authored by David Brownell's avatar David Brownell Committed by Greg Kroah-Hartman

[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.
parent 5eed9dde
......@@ -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 @@ static inline void disable (struct ohci_hcd *ohci)
#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 @@ static int ohci_urb_dequeue (struct usb_hcd *hcd, struct urb *urb)
* 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 @@ static int hc_reset (struct ohci_hcd *ohci)
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 @@ static int hc_start (struct ohci_hcd *ohci)
/* 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);
......
......@@ -128,6 +128,8 @@ ohci_hub_descriptor (
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? */
......
......@@ -266,6 +266,9 @@ static int ohci_pci_resume (struct usb_hcd *hcd)
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);
......
......@@ -30,7 +30,7 @@ static void urb_free_priv (struct ohci_hcd *hc, urb_priv_t *urb_priv)
/*
* 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 @@ finish_urb (struct ohci_hcd *ohci, struct urb *urb, struct pt_regs *regs)
}
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 @@ finish_urb (struct ohci_hcd *ohci, struct urb *urb, struct pt_regs *regs)
#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 @@ static void td_submit_urb (
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 @@ static void td_submit_urb (
*/
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 @@ static void td_submit_urb (
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 @@ finish_unlinks (struct ohci_hcd *ohci, u16 tick, struct pt_regs *regs)
/* 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 @@ dl_done_list (struct ohci_hcd *ohci, struct td *td, struct pt_regs *regs)
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))
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment