From 80bd203b4d46c13c2408d8f8f7118dd769b29de4 Mon Sep 17 00:00:00 2001 From: iosabi Date: Fri, 4 Dec 2020 23:03:22 +0100 Subject: [PATCH 1/2] cpu/qn908x: Add missing gpio & uart enum values. GPIO_BOTH gpio_flank_t; UART_PARTY_MARK and UART_PARTY_SPACE in uart_parity_t; and UART_DATA_BITS_5 and UART_DATA_BITS_6 uart_data_bits_t enum values where missing from the periph_cpu.h header since they are not supported by the CPU. This was causing some tests to fail to compile, but only after adding the periph_timer module. This patch adds those missing macros and makes the corresponding functions fail when trying to use them. A minor fix to the NWDT_TIME_LOWER_LIMIT value setting it to 1U to avoid a -Werror=type-limits error in the tests/periph_wdt test. In theory 0 is a totally valid value although a bit useless since it will trigger the WDT right away. --- cpu/qn908x/include/periph_cpu.h | 26 +++++++++++++++++++------- cpu/qn908x/periph/gpio.c | 7 +++++++ cpu/qn908x/periph/uart.c | 3 +++ 3 files changed, 29 insertions(+), 7 deletions(-) diff --git a/cpu/qn908x/include/periph_cpu.h b/cpu/qn908x/include/periph_cpu.h index 6d87741dc2..c94477f173 100644 --- a/cpu/qn908x/include/periph_cpu.h +++ b/cpu/qn908x/include/periph_cpu.h @@ -81,9 +81,9 @@ typedef uint16_t gpio_t; * clocks installed on the board. Figure out a way to configure this limit based * on the clock used. */ -#define NWDT_TIME_LOWER_LIMIT (0) +#define NWDT_TIME_LOWER_LIMIT (1U) #define NWDT_TIME_UPPER_LIMIT (268435U) -#define WWDT_TIME_LOWER_LIMIT (0) +#define WWDT_TIME_LOWER_LIMIT (1U) #define WWDT_TIME_UPPER_LIMIT (268435U) /** @} */ @@ -127,6 +127,7 @@ typedef enum { GPIO_HIGH = 1, /**< emit interrupt when the value is high */ GPIO_RISING = 2, /**< emit interrupt on rising flank */ GPIO_FALLING = 3, /**< emit interrupt on falling flank */ + GPIO_BOTH = 4, /**< not supported -- rising and falling flanks */ } gpio_flank_t; /** @} */ #endif /* ndef DOXYGEN */ @@ -153,6 +154,13 @@ typedef struct { gpio_t tx_pin; /**< TX pin, GPIO_UNDEF disables TX. */ } uart_conf_t; +/** + * @brief Invalid UART mode mask + * + * Signals that the mode is invalid or not supported by the CPU. + */ +#define UART_INVALID_MODE (0x80) + /** * @brief Definition of possible parity modes * @@ -161,9 +169,11 @@ typedef struct { * @{ */ typedef enum { - UART_PARITY_NONE = 0, /**< no parity */ - UART_PARITY_EVEN = 2, /**< even parity */ - UART_PARITY_ODD = 3, /**< odd parity */ + UART_PARITY_NONE = 0, /**< no parity */ + UART_PARITY_EVEN = 2, /**< even parity */ + UART_PARITY_ODD = 3, /**< odd parity */ + UART_PARITY_MARK = 0x10 | UART_INVALID_MODE, /**< mark parity */ + UART_PARITY_SPACE = 0x20 | UART_INVALID_MODE, /**< space parity */ } uart_parity_t; #define HAVE_UART_PARITY_T /** @} */ @@ -175,8 +185,10 @@ typedef enum { * @{ */ typedef enum { - UART_DATA_BITS_7 = 0, /**< 7 data bits */ - UART_DATA_BITS_8 = 1, /**< 8 data bits */ + UART_DATA_BITS_5 = 0x10 | UART_INVALID_MODE, /**< 5 data bits */ + UART_DATA_BITS_6 = 0x20 | UART_INVALID_MODE, /**< 6 data bits */ + UART_DATA_BITS_7 = 0, /**< 7 data bits */ + UART_DATA_BITS_8 = 1, /**< 8 data bits */ /* Note: There's a UART_DATA_BITS_9 possible in this hardware. */ } uart_data_bits_t; #define HAVE_UART_DATA_BITS_T diff --git a/cpu/qn908x/periph/gpio.c b/cpu/qn908x/periph/gpio.c index ea0eb7744a..6d9880a4ba 100644 --- a/cpu/qn908x/periph/gpio.c +++ b/cpu/qn908x/periph/gpio.c @@ -122,6 +122,10 @@ static gpio_isr_cb_state_t gpio_isr_state[TOTAL_GPIO_PINS] = {}; int gpio_init_int(gpio_t pin, gpio_mode_t mode, gpio_flank_t flank, gpio_cb_t cb, void *arg) { + if (flank == GPIO_BOTH) { + /* GPIO_BOTH is not supported. */ + return -1; + } uint8_t gpio_num = GPIO_T_PORT(pin) * PINS_PER_PORT + GPIO_T_PIN(pin); if (gpio_num >= TOTAL_GPIO_PINS) { @@ -154,6 +158,9 @@ int gpio_init_int(gpio_t pin, gpio_mode_t mode, gpio_flank_t flank, base->INTTYPESET = mask; /* SET = edge */ base->INTPOLSET = mask; /* SET = rising */ break; + case GPIO_BOTH: + /* Handled above */ + break; } gpio_irq_enable(pin); return 0; diff --git a/cpu/qn908x/periph/uart.c b/cpu/qn908x/periph/uart.c index 9844fd03ad..527890b169 100644 --- a/cpu/qn908x/periph/uart.c +++ b/cpu/qn908x/periph/uart.c @@ -223,6 +223,9 @@ int uart_init(uart_t uart, uint32_t baudrate, uart_rx_cb_t rx_cb, void *arg) int uart_mode(uart_t uart, uart_data_bits_t data_bits, uart_parity_t parity, uart_stop_bits_t stop_bits) { + if ((data_bits & UART_INVALID_MODE) || (parity & UART_INVALID_MODE)) { + return UART_NOMODE; + } /* Setup mode and enable USART. The values of the uart_data_bits_t, * uart_parity_t and uart_stop_bits_t enums were selected to match the * fields in this registers so there's no need to do any conversion. */ From ef4b58f4f00d39ebc58e7fe750df7c7f3030dbc7 Mon Sep 17 00:00:00 2001 From: iosabi Date: Fri, 24 Jan 2020 00:15:17 +0000 Subject: [PATCH 2/2] cpu/qn908x: Add timer driver based on CTIMER. The QN908x CPU has several timer modules: one RTC (Real-Time Clock) that can count from the 32kHz internal clock or 32.768 kHz external clock, four CTIMER that use the APB clock and have four channels each and one SCT timer with up to 10 channels running on the AHB clock. This patch implements a timer driver for the CTIMER blocks only, which is enough to make the xtimer module work. Future patches should improve on this module to support using the RTC CNT2 32-bit free-running counter unit and/or the SCT timer. --- boards/qn9080dk/Kconfig | 1 + boards/qn9080dk/Makefile.features | 1 + boards/qn9080dk/include/periph_conf.h | 8 +- cpu/qn908x/doc.txt | 19 +++ cpu/qn908x/include/periph_cpu.h | 8 ++ cpu/qn908x/periph/timer.c | 174 ++++++++++++++++++++++++++ 6 files changed, 210 insertions(+), 1 deletion(-) create mode 100644 cpu/qn908x/periph/timer.c 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 */