summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBill Richardson <wfrichar@chromium.org>2016-03-30 12:44:40 -0700
committerchrome-bot <chrome-bot@chromium.org>2016-03-31 14:53:52 -0700
commit9e0c450f726d3d6dfd3486d7b816fd90621a6321 (patch)
tree1a74bb11d78974f6a1eb0c7d6b9587fc435d407b
parente2deea36495ae4c6541db0ff0899c700b731ced7 (diff)
downloadchrome-ec-9e0c450f726d3d6dfd3486d7b816fd90621a6321.tar.gz
Cr50: First attempt at USB suspend with deep sleep
This is still in testing mode, so you have to take special steps to enable it (keep reading). But if you do the right dance, it does go into deep sleep for USB suspend, and resumes correctly. However, it doesn't yet wake for any other reason. That's coming next. Normal sleep is not yet supported, either. BUG=chrome-os-partner:49955, chrome-os-partner:50721 BRANCH=none TEST=make buildall; extensive tests on Cr50 Testing is a pain. First, you can't print anything in the idle task, because that just makes it stop being idle, so the only way to detect when it's triggered is by wiring up a GPIO and instrumenting things. Second, you have to manually reenable USB suspend on the host every time the Cr50 boots with echo auto > /sys/bus/usb/devices/<BLEH>/power/control where <BLEH> is the correct device. Third, for reasons probably related to the mysteries of HID devices combined with crbug.com/431886, you have to build the firmware without CONFIG_USB_HID (and the related items in board.h) Finally, because it's still a work in progress, you have to type idle d at the serial console after every boot (or resume) to reenable deep sleep in the idle task. If you do all that, then you'll see that it does go into deep sleep. Ping it again with "lsusb -v -d 18d1:5014" or ./test/usb_test/device_configuration, and it wakes up and responds! If you disconnect the USB while it's in deep sleep, it stays asleep. When you plug it in again, it wakes up, but it correctly recognizes that it shouldn't resume and does a normal reset instead. Change-Id: I3cc66e48ce671142a4d12edbe0eb9fdacecea0d9 Signed-off-by: Bill Richardson <wfrichar@chromium.org> Reviewed-on: https://chromium-review.googlesource.com/336279 Reviewed-by: Dominic Rizzo <domrizzo@google.com>
-rw-r--r--chip/g/idle.c50
-rw-r--r--chip/g/usb.c157
-rw-r--r--include/system.h1
3 files changed, 151 insertions, 57 deletions
diff --git a/chip/g/idle.c b/chip/g/idle.c
index fc955e0b30..7319c6edb8 100644
--- a/chip/g/idle.c
+++ b/chip/g/idle.c
@@ -5,6 +5,9 @@
#include "common.h"
#include "console.h"
+#include "pmu.h"
+#include "system.h"
+#include "task.h"
#include "util.h"
/* This function is assumed to exist, but we don't use it */
@@ -49,16 +52,61 @@ DECLARE_CONSOLE_COMMAND(idle, command_idle,
"Set or show the idle action: wfi, sleep, deep sleep",
NULL);
+static void prepare_to_deep_sleep(void)
+{
+ /* No task switching! */
+ interrupt_disable();
+
+ /*
+ * Preserve some state prior to deep sleep. Pretty much all we need is
+ * the device address, since everything else can be reinitialized on
+ * resume.
+ */
+ GREG32(PMU, PWRDN_SCRATCH18) = GR_USB_DCFG;
+
+ /* Latch the pinmux values */
+ GREG32(PINMUX, HOLD) = 1;
+
+ /* Wake only from USB for now */
+ GR_PMU_EXITPD_MASK =
+ GC_PMU_EXITPD_MASK_UTMI_SUSPEND_N_MASK;
+
+ /* Clamp the USB pins and shut the PHY down. We have to do this in
+ * three separate steps, or Bad Things happen. */
+ GWRITE_FIELD(USB, PCGCCTL, PWRCLMP, 1);
+ GWRITE_FIELD(USB, PCGCCTL, RSTPDWNMODULE, 1);
+ GWRITE_FIELD(USB, PCGCCTL, STOPPCLK, 1);
+
+ /* Get ready... */
+ GR_PMU_LOW_POWER_DIS =
+ /* The next "wfi" will trigger it */
+ GC_PMU_LOW_POWER_DIS_START_MASK |
+ /* ... with these rails off */
+ GC_PMU_LOW_POWER_DIS_VDDL_MASK | /* <= this means deep sleep */
+ GC_PMU_LOW_POWER_DIS_VDDIOF_MASK |
+ GC_PMU_LOW_POWER_DIS_VDDXO_MASK |
+ GC_PMU_LOW_POWER_DIS_JTR_RC_MASK;
+}
+
/* Custom idle task, executed when no tasks are ready to be scheduled. */
void __idle(void)
{
+ int sleep_ok;
+
while (1) {
/* Don't even bother unless we've enabled it */
if (idle_action == IDLE_WFI)
goto wfi;
- /* TODO(wfrichar): sleep/deep-sleep stuff goes here... */
+ /* Anyone still busy? */
+ sleep_ok = DEEP_SLEEP_ALLOWED;
+
+ /* We're allowed to sleep now, so set it up. */
+ if (sleep_ok)
+ if (idle_action == IDLE_DEEP_SLEEP)
+ prepare_to_deep_sleep();
+ /* Normal sleep is not yet implemented */
wfi:
/* Wait for the next irq event. This stops the CPU clock and
diff --git a/chip/g/usb.c b/chip/g/usb.c
index de028935fe..9963f2dff7 100644
--- a/chip/g/usb.c
+++ b/chip/g/usb.c
@@ -11,6 +11,7 @@
#include "hooks.h"
#include "link_defs.h"
#include "registers.h"
+#include "system.h"
#include "task.h"
#include "timer.h"
#include "util.h"
@@ -952,19 +953,6 @@ static void ep0_interrupt(uint32_t intr_on_out, uint32_t intr_on_in)
}
}
-/* Endpoint-specific callback for when the USB device recognizes a reset */
-static void ep0_reset(void)
-{
- /* Reset EP0 address */
- GWRITE_FIELD(USB, DCFG, DEVADDR, 0);
- initialize_dma_buffers();
- expect_setup_packet();
- /* Clear our internal state */
- device_state = DS_DEFAULT;
- configuration_value = 0;
-
-}
-
/****************************************************************************/
/* USB device initialization and shutdown routines */
@@ -1043,35 +1031,42 @@ static void setup_data_fifos(void)
; /* TODO: timeout 100ms */
}
-static void usb_reset(void)
+static void usb_init_endpoints(void)
{
int ep;
- print_later("usb_reset()", 0, 0, 0, 0, 0);
+ print_later("usb_init_endpoints()", 0, 0, 0, 0, 0);
- ep0_reset();
+ /* Prepare to receive packets on EP0 */
+ initialize_dma_buffers();
+ expect_setup_packet();
+
+ /* Reset the other endpoints */
for (ep = 1; ep < USB_EP_COUNT; ep++)
usb_ep_reset[ep]();
}
-static void usb_enumdone(void)
+static void usb_reset(void)
{
- print_later("usb_enumdone()", 0, 0, 0, 0, 0);
-}
+ print_later("usb_reset()", 0, 0, 0, 0, 0);
-static void usb_wakeup(void)
-{
- print_later("usb_wakeup()", 0, 0, 0, 0, 0);
-}
+ /* Clear our internal state */
+ device_state = DS_DEFAULT;
+ configuration_value = 0;
-static void usb_early_suspend(void)
-{
- print_later("usb_early_suspend()", 0, 0, 0, 0, 0);
+ /* Clear the device address */
+ GWRITE_FIELD(USB, DCFG, DEVADDR, 0);
+
+ /* Reinitialize all the endpoints */
+ usb_init_endpoints();
}
-static void usb_suspend(void)
+static void usb_resetdet(void)
{
- print_later("usb_suspend()", 0, 0, 0, 0, 0);
+ /* TODO: Same as normal reset, right? I think we only get this if we're
+ * suspended (sleeping) and the host resets us. Try it and see. */
+ print_later("usb_resetdet()", 0, 0, 0, 0, 0);
+ usb_reset();
}
void usb_interrupt(void)
@@ -1084,21 +1079,32 @@ void usb_interrupt(void)
print_later("interrupt: GINTSTS 0x%08x", status, 0, 0, 0, 0);
- if (status & GINTSTS(RESETDET))
- usb_wakeup();
+ /* We can suspend if the host stops talking to us. But if anything else
+ * comes along (even ERLYSUSP), we should NOT suspend. */
+ if (status & GINTSTS(USBSUSP)) {
+ print_later("usb_suspend()", 0, 0, 0, 0, 0);
+ enable_sleep(SLEEP_MASK_USB_DEVICE);
+ } else {
+ disable_sleep(SLEEP_MASK_USB_DEVICE);
+ }
+#ifdef DEBUG_ME
if (status & GINTSTS(ERLYSUSP))
- usb_early_suspend();
+ print_later("usb_early_suspend()", 0, 0, 0, 0, 0);
- if (status & GINTSTS(USBSUSP))
- usb_suspend();
+ if (status & GINTSTS(WKUPINT))
+ print_later("usb_wakeup()", 0, 0, 0, 0, 0);
+
+ if (status & GINTSTS(ENUMDONE))
+ print_later("usb_enumdone()", 0, 0, 0, 0, 0);
+#endif
+
+ if (status & GINTSTS(RESETDET))
+ usb_resetdet();
if (status & GINTSTS(USBRST))
usb_reset();
- if (status & GINTSTS(ENUMDONE))
- usb_enumdone();
-
/* Endpoint interrupts */
if (oepint || iepint) {
/* Note: It seems that the DAINT bits are only trustworthy for
@@ -1183,9 +1189,20 @@ void usb_disconnect(void)
void usb_init(void)
{
- int i;
+ int i, resume;
- print_later("usb_init()", 0, 0, 0, 0, 0);
+ /* USB is in use */
+ disable_sleep(SLEEP_MASK_USB_DEVICE);
+
+ /*
+ * Resuming from a deep sleep is a lot like a cold boot, but there are
+ * few things that we need to do slightly differently. However, we ONLY
+ * do them if we're really resuming due to a USB wakeup. If we're woken
+ * for some other reason, we just do a normal USB reset. The host
+ * doesn't mind.
+ */
+ resume = ((system_get_reset_flags() & RESET_FLAG_USB_RESUME) &&
+ (GR_USB_GINTSTS & GC_USB_GINTSTS_WKUPINT_MASK));
/* TODO(crosbug.com/p/46813): Clean this up. Do only what's needed, and
* use meaningful constants instead of magic numbers. */
@@ -1220,7 +1237,10 @@ void usb_init(void)
/* FIXME: Magic number! 14 is for 15MHz! Use 9 for 30MHz */
| GUSBCFG_USBTRDTIM(14);
- usb_softreset();
+ if (!resume)
+ /* Don't reset on resume, because some preserved internal state
+ * will be lost and there's no way to restore it. */
+ usb_softreset();
GR_USB_GUSBCFG = GUSBCFG_PHYSEL_FS | GUSBCFG_FSINTF_6PIN
| GUSBCFG_TOUTCAL(7)
@@ -1233,21 +1253,36 @@ void usb_init(void)
GAHBCFG_NP_TXF_EMP_LVL;
/* Be in disconnected state until we are ready */
- usb_disconnect();
+ if (!resume)
+ usb_disconnect();
+
+ if (resume)
+ /* DEVADDR is preserved in the USB module during deep sleep,
+ * but it doesn't show up in USB_DCFG on resume. If we don't
+ * restore it manually too, it doesn't work. */
+ GR_USB_DCFG = GREG32(PMU, PWRDN_SCRATCH18);
+ else
+ /* Init: USB2 FS, Scatter/Gather DMA, DEVADDR = 0x00 */
+ GR_USB_DCFG |= DCFG_DEVSPD_FS48 | DCFG_DESCDMA;
+
+ /* If we've restored a nonzero device address, update our state. */
+ if (GR_USB_DCFG & GC_USB_DCFG_DEVADDR_MASK) {
+ /* Caution: We only have one config TODAY, so there's no real
+ * difference between DS_CONFIGURED and DS_ADDRESS. */
+ device_state = DS_CONFIGURED;
+ configuration_value = 1;
+ } else {
+ device_state = DS_DEFAULT;
+ configuration_value = 0;
+ }
- /* Max speed: USB2 FS */
- GR_USB_DCFG = DCFG_DEVSPD_FS48 | DCFG_DESCDMA;
+ /* Now that DCFG.DesDMA is accurate, prepare the FIFOs */
+ setup_data_fifos();
- /* Setup FIFO configuration */
- setup_data_fifos();
-
- /* Device registers have been setup */
- GR_USB_DCTL |= DCTL_PWRONPRGDONE;
- udelay(10);
- GR_USB_DCTL &= ~DCTL_PWRONPRGDONE;
-
- /* Clear global NAKs */
- GR_USB_DCTL |= DCTL_CGOUTNAK | DCTL_CGNPINNAK;
+ /* If resuming, reinitialize the endpoints now. For a cold boot we'll
+ * do this as part of handling the host-driven reset. */
+ if (resume)
+ usb_init_endpoints();
/* Clear any pending interrupts */
for (i = 0; i < 16; i++) {
@@ -1273,16 +1308,23 @@ void usb_init(void)
/* Endpoint activity, cleared by the DOEPINT/DIEPINT regs */
GINTMSK(OEPINT) | GINTMSK(IEPINT) |
/* Reset detected while suspended. Need to wake up. */
- GINTMSK(RESETDET) |
+ GINTMSK(RESETDET) | /* TODO: Do we need this? */
/* Idle, Suspend detected. Should go to sleep. */
GINTMSK(ERLYSUSP) | GINTMSK(USBSUSP);
+ /* Device registers have been setup */
+ GR_USB_DCTL |= DCTL_PWRONPRGDONE;
+ udelay(10);
+ GR_USB_DCTL &= ~DCTL_PWRONPRGDONE;
+
+ /* Clear global NAKs */
+ GR_USB_DCTL |= DCTL_CGOUTNAK | DCTL_CGNPINNAK;
+
#ifndef CONFIG_USB_INHIBIT_CONNECT
/* Indicate our presence to the USB host */
- usb_connect();
+ if (!resume)
+ usb_connect();
#endif
-
- print_later("usb_init() done", 0, 0, 0, 0, 0);
}
#ifndef CONFIG_USB_INHIBIT_INIT
DECLARE_HOOK(HOOK_INIT, usb_init, HOOK_PRIO_DEFAULT);
@@ -1299,4 +1341,7 @@ void usb_release(void)
/* disable clocks */
clock_enable_module(MODULE_USB, 0);
/* TODO: pin-mux */
+
+ /* USB is off, so sleep whenever */
+ enable_sleep(SLEEP_MASK_USB_DEVICE);
}
diff --git a/include/system.h b/include/system.h
index 6c8bbea06d..67a867f39e 100644
--- a/include/system.h
+++ b/include/system.h
@@ -313,6 +313,7 @@ enum {
SLEEP_MASK_SPI = (1 << 6), /* SPI communications ongoing */
SLEEP_MASK_I2C_SLAVE = (1 << 7), /* I2C slave communication ongoing */
SLEEP_MASK_FAN = (1 << 8), /* Fan control loop ongoing */
+ SLEEP_MASK_USB_DEVICE = (1 << 9), /* Generic USB device in use */
SLEEP_MASK_FORCE_NO_DSLEEP = (1 << 15), /* Force disable. */