diff options
author | Bill Richardson <wfrichar@chromium.org> | 2016-03-31 15:02:33 -0700 |
---|---|---|
committer | chrome-bot <chrome-bot@chromium.org> | 2016-04-05 00:13:19 -0700 |
commit | 76850c555626c7b6efb13b02dac10f4635fe5bce (patch) | |
tree | ea8cace30c31da0b07cac5ea5abaee93261a59af | |
parent | 65d57259143277f779bf30c07320b6d33542f725 (diff) | |
download | chrome-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.c | 85 |
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(); } } |