Commit 465f2794 authored by Alan Stern's avatar Alan Stern Committed by Greg Kroah-Hartman

[PATCH] USB: Suspend/resume/wakeup support for UHCI root hub ports

This patch adds support for suspending, resuming, and remote wakeup
detection on root hub ports to the UHCI driver.  It doesn't add support
for suspending or resuming the root hub itself (beyond what's already
there) -- that will require considerably more work.  But at least devices
plugged directly into the computer will interact nicely with power
management.

Of lesser importance, the patch also simplifies some constant expressions.
Signed-off-by: default avatarAlan Stern <stern@rowland.harvard.edu>
Signed-off-by: default avatarGreg Kroah-Hartman <greg@kroah.com>
parent 9e0aea63
...@@ -1661,6 +1661,8 @@ static void stall_callback(unsigned long ptr) ...@@ -1661,6 +1661,8 @@ static void stall_callback(unsigned long ptr)
/* Poll for and perform state transitions */ /* Poll for and perform state transitions */
hc_state_transitions(uhci); hc_state_transitions(uhci);
if (unlikely(uhci->suspended_ports && uhci->state != UHCI_SUSPENDED))
uhci_check_resume(uhci);
init_stall_timer(hcd); init_stall_timer(hcd);
} }
......
...@@ -352,6 +352,12 @@ struct uhci_hcd { ...@@ -352,6 +352,12 @@ struct uhci_hcd {
int resume_detect; /* Need a Global Resume */ int resume_detect; /* Need a Global Resume */
unsigned int saved_framenumber; /* Save during PM suspend */ unsigned int saved_framenumber; /* Save during PM suspend */
/* Support for port suspend/resume */
unsigned long port_c_suspend; /* Bit-arrays of ports */
unsigned long suspended_ports;
unsigned long resuming_ports;
unsigned long resume_timeout; /* Time to stop signalling */
/* Main list of URB's currently controlled by this HC */ /* Main list of URB's currently controlled by this HC */
struct list_head urb_list; /* P: uhci->schedule_lock */ struct list_head urb_list; /* P: uhci->schedule_lock */
......
...@@ -36,13 +36,13 @@ static __u8 root_hub_hub_des[] = ...@@ -36,13 +36,13 @@ static __u8 root_hub_hub_des[] =
static int uhci_hub_status_data(struct usb_hcd *hcd, char *buf) static int uhci_hub_status_data(struct usb_hcd *hcd, char *buf)
{ {
struct uhci_hcd *uhci = hcd_to_uhci(hcd); struct uhci_hcd *uhci = hcd_to_uhci(hcd);
unsigned long io_addr = uhci->io_addr; int port;
int i;
*buf = 0; *buf = 0;
for (i = 0; i < uhci->rh_numports; i++) { for (port = 0; port < uhci->rh_numports; ++port) {
if (inw(io_addr + USBPORTSC1 + i * 2) & RWC_BITS) if ((inw(uhci->io_addr + USBPORTSC1 + port * 2) & RWC_BITS) ||
*buf |= (1 << (i + 1)); test_bit(port, &uhci->port_c_suspend))
*buf |= (1 << (port + 1));
} }
return !!*buf; return !!*buf;
} }
...@@ -62,6 +62,35 @@ static int uhci_hub_status_data(struct usb_hcd *hcd, char *buf) ...@@ -62,6 +62,35 @@ static int uhci_hub_status_data(struct usb_hcd *hcd, char *buf)
status &= ~(RWC_BITS|WZ_BITS); \ status &= ~(RWC_BITS|WZ_BITS); \
outw(status, port_addr) outw(status, port_addr)
/* UHCI controllers don't automatically stop resume signalling after 20 msec,
* so we have to poll and check timeouts in order to take care of it.
* FIXME: Synchronize access to these fields by a spinlock.
*/
static void uhci_check_resume(struct uhci_hcd *uhci)
{
unsigned int port;
unsigned int port_addr;
int status;
for (port = 0; port < uhci->rh_numports; ++port) {
port_addr = uhci->io_addr + USBPORTSC1 + 2 * port;
if (unlikely(inw(port_addr) & USBPORTSC_RD)) {
if (!test_bit(port, &uhci->resuming_ports)) {
/* Port received a wakeup request */
set_bit(port, &uhci->resuming_ports);
uhci->resume_timeout = jiffies +
msecs_to_jiffies(20);
} else if (time_after_eq(jiffies,
uhci->resume_timeout)) {
CLR_RH_PORTSTAT(USBPORTSC_SUSP | USBPORTSC_RD);
clear_bit(port, &uhci->resuming_ports);
clear_bit(port, &uhci->suspended_ports);
set_bit(port, &uhci->port_c_suspend);
}
}
}
}
/* size of returned buffer is part of USB spec */ /* size of returned buffer is part of USB spec */
static int uhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, static int uhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
...@@ -69,8 +98,9 @@ static int uhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, ...@@ -69,8 +98,9 @@ static int uhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
{ {
struct uhci_hcd *uhci = hcd_to_uhci(hcd); struct uhci_hcd *uhci = hcd_to_uhci(hcd);
int status, retval = 0, len = 0; int status, retval = 0, len = 0;
unsigned long port_addr = uhci->io_addr + USBPORTSC1 + 2 * (wIndex-1); unsigned int port = wIndex - 1;
__u16 wPortChange, wPortStatus; unsigned long port_addr = uhci->io_addr + USBPORTSC1 + 2 * port;
u16 wPortChange, wPortStatus;
switch (typeReq) { switch (typeReq) {
/* Request Destination: /* Request Destination:
...@@ -82,11 +112,15 @@ static int uhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, ...@@ -82,11 +112,15 @@ static int uhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
*/ */
case GetHubStatus: case GetHubStatus:
*(__u32 *)buf = cpu_to_le32(0); *(u32 *) buf = cpu_to_le32(0);
OK(4); /* hub power */ OK(4); /* hub power */
case GetPortStatus: case GetPortStatus:
if (!wIndex || wIndex > uhci->rh_numports) if (port >= uhci->rh_numports)
goto err; goto err;
if (uhci->resuming_ports)
uhci_check_resume(uhci);
status = inw(port_addr); status = inw(port_addr);
/* Intel controllers report the OverCurrent bit active on. /* Intel controllers report the OverCurrent bit active on.
...@@ -97,37 +131,39 @@ static int uhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, ...@@ -97,37 +131,39 @@ static int uhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
PCI_VENDOR_ID_VIA) PCI_VENDOR_ID_VIA)
status ^= USBPORTSC_OC; status ^= USBPORTSC_OC;
/* UHCI doesn't support C_SUSPEND and C_RESET (always false) */ /* UHCI doesn't support C_RESET (always false) */
wPortChange = 0; wPortChange = 0;
if (status & USBPORTSC_CSC) if (status & USBPORTSC_CSC)
wPortChange |= 1 << (USB_PORT_FEAT_C_CONNECTION - 16); wPortChange |= USB_PORT_STAT_C_CONNECTION;
if (status & USBPORTSC_PEC) if (status & USBPORTSC_PEC)
wPortChange |= 1 << (USB_PORT_FEAT_C_ENABLE - 16); wPortChange |= USB_PORT_STAT_C_ENABLE;
if (status & USBPORTSC_OCC) if (status & USBPORTSC_OCC)
wPortChange |= 1 << (USB_PORT_FEAT_C_OVER_CURRENT - 16); wPortChange |= USB_PORT_STAT_C_OVERCURRENT;
if (test_bit(port, &uhci->port_c_suspend))
wPortChange |= USB_PORT_STAT_C_SUSPEND;
/* UHCI has no power switching (always on) */ /* UHCI has no power switching (always on) */
wPortStatus = 1 << USB_PORT_FEAT_POWER; wPortStatus = USB_PORT_STAT_POWER;
if (status & USBPORTSC_CCS) if (status & USBPORTSC_CCS)
wPortStatus |= 1 << USB_PORT_FEAT_CONNECTION; wPortStatus |= USB_PORT_STAT_CONNECTION;
if (status & USBPORTSC_PE) { if (status & USBPORTSC_PE) {
wPortStatus |= 1 << USB_PORT_FEAT_ENABLE; wPortStatus |= USB_PORT_STAT_ENABLE;
if (status & (USBPORTSC_SUSP | USBPORTSC_RD)) if (status & (USBPORTSC_SUSP | USBPORTSC_RD))
wPortStatus |= 1 << USB_PORT_FEAT_SUSPEND; wPortStatus |= USB_PORT_STAT_SUSPEND;
} }
if (status & USBPORTSC_OC) if (status & USBPORTSC_OC)
wPortStatus |= 1 << USB_PORT_FEAT_OVER_CURRENT; wPortStatus |= USB_PORT_STAT_OVERCURRENT;
if (status & USBPORTSC_PR) if (status & USBPORTSC_PR)
wPortStatus |= 1 << USB_PORT_FEAT_RESET; wPortStatus |= USB_PORT_STAT_RESET;
if (status & USBPORTSC_LSDA) if (status & USBPORTSC_LSDA)
wPortStatus |= 1 << USB_PORT_FEAT_LOWSPEED; wPortStatus |= USB_PORT_STAT_LOW_SPEED;
if (wPortChange) if (wPortChange)
dev_dbg(uhci_dev(uhci), "port %d portsc %04x\n", dev_dbg(uhci_dev(uhci), "port %d portsc %04x\n",
wIndex, status); wIndex, status);
*(__u16 *)buf = cpu_to_le16(wPortStatus); *(u16 *) buf = cpu_to_le16(wPortStatus);
*(__u16 *)(buf + 2) = cpu_to_le16(wPortChange); *(u16 *) (buf + 2) = cpu_to_le16(wPortChange);
OK(4); OK(4);
case SetHubFeature: /* We don't implement these */ case SetHubFeature: /* We don't implement these */
case ClearHubFeature: case ClearHubFeature:
...@@ -140,11 +176,12 @@ static int uhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, ...@@ -140,11 +176,12 @@ static int uhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
} }
break; break;
case SetPortFeature: case SetPortFeature:
if (!wIndex || wIndex > uhci->rh_numports) if (port >= uhci->rh_numports)
goto err; goto err;
switch (wValue) { switch (wValue) {
case USB_PORT_FEAT_SUSPEND: case USB_PORT_FEAT_SUSPEND:
set_bit(port, &uhci->suspended_ports);
SET_RH_PORTSTAT(USBPORTSC_SUSP); SET_RH_PORTSTAT(USBPORTSC_SUSP);
OK(0); OK(0);
case USB_PORT_FEAT_RESET: case USB_PORT_FEAT_RESET:
...@@ -164,7 +201,7 @@ static int uhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, ...@@ -164,7 +201,7 @@ static int uhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
} }
break; break;
case ClearPortFeature: case ClearPortFeature:
if (!wIndex || wIndex > uhci->rh_numports) if (port >= uhci->rh_numports)
goto err; goto err;
switch (wValue) { switch (wValue) {
...@@ -175,10 +212,12 @@ static int uhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, ...@@ -175,10 +212,12 @@ static int uhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
CLR_RH_PORTSTAT(USBPORTSC_PEC); CLR_RH_PORTSTAT(USBPORTSC_PEC);
OK(0); OK(0);
case USB_PORT_FEAT_SUSPEND: case USB_PORT_FEAT_SUSPEND:
CLR_RH_PORTSTAT(USBPORTSC_SUSP); set_bit(port, &uhci->resuming_ports);
uhci->resume_timeout = jiffies + msecs_to_jiffies(20);
SET_RH_PORTSTAT(USBPORTSC_RD);
OK(0); OK(0);
case USB_PORT_FEAT_C_SUSPEND: case USB_PORT_FEAT_C_SUSPEND:
/* this driver won't report these */ clear_bit(port, &uhci->port_c_suspend);
OK(0); OK(0);
case USB_PORT_FEAT_POWER: case USB_PORT_FEAT_POWER:
/* UHCI has no power switching */ /* UHCI has no power switching */
......
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