diff options
Diffstat (limited to 'chip/stm32/hwtimer.c')
-rw-r--r-- | chip/stm32/hwtimer.c | 481 |
1 files changed, 481 insertions, 0 deletions
diff --git a/chip/stm32/hwtimer.c b/chip/stm32/hwtimer.c new file mode 100644 index 0000000000..3521347f3f --- /dev/null +++ b/chip/stm32/hwtimer.c @@ -0,0 +1,481 @@ +/* Copyright 2012 The ChromiumOS Authors + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/* Hardware timers driver */ + +#include "builtin/assert.h" +#include "clock.h" +#include "clock-f.h" +#include "common.h" +#include "hooks.h" +#include "hwtimer.h" +#include "panic.h" +#include "registers.h" +#include "task.h" +#include "timer.h" +#include "watchdog.h" + +/* + * Trigger select mapping for secondary timer from primary timer. This is + * unfortunately not very straightforward; there's no tidy way to do this + * algorithmically. To avoid burning memory for a lookup table, use macros to + * compute the offset. This also has the benefit that compilation will fail if + * an unsupported primary/secondary pairing is used. + */ +#ifdef CHIP_FAMILY_STM32F0 +/* + * Secondary Primary + * 1 15 2 3 17 + * 2 1 15 3 14 + * 3 1 2 15 14 + * 15 2 3 16 17 + * -------------------- + * ts = 0 1 2 3 + */ +#define STM32_TIM_TS_SECONDARY_1_PRIMARY_15 0 +#define STM32_TIM_TS_SECONDARY_1_PRIMARY_2 1 +#define STM32_TIM_TS_SECONDARY_1_PRIMARY_3 2 +#define STM32_TIM_TS_SECONDARY_1_PRIMARY_17 3 +#define STM32_TIM_TS_SECONDARY_2_PRIMARY_1 0 +#define STM32_TIM_TS_SECONDARY_2_PRIMARY_15 1 +#define STM32_TIM_TS_SECONDARY_2_PRIMARY_3 2 +#define STM32_TIM_TS_SECONDARY_2_PRIMARY_14 3 +#define STM32_TIM_TS_SECONDARY_3_PRIMARY_1 0 +#define STM32_TIM_TS_SECONDARY_3_PRIMARY_2 1 +#define STM32_TIM_TS_SECONDARY_3_PRIMARY_15 2 +#define STM32_TIM_TS_SECONDARY_3_PRIMARY_14 3 +#define STM32_TIM_TS_SECONDARY_15_PRIMARY_2 0 +#define STM32_TIM_TS_SECONDARY_15_PRIMARY_3 1 +#define STM32_TIM_TS_SECONDARY_15_PRIMARY_16 2 +#define STM32_TIM_TS_SECONDARY_15_PRIMARY_17 3 +#elif defined(CHIP_FAMILY_STM32F3) +/* + * Secondary Primary + * 2 19 15 3 14 + * 3 19 2 5 14 + * 4 19 2 3 15 + * 5 2 3 4 15 + * 12 4 5 13 14 + * 19 2 3 15 16 + * --------------------- + * ts = 0 1 2 3 + */ +#define STM32_TIM_TS_SECONDARY_2_PRIMARY_19 0 +#define STM32_TIM_TS_SECONDARY_2_PRIMARY_15 1 +#define STM32_TIM_TS_SECONDARY_2_PRIMARY_3 2 +#define STM32_TIM_TS_SECONDARY_2_PRIMARY_14 3 +#define STM32_TIM_TS_SECONDARY_3_PRIMARY_19 0 +#define STM32_TIM_TS_SECONDARY_3_PRIMARY_2 1 +#define STM32_TIM_TS_SECONDARY_3_PRIMARY_5 2 +#define STM32_TIM_TS_SECONDARY_3_PRIMARY_14 3 +#define STM32_TIM_TS_SECONDARY_4_PRIMARY_19 0 +#define STM32_TIM_TS_SECONDARY_4_PRIMARY_2 1 +#define STM32_TIM_TS_SECONDARY_4_PRIMARY_3 2 +#define STM32_TIM_TS_SECONDARY_4_PRIMARY_15 3 +#define STM32_TIM_TS_SECONDARY_5_PRIMARY_2 0 +#define STM32_TIM_TS_SECONDARY_5_PRIMARY_3 1 +#define STM32_TIM_TS_SECONDARY_5_PRIMARY_4 2 +#define STM32_TIM_TS_SECONDARY_5_PRIMARY_15 3 +#define STM32_TIM_TS_SECONDARY_12_PRIMARY_4 0 +#define STM32_TIM_TS_SECONDARY_12_PRIMARY_5 1 +#define STM32_TIM_TS_SECONDARY_12_PRIMARY_13 2 +#define STM32_TIM_TS_SECONDARY_12_PRIMARY_14 3 +#define STM32_TIM_TS_SECONDARY_19_PRIMARY_2 0 +#define STM32_TIM_TS_SECONDARY_19_PRIMARY_3 1 +#define STM32_TIM_TS_SECONDARY_19_PRIMARY_15 2 +#define STM32_TIM_TS_SECONDARY_19_PRIMARY_16 3 +#else /* !CHIP_FAMILY_STM32F0 && !CHIP_FAMILY_STM32F3 */ +/* + * Secondary Primary + * 1 15 2 3 4 (STM32F100 only) + * 2 9 10 3 4 + * 3 9 2 11 4 + * 4 10 2 3 9 + * 9 2 3 10 11 (STM32L15x only) + * -------------------- + * ts = 0 1 2 3 + */ +#define STM32_TIM_TS_SECONDARY_1_PRIMARY_15 0 +#define STM32_TIM_TS_SECONDARY_1_PRIMARY_2 1 +#define STM32_TIM_TS_SECONDARY_1_PRIMARY_3 2 +#define STM32_TIM_TS_SECONDARY_1_PRIMARY_4 3 +#define STM32_TIM_TS_SECONDARY_2_PRIMARY_9 0 +#define STM32_TIM_TS_SECONDARY_2_PRIMARY_10 1 +#define STM32_TIM_TS_SECONDARY_2_PRIMARY_3 2 +#define STM32_TIM_TS_SECONDARY_2_PRIMARY_4 3 +#define STM32_TIM_TS_SECONDARY_3_PRIMARY_9 0 +#define STM32_TIM_TS_SECONDARY_3_PRIMARY_2 1 +#define STM32_TIM_TS_SECONDARY_3_PRIMARY_11 2 +#define STM32_TIM_TS_SECONDARY_3_PRIMARY_4 3 +#define STM32_TIM_TS_SECONDARY_4_PRIMARY_10 0 +#define STM32_TIM_TS_SECONDARY_4_PRIMARY_2 1 +#define STM32_TIM_TS_SECONDARY_4_PRIMARY_3 2 +#define STM32_TIM_TS_SECONDARY_4_PRIMARY_9 3 +#define STM32_TIM_TS_SECONDARY_9_PRIMARY_2 0 +#define STM32_TIM_TS_SECONDARY_9_PRIMARY_3 1 +#define STM32_TIM_TS_SECONDARY_9_PRIMARY_10 2 +#define STM32_TIM_TS_SECONDARY_9_PRIMARY_11 3 +#endif /* !CHIP_FAMILY_STM32F0 */ +#define TSMAP(secondary, primary) \ + CONCAT4(STM32_TIM_TS_SECONDARY_, secondary, _PRIMARY_, primary) + +/* + * Timers are defined per board. This gives us flexibility to work around + * timers which are dedicated to board-specific PWM sources. + */ +#define IRQ_MSB IRQ_TIM(TIM_CLOCK_MSB) +#define IRQ_LSB IRQ_TIM(TIM_CLOCK_LSB) +#define IRQ_WD IRQ_TIM(TIM_WATCHDOG) + +/* TIM1 has fancy names for its IRQs; remap count-up IRQ for the macro above */ +#if defined TIM_WATCHDOG && (TIM_WATCHDOG == 1) +#define STM32_IRQ_TIM1 STM32_IRQ_TIM1_BRK_UP_TRG +#else /* !(TIM_WATCHDOG == 1) */ +#define STM32_IRQ_TIM1 STM32_IRQ_TIM1_CC +#endif /* !(TIM_WATCHDOG == 1) */ + +#define TIM_BASE(n) CONCAT3(STM32_TIM, n, _BASE) +#define TIM_WD_BASE TIM_BASE(TIM_WATCHDOG) + +static uint32_t last_deadline; + +void __hw_clock_event_set(uint32_t deadline) +{ + last_deadline = deadline; + + if ((deadline >> 16) > STM32_TIM_CNT(TIM_CLOCK_MSB)) { + /* first set a match on the MSB */ + STM32_TIM_CCR1(TIM_CLOCK_MSB) = deadline >> 16; + /* disable LSB match */ + STM32_TIM_DIER(TIM_CLOCK_LSB) &= ~2; + /* Clear the match flags */ + STM32_TIM_SR(TIM_CLOCK_MSB) = ~2; + STM32_TIM_SR(TIM_CLOCK_LSB) = ~2; + /* Set the match interrupt */ + STM32_TIM_DIER(TIM_CLOCK_MSB) |= 2; + } + /* + * In the unlikely case where the MSB has increased and matched + * the deadline MSB before we set the match interrupt, as the STM + * hardware timer won't trigger an interrupt, we fall back to the + * following LSB event code to set another interrupt. + */ + if ((deadline >> 16) == STM32_TIM_CNT(TIM_CLOCK_MSB)) { + /* we can set a match on the LSB only */ + STM32_TIM_CCR1(TIM_CLOCK_LSB) = deadline & 0xffff; + /* disable MSB match */ + STM32_TIM_DIER(TIM_CLOCK_MSB) &= ~2; + /* Clear the match flags */ + STM32_TIM_SR(TIM_CLOCK_MSB) = ~2; + STM32_TIM_SR(TIM_CLOCK_LSB) = ~2; + /* Set the match interrupt */ + STM32_TIM_DIER(TIM_CLOCK_LSB) |= 2; + } + /* + * If the LSB deadline is already in the past and won't trigger an + * interrupt, the common code in process_timers will deal with the + * expired timer and automatically set the next deadline, we don't need + * to do anything here. + */ +} + +uint32_t __hw_clock_event_get(void) +{ + return last_deadline; +} + +void __hw_clock_event_clear(void) +{ + /* Disable the match interrupts */ + STM32_TIM_DIER(TIM_CLOCK_LSB) &= ~2; + STM32_TIM_DIER(TIM_CLOCK_MSB) &= ~2; +} + +uint32_t __hw_clock_source_read(void) +{ + uint32_t hi; + uint32_t lo; + + /* Ensure the two half-words are coherent */ + do { + hi = STM32_TIM_CNT(TIM_CLOCK_MSB); + lo = STM32_TIM_CNT(TIM_CLOCK_LSB); + } while (hi != STM32_TIM_CNT(TIM_CLOCK_MSB)); + + return (hi << 16) | lo; +} + +void __hw_clock_source_set(uint32_t ts) +{ + ASSERT(!is_interrupt_enabled()); + + /* Stop counting (LSB first, then MSB) */ + STM32_TIM_CR1(TIM_CLOCK_LSB) &= ~1; + STM32_TIM_CR1(TIM_CLOCK_MSB) &= ~1; + + /* Set new value to counters */ + STM32_TIM_CNT(TIM_CLOCK_MSB) = ts >> 16; + STM32_TIM_CNT(TIM_CLOCK_LSB) = ts & 0xffff; + + /* + * Clear status. We may clear information other than timer overflow + * (eg. event timestamp was matched) but: + * - Bits other than overflow are unused (see __hw_clock_source_irq()) + * - After setting timestamp software will trigger timer interrupt using + * task_trigger_irq() (see force_time() in common/timer.c). + * process_timers() is called from timer interrupt, so if "match" bit + * was present in status (think: some task timers are expired) + * process_timers() will handle that correctly. + */ + STM32_TIM_SR(TIM_CLOCK_MSB) = 0; + STM32_TIM_SR(TIM_CLOCK_LSB) = 0; + + /* Start counting (MSB first, then LSB) */ + STM32_TIM_CR1(TIM_CLOCK_MSB) |= 1; + STM32_TIM_CR1(TIM_CLOCK_LSB) |= 1; +} + +static void __hw_clock_source_irq(void) +{ + uint32_t stat_tim_msb = STM32_TIM_SR(TIM_CLOCK_MSB); + + /* Clear status */ + STM32_TIM_SR(TIM_CLOCK_LSB) = 0; + STM32_TIM_SR(TIM_CLOCK_MSB) = 0; + + /* + * Find expired timers and set the new timer deadline + * signal overflow if the 16-bit MSB counter has overflowed. + */ + process_timers(stat_tim_msb & 0x01); +} +DECLARE_IRQ(IRQ_MSB, __hw_clock_source_irq, 1); +DECLARE_IRQ(IRQ_LSB, __hw_clock_source_irq, 1); + +void __hw_timer_enable_clock(int n, int enable) +{ + volatile uint32_t *reg; + uint32_t mask = 0; + + /* + * Mapping of timers to reg/mask is split into a few different ranges, + * some specific to individual chips. + */ +#if defined(CHIP_FAMILY_STM32F0) + if (n == 1) { + reg = &STM32_RCC_APB2ENR; + mask = STM32_RCC_PB2_TIM1; + } +#elif defined(CHIP_FAMILY_STM32L) || defined(CHIP_FAMILY_STM32F4) + if (n >= 9 && n <= 11) { + reg = &STM32_RCC_APB2ENR; + mask = STM32_RCC_PB2_TIM9 << (n - 9); + } +#endif + +#if defined(CHIP_FAMILY_STM32F0) + if (n >= 15 && n <= 17) { + reg = &STM32_RCC_APB2ENR; + mask = STM32_RCC_PB2_TIM15 << (n - 15); + } +#endif + +#if defined(CHIP_FAMILY_STM32F0) || defined(CHIP_FAMILY_STM32F3) + if (n == 14) { + reg = &STM32_RCC_APB1ENR; + mask = STM32_RCC_PB1_TIM14; + } +#endif + +#if defined(CHIP_FAMILY_STM32F3) + if (n == 12 || n == 13) { + reg = &STM32_RCC_APB1ENR; + mask = STM32_RCC_PB1_TIM12 << (n - 12); + } + if (n == 18) { + reg = &STM32_RCC_APB1ENR; + mask = STM32_RCC_PB1_TIM18; + } + if (n == 19) { + reg = &STM32_RCC_APB2ENR; + mask = STM32_RCC_PB2_TIM19; + } +#endif + + if (n >= 2 && n <= 7) { + reg = &STM32_RCC_APB1ENR; + mask = STM32_RCC_PB1_TIM2 << (n - 2); + } + + if (!mask) + return; + + if (enable) + *reg |= mask; + else + *reg &= ~mask; +} + +static void update_prescaler(void) +{ + /* + * Pre-scaler value : + * TIM_CLOCK_LSB is counting microseconds; + * TIM_CLOCK_MSB is counting every TIM_CLOCK_LSB overflow. + * + * This will take effect at the next update event (when the current + * prescaler counter ticks down, or if forced via EGR). + */ + STM32_TIM_PSC(TIM_CLOCK_MSB) = 0; + STM32_TIM_PSC(TIM_CLOCK_LSB) = (clock_get_timer_freq() / SECOND) - 1; +} +DECLARE_HOOK(HOOK_FREQ_CHANGE, update_prescaler, HOOK_PRIO_DEFAULT); + +int __hw_clock_source_init(uint32_t start_t) +{ + /* + * we use 2 chained 16-bit counters to emulate a 32-bit one : + * TIM_CLOCK_MSB is the MSB (Secondary) + * TIM_CLOCK_LSB is the LSB (Primary) + */ + + /* Enable TIM_CLOCK_MSB and TIM_CLOCK_LSB clocks */ + __hw_timer_enable_clock(TIM_CLOCK_MSB, 1); + __hw_timer_enable_clock(TIM_CLOCK_LSB, 1); + + /* Delay 1 APB clock cycle after the clock is enabled */ + clock_wait_bus_cycles(BUS_APB, 1); + + /* + * Timer configuration : Upcounter, counter disabled, update event only + * on overflow. + */ + STM32_TIM_CR1(TIM_CLOCK_MSB) = 0x0004; + STM32_TIM_CR1(TIM_CLOCK_LSB) = 0x0004; + /* + * TIM_CLOCK_LSB (primary mode) generates a periodic trigger signal on + * each UEV + */ + STM32_TIM_CR2(TIM_CLOCK_MSB) = 0x0000; + STM32_TIM_CR2(TIM_CLOCK_LSB) = 0x0020; + + STM32_TIM_SMCR(TIM_CLOCK_MSB) = + 0x0007 | (TSMAP(TIM_CLOCK_MSB, TIM_CLOCK_LSB) << 4); + STM32_TIM_SMCR(TIM_CLOCK_LSB) = 0x0000; + + /* Auto-reload value : 16-bit free-running counters */ + STM32_TIM_ARR(TIM_CLOCK_MSB) = 0xffff; + STM32_TIM_ARR(TIM_CLOCK_LSB) = 0xffff; + + /* Update prescaler */ + update_prescaler(); + + /* Reload the pre-scaler */ + STM32_TIM_EGR(TIM_CLOCK_MSB) = 0x0001; + STM32_TIM_EGR(TIM_CLOCK_LSB) = 0x0001; + + /* Set up the overflow interrupt on TIM_CLOCK_MSB */ + STM32_TIM_DIER(TIM_CLOCK_MSB) = 0x0001; + STM32_TIM_DIER(TIM_CLOCK_LSB) = 0x0000; + + /* Override the count with the start value */ + STM32_TIM_CNT(TIM_CLOCK_MSB) = start_t >> 16; + STM32_TIM_CNT(TIM_CLOCK_LSB) = start_t & 0xffff; + + /* Start counting */ + STM32_TIM_CR1(TIM_CLOCK_MSB) |= 1; + STM32_TIM_CR1(TIM_CLOCK_LSB) |= 1; + + /* Enable timer interrupts */ + task_enable_irq(IRQ_MSB); + task_enable_irq(IRQ_LSB); + + return IRQ_LSB; +} + +#ifdef CONFIG_WATCHDOG_HELP + +void __keep watchdog_check(uint32_t excep_lr, uint32_t excep_sp) +{ + struct timer_ctlr *timer = (struct timer_ctlr *)TIM_WD_BASE; + + /* clear status */ + timer->sr = 0; + + watchdog_trace(excep_lr, excep_sp); +} + +void IRQ_HANDLER(IRQ_WD)(void) __attribute__((naked)); +void IRQ_HANDLER(IRQ_WD)(void) +{ + /* Naked call so we can extract raw LR and SP */ + asm volatile("mov r0, lr\n" + "mov r1, sp\n" + /* Must push registers in pairs to keep 64-bit aligned + * stack for ARM EABI. */ + "push {r0, lr}\n" + "bl watchdog_check\n" + "pop {r0,pc}\n"); +} +const struct irq_priority __keep IRQ_PRIORITY(IRQ_WD) + __attribute__((section(".rodata.irqprio"))) = { + IRQ_WD, 0 + }; /* put the watchdog + at the highest + priority + */ + +void hwtimer_setup_watchdog(void) +{ + struct timer_ctlr *timer = (struct timer_ctlr *)TIM_WD_BASE; + + /* Enable clock */ + __hw_timer_enable_clock(TIM_WATCHDOG, 1); + + /* Delay 1 APB clock cycle after the clock is enabled */ + clock_wait_bus_cycles(BUS_APB, 1); + + /* + * Timer configuration : Down counter, counter disabled, update + * event only on overflow. + */ + timer->cr1 = 0x0014 | BIT(7); + + /* TIM (secondary mode) uses TIM_CLOCK_LSB as internal trigger */ + timer->smcr = 0x0007 | (TSMAP(TIM_WATCHDOG, TIM_CLOCK_LSB) << 4); + + /* + * The auto-reload value is based on the period between rollovers for + * TIM_CLOCK_LSB. Since TIM_CLOCK_LSB runs at 1MHz, it will overflow + * in 65.536ms. We divide our required watchdog period by this amount + * to obtain the number of times TIM_CLOCK_LSB can overflow before we + * generate an interrupt. + */ + timer->arr = timer->cnt = CONFIG_AUX_TIMER_PERIOD_MS * MSEC / BIT(16); + + /* count on every TIM_CLOCK_LSB overflow */ + timer->psc = 0; + + /* Reload the pre-scaler from arr when it goes below zero */ + timer->egr = 0x0000; + + /* setup the overflow interrupt */ + timer->dier = 0x0001; + + /* Start counting */ + timer->cr1 |= 1; + + /* Enable timer interrupts */ + task_enable_irq(IRQ_WD); +} + +void hwtimer_reset_watchdog(void) +{ + struct timer_ctlr *timer = (struct timer_ctlr *)TIM_WD_BASE; + + timer->cnt = timer->arr; +} + +#endif /* defined(CONFIG_WATCHDOG_HELP) */ |