aboutsummaryrefslogtreecommitdiffstats
path: root/libmaple/timer.c
diff options
context:
space:
mode:
Diffstat (limited to 'libmaple/timer.c')
-rw-r--r--libmaple/timer.c449
1 files changed, 449 insertions, 0 deletions
diff --git a/libmaple/timer.c b/libmaple/timer.c
new file mode 100644
index 0000000..ad0ec6f
--- /dev/null
+++ b/libmaple/timer.c
@@ -0,0 +1,449 @@
+/******************************************************************************
+ * The MIT License
+ *
+ * Copyright (c) 2011 LeafLabs, LLC.
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *****************************************************************************/
+
+/**
+ * @file timer.c
+ * @author Marti Bolivar <mbolivar@leaflabs.com>
+ * @brief New-style timer interface
+ */
+
+#include "timer.h"
+
+#if defined(STM32_MEDIUM_DENSITY)
+#define NR_TIMERS 4
+#elif defined(STM32_HIGH_DENSITY)
+#define NR_TIMERS 8
+#endif
+
+/* Just like the corresponding DIER bits:
+ * [0] = Update handler;
+ * [1,2,3,4] = capture/compare 1,2,3,4 handlers, respectively;
+ * [5] = COM;
+ * [6] = TRG;
+ * [7] = BRK. */
+#define NR_ADV_HANDLERS 8
+/* Update, capture/compare 1,2,3,4; <junk>; trigger. */
+#define NR_GEN_HANDLERS 6
+/* Update only. */
+#define NR_BAS_HANDLERS 1
+
+static timer_dev timer1 = {
+ .regs = {.adv = TIMER1_BASE},
+ .clk_id = RCC_TIMER1,
+ .type = TIMER_ADVANCED,
+ .handlers = { [NR_ADV_HANDLERS - 1] = 0 },
+};
+timer_dev *TIMER1 = &timer1;
+
+static timer_dev timer2 = {
+ .regs = {.gen = TIMER2_BASE},
+ .clk_id = RCC_TIMER2,
+ .type = TIMER_GENERAL,
+ .handlers = { [NR_GEN_HANDLERS - 1] = 0 },
+};
+timer_dev *TIMER2 = &timer2;
+
+static timer_dev timer3 = {
+ .regs = {.gen = TIMER3_BASE},
+ .clk_id = RCC_TIMER3,
+ .type = TIMER_GENERAL,
+ .handlers = { [NR_GEN_HANDLERS - 1] = 0 },
+};
+timer_dev *TIMER3 = &timer3;
+
+static timer_dev timer4 = {
+ .regs = {.gen = TIMER4_BASE},
+ .clk_id = RCC_TIMER4,
+ .type = TIMER_GENERAL,
+ .handlers = { [NR_GEN_HANDLERS - 1] = 0 },
+};
+timer_dev *TIMER4 = &timer4;
+
+#ifdef STM32_HIGH_DENSITY
+static timer_dev timer5 = {
+ .regs = {.gen = TIMER5_BASE},
+ .clk_id = RCC_TIMER5,
+ .type = TIMER_GENERAL,
+ .handlers = { [NR_GEN_HANDLERS - 1] = 0 },
+};
+timer_dev *TIMER5 = &timer5;
+
+static timer_dev timer6 = {
+ .regs = {.bas = TIMER6_BASE},
+ .clk_id = RCC_TIMER6,
+ .type = TIMER_BASIC,
+ .handlers = { [NR_BAS_HANDLERS - 1] = 0 },
+};
+timer_dev *TIMER6 = &timer6;
+
+static timer_dev timer7 = {
+ .regs = {.bas = TIMER7_BASE},
+ .clk_id = RCC_TIMER7,
+ .type = TIMER_BASIC,
+ .handlers = { [NR_BAS_HANDLERS - 1] = 0 },
+};
+timer_dev *TIMER7 = &timer7;
+
+static timer_dev timer8 = {
+ .regs = {.adv = TIMER8_BASE},
+ .clk_id = RCC_TIMER8,
+ .type = TIMER_ADVANCED,
+ .handlers = { [NR_ADV_HANDLERS - 1] = 0 },
+};
+timer_dev *TIMER8 = &timer8;
+#endif
+
+/*
+ * Convenience routines
+ */
+
+static void disable_channel(timer_dev *dev, uint8 channel);
+static void pwm_mode(timer_dev *dev, uint8 channel);
+static void output_compare_mode(timer_dev *dev, uint8 channel);
+
+static inline void enable_irq(timer_dev *dev, uint8 interrupt);
+
+/**
+ * Initialize a timer, and reset its register map.
+ * @param dev Timer to initialize
+ */
+void timer_init(timer_dev *dev) {
+ rcc_clk_enable(dev->clk_id);
+ rcc_reset_dev(dev->clk_id);
+}
+
+/**
+ * @brief Disable a timer.
+ *
+ * The timer will stop counting, all DMA requests and interrupts will
+ * be disabled, and no state changes will be output.
+ *
+ * @param dev Timer to disable.
+ */
+void timer_disable(timer_dev *dev) {
+ (dev->regs).bas->CR1 = 0;
+ (dev->regs).bas->DIER = 0;
+ switch (dev->type) {
+ case TIMER_ADVANCED: /* fall-through */
+ case TIMER_GENERAL:
+ (dev->regs).gen->CCER = 0;
+ break;
+ case TIMER_BASIC:
+ break;
+ }
+}
+
+/**
+ * Sets the mode of an individual timer channel.
+ *
+ * Note that not all timers can be configured in every mode. For
+ * example, basic timers cannot be configured to output compare mode.
+ * Be sure to use a timer which is appropriate for the mode you want.
+ *
+ * @param dev Timer whose channel mode to set
+ * @param channel Relevant channel
+ * @param mode New timer mode for channel
+ */
+void timer_set_mode(timer_dev *dev, uint8 channel, timer_mode mode) {
+ ASSERT(channel > 0 && channel <= 4);
+
+ /* TODO decide about the basic timers */
+ ASSERT(dev->type != TIMER_BASIC);
+ if (dev->type == TIMER_BASIC)
+ return;
+
+ switch (mode) {
+ case TIMER_DISABLED:
+ disable_channel(dev, channel);
+ break;
+ case TIMER_PWM:
+ pwm_mode(dev, channel);
+ break;
+ case TIMER_OUTPUT_COMPARE:
+ output_compare_mode(dev, channel);
+ break;
+ }
+}
+
+/**
+ * @brief Call a given function on all timers.
+ * @param fn Function to call on each timer.
+ */
+void timer_foreach(void (*fn)(timer_dev*)) {
+ fn(TIMER1);
+ fn(TIMER2);
+ fn(TIMER3);
+ fn(TIMER4);
+#ifdef STM32_HIGH_DENSITY
+ fn(TIMER5);
+ fn(TIMER6);
+ fn(TIMER7);
+ fn(TIMER8);
+#endif
+}
+
+/**
+ * @brief Attach a timer interrupt.
+ * @param dev Timer device
+ * @param interrupt Interrupt number to attach to; this may be any
+ * timer_interrupt_id or timer_channel value appropriate
+ * for the timer.
+ * @param handler Handler to attach to the given interrupt.
+ * @see timer_interrupt_id
+ * @see timer_channel
+ */
+void timer_attach_interrupt(timer_dev *dev,
+ uint8 interrupt,
+ voidFuncPtr handler) {
+ dev->handlers[interrupt] = handler;
+ timer_enable_interrupt(dev, interrupt);
+ enable_irq(dev, interrupt);
+}
+
+/**
+ * @brief Detach a timer interrupt.
+ * @param dev Timer device
+ * @param interrupt Interrupt number to detach; this may be any
+ * timer_interrupt_id or timer_channel value appropriate
+ * for the timer.
+ * @see timer_interrupt_id
+ * @see timer_channel
+ */
+void timer_detach_interrupt(timer_dev *dev, uint8 interrupt) {
+ timer_disable_interrupt(dev, interrupt);
+ dev->handlers[interrupt] = NULL;
+}
+
+/*
+ * IRQ handlers
+ */
+
+static inline void dispatch_adv_brk(timer_dev *dev);
+static inline void dispatch_adv_up(timer_dev *dev);
+static inline void dispatch_adv_trg_com(timer_dev *dev);
+static inline void dispatch_adv_cc(timer_dev *dev);
+static inline void dispatch_general(timer_dev *dev);
+static inline void dispatch_basic(timer_dev *dev);
+
+void __irq_tim1_brk(void) {
+ dispatch_adv_brk(TIMER1);
+}
+
+void __irq_tim1_up(void) {
+ dispatch_adv_up(TIMER1);
+}
+
+void __irq_tim1_trg_com(void) {
+ dispatch_adv_trg_com(TIMER1);
+}
+
+void __irq_tim1_cc(void) {
+ dispatch_adv_cc(TIMER1);
+}
+
+void __irq_tim2(void) {
+ dispatch_general(TIMER2);
+}
+
+void __irq_tim3(void) {
+ dispatch_general(TIMER3);
+}
+
+void __irq_tim4(void) {
+ dispatch_general(TIMER4);
+}
+
+#if defined(STM32_HIGH_DENSITY) || defined(STM32_XL_DENSITY)
+
+void __irq_tim5(void) {
+ dispatch_general(TIMER5);
+}
+
+void __irq_tim6(void) {
+ dispatch_basic(TIMER6);
+}
+
+void __irq_tim7(void) {
+ dispatch_basic(TIMER7);
+}
+
+void __irq_tim8_brk(void) {
+ dispatch_adv_brk(TIMER8);
+}
+
+void __irq_tim8_up(void) {
+ dispatch_adv_up(TIMER8);
+}
+
+void __irq_tim8_trg_com(void) {
+ dispatch_adv_trg_com(TIMER8);
+}
+
+void __irq_tim8_cc(void) {
+ dispatch_adv_cc(TIMER8);
+}
+#endif
+
+static inline void dispatch_irq(timer_dev *dev, uint8 iid, uint8 sr_bit);
+static inline void dispatch_cc_irqs(timer_dev *dev);
+
+static inline void dispatch_adv_brk(timer_dev *dev) {
+ dispatch_irq(dev, TIMER_BREAK_INTERRUPT, TIMER_SR_BIF_BIT);
+}
+
+static inline void dispatch_adv_up(timer_dev *dev) {
+ dispatch_irq(dev, TIMER_UPDATE_INTERRUPT, TIMER_SR_UIF_BIT);
+}
+
+static inline void dispatch_adv_trg_com(timer_dev *dev) {
+ dispatch_irq(dev, TIMER_TRG_INTERRUPT, TIMER_SR_TIF_BIT);
+ dispatch_irq(dev, TIMER_COM_INTERRUPT, TIMER_SR_COMIF_BIT);
+}
+
+static inline void dispatch_adv_cc(timer_dev *dev) {
+ dispatch_cc_irqs(dev);
+}
+
+static inline void dispatch_general(timer_dev *dev) {
+ dispatch_irq(dev, TIMER_TRG_INTERRUPT, TIMER_SR_TIF_BIT);
+ dispatch_cc_irqs(dev);
+ dispatch_irq(dev, TIMER_UPDATE_INTERRUPT, TIMER_SR_UIF_BIT);
+}
+
+static inline void dispatch_basic(timer_dev *dev) {
+ dispatch_irq(dev, TIMER_UPDATE_INTERRUPT, TIMER_SR_UIF_BIT);
+}
+
+static inline void dispatch_irq(timer_dev *dev, uint8 iid, uint8 sr_bit) {
+ __io uint32 *sr = &(dev->regs).bas->SR;
+ if (bb_peri_get_bit(sr, sr_bit)) {
+ if (dev->handlers[iid])
+ (dev->handlers[iid])();
+ bb_peri_set_bit(sr, sr_bit, 0);
+ }
+}
+
+static inline void dispatch_cc_irqs(timer_dev *dev) {
+ uint32 sr = (dev->regs).gen->SR;
+ uint32 sr_clear = 0;
+ uint32 b;
+
+ ASSERT(sr & (TIMER_SR_CC1IF | TIMER_SR_CC2IF |
+ TIMER_SR_CC3IF | TIMER_SR_CC4IF));
+
+ for (b = TIMER_SR_CC1IF_BIT; b <= TIMER_SR_CC4IF_BIT; b++) {
+ uint32 mask = BIT(b);
+ if (sr & mask) {
+ if (dev->handlers[b])
+ (dev->handlers[b])();
+ sr_clear |= mask;
+ }
+ }
+
+ (dev->regs).gen->SR &= ~sr_clear;
+}
+
+/*
+ * Utilities
+ */
+
+static void disable_channel(timer_dev *dev, uint8 channel) {
+ timer_detach_interrupt(dev, channel);
+ timer_cc_disable(dev, channel);
+}
+
+static void pwm_mode(timer_dev *dev, uint8 channel) {
+ timer_disable_interrupt(dev, channel);
+ timer_oc_set_mode(dev, channel, TIMER_OC_MODE_PWM_1, TIMER_OC_PE);
+ timer_cc_enable(dev, channel);
+}
+
+static void output_compare_mode(timer_dev *dev, uint8 channel) {
+ timer_oc_set_mode(dev, channel, TIMER_OC_MODE_ACTIVE_ON_MATCH, 0);
+ timer_cc_enable(dev, channel);
+}
+
+static void enable_advanced_irq(timer_dev *dev, timer_interrupt_id id);
+static void enable_nonmuxed_irq(timer_dev *dev);
+
+static inline void enable_irq(timer_dev *dev, timer_interrupt_id iid) {
+ if (dev->type == TIMER_ADVANCED) {
+ enable_advanced_irq(dev, iid);
+ } else {
+ enable_nonmuxed_irq(dev);
+ }
+}
+
+static void enable_advanced_irq(timer_dev *dev, timer_interrupt_id id) {
+ uint8 is_timer1 = dev->clk_id == RCC_TIMER1;
+
+ switch (id) {
+ case TIMER_UPDATE_INTERRUPT:
+ nvic_irq_enable(is_timer1 ? NVIC_TIMER1_UP : NVIC_TIMER8_UP);
+ break;
+ case TIMER_CC1_INTERRUPT:
+ case TIMER_CC2_INTERRUPT:
+ case TIMER_CC3_INTERRUPT:
+ case TIMER_CC4_INTERRUPT:
+ nvic_irq_enable(is_timer1 ? NVIC_TIMER1_CC : NVIC_TIMER8_CC);
+ break;
+ case TIMER_COM_INTERRUPT:
+ case TIMER_TRG_INTERRUPT:
+ nvic_irq_enable(is_timer1 ? NVIC_TIMER1_TRG_COM : NVIC_TIMER8_TRG_COM);
+ break;
+ case TIMER_BREAK_INTERRUPT:
+ nvic_irq_enable(is_timer1 ? NVIC_TIMER1_BRK : NVIC_TIMER8_BRK);
+ break;
+ }
+}
+
+static void enable_nonmuxed_irq(timer_dev *dev) {
+ switch (dev->clk_id) {
+ case RCC_TIMER2:
+ nvic_irq_enable(NVIC_TIMER2);
+ break;
+ case RCC_TIMER3:
+ nvic_irq_enable(NVIC_TIMER3);
+ break;
+ case RCC_TIMER4:
+ nvic_irq_enable(NVIC_TIMER4);
+ break;
+#ifdef STM32_HIGH_DENSITY
+ case RCC_TIMER5:
+ nvic_irq_enable(NVIC_TIMER5);
+ break;
+ case RCC_TIMER6:
+ nvic_irq_enable(NVIC_TIMER6);
+ break;
+ case RCC_TIMER7:
+ nvic_irq_enable(NVIC_TIMER7);
+ break;
+#endif
+ default:
+ ASSERT(0);
+ break;
+ }
+}