From 2be0577fe00c26cd99e7a7551e7157eff8c3998e Mon Sep 17 00:00:00 2001 From: Alec Berg Date: Wed, 3 Sep 2014 21:04:23 -0700 Subject: stm32f0: add RTC alarm functionality Implement RTC alarm, with resolution 50us, for stm32f0. This is useful for using low power modes and waking up after set period of time. BUG=chrome-os-partner:31226, chrome-os-partner:28335 BRANCH=none TEST=tested on samus_pd with CONFIG_CMD_RTC_ALARM defined and used rtc_alarm console command to test various timeout periods. Change-Id: Ibabd8662cfbea654c7de387669f7be83af4fd79d Signed-off-by: Alec Berg Reviewed-on: https://chromium-review.googlesource.com/218322 Reviewed-by: Todd Broch --- chip/stm32/clock-stm32f0.c | 206 +++++++++++++++++++++++++++++++++++++++++++++ chip/stm32/registers.h | 14 +++ 2 files changed, 220 insertions(+) diff --git a/chip/stm32/clock-stm32f0.c b/chip/stm32/clock-stm32f0.c index 3f503caad0..828dca0d4b 100644 --- a/chip/stm32/clock-stm32f0.c +++ b/chip/stm32/clock-stm32f0.c @@ -12,6 +12,8 @@ #include "cpu.h" #include "hooks.h" #include "registers.h" +#include "task.h" +#include "timer.h" #include "util.h" /* use 48Mhz USB-synchronized High-speed oscillator */ @@ -20,6 +22,153 @@ /* use PLL at 38.4MHz as system clock. */ #define PLL_CLOCK 38400000 +/* + * RTC clock frequency (connected to LSI clock) + * + * TODO(crosbug.com/p/12281): Calibrate LSI frequency on a per-chip basis. The + * LSI on any given chip can be between 30 kHz to 60 kHz. Without calibration, + * LSI frequency may be off by as much as 50%. Fortunately, we don't do any + * high-precision delays based solely on LSI. + */ +/* + * Set synchronous clock freq to HSI/2 (20kHz) to maximize subsecond + * resolution. Set asynchronous clock to 1 Hz. + */ +#define RTC_FREQ (40000 / 2) /* Hz */ +#define RTC_PREDIV_S (RTC_FREQ - 1) +#define RTC_PREDIV_A 1 +#define US_PER_RTC_TICK (1000000 / RTC_FREQ) + +/* Lock and unlock RTC write access */ +static inline void rtc_lock_regs(void) +{ + STM32_RTC_WPR = 0xff; +} +static inline void rtc_unlock_regs(void) +{ + STM32_RTC_WPR = 0xca; + STM32_RTC_WPR = 0x53; +} + +/* Convert between RTC regs in BCD and seconds */ +static inline uint32_t rtc_to_sec(uint32_t rtc) +{ + uint32_t sec; + + /* convert the hours field */ + sec = (((rtc & 0x300000) >> 20) * 10 + ((rtc & 0xf0000) >> 16)) * 3600; + /* convert the minutes field */ + sec += (((rtc & 0x7000) >> 12) * 10 + ((rtc & 0xf00) >> 8)) * 60; + /* convert the seconds field */ + sec += ((rtc & 0x70) >> 4) * 10 + (rtc & 0xf); + + return sec; +} +static inline uint32_t sec_to_rtc(uint32_t sec) +{ + uint32_t rtc; + + /* convert the hours field */ + rtc = ((sec / 36000) << 20) | (((sec / 3600) % 10) << 16); + /* convert the minutes field */ + rtc |= (((sec % 3600) / 600) << 12) | (((sec % 600) / 60) << 8); + /* convert the seconds field */ + rtc |= (((sec % 60) / 10) << 4) | (sec % 10); + + 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) +{ + int32_t diff; + + /* Note: this only looks at the diff mod 10 seconds */ + diff = ((rtc1 & 0xf) * SECOND + + (RTC_PREDIV_S - rtc1ss) * US_PER_RTC_TICK) - + ((rtc0 & 0xf) * SECOND + + (RTC_PREDIV_S - rtc0ss) * US_PER_RTC_TICK); + + return (diff < 0) ? (diff + 10*SECOND) : diff; +} +#endif + +static inline void rtc_read(uint32_t *rtc, uint32_t *rtcss) +{ + /* Read current time synchronously */ + do { + *rtc = STM32_RTC_TR; + *rtcss = STM32_RTC_SSR; + } while (*rtc != STM32_RTC_TR); +} + +void set_rtc_alarm(uint32_t delay_s, uint32_t delay_us, + uint32_t *rtc, uint32_t *rtcss) +{ + uint32_t alarm_sec, alarm_us; + + /* Alarm must be within 1 day (86400 seconds) */ + ASSERT((delay_s + delay_us / SECOND) < 86400); + + rtc_unlock_regs(); + + /* Make sure alarm is disabled */ + STM32_RTC_CR &= ~STM32_RTC_CR_ALRAE; + while (!(STM32_RTC_ISR & STM32_RTC_ISR_ALRAWF)) + ; + STM32_RTC_ISR &= ~STM32_RTC_ISR_ALRAF; + + rtc_read(rtc, rtcss); + + /* Calculate alarm time */ + alarm_sec = rtc_to_sec(*rtc) + delay_s; + alarm_us = (RTC_PREDIV_S - *rtcss) * US_PER_RTC_TICK + delay_us; + alarm_sec = (alarm_sec + alarm_us / SECOND) % 86400; + alarm_us = alarm_us % 1000000; + + /* Set alarm time */ + STM32_RTC_ALRMAR = sec_to_rtc(alarm_sec); + STM32_RTC_ALRMASSR = RTC_PREDIV_S - (alarm_us / US_PER_RTC_TICK); + /* Check for match on hours, minutes, seconds, and subsecond */ + STM32_RTC_ALRMAR |= 0xc0000000; + STM32_RTC_ALRMASSR |= 0x0f000000; + + /* Enable alarm and alarm interrupt */ + STM32_EXTI_PR = EXTI_RTC_ALR_EVENT; + STM32_EXTI_IMR |= EXTI_RTC_ALR_EVENT; + STM32_RTC_CR |= STM32_RTC_CR_ALRAE; + + rtc_lock_regs(); +} + +void reset_rtc_alarm(uint32_t *rtc, uint32_t *rtcss) +{ + rtc_unlock_regs(); + + /* Disable alarm */ + STM32_RTC_CR &= ~STM32_RTC_CR_ALRAE; + STM32_RTC_ISR &= ~STM32_RTC_ISR_ALRAF; + + /* Disable RTC alarm interrupt */ + STM32_EXTI_IMR &= ~EXTI_RTC_ALR_EVENT; + STM32_EXTI_PR = EXTI_RTC_ALR_EVENT; + + /* Read current time */ + rtc_read(rtc, rtcss); + + rtc_lock_regs(); +} + +void __rtc_alarm_irq(void) +{ + uint32_t rtc, rtcss; + + reset_rtc_alarm(&rtc, &rtcss); +} +DECLARE_IRQ(STM32_IRQ_RTC_WAKEUP, __rtc_alarm_irq, 1); + int clock_get_freq(void) { return CPU_CLOCK; @@ -98,4 +247,61 @@ void clock_init(void) #else #error "CPU_CLOCK must be either 48MHz or 38.4MHz" #endif + + rtc_unlock_regs(); + + /* Enter RTC initialize mode */ + STM32_RTC_ISR |= STM32_RTC_ISR_INIT; + while (!(STM32_RTC_ISR & STM32_RTC_ISR_INITF)) + ; + + /* Set clock prescalars */ + STM32_RTC_PRER = (RTC_PREDIV_A << 16) | RTC_PREDIV_S; + + /* Start RTC timer */ + STM32_RTC_ISR &= ~STM32_RTC_ISR_INIT; + while (STM32_RTC_ISR & STM32_RTC_ISR_INITF) + ; + + /* Enable RTC alarm interrupt */ + STM32_RTC_CR |= STM32_RTC_CR_ALRAIE | STM32_RTC_CR_BYPSHAD; + STM32_EXTI_RTSR |= EXTI_RTC_ALR_EVENT; + task_enable_irq(STM32_IRQ_RTC_WAKEUP); + + rtc_lock_regs(); } + +/*****************************************************************************/ +/* Console commands */ + +#ifdef CONFIG_CMD_RTC_ALARM +static int command_rtc_alarm_test(int argc, char **argv) +{ + int s = 1, us = 0; + uint32_t rtc, rtcss; + char *e; + + ccprintf("Setting RTC alarm\n"); + + if (argc > 1) { + s = strtoi(argv[1], &e, 10); + if (*e) + return EC_ERROR_PARAM1; + + } + if (argc > 2) { + us = strtoi(argv[2], &e, 10); + if (*e) + return EC_ERROR_PARAM2; + + } + + set_rtc_alarm(s, us, &rtc, &rtcss); + return EC_SUCCESS; +} +DECLARE_CONSOLE_COMMAND(rtc_alarm, command_rtc_alarm_test, + "[seconds [microseconds]]", + "Test alarm", + NULL); +#endif /* CONFIG_CMD_RTC_ALARM */ + diff --git a/chip/stm32/registers.h b/chip/stm32/registers.h index e6f98d8001..4f68ce5ca6 100644 --- a/chip/stm32/registers.h +++ b/chip/stm32/registers.h @@ -620,16 +620,26 @@ typedef volatile struct timer_ctlr timer_ctlr_t; #define STM32_RTC_TR REG32(STM32_RTC_BASE + 0x00) #define STM32_RTC_DR REG32(STM32_RTC_BASE + 0x04) #define STM32_RTC_CR REG32(STM32_RTC_BASE + 0x08) +#define STM32_RTC_CR_BYPSHAD (1 << 5) +#define STM32_RTC_CR_ALRAE (1 << 8) +#define STM32_RTC_CR_ALRAIE (1 << 12) #define STM32_RTC_ISR REG32(STM32_RTC_BASE + 0x0C) +#define STM32_RTC_ISR_ALRAWF (1 << 0) +#define STM32_RTC_ISR_RSF (1 << 5) +#define STM32_RTC_ISR_INITF (1 << 6) +#define STM32_RTC_ISR_INIT (1 << 7) +#define STM32_RTC_ISR_ALRAF (1 << 8) #define STM32_RTC_PRER REG32(STM32_RTC_BASE + 0x10) #define STM32_RTC_WUTR REG32(STM32_RTC_BASE + 0x14) #define STM32_RTC_CALIBR REG32(STM32_RTC_BASE + 0x18) #define STM32_RTC_ALRMAR REG32(STM32_RTC_BASE + 0x1C) #define STM32_RTC_ALRMBR REG32(STM32_RTC_BASE + 0x20) #define STM32_RTC_WPR REG32(STM32_RTC_BASE + 0x24) +#define STM32_RTC_SSR REG32(STM32_RTC_BASE + 0x28) #define STM32_RTC_TSTR REG32(STM32_RTC_BASE + 0x30) #define STM32_RTC_TSDR REG32(STM32_RTC_BASE + 0x34) #define STM32_RTC_TAFCR REG32(STM32_RTC_BASE + 0x40) +#define STM32_RTC_ALRMASSR REG32(STM32_RTC_BASE + 0x44) #define STM32_RTC_BACKUP(n) REG32(STM32_RTC_BASE + 0x50 + 4 * (n)) #define STM32_BKP_DATA(n) STM32_RTC_BACKUP(n) @@ -815,6 +825,10 @@ typedef volatile struct stm32_spi_regs stm32_spi_regs_t; #define STM32_EXTI_SWIER REG32(STM32_EXTI_BASE + 0x10) #define STM32_EXTI_PR REG32(STM32_EXTI_BASE + 0x14) +#if defined(CHIP_FAMILY_STM32F0) +#define EXTI_RTC_ALR_EVENT (1 << 17) +#endif + /* --- ADC --- */ #if defined(CHIP_VARIANT_STM32TS60) -- cgit v1.2.1