summaryrefslogtreecommitdiff
path: root/chip/stm32
diff options
context:
space:
mode:
authorVincent Palatin <vpalatin@chromium.org>2018-06-07 16:01:51 +0200
committerchrome-bot <chrome-bot@chromium.org>2018-06-15 10:56:48 -0700
commit13a0b3437eb59a168c4f9c4d1ebc3d217ced3ba5 (patch)
tree54ed4c967c27ec40009086a54779ad73bb2a2ab1 /chip/stm32
parent727c5dad9bad954c4b62144dc1a536dccd281666 (diff)
downloadchrome-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.c199
-rw-r--r--chip/stm32/registers.h65
-rw-r--r--chip/stm32/uart.c3
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.