mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-08-27 03:10:12 +00:00
[PATCH] 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 <stern@rowland.harvard.edu> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
This commit is contained in:
parent
4daaa87c8f
commit
6c1b445c22
4 changed files with 92 additions and 60 deletions
|
@ -84,6 +84,8 @@ static char *errbuf;
|
|||
|
||||
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 @@ static void reset_hc(struct uhci_hcd *uhci)
|
|||
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 @@ static void hc_died(struct uhci_hcd *uhci)
|
|||
{
|
||||
reset_hc(uhci);
|
||||
uhci->hc_inaccessible = 1;
|
||||
del_timer(&uhci->stall_timer);
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -302,14 +305,14 @@ __acquires(uhci->lock)
|
|||
|
||||
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 @@ static void start_rh(struct uhci_hcd *uhci)
|
|||
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 @@ __acquires(uhci->lock)
|
|||
}
|
||||
|
||||
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 @@ static void stall_callback(unsigned long _uhci)
|
|||
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 @@ static irqreturn_t uhci_irq(struct usb_hcd *hcd, struct pt_regs *regs)
|
|||
}
|
||||
|
||||
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 @@ static int uhci_start(struct usb_hcd *hcd)
|
|||
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 @@ static int uhci_start(struct usb_hcd *hcd)
|
|||
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 @@ static int uhci_start(struct usb_hcd *hcd)
|
|||
* 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 @@ static void uhci_stop(struct usb_hcd *hcd)
|
|||
{
|
||||
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 @@ static int uhci_suspend(struct usb_hcd *hcd, pm_message_t message)
|
|||
|
||||
done:
|
||||
spin_unlock_irq(&uhci->lock);
|
||||
if (rc == 0)
|
||||
del_timer_sync(&hcd->rh_timer);
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
@ -875,6 +848,9 @@ static int uhci_resume(struct usb_hcd *hcd)
|
|||
suspend_rh(uhci, UHCI_RH_SUSPENDED);
|
||||
|
||||
spin_unlock_irq(&uhci->lock);
|
||||
|
||||
if (hcd->poll_rh)
|
||||
usb_hcd_poll_rh_status(hcd);
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -327,18 +327,19 @@ static inline int __interval_to_skel(int interval)
|
|||
* 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 @@ struct uhci_hcd {
|
|||
|
||||
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 */
|
||||
|
|
|
@ -49,22 +49,16 @@ static int any_ports_active(struct uhci_hcd *uhci)
|
|||
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,6 +128,11 @@ static void uhci_check_ports(struct uhci_hcd *uhci)
|
|||
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);
|
||||
|
@ -142,6 +141,60 @@ static void uhci_check_ports(struct uhci_hcd *uhci)
|
|||
}
|
||||
}
|
||||
|
||||
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 */
|
||||
static int uhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
|
||||
u16 wIndex, char *buf, u16 wLength)
|
||||
|
|
|
@ -32,6 +32,8 @@ static void uhci_free_pending_tds(struct uhci_hcd *uhci);
|
|||
*/
|
||||
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 @@ static void uhci_scan_schedule(struct uhci_hcd *uhci, struct pt_regs *regs)
|
|||
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)
|
||||
|
|
Loading…
Reference in a new issue