Skip to content

Commit

Permalink
cpu/qn908x: Add timer driver based on CTIMER.
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
iosabi committed Dec 3, 2020
1 parent a1e9d3b commit 94f893c
Show file tree
Hide file tree
Showing 6 changed files with 210 additions and 1 deletion.
1 change: 1 addition & 0 deletions boards/qn9080dk/Kconfig
Expand Up @@ -19,5 +19,6 @@ config BOARD_QN9080DK
select BOARD_HAS_XTAL_32M
select HAS_PERIPH_UART
select HAS_PERIPH_UART_MODECFG
select HAS_PERIPH_TIMER

source "$(RIOTBOARD)/common/qn908x/Kconfig"
1 change: 1 addition & 0 deletions boards/qn9080dk/Makefile.features
Expand Up @@ -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.
Expand Down
8 changes: 7 additions & 1 deletion boards/qn9080dk/include/periph_conf.h
Expand Up @@ -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
Expand Down
19 changes: 19 additions & 0 deletions cpu/qn908x/doc.txt
Expand Up @@ -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
Expand Down
8 changes: 8 additions & 0 deletions cpu/qn908x/include/periph_cpu.h
Expand Up @@ -140,6 +140,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
*
Expand Down
174 changes: 174 additions & 0 deletions 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 <iosabi@protonmail.com>
*
* @}
*/

#include <stdlib.h>

#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 */

0 comments on commit 94f893c

Please sign in to comment.