summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBill Richardson <wfrichar@chromium.org>2016-03-31 15:02:33 -0700
committerchrome-bot <chrome-bot@chromium.org>2016-04-05 00:13:19 -0700
commit76850c555626c7b6efb13b02dac10f4635fe5bce (patch)
treeea8cace30c31da0b07cac5ea5abaee93261a59af
parent65d57259143277f779bf30c07320b6d33542f725 (diff)
downloadchrome-ec-76850c555626c7b6efb13b02dac10f4635fe5bce.tar.gz
Cr50: Enable normal sleep, too
This adds support for the "sleep" low-power mode. It consumes less power than simply waiting, but doesn't require a full warm boot to resume. BUG=chrome-os-partner:49955 BRANCH=none TEST=make buildall; test on Cr50 Configure the Cr50 to sleep when idle (refer to previous commit messages for the setup required). On the console, use the "idle" command to test the three different modes: idle w - wfi, wakes instantly idle s - sleep, wakes slowly but without rebooting idle d - deep sleep, wakes via warm boot You can tell the difference between wfi and sleep by observing that the first character is lost when typing on the serial console while in sleep (remember that it will wait at least 10 seconds after the last console input before sleeping). Signed-off-by: Bill Richardson <wfrichar@chromium.org> Change-Id: Ib2584aa44ab885f0c8369ec938ee17b935aa0898 Reviewed-on: https://chromium-review.googlesource.com/336836 Reviewed-by: Dominic Rizzo <domrizzo@google.com>
-rw-r--r--chip/g/idle.c85
1 files changed, 51 insertions, 34 deletions
diff --git a/chip/g/idle.c b/chip/g/idle.c
index 3123bc14c7..4c7afe86b1 100644
--- a/chip/g/idle.c
+++ b/chip/g/idle.c
@@ -47,24 +47,12 @@ DECLARE_CONSOLE_COMMAND(idle, command_idle,
"Set or show the idle action: wfi, sleep, deep sleep",
NULL);
-static void prepare_to_deep_sleep(void)
+static void prepare_to_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;
- /* And the idle action */
- GREG32(PMU, PWRDN_SCRATCH17) = idle_action;
-
- /* Latch the pinmux values */
- GREG32(PINMUX, HOLD) = 1;
-
- /*
* Specify the PINMUX pads that can wake us.
* A1 is UART RX. Idle is high, so wake on low level
* A12 is SPS_CS_L. Also wake on low.
@@ -88,23 +76,51 @@ static void prepare_to_deep_sleep(void)
GC_PMU_EXITPD_MASK_TIMELS0_PD_EXIT_TIMER0_MASK |
GC_PMU_EXITPD_MASK_TIMELS0_PD_EXIT_TIMER1_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... */
+ /* Which rails should we turn off? */
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;
+
+ if (idle_action == IDLE_DEEP_SLEEP) {
+ /*
+ * 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;
+ /* And the idle action */
+ GREG32(PMU, PWRDN_SCRATCH17) = idle_action;
+
+ /* Latch the pinmux values */
+ GREG32(PINMUX, HOLD) = 1;
+
+ /* 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);
+
+ /* Shut down one more power rail for deep sleep */
+ GR_PMU_LOW_POWER_DIS |=
+ GC_PMU_LOW_POWER_DIS_VDDL_MASK;
+ }
+
+ /* The next "wfi" will trigger it */
+ GR_PMU_LOW_POWER_DIS |= GC_PMU_LOW_POWER_DIS_START_MASK;
}
+/* This is for normal sleep only. Deep sleep resumes with a warm boot. */
+static void resume_from_sleep(void)
+{
+ /* Prevent accidental reentry */
+ GR_PMU_LOW_POWER_DIS = 0;
+
+ /* Allow task switching again */
+ interrupt_enable();
+}
+
+
/* The time in the future at which sleeping will be allowed. */
static timestamp_t next_sleep_time;
@@ -146,23 +162,24 @@ void __idle(void)
if (!sleep_delay_passed)
timer_arm(next_sleep_time, TASK_ID_IDLE);
- /* We're allowed to deep sleep, so set it up. */
+ /* We're allowed to sleep now, so set it up. */
if (sleep_ok && sleep_delay_passed)
- if (idle_action == IDLE_DEEP_SLEEP)
- prepare_to_deep_sleep();
- /* Normal sleep is not yet implemented */
+ if (idle_action != IDLE_WFI)
+ prepare_to_sleep();
/* Wait for the next irq event. This stops the CPU clock and
* may trigger sleep or deep sleep if enabled. */
asm("wfi");
/*
- * TODO: Normal sleep resumes by handling the interrupt, but we
- * need to clear PMU_LOW_POWER_DIS right away or we might sleep
- * again by accident. We can't do that here because we don't
- * get here until the next idle, so we'll have to do it in the
- * interrupt handler or when task switching. Deep sleep resumes
- * with a warm boot, which handles it differently.
+ * Note: After resuming from normal sleep we should clear
+ * PMU_LOW_POWER_DIS to prevent sleeping again by accident.
+ * Normal sleep eventually resumes here after the waking
+ * interrupt has been handled, but since all the other tasks
+ * will get a chance to run first it might be some time before
+ * that happens. If we find ourselves going back into sleep
+ * unexpectedly, that might be why.
*/
+ resume_from_sleep();
}
}