/*-------------------------------------------------------------------------*/
+#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,
}
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
/*-------------------------------------------------------------------------*/
+#define PORT_WAKE_BITS (PORT_WKOC_E|PORT_WKDISC_E|PORT_WKCONN_E)
+
static int ehci_hub_control (
struct usb_hcd *hcd,
u16 typeReq,
&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))
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,
}
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;
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;
&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.
* 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;