diff options
author | Vincent Palatin <vpalatin@chromium.org> | 2018-06-07 16:01:51 +0200 |
---|---|---|
committer | chrome-bot <chrome-bot@chromium.org> | 2018-06-15 10:56:48 -0700 |
commit | 13a0b3437eb59a168c4f9c4d1ebc3d217ced3ba5 (patch) | |
tree | 54ed4c967c27ec40009086a54779ad73bb2a2ab1 /chip/stm32 | |
parent | 727c5dad9bad954c4b62144dc1a536dccd281666 (diff) | |
download | chrome-ec-13a0b3437eb59a168c4f9c4d1ebc3d217ced3ba5.tar.gz |
stm32: low power idle for STM32H7
Enter STOP mode when possible.
Use LPTIM1 clocked on the 32-Khz LSI as a time keeper / wake-up event
during STOP mode.
Signed-off-by: Vincent Palatin <vpalatin@chromium.org>
BRANCH=poppy
BUG=b:75105319
TEST=On ZerbleBarn, use on-board INAs to measure idle power consumption,
w/o CONFIG_LOW_POWER_IDLE pp3300_h7_ma:14.0
with CONFIG_LOW_POWER_IDLE pp3300_h7_ma:1.84
Change-Id: I1b72a8f6964c7bc6174c07458f307dda57fe71f3
Reviewed-on: https://chromium-review.googlesource.com/1096767
Commit-Ready: Vincent Palatin <vpalatin@chromium.org>
Tested-by: Vincent Palatin <vpalatin@chromium.org>
Reviewed-by: Nicolas Boichat <drinkcat@chromium.org>
Diffstat (limited to 'chip/stm32')
-rw-r--r-- | chip/stm32/clock-stm32h7.c | 199 | ||||
-rw-r--r-- | chip/stm32/registers.h | 65 | ||||
-rw-r--r-- | chip/stm32/uart.c | 3 |
3 files changed, 265 insertions, 2 deletions
diff --git a/chip/stm32/clock-stm32h7.c b/chip/stm32/clock-stm32h7.c index f0fcdf8f37..e99ce03517 100644 --- a/chip/stm32/clock-stm32h7.c +++ b/chip/stm32/clock-stm32h7.c @@ -11,11 +11,30 @@ #include "console.h" #include "cpu.h" #include "hooks.h" +#include "hwtimer.h" #include "registers.h" +#include "system.h" +#include "task.h" +#include "uart.h" #include "util.h" +/* Console output macros */ +#define CPUTS(outstr) cputs(CC_CLOCK, outstr) +#define CPRINTF(format, args...) cprintf(CC_CLOCK, format, ## args) + /* High-speed oscillator default is 64 MHz */ #define STM32_HSI_CLOCK 64000000 +/* Low-speed oscillator is 32-Khz */ +#define STM32_LSI_CLOCK 32000 + +/* + * LPTIM is a 16-bit counter clocked by LSI + * with /4 prescaler (2^2): period 125 us, full range ~8s + */ +#define LPTIM_PRESCALER_LOG2 2 +#define LPTIM_PRESCALER (1 << LPTIM_PRESCALER_LOG2) +#define LPTIM_PERIOD_US (SECOND / (STM32_LSI_CLOCK / LPTIM_PRESCALER)) + /* * PLL1 configuration: * CPU freq = VCO / DIVP = HSI / DIVM * DIVN / DIVP @@ -181,10 +200,179 @@ static void clock_set_osc(enum clock_osc osc) void clock_enable_module(enum module_id module, int enable) { /* Assume we have a single task using MODULE_FAST_CPU */ - if (module == MODULE_FAST_CPU) + if (module == MODULE_FAST_CPU) { + /* the PLL would be off in low power mode, disable it */ + if (enable) + disable_sleep(SLEEP_MASK_PLL); + else + enable_sleep(SLEEP_MASK_PLL); clock_set_osc(enable ? OSC_PLL : OSC_HSI); + } +} + +#ifdef CONFIG_LOW_POWER_IDLE +/* Low power idle statistics */ +static int idle_sleep_cnt; +static int idle_dsleep_cnt; +static uint64_t idle_dsleep_time_us; +static int dsleep_recovery_margin_us = 1000000; + +/* STOP_MODE_LATENCY: delay to wake up from STOP mode with flash off in SVOS5 */ +#define STOP_MODE_LATENCY 50 /* us */ + +static void low_power_init(void) +{ + /* Clock LPTIM1 on the 32-kHz LSI for STOP mode time keeping */ + STM32_RCC_D2CCIP2R = (STM32_RCC_D2CCIP2R & + ~STM32_RCC_D2CCIP2_LPTIM1SEL_MASK) + | STM32_RCC_D2CCIP2_LPTIM1SEL_LSI; + + /* configure LPTIM1 as our 1-Khz low power timer in STOP mode */ + STM32_RCC_APB1LENR |= STM32_RCC_PB1_LPTIM1; + STM32_LPTIM_CR(1) = 0; /* ensure it's disabled before configuring */ + STM32_LPTIM_CFGR(1) = LPTIM_PRESCALER_LOG2 << 9; /* Prescaler /4 */ + STM32_LPTIM_IER(1) = STM32_LPTIM_INT_CMPM; /* Compare int for wake-up */ + /* Start the 16-bit free-running counter */ + STM32_LPTIM_CR(1) = STM32_LPTIM_CR_ENABLE; + STM32_LPTIM_ARR(1) = 0xFFFF; + STM32_LPTIM_CR(1) = STM32_LPTIM_CR_ENABLE | STM32_LPTIM_CR_CNTSTRT; + task_enable_irq(STM32_IRQ_LPTIM1); + + /* Wake-up interrupts from EXTI for USART and LPTIM */ + STM32_EXTI_CPUIMR1 |= 1 << 26; /* [26] wkup26: USART1 wake-up */ + STM32_EXTI_CPUIMR2 |= 1 << 15; /* [15] wkup47: LPTIM1 wake-up */ + + /* optimize power vs latency in STOP mode */ + STM32_PWR_CR = (STM32_PWR_CR & ~STM32_PWR_CR_SVOS_MASK) + | STM32_PWR_CR_SVOS5 + | STM32_PWR_CR_FLPS; } +void clock_refresh_console_in_use(void) +{ +} + +void lptim_interrupt(void) +{ + STM32_LPTIM_ICR(1) = STM32_LPTIM_INT_CMPM; +} +DECLARE_IRQ(STM32_IRQ_LPTIM1, lptim_interrupt, 2); + +static uint16_t lptim_read(void) +{ + uint16_t cnt; + + do { + cnt = STM32_LPTIM_CNT(1); + } while (cnt != STM32_LPTIM_CNT(1)); + + return cnt; +} + +static void set_lptim_event(int delay_us, uint16_t *lptim_cnt) +{ + uint16_t cnt = lptim_read(); + + STM32_LPTIM_CMP(1) = cnt + MIN(delay_us / LPTIM_PERIOD_US - 1, 0xffff); + /* clean-up previous event */ + STM32_LPTIM_ICR(1) = STM32_LPTIM_INT_CMPM; + *lptim_cnt = cnt; +} + +void __idle(void) +{ + timestamp_t t0; + int next_delay; + int margin_us, t_diff; + uint16_t lptim0; + + while (1) { + asm volatile("cpsid i"); + + t0 = get_time(); + next_delay = __hw_clock_event_get() - t0.le.lo; + + if (DEEP_SLEEP_ALLOWED && + next_delay > LPTIM_PERIOD_US + STOP_MODE_LATENCY) { + /* deep-sleep in STOP mode */ + idle_dsleep_cnt++; + + uart_enable_wakeup(1); + + /* set deep sleep bit */ + CPU_SCB_SYSCTRL |= 0x4; + + set_lptim_event(next_delay - STOP_MODE_LATENCY, + &lptim0); + + /* ensure outstanding memory transactions complete */ + asm volatile("dsb"); + + asm("wfi"); + + CPU_SCB_SYSCTRL &= ~0x4; + + /* fast forward timer according to low power counter */ + if (STM32_PWR_CPUCR & STM32_PWR_CPUCR_STOPF) { + uint16_t lptim_dt = lptim_read() - lptim0; + + t_diff = (int)lptim_dt * LPTIM_PERIOD_US; + t0.val = t0.val + t_diff; + force_time(t0); + /* clear STOPF flag */ + STM32_PWR_CPUCR |= STM32_PWR_CPUCR_CSSF; + } else { /* STOP entry was aborted, no fixup */ + t_diff = 0; + } + + uart_enable_wakeup(0); + + /* Record time spent in deep sleep. */ + idle_dsleep_time_us += t_diff; + + /* Calculate how close we were to missing deadline */ + margin_us = next_delay - t_diff; + if (margin_us < 0) + /* Use CPUTS to save stack space */ + CPUTS("Overslept!\n"); + + /* Record the closest to missing a deadline. */ + if (margin_us < dsleep_recovery_margin_us) + dsleep_recovery_margin_us = margin_us; + } else { + idle_sleep_cnt++; + + /* normal idle : only CPU clock stopped */ + asm("wfi"); + } + asm volatile("cpsie i"); + } +} + +#ifdef CONFIG_CMD_IDLE_STATS +/** + * Print low power idle statistics + */ +static int command_idle_stats(int argc, char **argv) +{ + timestamp_t ts = get_time(); + + ccprintf("Num idle calls that sleep: %d\n", idle_sleep_cnt); + ccprintf("Num idle calls that deep-sleep: %d\n", idle_dsleep_cnt); + ccprintf("Time spent in deep-sleep: %.6lds\n", + idle_dsleep_time_us); + ccprintf("Total time on: %.6lds\n", ts.val); + ccprintf("Deep-sleep closest to wake deadline: %dus\n", + dsleep_recovery_margin_us); + + return EC_SUCCESS; +} +DECLARE_CONSOLE_COMMAND(idlestats, command_idle_stats, + "", + "Print last idle stats"); +#endif /* CONFIG_CMD_IDLE_STATS */ +#endif /* CONFIG_LOW_POWER_IDLE */ + void clock_init(void) { /* @@ -208,6 +396,15 @@ void clock_init(void) /* Use more optimized flash latency settings for ACLK = HSI = 64 Mhz */ clock_flash_latency(FLASH_ACLK_64MHZ); + + /* Ensure that LSI is ON to clock LPTIM1 and IWDG */ + STM32_RCC_CSR |= STM32_RCC_CSR_LSION; + while (!(STM32_RCC_CSR & STM32_RCC_CSR_LSIRDY)) + ; + +#ifdef CONFIG_LOW_POWER_IDLE + low_power_init(); +#endif } static int command_clock(int argc, char **argv) diff --git a/chip/stm32/registers.h b/chip/stm32/registers.h index 251f63f1a6..b7d62af766 100644 --- a/chip/stm32/registers.h +++ b/chip/stm32/registers.h @@ -151,9 +151,14 @@ #define STM32_IRQ_FPU 81 /* STM32F373 only */ #ifdef CHIP_FAMILY_STM32H7 +#define STM32_IRQ_LPTIM1 93 #define STM32_IRQ_TIM15 116 #define STM32_IRQ_TIM16 117 #define STM32_IRQ_TIM17 118 +#define STM32_IRQ_LPTIM2 138 +#define STM32_IRQ_LPTIM3 139 +#define STM32_IRQ_LPTIM4 140 +#define STM32_IRQ_LPTIM5 141 #endif /* CHIP_FAMILY_STM32H7 */ /* To simplify code generation, define DMA channel 9..10 */ @@ -398,6 +403,12 @@ #define STM32_IWDG_BASE 0x58004800 +#define STM32_LPTIM1_BASE 0x40002400 +#define STM32_LPTIM2_BASE 0x58002400 +#define STM32_LPTIM3_BASE 0x58002800 +#define STM32_LPTIM4_BASE 0x58002C00 +#define STM32_LPTIM5_BASE 0x58003000 + #define STM32_PWR_BASE 0x58024800 #define STM32_RCC_BASE 0x58024400 #define STM32_RTC_BASE 0x58004000 @@ -589,6 +600,33 @@ struct timer_ctlr { /* Must be volatile, or compiler optimizes out repeated accesses */ typedef volatile struct timer_ctlr timer_ctlr_t; +/* --- Low power timers --- */ +#define STM32_LPTIM_BASE(n) CONCAT3(STM32_LPTIM, n, _BASE) + +#define STM32_LPTIM_REG(n, offset) REG32(STM32_LPTIM_BASE(n) + (offset)) + +#define STM32_LPTIM_ISR(n) STM32_LPTIM_REG(n, 0x00) +#define STM32_LPTIM_ICR(n) STM32_LPTIM_REG(n, 0x04) +#define STM32_LPTIM_IER(n) STM32_LPTIM_REG(n, 0x08) +#define STM32_LPTIM_INT_DOWN (1 << 6) +#define STM32_LPTIM_INT_UP (1 << 5) +#define STM32_LPTIM_INT_ARROK (1 << 4) +#define STM32_LPTIM_INT_CMPOK (1 << 3) +#define STM32_LPTIM_INT_EXTTRIG (1 << 2) +#define STM32_LPTIM_INT_ARRM (1 << 1) +#define STM32_LPTIM_INT_CMPM (1 << 0) +#define STM32_LPTIM_CFGR(n) STM32_LPTIM_REG(n, 0x0C) +#define STM32_LPTIM_CR(n) STM32_LPTIM_REG(n, 0x10) +#define STM32_LPTIM_CR_RSTARE (1 << 4) +#define STM32_LPTIM_CR_COUNTRST (1 << 3) +#define STM32_LPTIM_CR_CNTSTRT (1 << 2) +#define STM32_LPTIM_CR_SNGSTRT (1 << 1) +#define STM32_LPTIM_CR_ENABLE (1 << 0) +#define STM32_LPTIM_CMP(n) STM32_LPTIM_REG(n, 0x14) +#define STM32_LPTIM_ARR(n) STM32_LPTIM_REG(n, 0x18) +#define STM32_LPTIM_CNT(n) STM32_LPTIM_REG(n, 0x1C) +#define STM32_LPTIM_CFGR2(n) STM32_LPTIM_REG(n, 0x24) + /* --- GPIO --- */ #define GPIO_A STM32_GPIOA_BASE @@ -823,6 +861,12 @@ typedef volatile struct timer_ctlr timer_ctlr_t; /* --- Power / Reset / Clocks --- */ #define STM32_PWR_CR REG32(STM32_PWR_BASE + 0x00) #define STM32_PWR_CR_LPSDSR (1 << 0) +#define STM32_PWR_CR_FLPS (1 << 9) +#define STM32_PWR_CR_SVOS5 (1 << 14) +#define STM32_PWR_CR_SVOS4 (2 << 14) +#define STM32_PWR_CR_SVOS3 (3 << 14) +#define STM32_PWR_CR_SVOS_MASK (3 << 14) + #if defined(CHIP_FAMILY_STM32L4) #define STM32_PWR_CR2 REG32(STM32_PWR_BASE + 0x04) #define STM32_PWR_CSR REG32(STM32_PWR_BASE + 0x10) @@ -832,6 +876,15 @@ typedef volatile struct timer_ctlr timer_ctlr_t; #define STM32_PWR_CR2 REG32(STM32_PWR_BASE + 0x08) #define STM32_PWR_CR3 REG32(STM32_PWR_BASE + 0x0C) #define STM32_PWR_CPUCR REG32(STM32_PWR_BASE + 0x10) +#define STM32_PWR_CPUCR_PDDS_D1 (1 << 0) +#define STM32_PWR_CPUCR_PDDS_D2 (1 << 1) +#define STM32_PWR_CPUCR_PDDS_D3 (1 << 2) +#define STM32_PWR_CPUCR_STOPF (1 << 5) +#define STM32_PWR_CPUCR_SBF (1 << 6) +#define STM32_PWR_CPUCR_SBF_D1 (1 << 7) +#define STM32_PWR_CPUCR_SBF_D2 (1 << 8) +#define STM32_PWR_CPUCR_CSSF (1 << 9) +#define STM32_PWR_CPUCR_RUN_D3 (1 << 11) #define STM32_PWR_D3CR REG32(STM32_PWR_BASE + 0x18) #define STM32_PWR_WKUPCR REG32(STM32_PWR_BASE + 0x20) #define STM32_PWR_WKUPFR REG32(STM32_PWR_BASE + 0x24) @@ -1455,10 +1508,22 @@ typedef volatile struct timer_ctlr timer_ctlr_t; #define STM32_RCC_D2CCIP2_USART16SEL_CSI (4 << 3) #define STM32_RCC_D2CCIP2_USART16SEL_LSE (5 << 3) #define STM32_RCC_D2CCIP2_USART16SEL_MASK (7 << 3) +#define STM32_RCC_D2CCIP2_LPTIM1SEL_PCLK (0 << 28) +#define STM32_RCC_D2CCIP2_LPTIM1SEL_PLL2 (1 << 28) +#define STM32_RCC_D2CCIP2_LPTIM1SEL_PLL3 (2 << 28) +#define STM32_RCC_D2CCIP2_LPTIM1SEL_LSE (3 << 28) +#define STM32_RCC_D2CCIP2_LPTIM1SEL_LSI (4 << 28) +#define STM32_RCC_D2CCIP2_LPTIM1SEL_PER (5 << 28) +#define STM32_RCC_D2CCIP2_LPTIM1SEL_MASK (7 << 28) +#define STM32_RCC_CSR_LSION (1 << 0) +#define STM32_RCC_CSR_LSIRDY (1 << 1) #define STM32_SYSCFG_PMCR REG32(STM32_SYSCFG_BASE + 0x04) #define STM32_SYSCFG_EXTICR(n) REG32(STM32_SYSCFG_BASE + 8 + 4 * (n)) +/* Peripheral bits for APB1ENR regs */ +#define STM32_RCC_PB1_LPTIM1 (1 << 9) + /* Peripheral bits for APB2ENR regs */ #define STM32_RCC_PB2_TIM1 (1 << 0) #define STM32_RCC_PB2_TIM2 (1 << 1) diff --git a/chip/stm32/uart.c b/chip/stm32/uart.c index 313d2df91c..ce11126746 100644 --- a/chip/stm32/uart.c +++ b/chip/stm32/uart.c @@ -317,7 +317,8 @@ void uart_init(void) /* Configure GPIOs */ gpio_config_module(MODULE_UART, 1); -#if defined(CHIP_FAMILY_STM32F0) || defined(CHIP_FAMILY_STM32F3) +#if defined(CHIP_FAMILY_STM32F0) || defined(CHIP_FAMILY_STM32F3) \ +|| defined(CHIP_FAMILY_STM32H7) /* * Wake up on start bit detection. WUS can only be written when UE=0, * so clear UE first. |