mirror of
https://github.com/RIOT-OS/RIOT.git
synced 2024-12-29 04:50:03 +01:00
7044699388
- add support for multiple timers - add support for selecting clock source in the board's `periph_conf.h` - add support for the prescaler - implement `periph_timer_query_freqs` - add a second timer to all MSP430 boards - the first timer is fast ticking, high-power - the second is slow ticking, low-power
270 lines
6.3 KiB
C
270 lines
6.3 KiB
C
/*
|
|
* Copyright (C) 2015 Freie Universität Berlin
|
|
* 2023 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_msp430
|
|
* @ingroup drivers_periph_timer
|
|
* @{
|
|
*
|
|
* @file
|
|
* @brief Low-level timer driver implementation
|
|
*
|
|
* This implementation does only support one fixed timer, as defined in the
|
|
* boards periph_conf.h file.
|
|
*
|
|
* @todo Generalize to handle more timers and make them configurable
|
|
* through the board's `periph_conf.h`
|
|
*
|
|
* @author Hauke Petersen <hauke.petersen@fu-berlin.de>
|
|
* @author Marian Buschsieweke <marian.buschsieweke@posteo.net>
|
|
*
|
|
* @}
|
|
*/
|
|
|
|
#include "compiler_hints.h"
|
|
#include "cpu.h"
|
|
#include "periph/timer.h"
|
|
#include "periph_conf.h"
|
|
#include "periph_cpu.h"
|
|
|
|
/**
|
|
* @brief Interrupt context for each configured timer
|
|
*/
|
|
static timer_isr_ctx_t isr_ctx[TIMER_NUMOF];
|
|
|
|
/* Hack to count the number of ISR vectors provided by the board */
|
|
enum {
|
|
#ifdef TIMER0_ISR_CC0
|
|
TIMER_ISR_COUNT_HELPER_0,
|
|
#endif
|
|
#ifdef TIMER1_ISR_CC0
|
|
TIMER_ISR_COUNT_HELPER_1,
|
|
#endif
|
|
TIMER_ISR_NUMOF
|
|
};
|
|
|
|
static_assert((TXID_DIV_MAX << TXID_DIV_Pos) == TXID_DIV_Msk, "TXID_DIV_MAX or TXID_DIV_Pos is incorrect.");
|
|
static_assert(TIMER_ISR_NUMOF == TIMER_NUMOF,
|
|
"For each provided timer a corresponding IRQ number needs to be provided by the board.");
|
|
|
|
static uint32_t abs_diff(uint32_t a, uint32_t b)
|
|
{
|
|
if (a >= b) {
|
|
return a - b;
|
|
}
|
|
|
|
return b - a;
|
|
}
|
|
|
|
static uint16_t prescale(msp430_timer_clock_source_t clock_source, uint32_t freq)
|
|
{
|
|
uint32_t clock_freq;
|
|
assume((clock_source == TIMER_CLOCK_SOURCE_AUXILIARY_CLOCK) ||
|
|
(clock_source == TIMER_CLOCK_SOURCE_SUBMAIN_CLOCK));
|
|
switch (clock_source) {
|
|
default:
|
|
case TIMER_CLOCK_SOURCE_AUXILIARY_CLOCK:
|
|
clock_freq = msp430_auxiliary_clock_freq();
|
|
break;
|
|
case TIMER_CLOCK_SOURCE_SUBMAIN_CLOCK:
|
|
clock_freq = msp430_submain_clock_freq();
|
|
break;
|
|
}
|
|
|
|
unsigned prescaler = 0;
|
|
uint32_t best_diff = UINT32_MAX;
|
|
|
|
for (unsigned i = 0; i <= TXID_DIV_MAX; i++) {
|
|
uint32_t prescaled_freq = clock_freq >> i;
|
|
uint32_t off = abs_diff(freq, prescaled_freq);
|
|
if (off < best_diff) {
|
|
best_diff = off;
|
|
prescaler = i;
|
|
}
|
|
}
|
|
|
|
return clock_source | (prescaler << TXID_DIV_Pos);
|
|
}
|
|
|
|
int timer_init(tim_t dev, uint32_t freq, timer_cb_t cb, void *arg)
|
|
{
|
|
assume((unsigned)dev < TIMER_NUMOF);
|
|
msp430_timer_t *msptimer = timer_conf[dev].timer;
|
|
|
|
/* reset the timer A configuration */
|
|
msptimer->CTL = TACLR;
|
|
/* save callback */
|
|
isr_ctx[dev].cb = cb;
|
|
isr_ctx[dev].arg = arg;
|
|
/* compute prescaler */
|
|
uint16_t ctl = prescale(timer_conf[dev].clock_source, freq);
|
|
msptimer->CTL = ctl;
|
|
/* configure CC channels */
|
|
for (unsigned i = 0; i < timer_query_channel_numof(dev); i++) {
|
|
msptimer->CCTL[i] = 0;
|
|
}
|
|
/* start the timer in continuous mode */
|
|
msptimer->CTL = ctl | TXMC_CONT;
|
|
return 0;
|
|
}
|
|
|
|
int timer_set_absolute(tim_t dev, int channel, unsigned int value)
|
|
{
|
|
assume((unsigned)dev < TIMER_NUMOF);
|
|
msp430_timer_t *msptimer = timer_conf[dev].timer;
|
|
|
|
if ((unsigned)channel >= timer_query_channel_numof(dev)) {
|
|
return -1;
|
|
}
|
|
|
|
msptimer->CCR[channel] = value;
|
|
msptimer->CCTL[channel] &= ~(CCIFG);
|
|
msptimer->CCTL[channel] |= CCIE;
|
|
return 0;
|
|
}
|
|
|
|
int timer_clear(tim_t dev, int channel)
|
|
{
|
|
assume((unsigned)dev < TIMER_NUMOF);
|
|
msp430_timer_t *msptimer = timer_conf[dev].timer;
|
|
|
|
if ((unsigned)channel >= timer_query_channel_numof(dev)) {
|
|
return -1;
|
|
}
|
|
msptimer->CCTL[channel] &= ~(CCIE);
|
|
return 0;
|
|
}
|
|
|
|
unsigned int timer_read(tim_t dev)
|
|
{
|
|
assume((unsigned)dev < TIMER_NUMOF);
|
|
msp430_timer_t *msptimer = timer_conf[dev].timer;
|
|
return msptimer->R;
|
|
}
|
|
|
|
void timer_start(tim_t dev)
|
|
{
|
|
assume((unsigned)dev < TIMER_NUMOF);
|
|
msp430_timer_t *msptimer = timer_conf[dev].timer;
|
|
msptimer->CTL |= TXMC_CONT;
|
|
}
|
|
|
|
void timer_stop(tim_t dev)
|
|
{
|
|
assume((unsigned)dev < TIMER_NUMOF);
|
|
msp430_timer_t *msptimer = timer_conf[dev].timer;
|
|
msptimer->CTL &= ~(TXMC_MASK);
|
|
}
|
|
|
|
__attribute__((pure))
|
|
uword_t timer_query_freqs_numof(tim_t dev)
|
|
{
|
|
assume((unsigned)dev < TIMER_NUMOF);
|
|
/* Smallest div value is 0, so number is max + 1 */
|
|
return TXID_DIV_MAX + 1;
|
|
}
|
|
|
|
__attribute__((pure))
|
|
uword_t timer_query_channel_numof(tim_t dev)
|
|
{
|
|
assume((unsigned)dev < TIMER_NUMOF);
|
|
if (timer_conf[dev].timer == &TIMER_A) {
|
|
return 3;
|
|
}
|
|
|
|
return 7;
|
|
}
|
|
|
|
uint32_t timer_query_freqs(tim_t dev, uword_t index)
|
|
{
|
|
assume((unsigned)dev < TIMER_NUMOF);
|
|
const msp430_timer_clock_source_t clock_source = timer_conf[dev].clock_source;
|
|
assume((clock_source == TIMER_CLOCK_SOURCE_AUXILIARY_CLOCK) ||
|
|
(clock_source == TIMER_CLOCK_SOURCE_SUBMAIN_CLOCK));
|
|
|
|
uint32_t clock_freq;
|
|
switch (clock_source) {
|
|
default:
|
|
case TIMER_CLOCK_SOURCE_AUXILIARY_CLOCK:
|
|
clock_freq = msp430_auxiliary_clock_freq();
|
|
break;
|
|
case TIMER_CLOCK_SOURCE_SUBMAIN_CLOCK:
|
|
clock_freq = msp430_submain_clock_freq();
|
|
break;
|
|
}
|
|
|
|
return clock_freq >> index;
|
|
}
|
|
|
|
static void timer_isr(tim_t dev, unsigned chan)
|
|
{
|
|
assume((unsigned)dev < TIMER_NUMOF);
|
|
msp430_timer_t *msptimer = timer_conf[dev].timer;
|
|
/* disable IRQ */
|
|
msptimer->CCTL[chan] &= ~(CCIE);
|
|
isr_ctx[dev].cb(isr_ctx[dev].arg, chan);
|
|
|
|
}
|
|
static void timer_isr_cc0(tim_t dev)
|
|
{
|
|
assume((unsigned)dev < TIMER_NUMOF);
|
|
timer_isr(dev, 0);
|
|
}
|
|
|
|
static void timer_isr_ccx(tim_t dev)
|
|
{
|
|
assume((unsigned)dev < TIMER_NUMOF);
|
|
unsigned chan = *timer_conf[dev].irq_flags >> 1U;
|
|
if (chan >= timer_query_channel_numof(dev)) {
|
|
/* timer overflown */
|
|
}
|
|
else {
|
|
/* CC matched */
|
|
timer_isr(dev, chan);
|
|
}
|
|
}
|
|
|
|
ISR(TIMER0_ISR_CC0, isr_timer0_cc0)
|
|
{
|
|
__enter_isr();
|
|
|
|
timer_isr_cc0(0);
|
|
|
|
__exit_isr();
|
|
}
|
|
|
|
ISR(TIMER0_ISR_CCX, isr_timer0_ccx)
|
|
{
|
|
__enter_isr();
|
|
|
|
timer_isr_ccx(0);
|
|
|
|
__exit_isr();
|
|
}
|
|
|
|
#ifdef TIMER1_ISR_CC0
|
|
ISR(TIMER1_ISR_CC0, isr_timer1_cc0)
|
|
{
|
|
__enter_isr();
|
|
|
|
timer_isr_cc0(1);
|
|
|
|
__exit_isr();
|
|
}
|
|
|
|
ISR(TIMER1_ISR_CCX, isr_timer1_ccx)
|
|
{
|
|
__enter_isr();
|
|
|
|
timer_isr_ccx(1);
|
|
|
|
__exit_isr();
|
|
}
|
|
#endif
|