fedora core 6 1.2949 + vserver 2.2.0
[linux-2.6.git] / drivers / usb / host / ehci-hub.c
index 69b0b9b..2d36a8d 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * Copyright (C) 2001-2004 by David Brownell
- * 
+ *
  * This program is free software; you can redistribute it and/or modify it
  * under the terms of the GNU General Public License as published by the
  * Free Software Foundation; either version 2 of the License, or (at your
@@ -34,6 +34,7 @@ static int ehci_bus_suspend (struct usb_hcd *hcd)
 {
        struct ehci_hcd         *ehci = hcd_to_ehci (hcd);
        int                     port;
+       int                     mask;
 
        if (time_before (jiffies, ehci->next_statechange))
                msleep(5);
@@ -49,16 +50,27 @@ static int ehci_bus_suspend (struct usb_hcd *hcd)
        ehci->command = readl (&ehci->regs->command);
        if (ehci->reclaim)
                ehci->reclaim_ready = 1;
-       ehci_work(ehci, NULL);
+       ehci_work(ehci);
 
-       /* suspend any active/unsuspended ports, maybe allow wakeup */
+       /* Unlike other USB host controller types, EHCI doesn't have
+        * any notion of "global" or bus-wide suspend.  The driver has
+        * to manually suspend all the active unsuspended ports, and
+        * then manually resume them in the bus_resume() routine.
+        */
+       ehci->bus_suspended = 0;
        while (port--) {
                u32 __iomem     *reg = &ehci->regs->port_status [port];
                u32             t1 = readl (reg) & ~PORT_RWC_BITS;
                u32             t2 = t1;
 
-               if ((t1 & PORT_PE) && !(t1 & PORT_OWNER))
+               /* keep track of which ports we suspend */
+               if ((t1 & PORT_PE) && !(t1 & PORT_OWNER) &&
+                               !(t1 & PORT_SUSPEND)) {
                        t2 |= PORT_SUSPEND;
+                       set_bit(port, &ehci->bus_suspended);
+               }
+
+               /* enable remote wakeup on all ports */
                if (device_may_wakeup(&hcd->self.root_hub->dev))
                        t2 |= PORT_WKOC_E|PORT_WKDISC_E|PORT_WKCONN_E;
                else
@@ -76,6 +88,13 @@ static int ehci_bus_suspend (struct usb_hcd *hcd)
        ehci_halt (ehci);
        hcd->state = HC_STATE_SUSPENDED;
 
+       /* allow remote wakeup */
+       mask = INTR_MASK;
+       if (!device_may_wakeup(&hcd->self.root_hub->dev))
+               mask &= ~STS_PCD;
+       writel(mask, &ehci->regs->intr_enable);
+       readl(&ehci->regs->intr_enable);
+
        ehci->next_statechange = jiffies + msecs_to_jiffies(10);
        spin_unlock_irq (&ehci->lock);
        return 0;
@@ -88,7 +107,6 @@ static int ehci_bus_resume (struct usb_hcd *hcd)
        struct ehci_hcd         *ehci = hcd_to_ehci (hcd);
        u32                     temp;
        int                     i;
-       int                     intr_enable;
 
        if (time_before (jiffies, ehci->next_statechange))
                msleep(5);
@@ -100,31 +118,34 @@ static int ehci_bus_resume (struct usb_hcd *hcd)
         * the last user of the controller, not reset/pm hardware keeping
         * state we gave to it.
         */
+       temp = readl(&ehci->regs->intr_enable);
+       ehci_dbg(ehci, "resume root hub%s\n", temp ? "" : " after power loss");
 
-       /* re-init operational registers in case we lost power */
-       if (readl (&ehci->regs->intr_enable) == 0) {
-               /* at least some APM implementations will try to deliver
-                * IRQs right away, so delay them until we're ready.
-                */
-               intr_enable = 1;
-               writel (0, &ehci->regs->segment);
-               writel (ehci->periodic_dma, &ehci->regs->frame_list);
-               writel ((u32)ehci->async->qh_dma, &ehci->regs->async_next);
-       } else
-               intr_enable = 0;
-       ehci_dbg(ehci, "resume root hub%s\n",
-                       intr_enable ? " after power loss" : "");
+       /* at least some APM implementations will try to deliver
+        * IRQs right away, so delay them until we're ready.
+        */
+       writel(0, &ehci->regs->intr_enable);
+
+       /* re-init operational registers */
+       writel(0, &ehci->regs->segment);
+       writel(ehci->periodic_dma, &ehci->regs->frame_list);
+       writel((u32) ehci->async->qh_dma, &ehci->regs->async_next);
 
        /* restore CMD_RUN, framelist size, and irq threshold */
        writel (ehci->command, &ehci->regs->command);
 
-       /* take ports out of suspend */
+       /* Some controller/firmware combinations need a delay during which
+        * they set up the port statuses.  See Bugzilla #8190. */
+       mdelay(8);
+
+       /* manually resume the ports we suspended during bus_suspend() */
        i = HCS_N_PORTS (ehci->hcs_params);
        while (i--) {
                temp = readl (&ehci->regs->port_status [i]);
                temp &= ~(PORT_RWC_BITS
                        | PORT_WKOC_E | PORT_WKDISC_E | PORT_WKCONN_E);
-               if (temp & PORT_SUSPEND) {
+               if (test_bit(i, &ehci->bus_suspended) &&
+                               (temp & PORT_SUSPEND)) {
                        ehci->reset_done [i] = jiffies + msecs_to_jiffies (20);
                        temp |= PORT_RESUME;
                }
@@ -134,11 +155,12 @@ static int ehci_bus_resume (struct usb_hcd *hcd)
        mdelay (20);
        while (i--) {
                temp = readl (&ehci->regs->port_status [i]);
-               if ((temp & PORT_SUSPEND) == 0)
-                       continue;
-               temp &= ~(PORT_RWC_BITS | PORT_RESUME);
-               writel (temp, &ehci->regs->port_status [i]);
-               ehci_vdbg (ehci, "resumed port %d\n", i + 1);
+               if (test_bit(i, &ehci->bus_suspended) &&
+                               (temp & PORT_SUSPEND)) {
+                       temp &= ~(PORT_RWC_BITS | PORT_RESUME);
+                       writel (temp, &ehci->regs->port_status [i]);
+                       ehci_vdbg (ehci, "resumed port %d\n", i + 1);
+               }
        }
        (void) readl (&ehci->regs->command);
 
@@ -157,8 +179,7 @@ static int ehci_bus_resume (struct usb_hcd *hcd)
        hcd->state = HC_STATE_RUNNING;
 
        /* Now we can safely re-enable irqs */
-       if (intr_enable)
-               writel (INTR_MASK, &ehci->regs->intr_enable);
+       writel(INTR_MASK, &ehci->regs->intr_enable);
 
        spin_unlock_irq (&ehci->lock);
        return 0;
@@ -218,6 +239,7 @@ ehci_hub_status_data (struct usb_hcd *hcd, char *buf)
 {
        struct ehci_hcd *ehci = hcd_to_ehci (hcd);
        u32             temp, status = 0;
+       u32             mask;
        int             ports, i, retval = 1;
        unsigned long   flags;
 
@@ -232,7 +254,19 @@ ehci_hub_status_data (struct usb_hcd *hcd, char *buf)
                buf [1] = 0;
                retval++;
        }
-       
+
+       /* Some boards (mostly VIA?) report bogus overcurrent indications,
+        * causing massive log spam unless we completely ignore them.  It
+        * may be relevant that VIA VT8235 controlers, where PORT_POWER is
+        * always set, seem to clear PORT_OCC and PORT_CSC when writing to
+        * PORT_POWER; that's surprising, but maybe within-spec.
+        */
+       if (!ignore_oc)
+               mask = PORT_CSC | PORT_PEC | PORT_OCC;
+       else
+               mask = PORT_CSC | PORT_PEC;
+       // PORT_RESUME from hardware ~= PORT_STAT_C_SUSPEND
+
        /* no hub change reports (bit 0) for now (power, ...) */
 
        /* port N changes (bit N)? */
@@ -250,8 +284,7 @@ ehci_hub_status_data (struct usb_hcd *hcd, char *buf)
                }
                if (!(temp & PORT_CONNECT))
                        ehci->reset_done [i] = 0;
-               if ((temp & (PORT_CSC | PORT_PEC | PORT_OCC)) != 0
-                               // PORT_STAT_C_SUSPEND?
+               if ((temp & mask) != 0
                                || ((temp & PORT_RESUME) != 0
                                        && time_after (jiffies,
                                                ehci->reset_done [i]))) {
@@ -304,7 +337,7 @@ ehci_hub_descriptor (
 
 /*-------------------------------------------------------------------------*/
 
-#define        PORT_WAKE_BITS  (PORT_WKOC_E|PORT_WKDISC_E|PORT_WKCONN_E)
+#define        PORT_WAKE_BITS  (PORT_WKOC_E|PORT_WKDISC_E|PORT_WKCONN_E)
 
 static int ehci_hub_control (
        struct usb_hcd  *hcd,
@@ -319,6 +352,7 @@ static int ehci_hub_control (
        u32             temp, status;
        unsigned long   flags;
        int             retval = 0;
+       unsigned        selector;
 
        /*
         * FIXME:  support SetPortFeatures USB_PORT_FEAT_INDICATOR.
@@ -359,6 +393,8 @@ static int ehci_hub_control (
                case USB_PORT_FEAT_SUSPEND:
                        if (temp & PORT_RESET)
                                goto error;
+                       if (ehci->no_selective_suspend)
+                               break;
                        if (temp & PORT_SUSPEND) {
                                if ((temp & PORT_PE) == 0)
                                        goto error;
@@ -415,7 +451,7 @@ static int ehci_hub_control (
                        status |= 1 << USB_PORT_FEAT_C_CONNECTION;
                if (temp & PORT_PEC)
                        status |= 1 << USB_PORT_FEAT_C_ENABLE;
-               if (temp & PORT_OCC)
+               if ((temp & PORT_OCC) && !ignore_oc)
                        status |= 1 << USB_PORT_FEAT_C_OVER_CURRENT;
 
                /* whoever resumes must GetPortStatus to complete it!! */
@@ -504,6 +540,8 @@ static int ehci_hub_control (
                }
                break;
        case SetPortFeature:
+               selector = wIndex >> 8;
+               wIndex &= 0xff;
                if (!wIndex || wIndex > ports)
                        goto error;
                wIndex--;
@@ -514,6 +552,8 @@ static int ehci_hub_control (
                temp &= ~PORT_RWC_BITS;
                switch (wValue) {
                case USB_PORT_FEAT_SUSPEND:
+                       if (ehci->no_selective_suspend)
+                               break;
                        if ((temp & PORT_PE) == 0
                                        || (temp & PORT_RESET) != 0)
                                goto error;
@@ -555,6 +595,22 @@ static int ehci_hub_control (
                        }
                        writel (temp, &ehci->regs->port_status [wIndex]);
                        break;
+
+               /* For downstream facing ports (these):  one hub port is put
+                * into test mode according to USB2 11.24.2.13, then the hub
+                * must be reset (which for root hub now means rmmod+modprobe,
+                * or else system reboot).  See EHCI 2.3.9 and 4.14 for info
+                * about the EHCI-specific stuff.
+                */
+               case USB_PORT_FEAT_TEST:
+                       if (!selector || selector > 5)
+                               goto error;
+                       ehci_quiesce(ehci);
+                       ehci_halt(ehci);
+                       temp |= selector << 16;
+                       writel (temp, &ehci->regs->port_status [wIndex]);
+                       break;
+
                default:
                        goto error;
                }