From stern@rowland.harvard.edu Thu Apr 21 13:07:25 2005 Date: Thu, 21 Apr 2005 16:04:58 -0400 (EDT) From: Alan Stern To: Greg KH Subject: USB UHCI: Use root-hub IRQs while suspended This patch, which has as478b as a prerequisite, enables the uhci-hcd driver to take advantage of root-hub IRQs rather than polling during the time it is suspended. (Unfortunately the hardware doesn't support port-change interrupts while the controller is running.) It also turns off the driver's private timer while the controller is suspended, as it isn't needed then. The combined elimination of polling interrupts and timer interrupts ought to be enough to allow some systems to save a noticeable amount of power while they are otherwise idle. Signed-off-by: Alan Stern Signed-off-by: Greg Kroah-Hartman diff -u a/drivers/usb/host/uhci-hcd.c b/drivers/usb/host/uhci-hcd.c --- a/drivers/usb/host/uhci-hcd.c Fri Apr 15 23:21:05 2005 +++ b/drivers/usb/host/uhci-hcd.c Sun Apr 17 18:37:11 2005 @@ -84,6 +84,8 @@ static kmem_cache_t *uhci_up_cachep; /* urb_priv */ +static void suspend_rh(struct uhci_hcd *uhci, enum uhci_rh_state new_state); +static void wakeup_rh(struct uhci_hcd *uhci); static void uhci_get_current_frame_number(struct uhci_hcd *uhci); /* If a transfer is still active after this much time, turn off FSBR */ @@ -133,12 +135,12 @@ outw(0, uhci->io_addr + USBINTR); outw(0, uhci->io_addr + USBCMD); - uhci->resume_detect = 0; uhci->port_c_suspend = uhci->suspended_ports = uhci->resuming_ports = 0; uhci->rh_state = UHCI_RH_RESET; uhci->is_stopped = UHCI_IS_STOPPED; uhci_to_hcd(uhci)->state = HC_STATE_HALT; + uhci_to_hcd(uhci)->poll_rh = 0; } /* @@ -148,6 +150,7 @@ { reset_hc(uhci); uhci->hc_inaccessible = 1; + del_timer(&uhci->stall_timer); } /* @@ -302,14 +305,14 @@ uhci->rh_state = new_state; uhci->is_stopped = UHCI_IS_STOPPED; - uhci->resume_detect = 0; + del_timer(&uhci->stall_timer); + uhci_to_hcd(uhci)->poll_rh = !int_enable; uhci_scan_schedule(uhci, NULL); } static void start_rh(struct uhci_hcd *uhci) { - uhci->rh_state = UHCI_RH_RUNNING; uhci->is_stopped = 0; smp_wmb(); @@ -320,6 +323,9 @@ outw(USBINTR_TIMEOUT | USBINTR_RESUME | USBINTR_IOC | USBINTR_SP, uhci->io_addr + USBINTR); mb(); + uhci->rh_state = UHCI_RH_RUNNING; + uhci_to_hcd(uhci)->poll_rh = 1; + restart_timer(uhci); } static void wakeup_rh(struct uhci_hcd *uhci) @@ -353,36 +359,9 @@ } start_rh(uhci); -} - -static void rh_state_transitions(struct uhci_hcd *uhci) -{ - switch (uhci->rh_state) { - case UHCI_RH_RUNNING: - /* are any devices attached? */ - if (!any_ports_active(uhci)) { - uhci->rh_state = UHCI_RH_RUNNING_NODEVS; - uhci->auto_stop_time = jiffies + HZ; - } - break; - - case UHCI_RH_RUNNING_NODEVS: - /* auto-stop if nothing connected for 1 second */ - if (any_ports_active(uhci)) - uhci->rh_state = UHCI_RH_RUNNING; - else if (time_after_eq(jiffies, uhci->auto_stop_time)) - suspend_rh(uhci, UHCI_RH_AUTO_STOPPED); - break; - case UHCI_RH_AUTO_STOPPED: - /* wakeup if requested by a device */ - if (uhci->resume_detect) - wakeup_rh(uhci); - break; - - default: - break; - } + /* Restart root hub polling */ + mod_timer(&uhci_to_hcd(uhci)->rh_timer, jiffies); } static void stall_callback(unsigned long _uhci) @@ -394,14 +373,8 @@ uhci_scan_schedule(uhci, NULL); check_fsbr(uhci); - /* Poll for and perform state transitions */ - if (!uhci->hc_inaccessible) { - rh_state_transitions(uhci); - if (uhci->suspended_ports) - uhci_check_ports(uhci); - } - - restart_timer(uhci); + if (!uhci->is_stopped) + restart_timer(uhci); spin_unlock_irqrestore(&uhci->lock, flags); } @@ -443,7 +416,7 @@ } if (status & USBSTS_RD) - uhci->resume_detect = 1; + usb_hcd_poll_rh_status(hcd); spin_lock_irqsave(&uhci->lock, flags); uhci_scan_schedule(uhci, regs); @@ -542,6 +515,7 @@ struct dentry *dentry; io_size = (unsigned) hcd->rsrc_len; + hcd->uses_new_polling = 1; if (pci_find_capability(to_pci_dev(uhci_dev(uhci)), PCI_CAP_ID_PM)) hcd->can_wakeup = 1; /* Assume it supports PME# */ @@ -714,8 +688,6 @@ configure_hc(uhci); start_rh(uhci); - restart_timer(uhci); - udev->speed = USB_SPEED_FULL; if (usb_hcd_register_root_hub(udev, hcd) != 0) { @@ -730,8 +702,8 @@ * error exits: */ err_start_root_hub: - del_timer_sync(&uhci->stall_timer); reset_hc(uhci); + del_timer_sync(&uhci->stall_timer); err_alloc_skelqh: for (i = 0; i < UHCI_NUM_SKELQH; i++) @@ -771,13 +743,12 @@ { struct uhci_hcd *uhci = hcd_to_uhci(hcd); - del_timer_sync(&uhci->stall_timer); - spin_lock_irq(&uhci->lock); reset_hc(uhci); uhci_scan_schedule(uhci, NULL); spin_unlock_irq(&uhci->lock); - + + del_timer_sync(&uhci->stall_timer); release_uhci(uhci); } @@ -844,6 +815,8 @@ done: spin_unlock_irq(&uhci->lock); + if (rc == 0) + del_timer_sync(&hcd->rh_timer); return rc; } @@ -875,6 +848,9 @@ suspend_rh(uhci, UHCI_RH_SUSPENDED); spin_unlock_irq(&uhci->lock); + + if (hcd->poll_rh) + usb_hcd_poll_rh_status(hcd); return 0; } #endif diff -u a/drivers/usb/host/uhci-hcd.h b/drivers/usb/host/uhci-hcd.h --- a/drivers/usb/host/uhci-hcd.h Fri Apr 15 23:21:05 2005 +++ b/drivers/usb/host/uhci-hcd.h Sun Apr 17 12:30:53 2005 @@ -327,18 +327,19 @@ * driver learns to autosuspend.) */ enum uhci_rh_state { - /* In the next 4 states the HC must be halted */ - UHCI_RH_RESET, /* These two must come first */ + /* In the following states the HC must be halted. + * These two must come first */ + UHCI_RH_RESET, UHCI_RH_SUSPENDED, UHCI_RH_AUTO_STOPPED, UHCI_RH_RESUMING, - /* In the next state the HC changes from running to halted, so it - * can legally appear either way */ + /* In this state the HC changes from running to halted, + * so it can legally appear either way. */ UHCI_RH_SUSPENDING, - /* In the next two states it's an error if the HC is halted. + /* In the following states it's an error if the HC is halted. * These two must come last */ UHCI_RH_RUNNING, /* The normal state */ UHCI_RH_RUNNING_NODEVS, /* Running with no devices attached */ @@ -380,7 +381,6 @@ unsigned int scan_in_progress:1; /* Schedule scan is running */ unsigned int need_rescan:1; /* Redo the schedule scan */ - unsigned int resume_detect:1; /* Need a Global Resume */ unsigned int hc_inaccessible:1; /* HC is suspended or dead */ /* Support for port suspend/resume/reset */ diff -u a/drivers/usb/host/uhci-hub.c b/drivers/usb/host/uhci-hub.c --- a/drivers/usb/host/uhci-hub.c Fri Apr 15 23:21:05 2005 +++ b/drivers/usb/host/uhci-hub.c Sun Apr 17 18:37:11 2005 @@ -49,22 +49,16 @@ return 0; } -static int uhci_hub_status_data(struct usb_hcd *hcd, char *buf) +static inline int get_hub_status_data(struct uhci_hcd *uhci, char *buf) { - struct uhci_hcd *uhci = hcd_to_uhci(hcd); int port; - if (uhci->hc_inaccessible) - return 0; - *buf = 0; for (port = 0; port < uhci->rh_numports; ++port) { if ((inw(uhci->io_addr + USBPORTSC1 + port * 2) & RWC_BITS) || test_bit(port, &uhci->port_c_suspend)) *buf |= (1 << (port + 1)); } - if (*buf && uhci->is_stopped) - uhci->resume_detect = 1; return !!*buf; } @@ -134,12 +128,71 @@ set_bit(port, &uhci->resuming_ports); uhci->ports_timeout = jiffies + msecs_to_jiffies(20); + + /* Make sure we see the port again + * after the resuming period is over. */ + mod_timer(&uhci_to_hcd(uhci)->rh_timer, + uhci->ports_timeout); } else if (time_after_eq(jiffies, uhci->ports_timeout)) { uhci_finish_suspend(uhci, port, port_addr); } } } +} + +static int uhci_hub_status_data(struct usb_hcd *hcd, char *buf) +{ + struct uhci_hcd *uhci = hcd_to_uhci(hcd); + unsigned long flags; + int status; + + spin_lock_irqsave(&uhci->lock, flags); + if (uhci->hc_inaccessible) { + status = 0; + goto done; + } + + uhci_check_ports(uhci); + status = get_hub_status_data(uhci, buf); + + switch (uhci->rh_state) { + case UHCI_RH_SUSPENDING: + case UHCI_RH_SUSPENDED: + /* if port change, ask to be resumed */ + if (status) + usb_hcd_resume_root_hub(hcd); + break; + + case UHCI_RH_AUTO_STOPPED: + /* if port change, auto start */ + if (status) + wakeup_rh(uhci); + break; + + case UHCI_RH_RUNNING: + /* are any devices attached? */ + if (!any_ports_active(uhci)) { + uhci->rh_state = UHCI_RH_RUNNING_NODEVS; + uhci->auto_stop_time = jiffies + HZ; + } + break; + + case UHCI_RH_RUNNING_NODEVS: + /* auto-stop if nothing connected for 1 second */ + if (any_ports_active(uhci)) + uhci->rh_state = UHCI_RH_RUNNING; + else if (time_after_eq(jiffies, uhci->auto_stop_time)) + suspend_rh(uhci, UHCI_RH_AUTO_STOPPED); + break; + + default: + break; + } + +done: + spin_unlock_irqrestore(&uhci->lock, flags); + return status; } /* size of returned buffer is part of USB spec */ diff -u a/drivers/usb/host/uhci-q.c b/drivers/usb/host/uhci-q.c --- a/drivers/usb/host/uhci-q.c Fri Apr 15 23:21:05 2005 +++ b/drivers/usb/host/uhci-q.c Sun Apr 17 12:30:53 2005 @@ -32,6 +32,8 @@ */ static inline void uhci_set_next_interrupt(struct uhci_hcd *uhci) { + if (uhci->is_stopped) + mod_timer(&uhci->stall_timer, jiffies); uhci->term_td->status |= cpu_to_le32(TD_CTRL_IOC); } @@ -1497,6 +1499,7 @@ rescan: uhci->need_rescan = 0; + uhci_clear_next_interrupt(uhci); uhci_get_current_frame_number(uhci); if (uhci->frame_number + uhci->is_stopped != uhci->qh_remove_age)