mirror of
https://github.com/RIOT-OS/RIOT.git
synced 2024-12-29 04:50:03 +01:00
43f07fa261
This implements `pm_set_lowest()` for the MSP430. Unlike most other platforms, it intentionally does not use pm_layered. It is pretty similar to `pm_layered` in that is does use reference counters, but it uses them for two independent clock sources. The main difference is that the low frequency clock domain can be disabled even when the high frequency clock is still active. With the layers, disabling layer n-1 while layer n is still blocked would not work.
302 lines
7.4 KiB
C
302 lines
7.4 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"
|
|
|
|
/**
|
|
* @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;
|
|
|
|
static_assert(TACLR == TBCLR, "This driver assumes TIMER A and TIMER B "
|
|
"having the same register layout for the "
|
|
"features exposed by this driver. This "
|
|
"is backed by the datasheet for both "
|
|
"MSP430 x1xx and G2xx / F2xx MCUs.");
|
|
/* reset the timer 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;
|
|
}
|
|
|
|
timer_start(dev);
|
|
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;
|
|
/* acquire clock */
|
|
switch (timer_conf[dev].clock_source) {
|
|
case TIMER_CLOCK_SOURCE_SUBMAIN_CLOCK:
|
|
msp430_clock_acquire(MSP430_CLOCK_SUBMAIN);
|
|
break;
|
|
case TIMER_CLOCK_SOURCE_AUXILIARY_CLOCK:
|
|
msp430_clock_acquire(MSP430_CLOCK_AUXILIARY);
|
|
break;
|
|
default:
|
|
/* external clock source, safe to disable internal clocks */
|
|
break;
|
|
}
|
|
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_CONT);
|
|
|
|
/* release clock */
|
|
switch (timer_conf[dev].clock_source) {
|
|
case TIMER_CLOCK_SOURCE_SUBMAIN_CLOCK:
|
|
msp430_clock_release(MSP430_CLOCK_SUBMAIN);
|
|
break;
|
|
case TIMER_CLOCK_SOURCE_AUXILIARY_CLOCK:
|
|
msp430_clock_release(MSP430_CLOCK_AUXILIARY);
|
|
break;
|
|
default:
|
|
/* external clock source, nothing to release */
|
|
break;
|
|
}
|
|
}
|
|
|
|
__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));
|
|
|
|
if (index > TXID_DIV_MAX) {
|
|
return 0;
|
|
}
|
|
|
|
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
|