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

USB: OHCI avoids root hub timer polling

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: default avatarDavid Brownell <dbrownell@users.sourceforge.net>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@suse.de>
parent 06afff00
...@@ -239,7 +239,7 @@ static const struct hc_driver ohci_at91_hc_driver = { ...@@ -239,7 +239,7 @@ static const struct hc_driver ohci_at91_hc_driver = {
*/ */
.hub_status_data = ohci_hub_status_data, .hub_status_data = ohci_hub_status_data,
.hub_control = ohci_hub_control, .hub_control = ohci_hub_control,
.hub_irq_enable = ohci_rhsc_enable,
#ifdef CONFIG_PM #ifdef CONFIG_PM
.bus_suspend = ohci_bus_suspend, .bus_suspend = ohci_bus_suspend,
.bus_resume = ohci_bus_resume, .bus_resume = ohci_bus_resume,
......
...@@ -268,10 +268,6 @@ static const struct hc_driver ohci_au1xxx_hc_driver = { ...@@ -268,10 +268,6 @@ static const struct hc_driver ohci_au1xxx_hc_driver = {
* basic lifecycle operations * basic lifecycle operations
*/ */
.start = ohci_au1xxx_start, .start = ohci_au1xxx_start,
#ifdef CONFIG_PM
/* suspend: ohci_au1xxx_suspend, -- tbd */
/* resume: ohci_au1xxx_resume, -- tbd */
#endif /*CONFIG_PM*/
.stop = ohci_stop, .stop = ohci_stop,
/* /*
...@@ -291,6 +287,7 @@ static const struct hc_driver ohci_au1xxx_hc_driver = { ...@@ -291,6 +287,7 @@ static const struct hc_driver ohci_au1xxx_hc_driver = {
*/ */
.hub_status_data = ohci_hub_status_data, .hub_status_data = ohci_hub_status_data,
.hub_control = ohci_hub_control, .hub_control = ohci_hub_control,
.hub_irq_enable = ohci_rhsc_enable,
#ifdef CONFIG_PM #ifdef CONFIG_PM
.bus_suspend = ohci_bus_suspend, .bus_suspend = ohci_bus_suspend,
.bus_resume = ohci_bus_resume, .bus_resume = ohci_bus_resume,
......
...@@ -667,6 +667,11 @@ show_registers (struct class_device *class_dev, char *buf) ...@@ -667,6 +667,11 @@ show_registers (struct class_device *class_dev, char *buf)
size -= temp; size -= temp;
next += 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 */ /* roothub */
ohci_dump_roothub (ohci, 1, &next, &size); ohci_dump_roothub (ohci, 1, &next, &size);
......
...@@ -134,6 +134,7 @@ static struct hc_driver ohci_ep93xx_hc_driver = { ...@@ -134,6 +134,7 @@ static struct hc_driver ohci_ep93xx_hc_driver = {
.get_frame_number = ohci_get_frame, .get_frame_number = ohci_get_frame,
.hub_status_data = ohci_hub_status_data, .hub_status_data = ohci_hub_status_data,
.hub_control = ohci_hub_control, .hub_control = ohci_hub_control,
.hub_irq_enable = ohci_rhsc_enable,
#ifdef CONFIG_PM #ifdef CONFIG_PM
.bus_suspend = ohci_bus_suspend, .bus_suspend = ohci_bus_suspend,
.bus_resume = ohci_bus_resume, .bus_resume = ohci_bus_resume,
......
...@@ -101,7 +101,7 @@ ...@@ -101,7 +101,7 @@
#include "../core/hcd.h" #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_AUTHOR "Roman Weissgaerber, David Brownell"
#define DRIVER_DESC "USB 1.1 'Open' Host Controller (OHCI) Driver" #define DRIVER_DESC "USB 1.1 'Open' Host Controller (OHCI) Driver"
...@@ -112,7 +112,8 @@ ...@@ -112,7 +112,8 @@
/* For initializing controller (mask in an HCFS mode too) */ /* 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 \ #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__ #ifdef __hppa__
/* On PA-RISC, PDC can leave IR set incorrectly; ignore it there. */ /* On PA-RISC, PDC can leave IR set incorrectly; ignore it there. */
...@@ -128,6 +129,8 @@ ...@@ -128,6 +129,8 @@
static const char hcd_name [] = "ohci_hcd"; static const char hcd_name [] = "ohci_hcd";
#define STATECHANGE_DELAY msecs_to_jiffies(300)
#include "ohci.h" #include "ohci.h"
static void ohci_dump (struct ohci_hcd *ohci, int verbose); static void ohci_dump (struct ohci_hcd *ohci, int verbose);
...@@ -446,7 +449,6 @@ static int ohci_init (struct ohci_hcd *ohci) ...@@ -446,7 +449,6 @@ static int ohci_init (struct ohci_hcd *ohci)
disable (ohci); disable (ohci);
ohci->regs = hcd->regs; ohci->regs = hcd->regs;
ohci->next_statechange = jiffies;
/* REVISIT this BIOS handshake is now moved into PCI "quirks", and /* REVISIT this BIOS handshake is now moved into PCI "quirks", and
* was never needed for most non-PCI systems ... remove the code? * was never needed for most non-PCI systems ... remove the code?
...@@ -637,6 +639,10 @@ static int ohci_run (struct ohci_hcd *ohci) ...@@ -637,6 +639,10 @@ static int ohci_run (struct ohci_hcd *ohci)
return -EOVERFLOW; return -EOVERFLOW;
} }
/* use rhsc irqs after khubd is fully initialized */
hcd->poll_rh = 1;
hcd->uses_new_polling = 1;
/* start controller operations */ /* start controller operations */
ohci->hc_control &= OHCI_CTRL_RWC; ohci->hc_control &= OHCI_CTRL_RWC;
ohci->hc_control |= OHCI_CONTROL_INIT | OHCI_USB_OPER; ohci->hc_control |= OHCI_CONTROL_INIT | OHCI_USB_OPER;
...@@ -648,7 +654,7 @@ static int ohci_run (struct ohci_hcd *ohci) ...@@ -648,7 +654,7 @@ static int ohci_run (struct ohci_hcd *ohci)
/* Choose the interrupts we care about now, others later on demand */ /* Choose the interrupts we care about now, others later on demand */
mask = OHCI_INTR_INIT; 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); ohci_writel (ohci, mask, &ohci->regs->intrenable);
/* handle root hub init quirks ... */ /* handle root hub init quirks ... */
...@@ -672,6 +678,7 @@ static int ohci_run (struct ohci_hcd *ohci) ...@@ -672,6 +678,7 @@ static int ohci_run (struct ohci_hcd *ohci)
// flush those writes // flush those writes
(void) ohci_readl (ohci, &ohci->regs->control); (void) ohci_readl (ohci, &ohci->regs->control);
ohci->next_statechange = jiffies + STATECHANGE_DELAY;
spin_unlock_irq (&ohci->lock); spin_unlock_irq (&ohci->lock);
// POTPGT delay is bits 24-31, in 2 ms units. // POTPGT delay is bits 24-31, in 2 ms units.
...@@ -711,6 +718,22 @@ static irqreturn_t ohci_irq (struct usb_hcd *hcd, struct pt_regs *ptregs) ...@@ -711,6 +718,22 @@ static irqreturn_t ohci_irq (struct usb_hcd *hcd, struct pt_regs *ptregs)
return IRQ_NOTMINE; 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) { if (ints & OHCI_INTR_UE) {
disable (ohci); disable (ohci);
ohci_err (ohci, "OHCI Unrecoverable Error, disabled\n"); ohci_err (ohci, "OHCI Unrecoverable Error, disabled\n");
......
...@@ -36,6 +36,14 @@ ...@@ -36,6 +36,14 @@
/*-------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------*/
/* 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 #ifdef CONFIG_PM
#define OHCI_SCHED_ENABLES \ #define OHCI_SCHED_ENABLES \
...@@ -123,6 +131,9 @@ static int ohci_bus_suspend (struct usb_hcd *hcd) ...@@ -123,6 +131,9 @@ static int ohci_bus_suspend (struct usb_hcd *hcd)
/* no resumes until devices finish suspending */ /* no resumes until devices finish suspending */
ohci->next_statechange = jiffies + msecs_to_jiffies (5); ohci->next_statechange = jiffies + msecs_to_jiffies (5);
/* no timer polling */
hcd->poll_rh = 0;
done: done:
/* external suspend vs self autosuspend ... same effect */ /* external suspend vs self autosuspend ... same effect */
if (status == 0) if (status == 0)
...@@ -256,8 +267,8 @@ static int ohci_bus_resume (struct usb_hcd *hcd) ...@@ -256,8 +267,8 @@ static int ohci_bus_resume (struct usb_hcd *hcd)
/* TRSMRCY */ /* TRSMRCY */
msleep (10); msleep (10);
/* keep it alive for ~5x suspend + resume costs */ /* keep it alive for more than ~5x suspend + resume costs */
ohci->next_statechange = jiffies + msecs_to_jiffies (250); ohci->next_statechange = jiffies + STATECHANGE_DELAY;
/* maybe turn schedules back on */ /* maybe turn schedules back on */
enables = 0; enables = 0;
...@@ -302,9 +313,10 @@ ohci_hub_status_data (struct usb_hcd *hcd, char *buf) ...@@ -302,9 +313,10 @@ ohci_hub_status_data (struct usb_hcd *hcd, char *buf)
{ {
struct ohci_hcd *ohci = hcd_to_ohci (hcd); struct ohci_hcd *ohci = hcd_to_ohci (hcd);
int i, changed = 0, length = 1; int i, changed = 0, length = 1;
int can_suspend = device_may_wakeup(&hcd->self.root_hub->dev); int can_suspend;
unsigned long flags; unsigned long flags;
can_suspend = device_may_wakeup(&hcd->self.root_hub->dev);
spin_lock_irqsave (&ohci->lock, flags); spin_lock_irqsave (&ohci->lock, flags);
/* handle autosuspended root: finish resuming before /* handle autosuspended root: finish resuming before
...@@ -339,6 +351,10 @@ ohci_hub_status_data (struct usb_hcd *hcd, char *buf) ...@@ -339,6 +351,10 @@ ohci_hub_status_data (struct usb_hcd *hcd, char *buf)
for (i = 0; i < ohci->num_ports; i++) { for (i = 0; i < ohci->num_ports; i++) {
u32 status = roothub_portstatus (ohci, 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 if (status & (RH_PS_CSC | RH_PS_PESC | RH_PS_PSSC
| RH_PS_OCIC | RH_PS_PRSC)) { | RH_PS_OCIC | RH_PS_PRSC)) {
changed = 1; changed = 1;
...@@ -348,32 +364,41 @@ ohci_hub_status_data (struct usb_hcd *hcd, char *buf) ...@@ -348,32 +364,41 @@ ohci_hub_status_data (struct usb_hcd *hcd, char *buf)
buf [1] |= 1 << (i - 7); buf [1] |= 1 << (i - 7);
continue; continue;
} }
}
/* can suspend if no ports are enabled; or if all all /* after root hub changes, stop polling after debouncing
* enabled ports are suspended AND remote wakeup is on. * for a while and maybe kicking in autosuspend
*/ */
if (!(status & RH_PS_CCS)) if (changed) {
continue; ohci->next_statechange = jiffies + STATECHANGE_DELAY;
if ((status & RH_PS_PSS) && can_suspend)
continue;
can_suspend = 0; 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: done:
spin_unlock_irqrestore (&ohci->lock, flags); spin_unlock_irqrestore (&ohci->lock, flags);
#ifdef CONFIG_PM #ifdef CONFIG_PM
/* save power by suspending idle root hubs; /* save power by autosuspending idle root hubs;
* INTR_RD wakes us when there's work * INTR_RD wakes us when there's work
*/ */
if (can_suspend if (can_suspend && usb_trylock_device (hcd->self.root_hub) == 0) {
&& !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
) {
ohci_vdbg (ohci, "autosuspend\n"); ohci_vdbg (ohci, "autosuspend\n");
(void) ohci_bus_suspend (hcd); (void) ohci_bus_suspend (hcd);
usb_unlock_device (hcd->self.root_hub); usb_unlock_device (hcd->self.root_hub);
......
...@@ -173,10 +173,6 @@ static const struct hc_driver ohci_lh7a404_hc_driver = { ...@@ -173,10 +173,6 @@ static const struct hc_driver ohci_lh7a404_hc_driver = {
* basic lifecycle operations * basic lifecycle operations
*/ */
.start = ohci_lh7a404_start, .start = ohci_lh7a404_start,
#ifdef CONFIG_PM
/* suspend: ohci_lh7a404_suspend, -- tbd */
/* resume: ohci_lh7a404_resume, -- tbd */
#endif /*CONFIG_PM*/
.stop = ohci_stop, .stop = ohci_stop,
/* /*
...@@ -196,6 +192,7 @@ static const struct hc_driver ohci_lh7a404_hc_driver = { ...@@ -196,6 +192,7 @@ static const struct hc_driver ohci_lh7a404_hc_driver = {
*/ */
.hub_status_data = ohci_hub_status_data, .hub_status_data = ohci_hub_status_data,
.hub_control = ohci_hub_control, .hub_control = ohci_hub_control,
.hub_irq_enable = ohci_rhsc_enable,
#ifdef CONFIG_PM #ifdef CONFIG_PM
.bus_suspend = ohci_bus_suspend, .bus_suspend = ohci_bus_suspend,
.bus_resume = ohci_bus_resume, .bus_resume = ohci_bus_resume,
......
...@@ -382,8 +382,10 @@ ohci_omap_start (struct usb_hcd *hcd) ...@@ -382,8 +382,10 @@ ohci_omap_start (struct usb_hcd *hcd)
int ret; int ret;
config = hcd->self.controller->platform_data; 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); writel(OHCI_CTRL_RWC, &ohci->regs->control);
}
if ((ret = ohci_run (ohci)) < 0) { if ((ret = ohci_run (ohci)) < 0) {
dev_err(hcd->self.controller, "can't start\n"); dev_err(hcd->self.controller, "can't start\n");
...@@ -429,6 +431,7 @@ static const struct hc_driver ohci_omap_hc_driver = { ...@@ -429,6 +431,7 @@ static const struct hc_driver ohci_omap_hc_driver = {
*/ */
.hub_status_data = ohci_hub_status_data, .hub_status_data = ohci_hub_status_data,
.hub_control = ohci_hub_control, .hub_control = ohci_hub_control,
.hub_irq_enable = ohci_rhsc_enable,
#ifdef CONFIG_PM #ifdef CONFIG_PM
.bus_suspend = ohci_bus_suspend, .bus_suspend = ohci_bus_suspend,
.bus_resume = ohci_bus_resume, .bus_resume = ohci_bus_resume,
......
...@@ -176,11 +176,13 @@ static const struct hc_driver ohci_pci_hc_driver = { ...@@ -176,11 +176,13 @@ static const struct hc_driver ohci_pci_hc_driver = {
*/ */
.reset = ohci_pci_reset, .reset = ohci_pci_reset,
.start = ohci_pci_start, .start = ohci_pci_start,
.stop = ohci_stop,
#ifdef CONFIG_PM #ifdef CONFIG_PM
/* these suspend/resume entries are for upstream PCI glue ONLY */
.suspend = ohci_pci_suspend, .suspend = ohci_pci_suspend,
.resume = ohci_pci_resume, .resume = ohci_pci_resume,
#endif #endif
.stop = ohci_stop,
/* /*
* managing i/o requests and associated device resources * managing i/o requests and associated device resources
...@@ -199,6 +201,7 @@ static const struct hc_driver ohci_pci_hc_driver = { ...@@ -199,6 +201,7 @@ static const struct hc_driver ohci_pci_hc_driver = {
*/ */
.hub_status_data = ohci_hub_status_data, .hub_status_data = ohci_hub_status_data,
.hub_control = ohci_hub_control, .hub_control = ohci_hub_control,
.hub_irq_enable = ohci_rhsc_enable,
#ifdef CONFIG_PM #ifdef CONFIG_PM
.bus_suspend = ohci_bus_suspend, .bus_suspend = ohci_bus_suspend,
.bus_resume = ohci_bus_resume, .bus_resume = ohci_bus_resume,
......
...@@ -166,6 +166,7 @@ static const struct hc_driver ohci_ppc_soc_hc_driver = { ...@@ -166,6 +166,7 @@ static const struct hc_driver ohci_ppc_soc_hc_driver = {
*/ */
.hub_status_data = ohci_hub_status_data, .hub_status_data = ohci_hub_status_data,
.hub_control = ohci_hub_control, .hub_control = ohci_hub_control,
.hub_irq_enable = ohci_rhsc_enable,
#ifdef CONFIG_PM #ifdef CONFIG_PM
.bus_suspend = ohci_bus_suspend, .bus_suspend = ohci_bus_suspend,
.bus_resume = ohci_bus_resume, .bus_resume = ohci_bus_resume,
......
...@@ -288,6 +288,7 @@ static const struct hc_driver ohci_pxa27x_hc_driver = { ...@@ -288,6 +288,7 @@ static const struct hc_driver ohci_pxa27x_hc_driver = {
*/ */
.hub_status_data = ohci_hub_status_data, .hub_status_data = ohci_hub_status_data,
.hub_control = ohci_hub_control, .hub_control = ohci_hub_control,
.hub_irq_enable = ohci_rhsc_enable,
#ifdef CONFIG_PM #ifdef CONFIG_PM
.bus_suspend = ohci_bus_suspend, .bus_suspend = ohci_bus_suspend,
.bus_resume = ohci_bus_resume, .bus_resume = ohci_bus_resume,
......
...@@ -465,6 +465,7 @@ static const struct hc_driver ohci_s3c2410_hc_driver = { ...@@ -465,6 +465,7 @@ static const struct hc_driver ohci_s3c2410_hc_driver = {
*/ */
.hub_status_data = ohci_s3c2410_hub_status_data, .hub_status_data = ohci_s3c2410_hub_status_data,
.hub_control = ohci_s3c2410_hub_control, .hub_control = ohci_s3c2410_hub_control,
.hub_irq_enable = ohci_rhsc_enable,
#ifdef CONFIG_PM #ifdef CONFIG_PM
.bus_suspend = ohci_bus_suspend, .bus_suspend = ohci_bus_suspend,
.bus_resume = ohci_bus_resume, .bus_resume = ohci_bus_resume,
......
...@@ -212,10 +212,6 @@ static const struct hc_driver ohci_sa1111_hc_driver = { ...@@ -212,10 +212,6 @@ static const struct hc_driver ohci_sa1111_hc_driver = {
* basic lifecycle operations * basic lifecycle operations
*/ */
.start = ohci_sa1111_start, .start = ohci_sa1111_start,
#ifdef CONFIG_PM
/* suspend: ohci_sa1111_suspend, -- tbd */
/* resume: ohci_sa1111_resume, -- tbd */
#endif
.stop = ohci_stop, .stop = ohci_stop,
/* /*
...@@ -235,6 +231,7 @@ static const struct hc_driver ohci_sa1111_hc_driver = { ...@@ -235,6 +231,7 @@ static const struct hc_driver ohci_sa1111_hc_driver = {
*/ */
.hub_status_data = ohci_hub_status_data, .hub_status_data = ohci_hub_status_data,
.hub_control = ohci_hub_control, .hub_control = ohci_hub_control,
.hub_irq_enable = ohci_rhsc_enable,
#ifdef CONFIG_PM #ifdef CONFIG_PM
.bus_suspend = ohci_bus_suspend, .bus_suspend = ohci_bus_suspend,
.bus_resume = ohci_bus_resume, .bus_resume = ohci_bus_resume,
......
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