patch-2_6_7-vs1_9_1_12
[linux-2.6.git] / drivers / usb / host / ehci-hub.c
index 2113ff8..f721023 100644 (file)
 
 /*-------------------------------------------------------------------------*/
 
+#ifdef CONFIG_PM
+
+static int ehci_hub_suspend (struct usb_hcd *hcd)
+{
+       struct ehci_hcd         *ehci = hcd_to_ehci (hcd);
+       struct usb_device       *root = hcd_to_bus (&ehci->hcd)->root_hub;
+       int                     port;
+       int                     status = 0;
+
+       if (root->dev.power.power_state != 0)
+               return 0;
+       if (time_before (jiffies, ehci->next_statechange))
+               return -EAGAIN;
+
+       port = HCS_N_PORTS (ehci->hcs_params);
+       spin_lock_irq (&ehci->lock);
+
+       /* suspend any active/unsuspended ports, maybe allow wakeup */
+       while (port--) {
+               u32     t1 = readl (&ehci->regs->port_status [port]);
+               u32     t2 = t1;
+
+               if ((t1 & PORT_PE) && !(t1 & PORT_OWNER))
+                       t2 |= PORT_SUSPEND;
+               if (ehci->hcd.remote_wakeup)
+                       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, &ehci->regs->port_status [port]);
+               }
+       }
+
+       /* stop schedules, then turn off HC and clean any completed work */
+       if (hcd->state == USB_STATE_RUNNING)
+               ehci_ready (ehci);
+       ehci->command = readl (&ehci->regs->command);
+       writel (ehci->command & ~CMD_RUN, &ehci->regs->command);
+       if (ehci->reclaim)
+               ehci->reclaim_ready = 1;
+       ehci_work (ehci, 0);
+       (void) handshake (&ehci->regs->status, STS_HALT, STS_HALT, 2000);
+
+       root->dev.power.power_state = 3;
+       ehci->next_statechange = jiffies + msecs_to_jiffies(10);
+       spin_unlock_irq (&ehci->lock);
+       return status;
+}
+
+
+/* caller owns root->serialize, and should reset/reinit on error */
+static int ehci_hub_resume (struct usb_hcd *hcd)
+{
+       struct ehci_hcd         *ehci = hcd_to_ehci (hcd);
+       struct usb_device       *root = hcd_to_bus (&ehci->hcd)->root_hub;
+       u32                     temp;
+       int                     i;
+
+       if (!root->dev.power.power_state)
+               return 0;
+       if (time_before (jiffies, ehci->next_statechange))
+               return -EAGAIN;
+
+       /* re-init operational registers in case we lost power */
+       if (readl (&ehci->regs->intr_enable) == 0) {
+               writel (INTR_MASK, &ehci->regs->intr_enable);
+               writel (0, &ehci->regs->segment);
+               writel (ehci->periodic_dma, &ehci->regs->frame_list);
+               writel ((u32)ehci->async->qh_dma, &ehci->regs->async_next);
+               /* FIXME will this work even (pci) vAUX was lost? */
+       }
+
+       /* restore CMD_RUN, framelist size, and irq threshold */
+       writel (ehci->command, &ehci->regs->command);
+
+       /* take ports out of suspend */
+       i = HCS_N_PORTS (ehci->hcs_params);
+       while (i--) {
+               temp = readl (&ehci->regs->port_status [i]);
+               temp &= ~(PORT_WKOC_E|PORT_WKDISC_E|PORT_WKCONN_E);
+               if (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);
+       msleep (20);
+       while (i--) {
+               temp = readl (&ehci->regs->port_status [i]);
+               if ((temp & PORT_SUSPEND) == 0)
+                       continue;
+               temp &= ~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)
+               writel (ehci->command | temp, &ehci->regs->command);
+
+       root->dev.power.power_state = 0;
+       ehci->next_statechange = jiffies + msecs_to_jiffies(5);
+       ehci->hcd.state = USB_STATE_RUNNING;
+       return 0;
+}
+
+#else
+
+#define ehci_hub_suspend       0
+#define ehci_hub_resume                0
+
+#endif /* CONFIG_PM */
+
+/*-------------------------------------------------------------------------*/
+
 static int check_reset_complete (
        struct ehci_hcd *ehci,
        int             index,
@@ -99,7 +224,11 @@ 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) {
+               if ((temp & (PORT_CSC | PORT_PEC | PORT_OCC)) != 0
+                               // PORT_STAT_C_SUSPEND?
+                               || ((temp & PORT_RESUME) != 0
+                                       && time_after (jiffies,
+                                               ehci->reset_done [i]))) {
                        if (i < 7)
                            buf [0] |= 1 << (i + 1);
                        else
@@ -143,6 +272,8 @@ ehci_hub_descriptor (
 
 /*-------------------------------------------------------------------------*/
 
+#define        PORT_WAKE_BITS  (PORT_WKOC_E|PORT_WKDISC_E|PORT_WKCONN_E)
+
 static int ehci_hub_control (
        struct usb_hcd  *hcd,
        u16             typeReq,
@@ -194,8 +325,20 @@ static int ehci_hub_control (
                                &ehci->regs->port_status [wIndex]);
                        break;
                case USB_PORT_FEAT_SUSPEND:
+                       if (temp & PORT_RESET)
+                               goto error;
+                       if (temp & PORT_SUSPEND) {
+                               if ((temp & PORT_PE) == 0)
+                                       goto error;
+                               /* resume signaling for 20 msec */
+                               writel ((temp & ~PORT_WAKE_BITS) | 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))
@@ -239,15 +382,37 @@ 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)
                        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_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,
@@ -275,7 +440,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;
@@ -312,6 +477,11 @@ static int ehci_hub_control (
 
                switch (wValue) {
                case USB_PORT_FEAT_SUSPEND:
+                       if ((temp & PORT_PE) == 0
+                                       || (temp & PORT_RESET) != 0)
+                               goto error;
+                       if (ehci->hcd.remote_wakeup)
+                               temp |= PORT_WAKE_BITS;
                        writel (temp | PORT_SUSPEND,
                                &ehci->regs->port_status [wIndex]);
                        break;
@@ -321,6 +491,8 @@ 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.
@@ -342,7 +514,7 @@ 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;