diff options
author | David Brownell <david-b@pacbell.net> | 2006-08-04 14:31:55 -0400 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@suse.de> | 2006-09-27 14:58:48 -0400 |
commit | d413984ae936fad46678403b38d79c595e5aaafe (patch) | |
tree | f494a450d7289d941e3ca2b2e33bfacc29cac024 /drivers/usb/host/ohci-hub.c | |
parent | 06afff00bcab0e384afbef70194fd3469532abdf (diff) |
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: David Brownell <dbrownell@users.sourceforge.net>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
Diffstat (limited to 'drivers/usb/host/ohci-hub.c')
-rw-r--r-- | drivers/usb/host/ohci-hub.c | 67 |
1 files changed, 46 insertions, 21 deletions
diff --git a/drivers/usb/host/ohci-hub.c b/drivers/usb/host/ohci-hub.c index 5b0a23fd798b..f1b1ed086bde 100644 --- a/drivers/usb/host/ohci-hub.c +++ b/drivers/usb/host/ohci-hub.c | |||
@@ -36,6 +36,14 @@ | |||
36 | 36 | ||
37 | /*-------------------------------------------------------------------------*/ | 37 | /*-------------------------------------------------------------------------*/ |
38 | 38 | ||
39 | /* hcd->hub_irq_enable() */ | ||
40 | static void ohci_rhsc_enable (struct usb_hcd *hcd) | ||
41 | { | ||
42 | struct ohci_hcd *ohci = hcd_to_ohci (hcd); | ||
43 | |||
44 | ohci_writel (ohci, OHCI_INTR_RHSC, &ohci->regs->intrenable); | ||
45 | } | ||
46 | |||
39 | #ifdef CONFIG_PM | 47 | #ifdef CONFIG_PM |
40 | 48 | ||
41 | #define OHCI_SCHED_ENABLES \ | 49 | #define OHCI_SCHED_ENABLES \ |
@@ -123,6 +131,9 @@ static int ohci_bus_suspend (struct usb_hcd *hcd) | |||
123 | /* no resumes until devices finish suspending */ | 131 | /* no resumes until devices finish suspending */ |
124 | ohci->next_statechange = jiffies + msecs_to_jiffies (5); | 132 | ohci->next_statechange = jiffies + msecs_to_jiffies (5); |
125 | 133 | ||
134 | /* no timer polling */ | ||
135 | hcd->poll_rh = 0; | ||
136 | |||
126 | done: | 137 | done: |
127 | /* external suspend vs self autosuspend ... same effect */ | 138 | /* external suspend vs self autosuspend ... same effect */ |
128 | if (status == 0) | 139 | if (status == 0) |
@@ -256,8 +267,8 @@ static int ohci_bus_resume (struct usb_hcd *hcd) | |||
256 | /* TRSMRCY */ | 267 | /* TRSMRCY */ |
257 | msleep (10); | 268 | msleep (10); |
258 | 269 | ||
259 | /* keep it alive for ~5x suspend + resume costs */ | 270 | /* keep it alive for more than ~5x suspend + resume costs */ |
260 | ohci->next_statechange = jiffies + msecs_to_jiffies (250); | 271 | ohci->next_statechange = jiffies + STATECHANGE_DELAY; |
261 | 272 | ||
262 | /* maybe turn schedules back on */ | 273 | /* maybe turn schedules back on */ |
263 | enables = 0; | 274 | enables = 0; |
@@ -302,9 +313,10 @@ ohci_hub_status_data (struct usb_hcd *hcd, char *buf) | |||
302 | { | 313 | { |
303 | struct ohci_hcd *ohci = hcd_to_ohci (hcd); | 314 | struct ohci_hcd *ohci = hcd_to_ohci (hcd); |
304 | int i, changed = 0, length = 1; | 315 | int i, changed = 0, length = 1; |
305 | int can_suspend = device_may_wakeup(&hcd->self.root_hub->dev); | 316 | int can_suspend; |
306 | unsigned long flags; | 317 | unsigned long flags; |
307 | 318 | ||
319 | can_suspend = device_may_wakeup(&hcd->self.root_hub->dev); | ||
308 | spin_lock_irqsave (&ohci->lock, flags); | 320 | spin_lock_irqsave (&ohci->lock, flags); |
309 | 321 | ||
310 | /* handle autosuspended root: finish resuming before | 322 | /* handle autosuspended root: finish resuming before |
@@ -339,6 +351,10 @@ ohci_hub_status_data (struct usb_hcd *hcd, char *buf) | |||
339 | for (i = 0; i < ohci->num_ports; i++) { | 351 | for (i = 0; i < ohci->num_ports; i++) { |
340 | u32 status = roothub_portstatus (ohci, i); | 352 | u32 status = roothub_portstatus (ohci, i); |
341 | 353 | ||
354 | /* can't autosuspend with active ports */ | ||
355 | if ((status & RH_PS_PES) && !(status & RH_PS_PSS)) | ||
356 | can_suspend = 0; | ||
357 | |||
342 | if (status & (RH_PS_CSC | RH_PS_PESC | RH_PS_PSSC | 358 | if (status & (RH_PS_CSC | RH_PS_PESC | RH_PS_PSSC |
343 | | RH_PS_OCIC | RH_PS_PRSC)) { | 359 | | RH_PS_OCIC | RH_PS_PRSC)) { |
344 | changed = 1; | 360 | changed = 1; |
@@ -348,32 +364,41 @@ ohci_hub_status_data (struct usb_hcd *hcd, char *buf) | |||
348 | buf [1] |= 1 << (i - 7); | 364 | buf [1] |= 1 << (i - 7); |
349 | continue; | 365 | continue; |
350 | } | 366 | } |
367 | } | ||
351 | 368 | ||
352 | /* can suspend if no ports are enabled; or if all all | 369 | /* after root hub changes, stop polling after debouncing |
353 | * enabled ports are suspended AND remote wakeup is on. | 370 | * for a while and maybe kicking in autosuspend |
354 | */ | 371 | */ |
355 | if (!(status & RH_PS_CCS)) | 372 | if (changed) { |
356 | continue; | 373 | ohci->next_statechange = jiffies + STATECHANGE_DELAY; |
357 | if ((status & RH_PS_PSS) && can_suspend) | ||
358 | continue; | ||
359 | can_suspend = 0; | 374 | can_suspend = 0; |
375 | } else if (time_before (jiffies, ohci->next_statechange)) { | ||
376 | can_suspend = 0; | ||
377 | } else { | ||
378 | #ifdef CONFIG_PM | ||
379 | can_suspend = can_suspend | ||
380 | && !ohci->ed_rm_list | ||
381 | && ((OHCI_CTRL_HCFS | OHCI_SCHED_ENABLES) | ||
382 | & ohci->hc_control) | ||
383 | == OHCI_USB_OPER; | ||
384 | #endif | ||
385 | if (hcd->uses_new_polling) { | ||
386 | hcd->poll_rh = 0; | ||
387 | /* use INTR_RHSC iff INTR_RD won't apply */ | ||
388 | if (!can_suspend) | ||
389 | ohci_writel (ohci, OHCI_INTR_RHSC, | ||
390 | &ohci->regs->intrenable); | ||
391 | } | ||
360 | } | 392 | } |
393 | |||
361 | done: | 394 | done: |
362 | spin_unlock_irqrestore (&ohci->lock, flags); | 395 | spin_unlock_irqrestore (&ohci->lock, flags); |
363 | 396 | ||
364 | #ifdef CONFIG_PM | 397 | #ifdef CONFIG_PM |
365 | /* save power by suspending idle root hubs; | 398 | /* save power by autosuspending idle root hubs; |
366 | * INTR_RD wakes us when there's work | 399 | * INTR_RD wakes us when there's work |
367 | */ | 400 | */ |
368 | if (can_suspend | 401 | if (can_suspend && usb_trylock_device (hcd->self.root_hub) == 0) { |
369 | && !changed | ||
370 | && !ohci->ed_rm_list | ||
371 | && ((OHCI_CTRL_HCFS | OHCI_SCHED_ENABLES) | ||
372 | & ohci->hc_control) | ||
373 | == OHCI_USB_OPER | ||
374 | && time_after (jiffies, ohci->next_statechange) | ||
375 | && usb_trylock_device (hcd->self.root_hub) == 0 | ||
376 | ) { | ||
377 | ohci_vdbg (ohci, "autosuspend\n"); | 402 | ohci_vdbg (ohci, "autosuspend\n"); |
378 | (void) ohci_bus_suspend (hcd); | 403 | (void) ohci_bus_suspend (hcd); |
379 | usb_unlock_device (hcd->self.root_hub); | 404 | usb_unlock_device (hcd->self.root_hub); |