X-Git-Url: http://git.onelab.eu/?a=blobdiff_plain;f=drivers%2Fusb%2Fhost%2Fehci-hub.c;h=f7210234507f1e46c9d41fd80c6b3c8637df567e;hb=ef1d465b627b9aae1124eea2d961027107b39c73;hp=2113ff8dff3728fb6f0f4f623d81dd9241a68d3a;hpb=86090fcac5e27b630656fe3d963a6b80e26dac44;p=linux-2.6.git diff --git a/drivers/usb/host/ehci-hub.c b/drivers/usb/host/ehci-hub.c index 2113ff8df..f72102345 100644 --- a/drivers/usb/host/ehci-hub.c +++ b/drivers/usb/host/ehci-hub.c @@ -28,6 +28,131 @@ /*-------------------------------------------------------------------------*/ +#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;