-
Notifications
You must be signed in to change notification settings - Fork 2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
307 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
CPU := rpx0xx | ||
|
||
# Put defined MCU peripherals here (in alphabetical order) | ||
FEATURES_PROVIDED += periph_timer | ||
FEATURES_PROVIDED += periph_uart |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 <[email protected]> | ||
* | ||
* @} | ||
*/ | ||
|
||
#include <errno.h> | ||
#include <assert.h> | ||
#include <stdint.h> | ||
#include <stdbool.h> | ||
|
||
#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); | ||
} |