From 6cf973d15f98c45d8cbfd41fc0fa911e82688370 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabian=20H=C3=BC=C3=9Fler?= Date: Fri, 2 Jul 2021 09:17:42 +0200 Subject: [PATCH] cpu/rpx0xx: implement periph timer --- boards/rpi-pico/Makefile.features | 1 + boards/rpi-pico/include/periph_conf.h | 31 ++++ cpu/rpx0xx/Kconfig | 2 + cpu/rpx0xx/Makefile.features | 1 + cpu/rpx0xx/include/periph_cpu.h | 23 +++ cpu/rpx0xx/periph/timer.c | 251 ++++++++++++++++++++++++++ 6 files changed, 309 insertions(+) create mode 100644 cpu/rpx0xx/periph/timer.c diff --git a/boards/rpi-pico/Makefile.features b/boards/rpi-pico/Makefile.features index d807338931d4b..ddca0fd84900d 100644 --- a/boards/rpi-pico/Makefile.features +++ b/boards/rpi-pico/Makefile.features @@ -1,4 +1,5 @@ CPU := rpx0xx # Put defined MCU peripherals here (in alphabetical order) +FEATURES_PROVIDED += periph_timer FEATURES_PROVIDED += periph_uart diff --git a/boards/rpi-pico/include/periph_conf.h b/boards/rpi-pico/include/periph_conf.h index 3b54fd7f08a4b..659468dd63775 100644 --- a/boards/rpi-pico/include/periph_conf.h +++ b/boards/rpi-pico/include/periph_conf.h @@ -20,6 +20,7 @@ #include +#include "kernel_defines.h" #include "cpu.h" #include "periph_cpu.h" @@ -47,6 +48,36 @@ static const uart_conf_t uart_config[] = { #define UART_NUMOF ARRAY_SIZE(uart_config) +static const timer_channel_conf_t timer0_channel_config[] = { + { + .irqn = TIMER_IRQ_0_IRQn + }, + { + .irqn = TIMER_IRQ_1_IRQn + }, + { + .irqn = TIMER_IRQ_2_IRQn + }, + { + .irqn = TIMER_IRQ_3_IRQn + } +}; + +static const timer_conf_t timer_config[] = { + { + .dev = TIMER, + .ch = timer0_channel_config, + .chn = ARRAY_SIZE(timer0_channel_config) + } +}; + +#define TIMER_0_ISRA isr_timer0 +#define TIMER_0_ISRB isr_timer1 +#define TIMER_0_ISRC isr_timer2 +#define TIMER_0_ISRD isr_timer3 + +#define TIMER_NUMOF ARRAY_SIZE(timer_config) + #ifdef __cplusplus } #endif diff --git a/cpu/rpx0xx/Kconfig b/cpu/rpx0xx/Kconfig index 44f200a8887fb..d963f5e4c5c47 100644 --- a/cpu/rpx0xx/Kconfig +++ b/cpu/rpx0xx/Kconfig @@ -11,6 +11,8 @@ config CPU_FAM_RPX0XX select HAS_CPU_RPX0XX select HAS_PERIPH_GPIO select HAS_PERIPH_GPIO_IRQ + select HAS_PERIPH_TIMER + select HAS_PERIPH_TIMER_PERIODIC select HAS_PERIPH_UART_MODECFG select HAS_PERIPH_UART_RECONFIGURE diff --git a/cpu/rpx0xx/Makefile.features b/cpu/rpx0xx/Makefile.features index ac9f25f0f6c5d..d8eb1e56d46f1 100644 --- a/cpu/rpx0xx/Makefile.features +++ b/cpu/rpx0xx/Makefile.features @@ -5,5 +5,6 @@ include $(RIOTCPU)/cortexm_common/Makefile.features FEATURES_PROVIDED += periph_gpio FEATURES_PROVIDED += periph_gpio_irq +FEATURES_PROVIDED += periph_timer_periodic FEATURES_PROVIDED += periph_uart_reconfigure FEATURES_PROVIDED += periph_uart_modecfg diff --git a/cpu/rpx0xx/include/periph_cpu.h b/cpu/rpx0xx/include/periph_cpu.h index d72b7c5f9e624..a869f36169a31 100644 --- a/cpu/rpx0xx/include/periph_cpu.h +++ b/cpu/rpx0xx/include/periph_cpu.h @@ -400,6 +400,29 @@ typedef struct { IRQn_Type irqn; /**< IRQ number of the UART interface */ } uart_conf_t; +/** + * @brief Prevent shared timer functions from being used + */ +#define PERIPH_TIMER_PROVIDES_SET + +/** + * @brief Configuration type of a timer channel + */ +typedef struct { + IRQn_Type irqn; /**< timer channel interrupt number */ +} timer_channel_conf_t; + +/** + * @brief Configuration type of a timer device @ref timer_conf_t::dev, + * having @ref timer_conf_t::chn number of channels, + * each one modeled as @ref timer_channel_conf_t + */ +typedef struct { + TIMER_Type *dev; /**< pointer to timer base address */ + const timer_channel_conf_t *ch; /**< pointer to timer channel configuration */ + uint8_t chn; /**< number of timer channels */ +} timer_conf_t; + /** * @brief Get the PAD control register for the given GPIO pin as word * diff --git a/cpu/rpx0xx/periph/timer.c b/cpu/rpx0xx/periph/timer.c new file mode 100644 index 0000000000000..9053390cc7858 --- /dev/null +++ b/cpu/rpx0xx/periph/timer.c @@ -0,0 +1,251 @@ +/* + * Copyright (C) 2021 Otto-von-Guericke Universität Magdeburg + * + * 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_rpx0xx + * @ingroup drivers_periph_timer + * @{ + * + * @file + * @brief Timer implementation for the RPX0XX + * @details The RPX0XX has a 64 bit µs timer but timer interrupts match + * on the lower 32 bits. + * + * @author Fabian Hüßler + * + * @} + */ + +#include +#include +#include +#include + +#include "vendor/RP2040.h" +#include "io_reg.h" +#include "timex.h" +#include "periph_conf.h" +#include "periph/timer.h" + +#define DEV(d) (timer_config[d].dev) +#define ALARM(d, a) ((&(DEV(d)->ALARM0)) + (a)) + +static timer_cb_t _timer_ctx_cb[TIMER_NUMOF]; +static void *_timer_ctx_arg[TIMER_NUMOF]; +static unsigned _timer_flag_periodic[TIMER_NUMOF]; +static unsigned _timer_flag_reset[TIMER_NUMOF]; + +static inline uint64_t _timer_read_us(tim_t dev) +{ + /* This is not safe when the second core also accesses the timer */ + unsigned state = irq_disable(); + uint32_t lo = DEV(dev)->TIMELR; /* always read timelr to latch the value of timehr */ + uint32_t hi = DEV(dev)->TIMEHR; /* read timehr to unlatch */ + irq_restore(state); + return ((uint64_t)hi << 32U) | lo; +} + +static inline void _timer_reset(tim_t dev) +{ + unsigned state = irq_disable(); + DEV(dev)->TIMELW = 0; /* always write timelw before timehw */ + DEV(dev)->TIMEHW = 0; /* writes do not get copied to time until timehw is written */ + irq_restore(state); +} + +static inline void _timer_enable_periodic(tim_t dev, int channel, uint8_t flags) +{ + _timer_flag_periodic[dev] |= (1U << channel); + if (flags & TIM_FLAG_RESET_ON_MATCH) { + _timer_flag_reset[dev] |= (1U << channel); + } + else { + _timer_flag_reset[dev] &= ~(1U << channel); + } +} + +static inline void _timer_disable_periodic(tim_t dev, int channel) +{ + _timer_flag_periodic[dev] &= ~(1U << channel); +} + +static inline bool _timer_is_periodic(tim_t dev, int channel) +{ + return !!(_timer_flag_periodic[dev] & (1U << channel)); +} + +static inline bool _timer_reset_on_match(tim_t dev, int channel) +{ + return !!(_timer_flag_reset[dev] & (1U << channel)); +} + +static inline void _irq_enable(tim_t dev) +{ + for (uint8_t i = 0; i < timer_config[dev].chn; i++) { + NVIC_EnableIRQ(timer_config[dev].ch[i].irqn); + io_reg_atomic_set(&DEV(dev)->INTE.reg, (1U << i)); + } +} + +static void _isr(tim_t dev, int channel) +{ + /* clear latched interrupt */ + io_reg_atomic_clear(&DEV(dev)->INTR.reg, 1U << channel); + + if (_timer_is_periodic(dev, channel)) { + if (_timer_reset_on_match(dev, channel)) { + _timer_reset(dev); + } + /* rearm */ + *ALARM(dev, channel) = *ALARM(dev, channel); + } + if (_timer_ctx_cb[dev]) { + _timer_ctx_cb[dev](_timer_ctx_arg[dev], channel); + } + + cortexm_isr_end(); +} + +int timer_init(tim_t dev, uint32_t freq, timer_cb_t cb, void *arg) +{ + if (dev >= TIMER_NUMOF) { + return -ENODEV; + } + /* The timer must run at 1000000 Hz (µs precision) + because the number of cycles per µs is shared with the watchdog. + The reference clock (clk_ref) is divided by WATCHDOG->TICK.bits.CYCLES + to generate µs ticks. + */ + assert(freq == US_PER_SEC); (void)freq; + _timer_ctx_cb[dev] = cb; + _timer_ctx_arg[dev] = arg; + periph_reset(RESETS_RESET_timer_Msk); + periph_reset_done(RESETS_RESET_timer_Msk); + io_reg_write_dont_corrupt(&WATCHDOG->TICK.reg, + (CLOCK_XOSC / MHZ(1)) << WATCHDOG_TICK_CYCLES_Pos, + WATCHDOG_TICK_CYCLES_Msk); + _irq_enable(dev); + return 0; +} + +int timer_set(tim_t dev, int channel, unsigned int timeout) +{ + if (dev >= TIMER_NUMOF) { + return -ENODEV; + } + if (channel < 0 || channel >= timer_config[dev].chn) { + return -EINVAL; + } + if (!timeout) { + /* execute callback immediately if timeout equals 0, + to ctach the case that a tick happens right before arming the alarm + and causes a full timer period to elaps */ + if (_timer_ctx_cb[dev]) { + _timer_ctx_cb[dev](_timer_ctx_arg[dev], channel); + } + } + else { + unsigned state = irq_disable(); + _timer_disable_periodic(dev, channel); + /* an alarm interrupt matches on the lower 32 bit of the 64 bit timer counter */ + uint64_t target = DEV(dev)->TIMERAWL + timeout; + *ALARM(dev, channel) = (uint32_t)target; + irq_restore(state); + } + return 0; +} + +int timer_set_absolute(tim_t dev, int channel, unsigned int value) +{ + if (dev >= TIMER_NUMOF) { + return -ENODEV; + } + if (channel < 0 || channel >= timer_config[dev].chn) { + return -EINVAL; + } + unsigned state = irq_disable(); + _timer_disable_periodic(dev, channel); + *ALARM(dev, channel) = (uint32_t)value; + irq_restore(state); + return 0; +} + +int timer_set_periodic(tim_t dev, int channel, unsigned int value, uint8_t flags) +{ + if (dev >= TIMER_NUMOF) { + return -ENODEV; + } + if (channel < 0 || channel >= timer_config[dev].chn) { + return -EINVAL; + } + if (flags & TIM_FLAG_RESET_ON_SET) { + _timer_reset(dev); + } + unsigned state = irq_disable(); + _timer_enable_periodic(dev, channel, flags); + *ALARM(dev, channel) = (uint32_t)value; + irq_restore(state); + return 0; +} + +int timer_clear(tim_t dev, int channel) +{ + if (dev >= TIMER_NUMOF) { + return -ENODEV; + } + if (channel < 0 || channel >= timer_config[dev].chn) { + return -EINVAL; + } + /* ARMED bits are write clear */ + io_reg_atomic_set(&DEV(dev)->ARMED.reg, (1 << channel)); + unsigned state = irq_disable(); + _timer_disable_periodic(dev, channel); + irq_restore(state); + return 0; +} + +unsigned int timer_read(tim_t dev) +{ + if (dev >= TIMER_NUMOF) { + return -ENODEV; + } + return _timer_read_us(dev); +} + +void timer_start(tim_t dev) +{ + assert(dev < TIMER_NUMOF); + io_reg_atomic_clear(&DEV(dev)->PAUSE.reg, (1 << TIMER_PAUSE_PAUSE_Pos)); +} + +void timer_stop(tim_t dev) +{ + assert(dev < TIMER_NUMOF); + io_reg_atomic_set(&DEV(dev)->PAUSE.reg, (1 << TIMER_PAUSE_PAUSE_Pos)); +} + +/* timer 0 IRQ0 */ +void TIMER_0_ISRA(void) +{ + _isr(0, 0); +} +/* timer 0 IRQ1 */ +void TIMER_0_ISRB(void) +{ + _isr(0, 1); +} +/* timer 0 IRQ2 */ +void TIMER_0_ISRC(void) +{ + _isr(0, 2); +} +/* timer 0 IRQ3 */ +void TIMER_0_ISRD(void) +{ + _isr(0, 3); +}