This patch (as790) adds "auto-stop" support to ohci-hcd: the driver
will automatically stop the host controller when no devices have been
connected for at least one second.  This feature is useful when the
USB core autosuspend facility isn't available, such as when
CONFIG_USB_SUSPEND hasn't been set.

Signed-off-by: Alan Stern <[EMAIL PROTECTED]>

---

Dave:

One can ask whether this patch is really needed.  In fact it isn't,
provided these conditions are satisfied:

        People running without CONFIG_PM don't care about the extra
        overhead in power and DMA cycles of a non-suspended root hub.

        CONFIG_USB_SUSPEND is eliminated, i.e., made identical to
        CONFIG_PM.

        The USB core autosuspend patches get merged.

Until those conditions hold, I think we should have something like this.  
What I'm not sure about is whether the patch is completely correct!  I
changed several aspects of the driver, and some of the changes are a bit
questionable.  Some review and testing by the community would be good.

Here's a list of the more striking changes in the patch...

The code that disables RHSC (for level-triggered interrupts) was moved 
into the hub_status_data routine, and it checks first to see whether RHSC 
is active before disabling it, so on edge-triggered hardware the IRQ 
won't be disabled at all.  Maybe this extra test isn't worth the effort, 
I don't know.

I removed a check for HC_STATE_QUIESCING in the OHCI_INTR_RD case.  In 
general the driver has a bunch of checks on hcd->state, many (or all) of 
which aren't necessary -- they prevent things which _shouldn't_ happen but 
won't cause any harm if they _do_ happen.  Yet another instance of my 
belief that hcd->state should vanish...

The root-hub suspend and resume routines are now outside the "#ifdef
CONFIG_PM" region.  This is so that auto-stop can work even when
CONFIG_PM isn't set; it seems silly to leave a controller turned on when
no devices are attached.  Of course, if CONFIG_PM isn't set then
presumably the user doesn't care at all about saving power, so again this
is debatable.  (BTW I couldn't figure out how to turn off CONFIG_PM, so I
couldn't test if the code works -- or even compiles -- in that case.)

The part of the suspend routine that waits for the schedules to stop is
now a simple msleep(8).  Nothing more complicated seemed to be needed; is
that right?

In both the suspend and resume routines, I took out lines that cleared the 
Interrupt Status register.  It's not clear why they were there at all; 
wouldn't they lead to the possibility of losing IRQs?

The resume routine contains three msleeps, for 33 ms, 3 ms, and 10 ms
respectively.  In the case where it's known that no devices are attached,
I bypass all those sleeps so that the code can run in interrupt context.  
It worked okay on my system, but there's a worrying comment about a Lucent
erratum.  Is this safe in general?

Alan Stern


Index: usb-2.6/drivers/usb/host/ohci-hcd.c
===================================================================
--- usb-2.6.orig/drivers/usb/host/ohci-hcd.c
+++ usb-2.6/drivers/usb/host/ohci-hcd.c
@@ -715,17 +715,8 @@ static irqreturn_t ohci_irq (struct usb_
                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);
@@ -743,13 +734,18 @@ static irqreturn_t ohci_irq (struct usb_
        if (ints & OHCI_INTR_RD) {
                ohci_vdbg (ohci, "resume detect\n");
                ohci_writel (ohci, OHCI_INTR_RD, &regs->intrstatus);
-               if (hcd->state != HC_STATE_QUIESCING)
+               hcd->poll_rh = 1;
+               if (ohci->autostop) {
+                       spin_lock (&ohci->lock);
+                       ohci_rh_resume (ohci);
+                       spin_unlock (&ohci->lock);
+               } else
                        usb_hcd_resume_root_hub(hcd);
        }
 
        if (ints & OHCI_INTR_WDH) {
                if (HC_IS_RUNNING(hcd->state))
-                       ohci_writel (ohci, OHCI_INTR_WDH, &regs->intrdisable);  
+                       ohci_writel (ohci, OHCI_INTR_WDH, &regs->intrdisable);
                spin_lock (&ohci->lock);
                dl_done_list (ohci, ptregs);
                spin_unlock (&ohci->lock);
Index: usb-2.6/drivers/usb/host/ohci.h
===================================================================
--- usb-2.6.orig/drivers/usb/host/ohci.h
+++ usb-2.6/drivers/usb/host/ohci.h
@@ -388,6 +388,7 @@ struct ohci_hcd {
        u32                     hc_control;     /* copy of hc control reg */
        unsigned long           next_statechange;       /* suspend/resume */
        u32                     fminterval;             /* saved register */
+       unsigned                autostop:1;     /* rh auto stopping/stopped */
 
        unsigned long           flags;          /* for HC bugs */
 #define        OHCI_QUIRK_AMD756       0x01                    /* erratum #4 */
Index: usb-2.6/drivers/usb/host/ohci-hub.c
===================================================================
--- usb-2.6.orig/drivers/usb/host/ohci-hub.c
+++ usb-2.6/drivers/usb/host/ohci-hub.c
@@ -41,12 +41,9 @@ static void ohci_rhsc_enable (struct usb
 {
        struct ohci_hcd         *ohci = hcd_to_ohci (hcd);
 
-       hcd->poll_rh = 0;
        ohci_writel (ohci, OHCI_INTR_RHSC, &ohci->regs->intrenable);
 }
 
-#ifdef CONFIG_PM
-
 #define OHCI_SCHED_ENABLES \
        (OHCI_CTRL_CLE|OHCI_CTRL_BLE|OHCI_CTRL_PLE|OHCI_CTRL_IE)
 
@@ -54,18 +51,11 @@ static void dl_done_list (struct ohci_hc
 static void finish_unlinks (struct ohci_hcd *, u16 , struct pt_regs *);
 static int ohci_restart (struct ohci_hcd *ohci);
 
-static int ohci_bus_suspend (struct usb_hcd *hcd)
+static int ohci_rh_suspend (struct ohci_hcd *ohci, int autostop)
+__releases(ohci->lock)
+__acquires(ohci->lock)
 {
-       struct ohci_hcd         *ohci = hcd_to_ohci (hcd);
        int                     status = 0;
-       unsigned long           flags;
-
-       spin_lock_irqsave (&ohci->lock, flags);
-
-       if (unlikely(!test_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags))) {
-               spin_unlock_irqrestore (&ohci->lock, flags);
-               return -ESHUTDOWN;
-       }
 
        ohci->hc_control = ohci_readl (ohci, &ohci->regs->control);
        switch (ohci->hc_control & OHCI_CTRL_HCFS) {
@@ -81,15 +71,16 @@ static int ohci_bus_suspend (struct usb_
                ohci_dbg (ohci, "needs reinit!\n");
                goto done;
        case OHCI_USB_SUSPEND:
-               ohci_dbg (ohci, "already suspended\n");
-               goto done;
+               if (!ohci->autostop) {
+                       ohci_dbg (ohci, "already suspended\n");
+                       goto done;
+               }
        }
-       ohci_dbg (ohci, "suspend root hub\n");
+       ohci_dbg (ohci, "%s root hub\n",
+                       autostop ? "auto-stop" : "suspend");
 
        /* First stop any processing */
-       if (ohci->hc_control & OHCI_SCHED_ENABLES) {
-               int             limit;
-
+       if (!autostop && (ohci->hc_control & OHCI_SCHED_ENABLES)) {
                ohci->hc_control &= ~OHCI_SCHED_ENABLES;
                ohci_writel (ohci, ohci->hc_control, &ohci->regs->control);
                ohci->hc_control = ohci_readl (ohci, &ohci->regs->control);
@@ -99,24 +90,17 @@ static int ohci_bus_suspend (struct usb_
                 * then the last WDH could take 6+ msec
                 */
                ohci_dbg (ohci, "stopping schedules ...\n");
-               limit = 2000;
-               while (limit > 0) {
-                       udelay (250);
-                       limit =- 250;
-                       if (ohci_readl (ohci, &ohci->regs->intrstatus)
-                                       & OHCI_INTR_SF)
-                               break;
-               }
-               dl_done_list (ohci, NULL);
-               mdelay (7);
+               ohci->autostop = 0;
+               spin_unlock_irq (&ohci->lock);
+               msleep (8);
+               spin_lock_irq (&ohci->lock);
        }
        dl_done_list (ohci, NULL);
        finish_unlinks (ohci, ohci_frame_no(ohci), NULL);
-       ohci_writel (ohci, ohci_readl (ohci, &ohci->regs->intrstatus),
-                       &ohci->regs->intrstatus);
 
        /* maybe resume can wake root hub */
-       if (device_may_wakeup(&ohci_to_hcd(ohci)->self.root_hub->dev))
+       if (device_may_wakeup(&ohci_to_hcd(ohci)->self.root_hub->dev) ||
+                       autostop)
                ohci->hc_control |= OHCI_CTRL_RWE;
        else {
                ohci_writel (ohci, OHCI_INTR_RHSC, &ohci->regs->intrdisable);
@@ -132,13 +116,12 @@ static int ohci_bus_suspend (struct usb_
        (void) ohci_readl (ohci, &ohci->regs->control);
 
        /* no resumes until devices finish suspending */
-       ohci->next_statechange = jiffies + msecs_to_jiffies (5);
-
-       /* no timer polling */
-       hcd->poll_rh = 0;
+       if (!autostop) {
+               ohci->next_statechange = jiffies + msecs_to_jiffies (5);
+               ohci->autostop = 0;
+       }
 
 done:
-       spin_unlock_irqrestore (&ohci->lock, flags);
        return status;
 }
 
@@ -151,24 +134,16 @@ static inline struct ed *find_head (stru
 }
 
 /* caller has locked the root hub */
-static int ohci_bus_resume (struct usb_hcd *hcd)
+static int ohci_rh_resume (struct ohci_hcd *ohci)
+__releases(ohci->lock)
+__acquires(ohci->lock)
 {
-       struct ohci_hcd         *ohci = hcd_to_ohci (hcd);
+       struct usb_hcd          *hcd = ohci_to_hcd (ohci);
        u32                     temp, enables;
        int                     status = -EINPROGRESS;
-       unsigned long           flags;
-
-       if (time_before (jiffies, ohci->next_statechange))
-               msleep(5);
-
-       spin_lock_irqsave (&ohci->lock, flags);
-
-       if (unlikely(!test_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags))) {
-               spin_unlock_irqrestore (&ohci->lock, flags);
-               return -ESHUTDOWN;
-       }
-
+       int                     autostopped = ohci->autostop;
 
+       ohci->autostop = 0;
        ohci->hc_control = ohci_readl (ohci, &ohci->regs->control);
 
        if (ohci->hc_control & (OHCI_CTRL_IR | OHCI_SCHED_ENABLES)) {
@@ -188,7 +163,8 @@ static int ohci_bus_resume (struct usb_h
                ohci->hc_control |= OHCI_USB_RESUME;
                ohci_writel (ohci, ohci->hc_control, &ohci->regs->control);
                (void) ohci_readl (ohci, &ohci->regs->control);
-               ohci_dbg (ohci, "resume root hub\n");
+               ohci_dbg (ohci, "%s root hub\n",
+                               autostopped ? "auto-start" : "resume");
                break;
        case OHCI_USB_RESUME:
                /* HCFS changes sometime after INTR_RD */
@@ -203,16 +179,22 @@ static int ohci_bus_resume (struct usb_h
                ohci_dbg (ohci, "lost power\n");
                status = -EBUSY;
        }
-       spin_unlock_irqrestore (&ohci->lock, flags);
        if (status == -EBUSY) {
-               (void) ohci_init (ohci);
-               return ohci_restart (ohci);
+               if (!autostopped) {
+                       spin_unlock_irq (&ohci->lock);
+                       (void) ohci_init (ohci);
+                       status = ohci_restart (ohci);
+                       spin_lock_irq (&ohci->lock);
+               }
+               return status;
        }
        if (status != -EINPROGRESS)
                return status;
+       if (autostopped)
+               goto skip_resume;
+       spin_unlock_irq (&ohci->lock);
 
        temp = ohci->num_ports;
-       enables = 0;
        while (temp--) {
                u32 stat = ohci_readl (ohci,
                                       &ohci->regs->roothub.portstatus [temp]);
@@ -245,17 +227,21 @@ static int ohci_bus_resume (struct usb_h
        /* Sometimes PCI D3 suspend trashes frame timings ... */
        periodic_reinit (ohci);
 
+       /* the following code is executed with ohci->lock held and
+        * irqs disabled if and only if autostopped is true
+        */
+
+skip_resume:
        /* interrupts might have been disabled */
        ohci_writel (ohci, OHCI_INTR_INIT, &ohci->regs->intrenable);
        if (ohci->ed_rm_list)
                ohci_writel (ohci, OHCI_INTR_SF, &ohci->regs->intrenable);
-       ohci_writel (ohci, ohci_readl (ohci, &ohci->regs->intrstatus),
-                       &ohci->regs->intrstatus);
 
        /* Then re-enable operations */
        ohci_writel (ohci, OHCI_USB_OPER, &ohci->regs->control);
        (void) ohci_readl (ohci, &ohci->regs->control);
-       msleep (3);
+       if (!autostopped)
+               msleep (3);
 
        temp = ohci->hc_control;
        temp &= OHCI_CTRL_RWC;
@@ -265,7 +251,11 @@ static int ohci_bus_resume (struct usb_h
        (void) ohci_readl (ohci, &ohci->regs->control);
 
        /* TRSMRCY */
-       msleep (10);
+       if (!autostopped) {
+               msleep (10);
+               spin_lock_irq (&ohci->lock);
+       }
+       /* now ohci->lock is always held and irqs are always disabled */
 
        /* keep it alive for more than ~5x suspend + resume costs */
        ohci->next_statechange = jiffies + STATECHANGE_DELAY;
@@ -302,6 +292,45 @@ static int ohci_bus_resume (struct usb_h
        return 0;
 }
 
+#ifdef CONFIG_PM
+
+static int ohci_bus_suspend (struct usb_hcd *hcd)
+{
+       struct ohci_hcd         *ohci = hcd_to_ohci (hcd);
+       int                     rc;
+
+       spin_lock_irq (&ohci->lock);
+
+       if (unlikely(!test_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags)))
+               rc = -ESHUTDOWN;
+       else
+               rc = ohci_rh_suspend (ohci, 0);
+       spin_unlock_irq (&ohci->lock);
+       return rc;
+}
+
+static int ohci_bus_resume (struct usb_hcd *hcd)
+{
+       struct ohci_hcd         *ohci = hcd_to_ohci (hcd);
+       int                     rc;
+
+       if (time_before (jiffies, ohci->next_statechange))
+               msleep(5);
+
+       spin_lock_irq (&ohci->lock);
+
+       if (unlikely(!test_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags)))
+               rc = -ESHUTDOWN;
+       else
+               rc = ohci_rh_resume (ohci);
+       spin_unlock_irq (&ohci->lock);
+
+       /* poll until we know a device is connected or we autostop */
+       if (rc == 0)
+               usb_hcd_poll_rh_status(hcd);
+       return rc;
+}
+
 #endif /* CONFIG_PM */
 
 /*-------------------------------------------------------------------------*/
@@ -313,17 +342,11 @@ ohci_hub_status_data (struct usb_hcd *hc
 {
        struct ohci_hcd *ohci = hcd_to_ohci (hcd);
        int             i, changed = 0, length = 1;
+       int             any_connected = 0, rhsc_enabled = 1;
        unsigned long   flags;
 
        spin_lock_irqsave (&ohci->lock, flags);
 
-       /* handle autosuspended root:  finish resuming before
-        * letting khubd or root hub timer see state changes.
-        */
-       if (unlikely((ohci->hc_control & OHCI_CTRL_HCFS) != OHCI_USB_OPER
-                    || !HC_IS_RUNNING(hcd->state)))
-               goto done;
-
        /* undocumented erratum seen on at least rev D */
        if ((ohci->flags & OHCI_QUIRK_AMD756)
                        && (roothub_a (ohci) & RH_A_NDP) > MAX_ROOT_PORTS) {
@@ -347,6 +370,9 @@ ohci_hub_status_data (struct usb_hcd *hc
        for (i = 0; i < ohci->num_ports; i++) {
                u32     status = roothub_portstatus (ohci, i);
 
+               /* can't autostop if ports are connected */
+               any_connected |= (status & RH_PS_CCS);
+
                if (status & (RH_PS_CSC | RH_PS_PESC | RH_PS_PSSC
                                | RH_PS_OCIC | RH_PS_PRSC)) {
                        changed = 1;
@@ -354,15 +380,69 @@ ohci_hub_status_data (struct usb_hcd *hc
                            buf [0] |= 1 << (i + 1);
                        else
                            buf [1] |= 1 << (i - 7);
-                       continue;
                }
        }
 
-       /* after root hub changes, stop polling after debouncing
-        * for a while and maybe kicking in autosuspend
+       /* 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.  If it's still
+        * set (level-triggered) we must disable it and rely on
+        * polling until khubd re-enables it.
         */
-       if (changed)
-               ohci->next_statechange = jiffies + STATECHANGE_DELAY;
+       if (ohci_readl (ohci, &ohci->regs->intrstatus) & OHCI_INTR_RHSC) {
+               ohci_writel (ohci, OHCI_INTR_RHSC, &ohci->regs->intrdisable);
+               (void) ohci_readl (ohci, &ohci->regs->intrdisable);
+               rhsc_enabled = 0;
+       }
+       hcd->poll_rh = 1;
+
+       /* carry out appropriate state changes */
+       switch (ohci->hc_control & OHCI_CTRL_HCFS) {
+
+       case OHCI_USB_OPER:
+               /* keep on polling until we know a device is connected
+                * and RHSC is enabled */
+               if (!ohci->autostop) {
+                       if (any_connected) {
+                               if (rhsc_enabled)
+                                       hcd->poll_rh = 0;
+                       } else {
+                               ohci->autostop = 1;
+                               ohci->next_statechange = jiffies + HZ;
+                       }
+
+               /* if no devices have been attached for one second, autostop */
+               } else {
+                       if (changed || any_connected) {
+                               ohci->autostop = 0;
+                               ohci->next_statechange = jiffies +
+                                               STATECHANGE_DELAY;
+                       } else if (time_after_eq (jiffies,
+                                               ohci->next_statechange)
+                                       && !ohci->ed_rm_list
+                                       && !(ohci->hc_control &
+                                               OHCI_SCHED_ENABLES)) {
+                               ohci_rh_suspend (ohci, 1);
+                       }
+               }
+               break;
+
+       /* if there is a port change, autostart or ask to be resumed */
+       case OHCI_USB_SUSPEND:
+       case OHCI_USB_RESUME:
+               if (changed) {
+                       if (ohci->autostop)
+                               ohci_rh_resume (ohci);
+                       else
+                               usb_hcd_resume_root_hub (hcd);
+               } else {
+                       /* everything is idle, no need for polling */
+                       hcd->poll_rh = 0;
+               }
+               break;
+       }
 
 done:
        spin_unlock_irqrestore (&ohci->lock, flags);


-------------------------------------------------------------------------
Take Surveys. Earn Cash. Influence the Future of IT
Join SourceForge.net's Techsay panel and you'll get the chance to share your
opinions on IT & business topics through brief surveys -- and earn cash
http://www.techsay.com/default.php?page=join.php&p=sourceforge&CID=DEVDEV
_______________________________________________
linux-usb-devel@lists.sourceforge.net
To unsubscribe, use the last form field at:
https://lists.sourceforge.net/lists/listinfo/linux-usb-devel

Reply via email to