fedora core 6 1.2949 + vserver 2.2.0
[linux-2.6.git] / drivers / usb / host / ehci-hub.c
index 2113ff8..2d36a8d 100644 (file)
@@ -1,6 +1,6 @@
 /*
- * Copyright (c) 2001-2002 by David Brownell
- * 
+ * 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
 
 /*-------------------------------------------------------------------------*/
 
+#ifdef CONFIG_PM
+
+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);
+
+       port = HCS_N_PORTS (ehci->hcs_params);
+       spin_lock_irq (&ehci->lock);
+
+       /* stop schedules, clean any completed work */
+       if (HC_IS_RUNNING(hcd->state)) {
+               ehci_quiesce (ehci);
+               hcd->state = HC_STATE_QUIESCING;
+       }
+       ehci->command = readl (&ehci->regs->command);
+       if (ehci->reclaim)
+               ehci->reclaim_ready = 1;
+       ehci_work(ehci);
+
+       /* 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;
+
+               /* 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
+                       t2 &= ~(PORT_WKOC_E|PORT_WKDISC_E|PORT_WKCONN_E);
+
+               if (t1 != t2) {
+                       ehci_vdbg (ehci, "port %d, %08x -> %08x\n",
+                               port + 1, t1, t2);
+                       writel (t2, reg);
+               }
+       }
+
+       /* turn off now-idle HC */
+       del_timer_sync (&ehci->watchdog);
+       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;
+}
+
+
+/* caller has locked the root hub, and should reset/reinit on error */
+static int ehci_bus_resume (struct usb_hcd *hcd)
+{
+       struct ehci_hcd         *ehci = hcd_to_ehci (hcd);
+       u32                     temp;
+       int                     i;
+
+       if (time_before (jiffies, ehci->next_statechange))
+               msleep(5);
+       spin_lock_irq (&ehci->lock);
+
+       /* Ideally and we've got a real resume here, and no port's power
+        * was lost.  (For PCI, that means Vaux was maintained.)  But we
+        * could instead be restoring a swsusp snapshot -- so that BIOS was
+        * 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");
+
+       /* 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);
+
+       /* 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 (test_bit(i, &ehci->bus_suspended) &&
+                               (temp & PORT_SUSPEND)) {
+                       ehci->reset_done [i] = jiffies + msecs_to_jiffies (20);
+                       temp |= PORT_RESUME;
+               }
+               writel (temp, &ehci->regs->port_status [i]);
+       }
+       i = HCS_N_PORTS (ehci->hcs_params);
+       mdelay (20);
+       while (i--) {
+               temp = readl (&ehci->regs->port_status [i]);
+               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);
+
+       /* maybe re-activate the schedule(s) */
+       temp = 0;
+       if (ehci->async->qh_next.qh)
+               temp |= CMD_ASE;
+       if (ehci->periodic_sched)
+               temp |= CMD_PSE;
+       if (temp) {
+               ehci->command |= temp;
+               writel (ehci->command, &ehci->regs->command);
+       }
+
+       ehci->next_statechange = jiffies + msecs_to_jiffies(5);
+       hcd->state = HC_STATE_RUNNING;
+
+       /* Now we can safely re-enable irqs */
+       writel(INTR_MASK, &ehci->regs->intr_enable);
+
+       spin_unlock_irq (&ehci->lock);
+       return 0;
+}
+
+#else
+
+#define ehci_bus_suspend       NULL
+#define ehci_bus_resume                NULL
+
+#endif /* CONFIG_PM */
+
+/*-------------------------------------------------------------------------*/
+
 static int check_reset_complete (
        struct ehci_hcd *ehci,
        int             index,
@@ -42,7 +208,7 @@ static int check_reset_complete (
        if (!(port_status & PORT_PE)) {
 
                /* with integrated TT, there's nobody to hand it to! */
-               if (ehci_is_ARC(ehci)) {
+               if (ehci_is_TDI(ehci)) {
                        ehci_dbg (ehci,
                                "Failed to enable port %d on root hub TT\n",
                                index+1);
@@ -54,6 +220,7 @@ static int check_reset_complete (
 
                // what happens if HCS_N_CC(params) == 0 ?
                port_status |= PORT_OWNER;
+               port_status &= ~PORT_RWC_BITS;
                writel (port_status, &ehci->regs->port_status [index]);
 
        } else
@@ -72,9 +239,14 @@ 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;
 
+       /* if !USB_SUSPEND, root hub timers won't get shut down ... */
+       if (!HC_IS_RUNNING(hcd->state))
+               return 0;
+
        /* init status to no-changes */
        buf [0] = 0;
        ports = HCS_N_PORTS (ehci->hcs_params);
@@ -82,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)? */
@@ -92,14 +276,18 @@ ehci_hub_status_data (struct usb_hcd *hcd, char *buf)
                if (temp & PORT_OWNER) {
                        /* don't report this in GetPortStatus */
                        if (temp & PORT_CSC) {
-                               temp &= ~PORT_CSC;
+                               temp &= ~PORT_RWC_BITS;
+                               temp |= PORT_CSC;
                                writel (temp, &ehci->regs->port_status [i]);
                        }
                        continue;
                }
                if (!(temp & PORT_CONNECT))
                        ehci->reset_done [i] = 0;
-               if ((temp & (PORT_CSC | PORT_PEC | PORT_OCC)) != 0) {
+               if ((temp & mask) != 0
+                               || ((temp & PORT_RESUME) != 0
+                                       && time_after (jiffies,
+                                               ehci->reset_done [i]))) {
                        if (i < 7)
                            buf [0] |= 1 << (i + 1);
                        else
@@ -107,6 +295,7 @@ ehci_hub_status_data (struct usb_hcd *hcd, char *buf)
                        status = STS_PCD;
                }
        }
+       /* FIXME autosuspend idle root hubs */
        spin_unlock_irqrestore (&ehci->lock, flags);
        return status ? retval : 0;
 }
@@ -136,13 +325,20 @@ ehci_hub_descriptor (
        temp = 0x0008;                  /* per-port overcurrent reporting */
        if (HCS_PPC (ehci->hcs_params))
                temp |= 0x0001;         /* per-port power control */
+       else
+               temp |= 0x0002;         /* no power switching */
+#if 0
+// re-enable when we support USB_PORT_FEAT_INDICATOR below.
        if (HCS_INDICATOR (ehci->hcs_params))
                temp |= 0x0080;         /* per-port indicators (LEDs) */
-       desc->wHubCharacteristics = cpu_to_le16 (temp);
+#endif
+       desc->wHubCharacteristics = (__force __u16)cpu_to_le16 (temp);
 }
 
 /*-------------------------------------------------------------------------*/
 
+#define        PORT_WAKE_BITS  (PORT_WKOC_E|PORT_WKDISC_E|PORT_WKCONN_E)
+
 static int ehci_hub_control (
        struct usb_hcd  *hcd,
        u16             typeReq,
@@ -156,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.
@@ -190,24 +387,39 @@ static int ehci_hub_control (
                                &ehci->regs->port_status [wIndex]);
                        break;
                case USB_PORT_FEAT_C_ENABLE:
-                       writel (temp | PORT_PEC,
+                       writel((temp & ~PORT_RWC_BITS) | PORT_PEC,
                                &ehci->regs->port_status [wIndex]);
                        break;
                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;
+                               /* resume signaling for 20 msec */
+                               temp &= ~(PORT_RWC_BITS | PORT_WAKE_BITS);
+                               writel (temp | PORT_RESUME,
+                                       &ehci->regs->port_status [wIndex]);
+                               ehci->reset_done [wIndex] = jiffies
+                                               + msecs_to_jiffies (20);
+                       }
+                       break;
                case USB_PORT_FEAT_C_SUSPEND:
-                       /* ? */
+                       /* we auto-clear this feature */
                        break;
                case USB_PORT_FEAT_POWER:
                        if (HCS_PPC (ehci->hcs_params))
-                               writel (temp & ~PORT_POWER,
+                               writel (temp & ~(PORT_RWC_BITS | PORT_POWER),
                                        &ehci->regs->port_status [wIndex]);
                        break;
                case USB_PORT_FEAT_C_CONNECTION:
-                       writel (temp | PORT_CSC,
+                       writel((temp & ~PORT_RWC_BITS) | PORT_CSC,
                                &ehci->regs->port_status [wIndex]);
                        break;
                case USB_PORT_FEAT_C_OVER_CURRENT:
-                       writel (temp | PORT_OCC,
+                       writel((temp & ~PORT_RWC_BITS) | PORT_OCC,
                                &ehci->regs->port_status [wIndex]);
                        break;
                case USB_PORT_FEAT_C_RESET:
@@ -239,22 +451,47 @@ static int ehci_hub_control (
                        status |= 1 << USB_PORT_FEAT_C_CONNECTION;
                if (temp & PORT_PEC)
                        status |= 1 << USB_PORT_FEAT_C_ENABLE;
-               // USB_PORT_FEAT_C_SUSPEND
-               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!! */
+               if ((temp & PORT_RESUME)
+                               && time_after (jiffies,
+                                       ehci->reset_done [wIndex])) {
+                       status |= 1 << USB_PORT_FEAT_C_SUSPEND;
+                       ehci->reset_done [wIndex] = 0;
+
+                       /* stop resume signaling */
+                       temp = readl (&ehci->regs->port_status [wIndex]);
+                       writel (temp & ~(PORT_RWC_BITS | PORT_RESUME),
+                               &ehci->regs->port_status [wIndex]);
+                       retval = handshake (
+                                       &ehci->regs->port_status [wIndex],
+                                       PORT_RESUME, 0, 2000 /* 2msec */);
+                       if (retval != 0) {
+                               ehci_err (ehci, "port %d resume error %d\n",
+                                       wIndex + 1, retval);
+                               goto error;
+                       }
+                       temp &= ~(PORT_SUSPEND|PORT_RESUME|(3<<10));
+               }
+
                /* whoever resets must GetPortStatus to complete it!! */
                if ((temp & PORT_RESET)
                                && time_after (jiffies,
                                        ehci->reset_done [wIndex])) {
                        status |= 1 << USB_PORT_FEAT_C_RESET;
+                       ehci->reset_done [wIndex] = 0;
 
                        /* force reset to complete */
-                       writel (temp & ~PORT_RESET,
+                       writel (temp & ~(PORT_RWC_BITS | PORT_RESET),
                                        &ehci->regs->port_status [wIndex]);
+                       /* REVISIT:  some hardware needs 550+ usec to clear
+                        * this bit; seems too long to spin routinely...
+                        */
                        retval = handshake (
                                        &ehci->regs->port_status [wIndex],
-                                       PORT_RESET, 0, 500);
+                                       PORT_RESET, 0, 750);
                        if (retval != 0) {
                                ehci_err (ehci, "port %d reset error %d\n",
                                        wIndex + 1, retval);
@@ -275,7 +512,7 @@ static int ehci_hub_control (
                        }
                        if (temp & PORT_PE)
                                status |= 1 << USB_PORT_FEAT_ENABLE;
-                       if (temp & PORT_SUSPEND)
+                       if (temp & (PORT_SUSPEND|PORT_RESUME))
                                status |= 1 << USB_PORT_FEAT_SUSPEND;
                        if (temp & PORT_OC)
                                status |= 1 << USB_PORT_FEAT_OVER_CURRENT;
@@ -290,7 +527,7 @@ static int ehci_hub_control (
 #endif
                dbg_port (ehci, "GetStatus", wIndex + 1, temp);
                // we "know" this alignment is good, caller used kmalloc()...
-               *((u32 *) buf) = cpu_to_le32 (status);
+               *((__le32 *) buf) = cpu_to_le32 (status);
                break;
        case SetHubFeature:
                switch (wValue) {
@@ -303,6 +540,8 @@ static int ehci_hub_control (
                }
                break;
        case SetPortFeature:
+               selector = wIndex >> 8;
+               wIndex &= 0xff;
                if (!wIndex || wIndex > ports)
                        goto error;
                wIndex--;
@@ -310,8 +549,16 @@ static int ehci_hub_control (
                if (temp & PORT_OWNER)
                        break;
 
+               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;
+                       if (device_may_wakeup(&hcd->self.root_hub->dev))
+                               temp |= PORT_WAKE_BITS;
                        writel (temp | PORT_SUSPEND,
                                &ehci->regs->port_status [wIndex]);
                        break;
@@ -321,12 +568,14 @@ static int ehci_hub_control (
                                        &ehci->regs->port_status [wIndex]);
                        break;
                case USB_PORT_FEAT_RESET:
+                       if (temp & PORT_RESUME)
+                               goto error;
                        /* line status bits may report this as low speed,
                         * which can be fine if this root hub has a
                         * transaction translator built in.
                         */
                        if ((temp & (PORT_PE|PORT_CONNECT)) == PORT_CONNECT
-                                       && !ehci_is_ARC(ehci)
+                                       && !ehci_is_TDI(ehci)
                                        && PORT_USB11 (temp)) {
                                ehci_dbg (ehci,
                                        "port %d low speed --> companion\n",
@@ -342,10 +591,26 @@ static int ehci_hub_control (
                                 * usb 2.0 spec says 50 ms resets on root
                                 */
                                ehci->reset_done [wIndex] = jiffies
-                                       + ((50 /* msec */ * HZ) / 1000);
+                                               + msecs_to_jiffies (50);
                        }
                        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;
                }