]> git.hungrycats.org Git - linux/commitdiff
[PATCH] USB: USB misc OHCI updates
authorDavid Brownell <david-b@pacbell.net>
Fri, 6 Feb 2004 06:19:24 +0000 (22:19 -0800)
committerGreg Kroah-Hartman <greg@kroah.com>
Fri, 6 Feb 2004 06:19:24 +0000 (22:19 -0800)
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
drivers/usb/host/ohci-hub.c
drivers/usb/host/ohci-pci.c
drivers/usb/host/ohci-q.c

index 1c3c8e509964e9f879ae9fa0a81d87168ebc9089..cc2c9c259197bc527fc16e0dcf7499444195e523 100644 (file)
@@ -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>
 #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"
 
 // #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);
 
index 4f8260c91ba4b6e06d4b38410bc7480f19489b4e..25aa7c85c388605ba534af9ccf183b0e9cdc7c31 100644 (file)
@@ -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? */
index 431795328c264c63382009549af5e3e2f2868163..ea4fb88f7fe22adc155a830d4621cbe50a7b204e 100644 (file)
@@ -266,6 +266,9 @@ restart:
                        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);
 
index 9b26e81aa16f2394aabc7037baeaab5eed66913e..8bf0102b72c0d14e0cbcb5af73ca9b28e0f7c31f 100644 (file)
@@ -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 @@ rescan_this:
                        /* 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))