Commit 59f13fee authored by David Brownell's avatar David Brownell Committed by Greg Kroah-Hartman

[PATCH] EHCI suspend/resume updates

This patch much improves suspend/resume behavior of EHCI, as tested with
"echo suspend > /sys/power/state" and "echo disk > /sys/power/state".

 - Quiescing the controller has to handle a case where the software has
   finished shutting down periodic and async schedules, but the hardware
   hasn't yet finished its part.

 - When suspending the root hub, be more by-the-book so that resumes
   behave better.

 - When resuming after suspend-to-disk, or in general when no port is
   suspended, the driver re-uses the initialization logic.  That logic
   needed to understand that resources don't need re-allocation, and
   that a few things need to be reset to the default state.

 - More code paths needed to notice a suspended controller, and stop
   right away.

 - To shut down the controller, start with the root hub ports and
   work up from there.

Tested by writing "suspend" or "disk" to /sys/power/state; several
systems resumed OK, at least given the associated usbcore patches.
Signed-off-by: default avatarDavid Brownell <dbrownell@users.sourceforge.net>
Signed-off-by: default avatarGreg Kroah-Hartman <greg@kroah.com>
parent 84050a84
......@@ -97,7 +97,7 @@
* 2001-June Works with usb-storage and NEC EHCI on 2.4
*/
#define DRIVER_VERSION "2004-May-10"
#define DRIVER_VERSION "26 Oct 2004"
#define DRIVER_AUTHOR "David Brownell"
#define DRIVER_DESC "USB 2.0 'Enhanced' Host Controller (EHCI) Driver"
......@@ -207,7 +207,7 @@ static int ehci_reset (struct ehci_hcd *ehci)
}
/* idle the controller (from running) */
static void ehci_ready (struct ehci_hcd *ehci)
static void ehci_quiesce (struct ehci_hcd *ehci)
{
u32 temp;
......@@ -217,11 +217,8 @@ static void ehci_ready (struct ehci_hcd *ehci)
#endif
/* wait for any schedule enables/disables to take effect */
temp = 0;
if (ehci->async->qh_next.qh)
temp = STS_ASS;
if (ehci->next_uframe != -1)
temp |= STS_PSS;
temp = readl (&ehci->regs->command) << 10;
temp &= STS_ASS | STS_PSS;
if (handshake (&ehci->regs->status, STS_ASS | STS_PSS,
temp, 16 * 125) != 0) {
ehci->hcd.state = USB_STATE_HALT;
......@@ -402,17 +399,22 @@ static int ehci_start (struct usb_hcd *hcd)
int retval;
u32 hcc_params;
u8 sbrn = 0;
init_timer (&ehci->watchdog);
ehci->watchdog.function = ehci_watchdog;
ehci->watchdog.data = (unsigned long) ehci;
int first;
/* skip some things on restart paths */
first = (ehci->watchdog.data == 0);
if (first) {
init_timer (&ehci->watchdog);
ehci->watchdog.function = ehci_watchdog;
ehci->watchdog.data = (unsigned long) ehci;
}
/*
* hw default: 1K periodic list heads, one per frame.
* periodic_size can shrink by USBCMD update if hcc_params allows.
*/
ehci->periodic_size = DEFAULT_I_TDPS;
if ((retval = ehci_mem_init (ehci, GFP_KERNEL)) < 0)
if (first && (retval = ehci_mem_init (ehci, GFP_KERNEL)) < 0)
return retval;
/* controllers may cache some of the periodic schedule ... */
......@@ -423,6 +425,7 @@ static int ehci_start (struct usb_hcd *hcd)
ehci->i_thresh = 2 + HCC_ISOC_THRES (hcc_params);
ehci->reclaim = NULL;
ehci->reclaim_ready = 0;
ehci->next_uframe = -1;
/* controller state: unknown --> reset */
......@@ -469,14 +472,16 @@ static int ehci_start (struct usb_hcd *hcd)
* its dummy is used in hw_alt_next of many tds, to prevent the qh
* from automatically advancing to the next td after short reads.
*/
ehci->async->qh_next.qh = NULL;
ehci->async->hw_next = QH_NEXT (ehci->async->qh_dma);
ehci->async->hw_info1 = cpu_to_le32 (QH_HEAD);
ehci->async->hw_token = cpu_to_le32 (QTD_STS_HALT);
ehci->async->hw_qtd_next = EHCI_LIST_END;
ehci->async->qh_state = QH_STATE_LINKED;
ehci->async->hw_alt_next = QTD_NEXT (ehci->async->dummy->qtd_dma);
writel ((u32)ehci->async->qh_dma, &ehci->regs->async_next);
if (first) {
ehci->async->qh_next.qh = NULL;
ehci->async->hw_next = QH_NEXT (ehci->async->qh_dma);
ehci->async->hw_info1 = cpu_to_le32 (QH_HEAD);
ehci->async->hw_token = cpu_to_le32 (QTD_STS_HALT);
ehci->async->hw_qtd_next = EHCI_LIST_END;
ehci->async->qh_state = QH_STATE_LINKED;
ehci->async->hw_alt_next = QTD_NEXT (ehci->async->dummy->qtd_dma);
writel ((u32)ehci->async->qh_dma, &ehci->regs->async_next);
}
/*
* hcc_params controls whether ehci->regs->segment must (!!!)
......@@ -527,12 +532,15 @@ static int ehci_start (struct usb_hcd *hcd)
/* wire up the root hub */
bus = hcd_to_bus (hcd);
udev = usb_alloc_dev (NULL, bus, 0);
udev = first ? usb_alloc_dev (NULL, bus, 0) : bus->root_hub;
if (!udev) {
done2:
ehci_mem_cleanup (ehci);
return -ENOMEM;
}
udev->speed = USB_SPEED_HIGH;
udev->state = first ? USB_STATE_ATTACHED : USB_STATE_CONFIGURED;
udev->dev.power.power_state = PM_SUSPEND_ON;
/*
* Start, enabling full USB 2.0 functionality ... usb 1.1 devices
......@@ -540,8 +548,10 @@ static int ehci_start (struct usb_hcd *hcd)
* involved with the root hub. (Except where one is integrated,
* and there's no companion controller unless maybe for USB OTG.)
*/
ehci->reboot_notifier.notifier_call = ehci_reboot;
register_reboot_notifier (&ehci->reboot_notifier);
if (first) {
ehci->reboot_notifier.notifier_call = ehci_reboot;
register_reboot_notifier (&ehci->reboot_notifier);
}
ehci->hcd.state = USB_STATE_RUNNING;
writel (FLAG_CF, &ehci->regs->configured_flag);
......@@ -549,21 +559,23 @@ static int ehci_start (struct usb_hcd *hcd)
temp = HC_VERSION(readl (&ehci->caps->hc_capbase));
ehci_info (ehci,
"USB %x.%x enabled, EHCI %x.%02x, driver %s\n",
"USB %x.%x %s, EHCI %x.%02x, driver %s\n",
((sbrn & 0xf0)>>4), (sbrn & 0x0f),
first ? "initialized" : "restarted",
temp >> 8, temp & 0xff, DRIVER_VERSION);
/*
* From here on, khubd concurrently accesses the root
* hub; drivers will be talking to enumerated devices.
* (On restart paths, khubd already knows about the root
* hub and could find work as soon as we wrote FLAG_CF.)
*
* Before this point the HC was idle/ready. After, khubd
* and device drivers may start it running.
*/
udev->speed = USB_SPEED_HIGH;
if (hcd_register_root (udev, hcd) != 0) {
if (first && hcd_register_root (udev, hcd) != 0) {
if (hcd->state == USB_STATE_RUNNING)
ehci_ready (ehci);
ehci_quiesce (ehci);
ehci_reset (ehci);
usb_put_dev (udev);
retval = -ENODEV;
......@@ -572,7 +584,8 @@ static int ehci_start (struct usb_hcd *hcd)
writel (INTR_MASK, &ehci->regs->intr_enable); /* Turn On Interrupts */
create_debug_files (ehci);
if (first)
create_debug_files (ehci);
return 0;
}
......@@ -586,24 +599,23 @@ static void ehci_stop (struct usb_hcd *hcd)
ehci_dbg (ehci, "stop\n");
/* no more interrupts ... */
if (hcd->state == USB_STATE_RUNNING)
ehci_ready (ehci);
if (in_interrupt ()) { /* must not happen!! */
ehci_err (ehci, "stopped in_interrupt!\n");
return;
}
del_timer_sync (&ehci->watchdog);
/* Turn off port power on all root hub ports. */
rh_ports = HCS_N_PORTS (ehci->hcs_params);
for (port = 1; port <= rh_ports; port++) {
ehci_hub_control(hcd, ClearPortFeature, USB_PORT_FEAT_POWER,
for (port = 1; port <= rh_ports; port++)
(void) ehci_hub_control(hcd,
ClearPortFeature, USB_PORT_FEAT_POWER,
port, NULL, 0);
}
/* no more interrupts ... */
del_timer_sync (&ehci->watchdog);
spin_lock_irq(&ehci->lock);
if (HCD_IS_RUNNING (ehci->hcd.state))
ehci_quiesce (ehci);
ehci_reset (ehci);
writel (0, &ehci->regs->intr_enable);
spin_unlock_irq(&ehci->lock);
/* let companion controllers work when we aren't */
writel (0, &ehci->regs->configured_flag);
......@@ -641,7 +653,8 @@ static int ehci_get_frame (struct usb_hcd *hcd)
/* suspend/resume, section 4.3 */
/* These routines rely on PCI to handle powerdown and wakeup, and
/* These routines rely on the bus (pci, platform, etc)
* to handle powerdown and wakeup, and currently also on
* transceivers that don't need any software attention to set up
* the right sort of wakeup.
*/
......@@ -650,6 +663,9 @@ static int ehci_suspend (struct usb_hcd *hcd, u32 state)
{
struct ehci_hcd *ehci = hcd_to_ehci (hcd);
if (hcd->self.root_hub->dev.power.power_state)
return 0;
while (time_before (jiffies, ehci->next_statechange))
msleep (100);
......@@ -668,19 +684,51 @@ static int ehci_suspend (struct usb_hcd *hcd, u32 state)
static int ehci_resume (struct usb_hcd *hcd)
{
struct ehci_hcd *ehci = hcd_to_ehci (hcd);
int retval;
unsigned port;
struct usb_device *root = hcd->self.root_hub;
int retval = -EINVAL;
// maybe restore (PCI) FLADJ
while (time_before (jiffies, ehci->next_statechange))
msleep (100);
#ifdef CONFIG_USB_SUSPEND
retval = usb_resume_device (hcd->self.root_hub);
#else
/* FIXME lock root hub */
retval = ehci_hub_resume (hcd);
#endif
/* If any port is suspended, we know we can/must resume the HC. */
for (port = HCS_N_PORTS (ehci->hcs_params); port > 0; ) {
u32 status;
port--;
status = readl (&ehci->regs->port_status [port]);
if (status & PORT_SUSPEND) {
down (&hcd->self.root_hub->serialize);
retval = ehci_hub_resume (hcd);
up (&hcd->self.root_hub->serialize);
break;
}
if (!root->children [port])
continue;
dbg_port (ehci, __FUNCTION__, port + 1, status);
usb_set_device_state (root->children[port],
USB_STATE_NOTATTACHED);
}
/* Else reset, to cope with power loss or flush-to-storage
* style "resume" having activated BIOS during reboot.
*/
if (port == 0) {
(void) ehci_halt (ehci);
(void) ehci_reset (ehci);
(void) ehci_hc_reset (hcd);
/* emptying the schedule aborts any urbs */
spin_lock_irq (&ehci->lock);
if (ehci->reclaim)
ehci->reclaim_ready = 1;
ehci_work (ehci, NULL);
spin_unlock_irq (&ehci->lock);
/* restart; khubd will disconnect devices */
retval = ehci_start (hcd);
}
if (retval == 0)
hcd->self.controller->power.power_state = 0;
return retval;
......@@ -716,7 +764,8 @@ static void ehci_work (struct ehci_hcd *ehci, struct pt_regs *regs)
* misplace IRQs, and should let us run completely without IRQs.
* such lossage has been observed on both VT6202 and VT8235.
*/
if ((ehci->async->qh_next.ptr != 0) || (ehci->periodic_sched != 0))
if (HCD_IS_RUNNING (ehci->hcd.state) && (ehci->async->qh_next.ptr != 0
|| ehci->periodic_sched != 0))
timer_action (ehci, TIMER_IO_WATCHDOG);
}
......
......@@ -35,7 +35,6 @@ 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;
......@@ -45,10 +44,23 @@ static int ehci_hub_suspend (struct usb_hcd *hcd)
port = HCS_N_PORTS (ehci->hcs_params);
spin_lock_irq (&ehci->lock);
/* for hcd->state HCD_STATE_SUSPENDED, also stop the non-USB side */
root->dev.power.power_state = 3;
root->state = USB_STATE_SUSPENDED;
/* stop schedules, clean any completed work */
if (HCD_IS_RUNNING(hcd->state))
ehci_quiesce (ehci);
ehci->command = readl (&ehci->regs->command);
if (ehci->reclaim)
ehci->reclaim_ready = 1;
ehci_work(ehci, NULL);
/* suspend any active/unsuspended ports, maybe allow wakeup */
while (port--) {
u32 t1 = readl (&ehci->regs->port_status [port]);
u32 t2 = t1;
u32 __iomem *reg = &ehci->regs->port_status [port];
u32 t1 = readl (reg);
u32 t2 = t1;
if ((t1 & PORT_PE) && !(t1 & PORT_OWNER))
t2 |= PORT_SUSPEND;
......@@ -60,24 +72,16 @@ static int ehci_hub_suspend (struct usb_hcd *hcd)
if (t1 != t2) {
ehci_vdbg (ehci, "port %d, %08x -> %08x\n",
port + 1, t1, t2);
writel (t2, &ehci->regs->port_status [port]);
writel (t2, reg);
}
}
/* 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, NULL);
(void) handshake (&ehci->regs->status, STS_HALT, STS_HALT, 2000);
/* turn off now-idle HC */
ehci_halt (ehci);
root->dev.power.power_state = 3;
ehci->next_statechange = jiffies + msecs_to_jiffies(10);
spin_unlock_irq (&ehci->lock);
return status;
return 0;
}
......@@ -96,12 +100,16 @@ static int ehci_hub_resume (struct usb_hcd *hcd)
/* re-init operational registers in case we lost power */
if (readl (&ehci->regs->intr_enable) == 0) {
temp = 1;
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? */
}
/* FIXME will this work even if (pci) vAUX was lost? */
} else
temp = 0;
ehci_dbg(ehci, "resume root hub%s\n",
temp ? " after power loss" : "");
/* restore CMD_RUN, framelist size, and irq threshold */
writel (ehci->command, &ehci->regs->command);
......@@ -135,8 +143,10 @@ static int ehci_hub_resume (struct usb_hcd *hcd)
temp |= CMD_ASE;
if (ehci->periodic_sched)
temp |= CMD_PSE;
if (temp)
writel (ehci->command | temp, &ehci->regs->command);
if (temp) {
ehci->command |= temp;
writel (ehci->command, &ehci->regs->command);
}
root->dev.power.power_state = 0;
ehci->next_statechange = jiffies + msecs_to_jiffies(5);
......
......@@ -307,6 +307,9 @@ qh_completions (struct ehci_hcd *ehci, struct ehci_qh *qh, struct pt_regs *regs)
} else {
stopped = 1;
if (unlikely (!HCD_IS_RUNNING (ehci->hcd.state)))
urb->status = -ESHUTDOWN;
/* ignore active urbs unless some previous qtd
* for the urb faulted (including short read) or
* its urb was canceled. we may patch qh or qtds.
......
......@@ -1836,7 +1836,9 @@ scan_periodic (struct ehci_hcd *ehci, struct pt_regs *regs)
while (q.ptr != 0) {
unsigned uf;
union ehci_shadow temp;
int live;
live = HCD_IS_RUNNING (ehci->hcd.state);
switch (type) {
case Q_TYPE_QH:
/* handle any completions */
......@@ -1861,7 +1863,7 @@ scan_periodic (struct ehci_hcd *ehci, struct pt_regs *regs)
case Q_TYPE_ITD:
/* skip itds for later in the frame */
rmb ();
for (uf = uframes; uf < 8; uf++) {
for (uf = live ? uframes : 8; uf < 8; uf++) {
if (0 == (q.itd->hw_transaction [uf]
& ITD_ACTIVE))
continue;
......@@ -1885,7 +1887,8 @@ scan_periodic (struct ehci_hcd *ehci, struct pt_regs *regs)
q = *q_p;
break;
case Q_TYPE_SITD:
if (q.sitd->hw_results & SITD_ACTIVE) {
if ((q.sitd->hw_results & SITD_ACTIVE)
&& live) {
q_p = &q.sitd->sitd_next;
hw_p = &q.sitd->hw_next;
type = Q_NEXT_TYPE (q.sitd->hw_next);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment