diff --git a/boards/qn9080dk/Kconfig b/boards/qn9080dk/Kconfig index 1f44df993d..9dde9a2d30 100644 --- a/boards/qn9080dk/Kconfig +++ b/boards/qn9080dk/Kconfig @@ -17,6 +17,7 @@ config BOARD_QN9080DK # Put defined MCU peripherals here (in alphabetical order) select BOARD_HAS_XTAL32K select BOARD_HAS_XTAL_32M + select HAS_PERIPH_TIMER select HAS_PERIPH_UART select HAS_PERIPH_UART_MODECFG diff --git a/boards/qn9080dk/Makefile.features b/boards/qn9080dk/Makefile.features index f4cfcb4ccc..81ce8c8757 100644 --- a/boards/qn9080dk/Makefile.features +++ b/boards/qn9080dk/Makefile.features @@ -3,6 +3,7 @@ CPU_MODEL = qn9080xhn # Put defined MCU peripherals here (in alphabetical order) FEATURES_PROVIDED += periph_gpio periph_gpio_irq +FEATURES_PROVIDED += periph_timer FEATURES_PROVIDED += periph_uart periph_uart_modecfg # Include the common qn908x board features. diff --git a/boards/qn9080dk/include/periph_conf.h b/boards/qn9080dk/include/periph_conf.h index abfaee80fb..3687ba5a5b 100644 --- a/boards/qn9080dk/include/periph_conf.h +++ b/boards/qn9080dk/include/periph_conf.h @@ -43,9 +43,15 @@ static const uart_conf_t uart_config[] = { #define UART_NUMOF ARRAY_SIZE(uart_config) /** @} */ +/** + * @name Timer configuration + * @{ + */ +#define TIMER_NUMOF 4 +/** @} */ + /* put here the board peripherals definitions: - Available clocks - - Timers - PWMs - SPIs - I2C diff --git a/cpu/qn908x/doc.txt b/cpu/qn908x/doc.txt index e77ea1896c..e574b6736b 100644 --- a/cpu/qn908x/doc.txt +++ b/cpu/qn908x/doc.txt @@ -31,6 +31,25 @@ The GPIO driver uses the @ref GPIO_PIN(port, pin) macro to declare pins. No configuration is necessary. +@defgroup cpu_qn908x_timer NXP QN908x Standard counter/timers (CTIMER) +@ingroup cpu_qn908x +@brief NXP QN908x timer driver + +The QN908x have 4 standard counter/timers (CTIMER). These timers allow to count +clock cycles from the APB clock with a 32-bit prescaler, effectively dividing +the APB clock frequency by a configurable number up to 2^32, allowing a great +range of timer frequencies selected at runtime. Each timer has 4 independent +channels to match against which can generate an interrupt. + +TODO: These CTIMERs and the SCT timers can both be used as PWM as well, with +different set of capabilities. Boards should be able to split these CTIMER and +SCT blocks between pwm and timer functions. + +### Timer configuration example (for periph_conf.h) ### + + #define TIMER_NUMOF 4 + + @defgroup cpu_qn908x_uart NXP QN908x UART @ingroup cpu_qn908x @brief NXP QN908x UART driver diff --git a/cpu/qn908x/include/periph_cpu.h b/cpu/qn908x/include/periph_cpu.h index c94477f173..49191ddc8f 100644 --- a/cpu/qn908x/include/periph_cpu.h +++ b/cpu/qn908x/include/periph_cpu.h @@ -141,6 +141,14 @@ enum { GPIO_PORTS_NUMOF /**< overall number of available ports */ }; +/** + * @brief CPU specific timer Counter/Timers (CTIMER) configuration + * @{ + */ +#define TIMER_CHANNELS (4) +#define TIMER_MAX_VALUE (0xffffffff) +/** @} */ + /** * @brief UART module configuration options * diff --git a/cpu/qn908x/periph/timer.c b/cpu/qn908x/periph/timer.c new file mode 100644 index 0000000000..775abb5d3c --- /dev/null +++ b/cpu/qn908x/periph/timer.c @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2020 iosabi + * + * This file is subject to the terms and conditions of the GNU Lesser General + * Public License v2.1. See the file LICENSE in the top level directory for more + * details. + */ + +/** + * @ingroup cpu_qn908x + * @ingroup drivers_periph_timer + * + * @{ + * + * @file + * @brief Low-level timer driver implementation + * + * This driver leverages the Freescale/NXP implementation distributed with the + * SDK. + * + * @author iosabi + * + * @} + */ + +#include + +#include "cpu.h" +#include "board.h" +#include "periph_conf.h" +#include "periph/timer.h" + +#include "vendor/drivers/fsl_clock.h" + +#define ENABLE_DEBUG (0) +#include "debug.h" + +/** + * @brief Interrupt context information for configured timers. + */ +static timer_isr_ctx_t isr_ctx[FSL_FEATURE_SOC_CTIMER_COUNT]; + +/** + * @brief CTIMER peripheral base pointers. + */ +static CTIMER_Type* const ctimers[FSL_FEATURE_SOC_CTIMER_COUNT] = + CTIMER_BASE_PTRS; + +/** + * @brief CTIMER IRQ numbers. + */ +static IRQn_Type const ctimers_irqn[FSL_FEATURE_SOC_CTIMER_COUNT] = + CTIMER_IRQS; + +/** + * @brief CTIMER Clocks. + */ +static const clock_ip_name_t ctimers_clocks[FSL_FEATURE_SOC_CTIMER_COUNT] = + CTIMER_CLOCKS; + + +/** + * @brief Check the board config to make sure we do not exceed max number of + * timers + */ +#if TIMER_NUMOF > FSL_FEATURE_SOC_CTIMER_COUNT +#error "ERROR in board timer configuration: too many timers defined" +#endif + +int timer_init(tim_t tim, unsigned long freq, timer_cb_t cb, void *arg) +{ + DEBUG("timer_init(%u, %lu)\n", tim, freq); + if (tim >= TIMER_NUMOF) { + return -1; + } + + isr_ctx[tim].cb = cb; + isr_ctx[tim].arg = arg; + + CLOCK_EnableClock(ctimers_clocks[tim]); + + CTIMER_Type *dev = ctimers[tim]; + + /* CTIMER blocks are driven from the APB clock. */ + uint32_t core_freq = CLOCK_GetFreq(kCLOCK_ApbClk); + uint32_t prescale = (core_freq + freq / 2) / freq - 1; + if (prescale == (uint32_t)(-1)) { + DEBUG("timer_init: Frequency %lu is too fast for core_freq=%lu", + freq, core_freq); + return -1; + } + + dev->CTCR = CTIMER_CTCR_CTMODE(0); /* timer mode */ + dev->PR = CTIMER_PR_PRVAL(prescale); + /* Enable timer interrupts in the NVIC. */ + NVIC_EnableIRQ(ctimers_irqn[tim]); + + /* Timer should be started after init. */ + dev->TCR |= CTIMER_TCR_CEN_MASK; + return 0; +} + +int timer_set_absolute(tim_t tim, int channel, unsigned int value) +{ + DEBUG("timer_set_absolute(%u, %u, %u)\n", tim, channel, value); + if ((tim >= TIMER_NUMOF) || (channel >= TIMER_CHANNELS)) { + return -1; + } + CTIMER_Type* const dev = ctimers[tim]; + dev->MR[channel] = value; + dev->MCR |= (CTIMER_MCR_MR0I_MASK << (channel * 3)); + return 0; +} + +int timer_clear(tim_t tim, int channel) +{ + DEBUG("timer_clear(%u, %d)\n", tim, channel); + if ((tim >= TIMER_NUMOF) || (channel >= TIMER_CHANNELS)) { + return -1; + } + CTIMER_Type* const dev = ctimers[tim]; + dev->MCR &= ~(CTIMER_MCR_MR0I_MASK << (channel * 3)); + return 0; +} + +unsigned int timer_read(tim_t tim) +{ + DEBUG("timer_read(%u) --> %" PRIu32 "\n", tim, ctimers[tim]->TC); + return ctimers[tim]->TC; +} + +void timer_start(tim_t tim) +{ + DEBUG("timer_start(%u)\n", tim); + ctimers[tim]->TCR |= CTIMER_TCR_CEN_MASK; +} + +void timer_stop(tim_t tim) +{ + DEBUG("timer_stop(%u)\n", tim); + ctimers[tim]->TCR &= ~CTIMER_TCR_CEN_MASK; +} + +static inline void isr_ctimer_n(CTIMER_Type *dev, uint32_t ctimer_num) +{ + DEBUG("isr_ctimer_%" PRIu32 " flags=0x%" PRIx32 "\n", + ctimer_num, dev->IR); + for (uint32_t i = 0; i < TIMER_CHANNELS; i++) { + if (dev->IR & (1u << i)) { + /* Note: setting the bit to 1 in the flag register will clear the + * bit. */ + dev->IR = 1u << i; + dev->MCR &= ~(CTIMER_MCR_MR0I_MASK << (i * 3)); + isr_ctx[ctimer_num].cb(isr_ctx[ctimer_num].arg, 0); + } + } + cortexm_isr_end(); +} + +#ifdef CTIMER0 +void isr_ctimer0(void) { isr_ctimer_n(CTIMER0, 0); } +#endif /* CTIMER0 */ + +#ifdef CTIMER1 +void isr_ctimer1(void) { isr_ctimer_n(CTIMER1, 1); } +#endif /* CTIMER1 */ + +#ifdef CTIMER2 +void isr_ctimer2(void) { isr_ctimer_n(CTIMER2, 2); } +#endif /* CTIMER2 */ + +#ifdef CTIMER3 +void isr_ctimer3(void) { isr_ctimer_n(CTIMER3, 3); } +#endif /* CTIMER3 */