From cf4abddca8ee6aa5936b0fb34343146704ddf273 Mon Sep 17 00:00:00 2001 From: Alec Berg Date: Thu, 18 Sep 2014 11:13:45 -0700 Subject: stm32f0: low power idle task Add low power idle task to stm32f0. This can be enabled by defining CONFIG_LOW_POWER_IDLE. This low power idle uses STOP mode to conserve power. BUG=chrome-os-partner:31226, chrome-os-partner:28335 BRANCH=none TEST=add #define CONFIG_LOW_POWER_IDLE to samus and use idlestats console command to verify using deep sleep. also #define CONFIG_FORCE_CONSOLE_RESUME and make sure serial console works without problems when going into deep sleep. Change-Id: I76b0ceb8587a139faa74353d3d8efb4f689fc669 Signed-off-by: Alec Berg Reviewed-on: https://chromium-review.googlesource.com/218956 Reviewed-by: Vincent Palatin --- chip/stm32/clock-stm32f0.c | 190 +++++++++++++++++++++++++++++++++++++++------ chip/stm32/registers.h | 2 + 2 files changed, 170 insertions(+), 22 deletions(-) diff --git a/chip/stm32/clock-stm32f0.c b/chip/stm32/clock-stm32f0.c index 828dca0d4b..99ef0b2681 100644 --- a/chip/stm32/clock-stm32f0.c +++ b/chip/stm32/clock-stm32f0.c @@ -11,17 +11,46 @@ #include "console.h" #include "cpu.h" #include "hooks.h" +#include "hwtimer.h" #include "registers.h" +#include "system.h" #include "task.h" #include "timer.h" #include "util.h" +/* Console output macros */ +#define CPUTS(outstr) cputs(CC_CLOCK, outstr) +#define CPRINTS(format, args...) cprints(CC_CLOCK, format, ## args) + /* use 48Mhz USB-synchronized High-speed oscillator */ #define HSI48_CLOCK 48000000 /* use PLL at 38.4MHz as system clock. */ #define PLL_CLOCK 38400000 +/* Low power idle statistics */ +#ifdef CONFIG_LOW_POWER_IDLE +static int idle_sleep_cnt; +static int idle_dsleep_cnt; +static uint64_t idle_dsleep_time_us; +static int dsleep_recovery_margin_us = 1000000; + +/* + * minimum delay to enter stop mode + * STOP_MODE_LATENCY: max time to wake up from STOP mode with regulator in low + * power mode is 5 us + PLL locking time is 200us. + * SET_RTC_MATCH_DELAY: max time to set RTC match alarm. if we set the alarm + * in the past, it will never wake up and cause a watchdog. + */ +#if (CPU_CLOCK == PLL_CLOCK) +#define STOP_MODE_LATENCY 300 /* us */ +#else +#define STOP_MODE_LATENCY 50 /* us */ +#endif +#define SET_RTC_MATCH_DELAY 200 /* us */ + +#endif /* CONFIG_LOW_POWER_IDLE */ + /* * RTC clock frequency (connected to LSI clock) * @@ -31,7 +60,7 @@ * high-precision delays based solely on LSI. */ /* - * Set synchronous clock freq to HSI/2 (20kHz) to maximize subsecond + * Set synchronous clock freq to LSI/2 (20kHz) to maximize subsecond * resolution. Set asynchronous clock to 1 Hz. */ #define RTC_FREQ (40000 / 2) /* Hz */ @@ -78,7 +107,6 @@ static inline uint32_t sec_to_rtc(uint32_t sec) return rtc; } -#if 0 /* Return time diff between two rtc readings */ static inline int32_t get_rtc_diff(uint32_t rtc0, uint32_t rtc0ss, uint32_t rtc1, uint32_t rtc1ss) @@ -93,7 +121,6 @@ static inline int32_t get_rtc_diff(uint32_t rtc0, uint32_t rtc0ss, return (diff < 0) ? (diff + 10*SECOND) : diff; } -#endif static inline void rtc_read(uint32_t *rtc, uint32_t *rtcss) { @@ -169,26 +196,8 @@ void __rtc_alarm_irq(void) } DECLARE_IRQ(STM32_IRQ_RTC_WAKEUP, __rtc_alarm_irq, 1); -int clock_get_freq(void) -{ - return CPU_CLOCK; -} - -void clock_enable_module(enum module_id module, int enable) +static void config_hispeed_clock(void) { -} - -void clock_init(void) -{ - /* - * The initial state : - * SYSCLK from HSI (=8MHz), no divider on AHB, APB1, APB2 - * PLL unlocked, RTC enabled on LSE - */ - - /* put 1 Wait-State for flash access to ensure proper reads at 48Mhz */ - STM32_FLASH_ACR = 0x1001; /* 1 WS / Prefetch enabled */ - /* Ensure that HSI48 is ON */ if (!(STM32_RCC_CR2 & (1 << 17))) { /* Enable HSI */ @@ -247,6 +256,119 @@ void clock_init(void) #else #error "CPU_CLOCK must be either 48MHz or 38.4MHz" #endif +} + +#ifdef CONFIG_LOW_POWER_IDLE + +void clock_refresh_console_in_use(void) +{ +} + +#ifdef CONFIG_FORCE_CONSOLE_RESUME +#define UARTN_BASE STM32_USART_BASE(CONFIG_UART_CONSOLE) +static void enable_serial_wakeup(int enable) +{ + if (enable) + /* + * Allow UART wake up from STOP mode. Note, UART clock must + * be HSI(8MHz) for wakeup to work. + */ + STM32_USART_CR1(UARTN_BASE) |= STM32_USART_CR1_UESM; + else + /* Disable wake up from STOP mode. */ + STM32_USART_CR1(UARTN_BASE) &= ~STM32_USART_CR1_UESM; +} +#else +static void enable_serial_wakeup(int enable) +{ +} +#endif + +void __idle(void) +{ + timestamp_t t0; + int next_delay, margin_us, rtc_diff; + uint32_t rtc0, rtc0ss, rtc1, rtc1ss; + + while (1) { + asm volatile("cpsid i"); + + t0 = get_time(); + next_delay = __hw_clock_event_get() - t0.le.lo; + + if (DEEP_SLEEP_ALLOWED && + (next_delay > (STOP_MODE_LATENCY + SET_RTC_MATCH_DELAY))) { + /* deep-sleep in STOP mode */ + idle_dsleep_cnt++; + + enable_serial_wakeup(1); + + /* set deep sleep bit */ + CPU_SCB_SYSCTRL |= 0x4; + + set_rtc_alarm(0, next_delay - STOP_MODE_LATENCY, + &rtc0, &rtc0ss); + asm("wfi"); + + CPU_SCB_SYSCTRL &= ~0x4; + + enable_serial_wakeup(0); + + /* + * By default only HSI 8MHz is enabled here. Re-enable + * high-speed clock if in use. + */ + config_hispeed_clock(); + + /* fast forward timer according to RTC counter */ + reset_rtc_alarm(&rtc1, &rtc1ss); + rtc_diff = get_rtc_diff(rtc0, rtc0ss, rtc1, rtc1ss); + t0.val = t0.val + rtc_diff; + force_time(t0); + + /* Record time spent in deep sleep. */ + idle_dsleep_time_us += rtc_diff; + + /* Calculate how close we were to missing deadline */ + margin_us = next_delay - rtc_diff; + if (margin_us < 0) + CPRINTS("overslept by %dus", -margin_us); + + /* 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"); + } +} +#endif /* CONFIG_LOW_POWER_IDLE */ + +int clock_get_freq(void) +{ + return CPU_CLOCK; +} + +void clock_enable_module(enum module_id module, int enable) +{ +} + +void clock_init(void) +{ + /* + * The initial state : + * SYSCLK from HSI (=8MHz), no divider on AHB, APB1, APB2 + * PLL unlocked, RTC enabled on LSE + */ + + /* put 1 Wait-State for flash access to ensure proper reads at 48Mhz */ + STM32_FLASH_ACR = STM32_FLASH_ACR_LATENCY; /* 1 WS / Prefetch enabled */ + + config_hispeed_clock(); rtc_unlock_regs(); @@ -305,3 +427,27 @@ DECLARE_CONSOLE_COMMAND(rtc_alarm, command_rtc_alarm_test, NULL); #endif /* CONFIG_CMD_RTC_ALARM */ +#ifdef CONFIG_LOW_POWER_IDLE +/** + * 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", + NULL); +#endif + diff --git a/chip/stm32/registers.h b/chip/stm32/registers.h index 4f68ce5ca6..4b557cb8e9 100644 --- a/chip/stm32/registers.h +++ b/chip/stm32/registers.h @@ -153,6 +153,7 @@ #ifdef CHIP_FAMILY_STM32F0 #define STM32_USART_CR1(base) STM32_USART_REG(base, 0x00) #define STM32_USART_CR1_UE (1 << 0) +#define STM32_USART_CR1_UESM (1 << 1) #define STM32_USART_CR1_RE (1 << 2) #define STM32_USART_CR1_TE (1 << 3) #define STM32_USART_CR1_RXNEIE (1 << 5) @@ -794,6 +795,7 @@ typedef volatile struct stm32_spi_regs stm32_spi_regs_t; #define STM32_FLASH_REGS_BASE 0x40022000 #define STM32_FLASH_ACR REG32(STM32_FLASH_REGS_BASE + 0x00) +#define STM32_FLASH_ACR_LATENCY (1 << 0) #define STM32_FLASH_KEYR REG32(STM32_FLASH_REGS_BASE + 0x04) #define STM32_FLASH_OPTKEYR REG32(STM32_FLASH_REGS_BASE + 0x08) #define STM32_FLASH_SR REG32(STM32_FLASH_REGS_BASE + 0x0c) -- cgit v1.2.1