USB: OHCI avoids root hub timer polling
authorDavid Brownell <david-b@pacbell.net>
Fri, 4 Aug 2006 18:31:55 +0000 (11:31 -0700)
committerGreg Kroah-Hartman <gregkh@suse.de>
Wed, 27 Sep 2006 18:58:48 +0000 (11:58 -0700)
This teaches OHCI to use the root hub status change (RHSC) IRQ, bypassing
root hub timers most of the time and switching over to the "new" root hub
polling scheme.  It's complicated by the fact that implementations of OHCI
trigger and ack that IRQ differently (the spec is vague there).

Avoiding root hub timers helps mechanisms like "dynamic tick" leave the
CPU in lowpower modes for longer intervals.

Signed-off-by: David Brownell <dbrownell@users.sourceforge.net>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
13 files changed:
drivers/usb/host/ohci-at91.c
drivers/usb/host/ohci-au1xxx.c
drivers/usb/host/ohci-dbg.c
drivers/usb/host/ohci-ep93xx.c
drivers/usb/host/ohci-hcd.c
drivers/usb/host/ohci-hub.c
drivers/usb/host/ohci-lh7a404.c
drivers/usb/host/ohci-omap.c
drivers/usb/host/ohci-pci.c
drivers/usb/host/ohci-ppc-soc.c
drivers/usb/host/ohci-pxa27x.c
drivers/usb/host/ohci-s3c2410.c
drivers/usb/host/ohci-sa1111.c

index 85cc059705a645e947eae76f13be929048f8eef4..33b75087bc0c169270379f93be9caf509df4dd12 100644 (file)
@@ -239,7 +239,7 @@ static const struct hc_driver ohci_at91_hc_driver = {
         */
        .hub_status_data =      ohci_hub_status_data,
        .hub_control =          ohci_hub_control,
-
+       .hub_irq_enable =       ohci_rhsc_enable,
 #ifdef CONFIG_PM
        .bus_suspend =          ohci_bus_suspend,
        .bus_resume =           ohci_bus_resume,
index f7a975d5db097757965b758baf14612c86b72f62..44ed3a4c01efe4f9fb8a9e7239240e30258daa67 100644 (file)
@@ -268,10 +268,6 @@ static const struct hc_driver ohci_au1xxx_hc_driver = {
         * basic lifecycle operations
         */
        .start =                ohci_au1xxx_start,
-#ifdef CONFIG_PM
-       /* suspend:             ohci_au1xxx_suspend,  -- tbd */
-       /* resume:              ohci_au1xxx_resume,   -- tbd */
-#endif /*CONFIG_PM*/
        .stop =                 ohci_stop,
 
        /*
@@ -291,6 +287,7 @@ static const struct hc_driver ohci_au1xxx_hc_driver = {
         */
        .hub_status_data =      ohci_hub_status_data,
        .hub_control =          ohci_hub_control,
+       .hub_irq_enable =       ohci_rhsc_enable,
 #ifdef CONFIG_PM
        .bus_suspend =          ohci_bus_suspend,
        .bus_resume =           ohci_bus_resume,
index 7bfffcbbd226ec5a6089e775a2116825027b5988..da52609a929028487df3a0249b2d88cb023d2f66 100644 (file)
@@ -667,6 +667,11 @@ show_registers (struct class_device *class_dev, char *buf)
        size -= temp;
        next += temp;
 
+       temp = scnprintf (next, size, "hub poll timer %s\n",
+                       ohci_to_hcd(ohci)->poll_rh ? "ON" : "off");
+       size -= temp;
+       next += temp;
+
        /* roothub */
        ohci_dump_roothub (ohci, 1, &next, &size);
 
index 6531c4d26527208eb6dac426a93d59ef36939546..1a1d320b7995d1451701daf0ca570c8a94de05da 100644 (file)
@@ -134,6 +134,7 @@ static struct hc_driver ohci_ep93xx_hc_driver = {
        .get_frame_number       = ohci_get_frame,
        .hub_status_data        = ohci_hub_status_data,
        .hub_control            = ohci_hub_control,
+       .hub_irq_enable         = ohci_rhsc_enable,
 #ifdef CONFIG_PM
        .bus_suspend            = ohci_bus_suspend,
        .bus_resume             = ohci_bus_resume,
index 94d8cf4b36c19cf1472ae3c64d5874d143fd60fa..0684f57c14a0ebe3b58ff61dd60feb175df6c095 100644 (file)
 
 #include "../core/hcd.h"
 
-#define DRIVER_VERSION "2005 April 22"
+#define DRIVER_VERSION "2006 August 04"
 #define DRIVER_AUTHOR "Roman Weissgaerber, David Brownell"
 #define DRIVER_DESC "USB 1.1 'Open' Host Controller (OHCI) Driver"
 
 #undef OHCI_VERBOSE_DEBUG      /* not always helpful */
 
 /* For initializing controller (mask in an HCFS mode too) */
-#define        OHCI_CONTROL_INIT       OHCI_CTRL_CBSR
+#define        OHCI_CONTROL_INIT       OHCI_CTRL_CBSR
 #define        OHCI_INTR_INIT \
-       (OHCI_INTR_MIE | OHCI_INTR_UE | OHCI_INTR_RD | OHCI_INTR_WDH)
+               (OHCI_INTR_MIE | OHCI_INTR_RHSC | OHCI_INTR_UE \
+               | OHCI_INTR_RD | OHCI_INTR_WDH)
 
 #ifdef __hppa__
 /* On PA-RISC, PDC can leave IR set incorrectly; ignore it there. */
 
 static const char      hcd_name [] = "ohci_hcd";
 
+#define        STATECHANGE_DELAY       msecs_to_jiffies(300)
+
 #include "ohci.h"
 
 static void ohci_dump (struct ohci_hcd *ohci, int verbose);
@@ -446,7 +449,6 @@ static int ohci_init (struct ohci_hcd *ohci)
 
        disable (ohci);
        ohci->regs = hcd->regs;
-       ohci->next_statechange = jiffies;
 
        /* REVISIT this BIOS handshake is now moved into PCI "quirks", and
         * was never needed for most non-PCI systems ... remove the code?
@@ -637,10 +639,14 @@ retry:
                return -EOVERFLOW;
        }
 
-       /* start controller operations */
+       /* use rhsc irqs after khubd is fully initialized */
+       hcd->poll_rh = 1;
+       hcd->uses_new_polling = 1;
+
+       /* start controller operations */
        ohci->hc_control &= OHCI_CTRL_RWC;
-       ohci->hc_control |= OHCI_CONTROL_INIT | OHCI_USB_OPER;
-       ohci_writel (ohci, ohci->hc_control, &ohci->regs->control);
+       ohci->hc_control |= OHCI_CONTROL_INIT | OHCI_USB_OPER;
+       ohci_writel (ohci, ohci->hc_control, &ohci->regs->control);
        hcd->state = HC_STATE_RUNNING;
 
        /* wake on ConnectStatusChange, matching external hubs */
@@ -648,7 +654,7 @@ retry:
 
        /* Choose the interrupts we care about now, others later on demand */
        mask = OHCI_INTR_INIT;
-       ohci_writel (ohci, mask, &ohci->regs->intrstatus);
+       ohci_writel (ohci, ~0, &ohci->regs->intrstatus);
        ohci_writel (ohci, mask, &ohci->regs->intrenable);
 
        /* handle root hub init quirks ... */
@@ -672,6 +678,7 @@ retry:
        // flush those writes
        (void) ohci_readl (ohci, &ohci->regs->control);
 
+       ohci->next_statechange = jiffies + STATECHANGE_DELAY;
        spin_unlock_irq (&ohci->lock);
 
        // POTPGT delay is bits 24-31, in 2 ms units.
@@ -709,7 +716,23 @@ static irqreturn_t ohci_irq (struct usb_hcd *hcd, struct pt_regs *ptregs)
        /* interrupt for some other device? */
        } else if ((ints &= ohci_readl (ohci, &regs->intrenable)) == 0) {
                return IRQ_NOTMINE;
-       } 
+       }
+
+       /* NOTE:  vendors didn't always make the same implementation
+        * choices for RHSC.  Sometimes it triggers on an edge (like
+        * setting and maybe clearing a port status change bit); and
+        * it's level-triggered on other silicon, active until khubd
+        * clears all active port status change bits.  Poll by timer
+        * til it's fully debounced and the difference won't matter.
+        */
+       if (ints & OHCI_INTR_RHSC) {
+               ohci_vdbg (ohci, "rhsc\n");
+               ohci_writel (ohci, OHCI_INTR_RHSC, &regs->intrdisable);
+               hcd->poll_rh = 1;
+               ohci->next_statechange = jiffies + STATECHANGE_DELAY;
+               ohci_writel (ohci, OHCI_INTR_RHSC, &regs->intrstatus);
+               usb_hcd_poll_rh_status(hcd);
+       }
 
        if (ints & OHCI_INTR_UE) {
                disable (ohci);
index 5b0a23fd798bb46294885432878e4c6429a660ed..f1b1ed086bde25d1859d3cb79e3ed7ff905d7a92 100644 (file)
 
 /*-------------------------------------------------------------------------*/
 
+/* hcd->hub_irq_enable() */
+static void ohci_rhsc_enable (struct usb_hcd *hcd)
+{
+       struct ohci_hcd         *ohci = hcd_to_ohci (hcd);
+
+       ohci_writel (ohci, OHCI_INTR_RHSC, &ohci->regs->intrenable);
+}
+
 #ifdef CONFIG_PM
 
 #define OHCI_SCHED_ENABLES \
@@ -123,6 +131,9 @@ static int ohci_bus_suspend (struct usb_hcd *hcd)
        /* no resumes until devices finish suspending */
        ohci->next_statechange = jiffies + msecs_to_jiffies (5);
 
+       /* no timer polling */
+       hcd->poll_rh = 0;
+
 done:
        /* external suspend vs self autosuspend ... same effect */
        if (status == 0)
@@ -256,8 +267,8 @@ static int ohci_bus_resume (struct usb_hcd *hcd)
        /* TRSMRCY */
        msleep (10);
 
-       /* keep it alive for ~5x suspend + resume costs */
-       ohci->next_statechange = jiffies + msecs_to_jiffies (250);
+       /* keep it alive for more than ~5x suspend + resume costs */
+       ohci->next_statechange = jiffies + STATECHANGE_DELAY;
 
        /* maybe turn schedules back on */
        enables = 0;
@@ -302,9 +313,10 @@ ohci_hub_status_data (struct usb_hcd *hcd, char *buf)
 {
        struct ohci_hcd *ohci = hcd_to_ohci (hcd);
        int             i, changed = 0, length = 1;
-       int             can_suspend = device_may_wakeup(&hcd->self.root_hub->dev);
+       int             can_suspend;
        unsigned long   flags;
 
+       can_suspend = device_may_wakeup(&hcd->self.root_hub->dev);
        spin_lock_irqsave (&ohci->lock, flags);
 
        /* handle autosuspended root:  finish resuming before
@@ -339,6 +351,10 @@ ohci_hub_status_data (struct usb_hcd *hcd, char *buf)
        for (i = 0; i < ohci->num_ports; i++) {
                u32     status = roothub_portstatus (ohci, i);
 
+               /* can't autosuspend with active ports */
+               if ((status & RH_PS_PES) && !(status & RH_PS_PSS))
+                       can_suspend = 0;
+
                if (status & (RH_PS_CSC | RH_PS_PESC | RH_PS_PSSC
                                | RH_PS_OCIC | RH_PS_PRSC)) {
                        changed = 1;
@@ -348,32 +364,41 @@ ohci_hub_status_data (struct usb_hcd *hcd, char *buf)
                            buf [1] |= 1 << (i - 7);
                        continue;
                }
+       }
 
-               /* can suspend if no ports are enabled; or if all all
-                * enabled ports are suspended AND remote wakeup is on.
-                */
-               if (!(status & RH_PS_CCS))
-                       continue;
-               if ((status & RH_PS_PSS) && can_suspend)
-                       continue;
+       /* after root hub changes, stop polling after debouncing
+        * for a while and maybe kicking in autosuspend
+        */
+       if (changed) {
+               ohci->next_statechange = jiffies + STATECHANGE_DELAY;
                can_suspend = 0;
+       } else if (time_before (jiffies, ohci->next_statechange)) {
+               can_suspend = 0;
+       } else {
+#ifdef CONFIG_PM
+               can_suspend = can_suspend
+                       && !ohci->ed_rm_list
+                       && ((OHCI_CTRL_HCFS | OHCI_SCHED_ENABLES)
+                                       & ohci->hc_control)
+                               == OHCI_USB_OPER;
+#endif
+               if (hcd->uses_new_polling) {
+                       hcd->poll_rh = 0;
+                       /* use INTR_RHSC iff INTR_RD won't apply */
+                       if (!can_suspend)
+                               ohci_writel (ohci, OHCI_INTR_RHSC,
+                                               &ohci->regs->intrenable);
+               }
        }
+
 done:
        spin_unlock_irqrestore (&ohci->lock, flags);
 
-#ifdef CONFIG_PM
-       /* save power by suspending idle root hubs;
+#ifdef CONFIG_PM
+       /* save power by autosuspending idle root hubs;
         * INTR_RD wakes us when there's work
         */
-       if (can_suspend
-                       && !changed
-                       && !ohci->ed_rm_list
-                       && ((OHCI_CTRL_HCFS | OHCI_SCHED_ENABLES)
-                                       & ohci->hc_control)
-                               == OHCI_USB_OPER
-                       && time_after (jiffies, ohci->next_statechange)
-                       && usb_trylock_device (hcd->self.root_hub) == 0
-                       ) {
+       if (can_suspend && usb_trylock_device (hcd->self.root_hub) == 0) {
                ohci_vdbg (ohci, "autosuspend\n");
                (void) ohci_bus_suspend (hcd);
                usb_unlock_device (hcd->self.root_hub);
index 5602da9bd52c9446e6706544f7acb11a57672aea..f2c9161d6d6aa7d4ef0bd9309f72944765bd0d5b 100644 (file)
@@ -173,10 +173,6 @@ static const struct hc_driver ohci_lh7a404_hc_driver = {
         * basic lifecycle operations
         */
        .start =                ohci_lh7a404_start,
-#ifdef CONFIG_PM
-       /* suspend:             ohci_lh7a404_suspend,  -- tbd */
-       /* resume:              ohci_lh7a404_resume,   -- tbd */
-#endif /*CONFIG_PM*/
        .stop =                 ohci_stop,
 
        /*
@@ -196,6 +192,7 @@ static const struct hc_driver ohci_lh7a404_hc_driver = {
         */
        .hub_status_data =      ohci_hub_status_data,
        .hub_control =          ohci_hub_control,
+       .hub_irq_enable =       ohci_rhsc_enable,
 #ifdef CONFIG_PM
        .bus_suspend =          ohci_bus_suspend,
        .bus_resume =           ohci_bus_resume,
index c4c4babd4767f52bd12ed0a67586021a471f1b43..47f1c9bbef878852e785284826bfacc71f715ccf 100644 (file)
@@ -382,8 +382,10 @@ ohci_omap_start (struct usb_hcd *hcd)
        int             ret;
 
        config = hcd->self.controller->platform_data;
-       if (config->otg || config->rwc)
+       if (config->otg || config->rwc) {
+               ohci->hc_control = OHCI_CTRL_RWC;
                writel(OHCI_CTRL_RWC, &ohci->regs->control);
+       }
 
        if ((ret = ohci_run (ohci)) < 0) {
                dev_err(hcd->self.controller, "can't start\n");
@@ -429,6 +431,7 @@ static const struct hc_driver ohci_omap_hc_driver = {
         */
        .hub_status_data =      ohci_hub_status_data,
        .hub_control =          ohci_hub_control,
+       .hub_irq_enable =       ohci_rhsc_enable,
 #ifdef CONFIG_PM
        .bus_suspend =          ohci_bus_suspend,
        .bus_resume =           ohci_bus_resume,
index 37e122812b6797ee7a934a0fecd3cd0eaab1406b..ef874443aa9f3d4523f63bd48c2c498cc2bfc5bc 100644 (file)
@@ -176,11 +176,13 @@ static const struct hc_driver ohci_pci_hc_driver = {
         */
        .reset =                ohci_pci_reset,
        .start =                ohci_pci_start,
+       .stop =                 ohci_stop,
+
 #ifdef CONFIG_PM
+       /* these suspend/resume entries are for upstream PCI glue ONLY */
        .suspend =              ohci_pci_suspend,
        .resume =               ohci_pci_resume,
 #endif
-       .stop =                 ohci_stop,
 
        /*
         * managing i/o requests and associated device resources
@@ -199,6 +201,7 @@ static const struct hc_driver ohci_pci_hc_driver = {
         */
        .hub_status_data =      ohci_hub_status_data,
        .hub_control =          ohci_hub_control,
+       .hub_irq_enable =       ohci_rhsc_enable,
 #ifdef CONFIG_PM
        .bus_suspend =          ohci_bus_suspend,
        .bus_resume =           ohci_bus_resume,
index 9fe56ff1615dab3740cf6d75b4149c1b3556c131..270aaaad8c6dd74c14ce6ca41e055872a559005c 100644 (file)
@@ -166,6 +166,7 @@ static const struct hc_driver ohci_ppc_soc_hc_driver = {
         */
        .hub_status_data =      ohci_hub_status_data,
        .hub_control =          ohci_hub_control,
+       .hub_irq_enable =       ohci_rhsc_enable,
 #ifdef CONFIG_PM
        .bus_suspend =          ohci_bus_suspend,
        .bus_resume =           ohci_bus_resume,
index 6f559e102789462e9efbff65ee30bcbf45e92bf4..2752d36c2a78217e3a09ea6c83ec60fbcb315fc2 100644 (file)
@@ -288,6 +288,7 @@ static const struct hc_driver ohci_pxa27x_hc_driver = {
         */
        .hub_status_data =      ohci_hub_status_data,
        .hub_control =          ohci_hub_control,
+       .hub_irq_enable =       ohci_rhsc_enable,
 #ifdef  CONFIG_PM
        .bus_suspend =          ohci_bus_suspend,
        .bus_resume =           ohci_bus_resume,
index d2fc6969a9f77ae6784fe2838aee33e9fe5f9bbe..c4c77d26ca1ff5200543675d42b2628e575f6fab 100644 (file)
@@ -465,6 +465,7 @@ static const struct hc_driver ohci_s3c2410_hc_driver = {
         */
        .hub_status_data =      ohci_s3c2410_hub_status_data,
        .hub_control =          ohci_s3c2410_hub_control,
+       .hub_irq_enable =       ohci_rhsc_enable,
 #ifdef CONFIG_PM
        .bus_suspend =          ohci_bus_suspend,
        .bus_resume =           ohci_bus_resume,
index ce3de106cadc46872f8d57491b634c7a2aa2ac67..71371de32ada4c21bed7e61d7a30914ea35db9b4 100644 (file)
@@ -212,10 +212,6 @@ static const struct hc_driver ohci_sa1111_hc_driver = {
         * basic lifecycle operations
         */
        .start =                ohci_sa1111_start,
-#ifdef CONFIG_PM
-       /* suspend:             ohci_sa1111_suspend,  -- tbd */
-       /* resume:              ohci_sa1111_resume,   -- tbd */
-#endif
        .stop =                 ohci_stop,
 
        /*
@@ -235,6 +231,7 @@ static const struct hc_driver ohci_sa1111_hc_driver = {
         */
        .hub_status_data =      ohci_hub_status_data,
        .hub_control =          ohci_hub_control,
+       .hub_irq_enable =       ohci_rhsc_enable,
 #ifdef CONFIG_PM
        .bus_suspend =          ohci_bus_suspend,
        .bus_resume =           ohci_bus_resume,