David:
Here's a first pass at adding support for port suspend and resume to the
UHCI driver's root hub. I haven't tried it out at all; merging your
latest patch looks like too much work because of the recent divergence
in hub.c. But you can try it and tell me if anything needs to be fixed.
There are several loose ends that I probably won't clean up for a while.
For instance, the whole thing needs spinlock protection. And it doesn't
support global suspend yet. However, you ought to be able to suspend and
resume ports on the root hub successfully.
I trust it won't matter if resume signalling continues for significantly
longer than 20 msec. The UHCI specification says that the controller
doesn't interrupt to report remote wakeups -- not being able to try it, I
can't say whether that's true. But it means that the driver can only
detect such things during its periodic 100-msec polling. So the resume
signal is likely to remain on for something between 100 and 200 msec.
Alan Stern
--- usb-2.6/drivers/usb/host/uhci-hcd.h Fri May 28 17:09:18 2004
+++ usb-2.6/drivers/usb/host/uhci-hcd.h Sun May 30 16:35:52 2004
@@ -352,6 +352,12 @@
int resume_detect; /* Need a Global Resume */
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 */
struct list_head urb_list; /* P: uhci->schedule_lock */
--- usb-2.6/drivers/usb/host/uhci-hcd.c Fri Feb 27 15:21:31 2004
+++ usb-2.6/drivers/usb/host/uhci-hcd.c Sun May 30 17:26:25 2004
@@ -1665,6 +1665,8 @@
/* Poll for and perform state transitions */
hc_state_transitions(uhci);
+ if (unlikely(uhci->suspended_ports))
+ uhci_check_resume(uhci);
init_stall_timer(hcd);
}
--- usb-2.6/drivers/usb/host/uhci-hub.c Fri Feb 27 15:21:31 2004
+++ usb-2.6/drivers/usb/host/uhci-hub.c Sun May 30 17:26:25 2004
@@ -36,13 +36,13 @@
static int uhci_hub_status_data(struct usb_hcd *hcd, char *buf)
{
struct uhci_hcd *uhci = hcd_to_uhci(hcd);
- unsigned int io_addr = uhci->io_addr;
- int i;
+ int port;
*buf = 0;
- for (i = 0; i < uhci->rh_numports; i++) {
- if (inw(io_addr + USBPORTSC1 + i * 2) & RWC_BITS)
- *buf |= (1 << (i + 1));
+ 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));
}
return !!*buf;
}
@@ -62,6 +62,35 @@
status &= ~(RWC_BITS|WZ_BITS); \
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;
+ int timed_out = time_after_eq(jiffies, uhci->resume_timeout);
+
+ 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 (timed_out) {
+ 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 */
static int uhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
@@ -69,8 +98,9 @@
{
struct uhci_hcd *uhci = hcd_to_uhci(hcd);
int status, retval = 0, len = 0;
- unsigned int port_addr = uhci->io_addr + USBPORTSC1 + 2 * (wIndex-1);
- __u16 wPortChange, wPortStatus;
+ unsigned int port = wIndex - 1;
+ unsigned int port_addr = uhci->io_addr + USBPORTSC1 + 2 * port;
+ u16 wPortChange, wPortStatus;
switch (typeReq) {
/* Request Destination:
@@ -82,11 +112,15 @@
*/
case GetHubStatus:
- *(__u32 *)buf = cpu_to_le32(0);
+ *(u32 *) buf = cpu_to_le32(0);
OK(4); /* hub power */
case GetPortStatus:
- if (!wIndex || wIndex > uhci->rh_numports)
+ if (port >= uhci->rh_numports)
goto err;
+
+ if (uhci->resuming_ports)
+ uhci_check_resume(uhci);
+
status = inw(port_addr);
/* Intel controllers report the OverCurrent bit active on.
@@ -97,37 +131,39 @@
PCI_VENDOR_ID_VIA)
status ^= USBPORTSC_OC;
- /* UHCI doesn't support C_SUSPEND and C_RESET (always false) */
+ /* UHCI doesn't support C_RESET (always false) */
wPortChange = 0;
if (status & USBPORTSC_CSC)
- wPortChange |= 1 << (USB_PORT_FEAT_C_CONNECTION - 16);
+ wPortChange |= USB_PORT_STAT_C_CONNECTION;
if (status & USBPORTSC_PEC)
- wPortChange |= 1 << (USB_PORT_FEAT_C_ENABLE - 16);
+ wPortChange |= USB_PORT_STAT_C_ENABLE;
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) */
- wPortStatus = 1 << USB_PORT_FEAT_POWER;
+ wPortStatus = USB_PORT_STAT_POWER;
if (status & USBPORTSC_CCS)
- wPortStatus |= 1 << USB_PORT_FEAT_CONNECTION;
+ wPortStatus |= USB_PORT_STAT_CONNECTION;
if (status & USBPORTSC_PE) {
- wPortStatus |= 1 << USB_PORT_FEAT_ENABLE;
+ wPortStatus |= USB_PORT_STAT_ENABLE;
if (status & (USBPORTSC_SUSP | USBPORTSC_RD))
- wPortStatus |= 1 << USB_PORT_FEAT_SUSPEND;
+ wPortStatus |= USB_PORT_STAT_SUSPEND;
}
if (status & USBPORTSC_OC)
- wPortStatus |= 1 << USB_PORT_FEAT_OVER_CURRENT;
+ wPortStatus |= USB_PORT_STAT_OVERCURRENT;
if (status & USBPORTSC_PR)
- wPortStatus |= 1 << USB_PORT_FEAT_RESET;
+ wPortStatus |= USB_PORT_STAT_RESET;
if (status & USBPORTSC_LSDA)
- wPortStatus |= 1 << USB_PORT_FEAT_LOWSPEED;
+ wPortStatus |= USB_PORT_STAT_LOW_SPEED;
if (wPortChange)
dev_dbg(uhci_dev(uhci), "port %d portsc %04x\n",
wIndex, status);
- *(__u16 *)buf = cpu_to_le16(wPortStatus);
- *(__u16 *)(buf + 2) = cpu_to_le16(wPortChange);
+ *(u16 *) buf = cpu_to_le16(wPortStatus);
+ *(u16 *) (buf + 2) = cpu_to_le16(wPortChange);
OK(4);
case SetHubFeature: /* We don't implement these */
case ClearHubFeature:
@@ -140,11 +176,12 @@
}
break;
case SetPortFeature:
- if (!wIndex || wIndex > uhci->rh_numports)
+ if (port >= uhci->rh_numports)
goto err;
switch (wValue) {
case USB_PORT_FEAT_SUSPEND:
+ set_bit(port, &uhci->suspended_ports);
SET_RH_PORTSTAT(USBPORTSC_SUSP);
OK(0);
case USB_PORT_FEAT_RESET:
@@ -164,7 +201,7 @@
}
break;
case ClearPortFeature:
- if (!wIndex || wIndex > uhci->rh_numports)
+ if (port >= uhci->rh_numports)
goto err;
switch (wValue) {
@@ -175,10 +212,12 @@
CLR_RH_PORTSTAT(USBPORTSC_PEC);
OK(0);
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);
case USB_PORT_FEAT_C_SUSPEND:
- /* this driver won't report these */
+ clear_bit(port, &uhci->port_c_suspend);
OK(0);
case USB_PORT_FEAT_POWER:
/* UHCI has no power switching */
-------------------------------------------------------
This SF.Net email is sponsored by: Oracle 10g
Get certified on the hottest thing ever to hit the market... Oracle 10g.
Take an Oracle 10g class now, and we'll give you the exam FREE.
http://ads.osdn.com/?ad_id=3149&alloc_id=8166&op=click
_______________________________________________
[EMAIL PROTECTED]
To unsubscribe, use the last form field at:
https://lists.sourceforge.net/lists/listinfo/linux-usb-devel