X-Git-Url: http://git.onelab.eu/?a=blobdiff_plain;f=drivers%2Fusb%2Fhost%2Fuhci-hub.c;h=1feccf2f03b53e8cd6c49179543d92e48278686b;hb=6a77f38946aaee1cd85eeec6cf4229b204c15071;hp=22bbd70063ce6e681dab84912daaa056763988d8;hpb=87fc8d1bb10cd459024a742c6a10961fefcef18f;p=linux-2.6.git diff --git a/drivers/usb/host/uhci-hub.c b/drivers/usb/host/uhci-hub.c index 22bbd7006..1feccf2f0 100644 --- a/drivers/usb/host/uhci-hub.c +++ b/drivers/usb/host/uhci-hub.c @@ -36,14 +36,16 @@ static __u8 root_hub_hub_des[] = static int uhci_hub_status_data(struct usb_hcd *hcd, char *buf) { struct uhci_hcd *uhci = hcd_to_uhci(hcd); - unsigned long 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)); } + if (*buf && uhci->state == UHCI_SUSPENDED) + uhci->resume_detect = 1; return !!*buf; } @@ -62,31 +64,73 @@ static int uhci_hub_status_data(struct usb_hcd *hcd, char *buf) 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_finish_suspend(struct uhci_hcd *uhci, int port, + unsigned long port_addr) +{ + int status; + + if (test_bit(port, &uhci->suspended_ports)) { + CLR_RH_PORTSTAT(USBPORTSC_SUSP | USBPORTSC_RD); + clear_bit(port, &uhci->suspended_ports); + clear_bit(port, &uhci->resuming_ports); + set_bit(port, &uhci->port_c_suspend); + + /* The controller won't actually turn off the RD bit until + * it has had a chance to send a low-speed EOP sequence, + * which takes 3 bit times (= 2 microseconds). We'll delay + * slightly longer for good luck. */ + udelay(4); + } +} + +static void uhci_check_resume(struct uhci_hcd *uhci) +{ + unsigned int port; + unsigned long port_addr; + + 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)) { + uhci_finish_suspend(uhci, port, port_addr); + } + } + } +} /* 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) { struct uhci_hcd *uhci = hcd_to_uhci(hcd); - int status, retval = 0, len = 0; - unsigned long port_addr = uhci->io_addr + USBPORTSC1 + 2 * (wIndex-1); - __u16 wPortChange, wPortStatus; + int status, lstatus, retval = 0, len = 0; + unsigned int port = wIndex - 1; + unsigned long port_addr = uhci->io_addr + USBPORTSC1 + 2 * port; + u16 wPortChange, wPortStatus; switch (typeReq) { - /* Request Destination: - without flags: Device, - RH_INTERFACE: interface, - RH_ENDPOINT: endpoint, - RH_CLASS means HUB here, - RH_OTHER | RH_CLASS almost ever means HUB_PORT here - */ case GetHubStatus: *(__le32 *)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,34 +141,43 @@ static int uhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, PCI_VENDOR_ID_VIA) status ^= USBPORTSC_OC; - /* UHCI doesn't support C_SUSPEND and C_RESET (always false) */ - wPortChange = 0; + /* UHCI doesn't support C_RESET (always false) */ + wPortChange = lstatus = 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; + lstatus |= 1; + } + if (test_bit(port, &uhci->suspended_ports)) + lstatus |= 2; + if (test_bit(port, &uhci->resuming_ports)) + lstatus |= 4; /* 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); + dev_dbg(uhci_dev(uhci), "port %d portsc %04x,%02x\n", + wIndex, status, lstatus); *(__le16 *)buf = cpu_to_le16(wPortStatus); *(__le16 *)(buf + 2) = cpu_to_le16(wPortChange); @@ -140,11 +193,12 @@ static int uhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, } 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: @@ -152,6 +206,9 @@ static int uhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, mdelay(50); /* USB v1.1 7.1.7.3 */ CLR_RH_PORTSTAT(USBPORTSC_PR); udelay(10); + + /* Reset terminates Resume signalling */ + uhci_finish_suspend(uhci, port, port_addr); SET_RH_PORTSTAT(USBPORTSC_PE); mdelay(10); CLR_RH_PORTSTAT(USBPORTSC_PEC|USBPORTSC_CSC); @@ -164,21 +221,38 @@ static int uhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, } break; case ClearPortFeature: - if (!wIndex || wIndex > uhci->rh_numports) + if (port >= uhci->rh_numports) goto err; switch (wValue) { case USB_PORT_FEAT_ENABLE: CLR_RH_PORTSTAT(USBPORTSC_PE); + + /* Disable terminates Resume signalling */ + uhci_finish_suspend(uhci, port, port_addr); OK(0); case USB_PORT_FEAT_C_ENABLE: CLR_RH_PORTSTAT(USBPORTSC_PEC); OK(0); case USB_PORT_FEAT_SUSPEND: - CLR_RH_PORTSTAT(USBPORTSC_SUSP); + if (test_bit(port, &uhci->suspended_ports) && + !test_and_set_bit(port, + &uhci->resuming_ports)) { + uhci->resume_timeout = jiffies + + msecs_to_jiffies(20); + SET_RH_PORTSTAT(USBPORTSC_RD); + + /* The controller won't allow RD to be set + * if the port is disabled. When this happens + * just skip the Resume signalling. + */ + if (!(inw(port_addr) & USBPORTSC_RD)) + uhci_finish_suspend(uhci, port, + port_addr); + } 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 */