1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2024-12-29 04:50:03 +01:00
RIOT/cpu/atxmega/periph/timer.c
Gerson Fernando Budke 93ed3cd9d6 cpu/atxmega: Add periph power management
The current xmega don't have a way to disable peripherals that are
not in used.  Add peripheral management to allow enable only the mcu
blocks that will be used by application.  This saves power on active
and sleep modes.  By default, at clock initialization, all peripherals
are now disabled and each drive must activate at initialization phase.
The periph_timer and periph_uart were updated with this new feature.

Signed-off-by: Gerson Fernando Budke <nandojve@gmail.com>
2021-04-02 14:24:31 -03:00

539 lines
10 KiB
C

/*
* Copyright (C) 2021 Gerson Fernando Budke <nandojve@gmail.com>
*
* 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_atxmega
* @ingroup cpu_atxmega_periph
* @{
*
* @file
* @brief Low-level TIMER driver implementation
*
* @author Gerson Fernando Budke <nandojve@gmail.com>
*
* @}
*/
#include <avr/interrupt.h>
#include <assert.h>
#include "cpu.h"
#include "cpu_pm.h"
#include "thread.h"
#include "periph/timer.h"
#include "board.h"
#include "periph_conf.h"
#define ENABLE_DEBUG 0
#include "debug.h"
/**
* @brief We have 7 possible prescaler values
*/
#define PRESCALE_NUMOF (7U)
/**
* @brief Possible prescaler values, encoded as 2 ^ val
*/
static const uint8_t prescalers[] = { 0, 1, 2, 3, 6, 8, 10 };
/**
* @brief Timer state context
*/
typedef struct {
timer_cb_t cb; /**< interrupt callback */
void *arg; /**< interrupt callback argument */
uint8_t prescaler; /**< remember the prescaler value */
uint8_t channels; /**< number of channels */
} ctx_t;
/**
* @brief Allocate memory for saving the device states
* @{
*/
#ifdef TIMER_NUMOF
static ctx_t ctx[TIMER_NUMOF] = { { NULL } };
#else
/* fallback if no timer is configured */
static ctx_t *ctx[] = { { NULL } };
#endif
/** @} */
static uint32_t _oneshot;
static inline void set_oneshot(tim_t tim, int chan)
{
_oneshot |= (1 << chan) << (TIMER_CH_MAX_NUMOF * tim);
}
static inline void clear_oneshot(tim_t tim, int chan)
{
_oneshot &= ~((1 << chan) << (TIMER_CH_MAX_NUMOF * tim));
}
static inline bool is_oneshot(tim_t tim, int chan)
{
return _oneshot & ((1 << chan) << (TIMER_CH_MAX_NUMOF * tim));
}
/**
* @brief Setup the given timer
*/
int timer_init(tim_t tim, unsigned long freq, timer_cb_t cb, void *arg)
{
DEBUG("timer.c: freq = %ld, Core Clock = %ld\n", freq, CLOCK_CORECLOCK);
TC0_t *dev;
uint8_t pre;
uint8_t ch;
assert(TIMER_CH_MAX_NUMOF * TIMER_NUMOF < 32);
/* make sure given device is valid */
if (tim >= TIMER_NUMOF) {
return -1;
}
/* figure out if freq is applicable */
for (pre = 0; pre < PRESCALE_NUMOF; pre++) {
if ((CLOCK_CORECLOCK >> prescalers[pre]) == freq) {
break;
}
}
if (pre == PRESCALE_NUMOF) {
DEBUG("timer.c: prescaling failed!\n");
return -1;
}
pm_periph_enable(timer_config[tim].pwr);
dev = timer_config[tim].dev;
/* stop and reset timer */
dev->CTRLA = 0; /* Stop */
dev->CTRLFSET = TC_CMD_RESET_gc; /* Force Reset */
/* save interrupt context and timer Prescaler */
ctx[tim].cb = cb;
ctx[tim].arg = arg;
ctx[tim].prescaler = (0x07 & (pre + 1));
/* Check enabled channels */
ctx[tim].channels = 0;
for (ch = 0; ch < TIMER_CH_MAX_NUMOF; ch++) {
if (timer_config[tim].int_lvl[ch] != CPU_INT_LVL_OFF) {
ctx[tim].channels++;
}
}
if (timer_config[tim].type != TC_TYPE_0
&& timer_config[tim].type != TC_TYPE_4) {
if (ctx[tim].channels > 2) {
DEBUG("timer.c: wrong number of channels. max value is 2.\n");
return -1;
}
}
if (timer_config[tim].type == TC_TYPE_2) {
DEBUG("timer.c: Timer version %d is current not supported.\n",
timer_config[tim].type);
return -1;
}
/* Normal Counter with rollover */
dev->CTRLB = TC_WGMODE_NORMAL_gc;
/* Compare or Capture disable all channels */
dev->INTCTRLB = 0;
/* Free running counter */
dev->PER = 0xFFFF;
DEBUG("timer.c: prescaler set to %d \n", ctx[tim].prescaler);
dev->CTRLA = ctx[tim].prescaler;
return 0;
}
int timer_set_absolute(tim_t tim, int channel, unsigned int value)
{
if (tim >= TIMER_NUMOF) {
return -1;
}
if (channel >= ctx[tim].channels) {
return -1;
}
DEBUG("Setting timer %i channel %i to %04x\n", tim, channel, value);
TC0_t *dev = timer_config[tim].dev;
dev->INTCTRLB &= ~(TC0_CCAINTLVL_gm << (channel * 2));
dev->INTFLAGS |= TC0_CCAIF_bm << channel;
uint8_t irq_state = irq_disable();
*(((uint16_t *)(&dev->CCA)) + channel) = (uint16_t)value;
irq_restore(irq_state);
set_oneshot(tim, channel);
dev->INTCTRLB |= (timer_config[tim].int_lvl[channel] << (channel * 2));
return 0;
}
int timer_set_periodic(tim_t tim, int channel, unsigned int value, uint8_t flags)
{
(void)flags;
if (tim >= TIMER_NUMOF) {
return -1;
}
if (channel > 0 || ctx[tim].channels != 1) {
DEBUG("Only channel 0 can be set as periodic and channels must be 1\n");
return -1;
}
DEBUG("Setting timer %i channel 0 to %i and flags %i (repeating)\n",
tim, value, flags);
TC0_t *dev = timer_config[tim].dev;
uint8_t irq_state = irq_disable();
dev->CTRLA = 0;
dev->CTRLFSET = TC_CMD_RESET_gc;
dev->CTRLB = TC_WGMODE_FRQ_gc;
dev->INTCTRLB = 0;
dev->INTFLAGS |= TC0_CCAIF_bm;
dev->CCA = (uint16_t)value;
dev->INTCTRLB = timer_config[tim].int_lvl[0];
dev->CTRLA = ctx[tim].prescaler;
clear_oneshot(tim, channel);
irq_restore(irq_state);
return 0;
}
int timer_clear(tim_t tim, int channel)
{
if (tim >= TIMER_NUMOF) {
return -1;
}
if (channel >= ctx[tim].channels) {
return -1;
}
DEBUG("timer_clear channel %d\n", channel );
TC0_t *dev = timer_config[tim].dev;
/* Compare or Capture Disable */
dev->INTCTRLB &= ~(TC0_CCAINTLVL_gm << (channel * 2));
/* Clear Interrupt Flag
* The CCxIF is automatically cleared when the corresponding
* interrupt vector is executed.*/
dev->INTFLAGS |= TC0_CCAIF_bm << channel;
return 0;
}
int timer_set(tim_t tim, int channel, unsigned int timeout)
{
if (tim >= TIMER_NUMOF) {
return -1;
}
if (channel >= ctx[tim].channels) {
return -1;
}
TC0_t *dev = timer_config[tim].dev;
/* Compare or Capture Disable */
dev->INTCTRLB &= ~(TC0_CCAINTLVL_gm << (channel * 2));
/* Clear Interrupt Flag */
dev->INTFLAGS |= TC0_CCAIF_bm << channel;
uint8_t irq_state = irq_disable();
/* set value to compare with rollover */
uint16_t absolute = dev->CNT + timeout;
*(((uint16_t *)(&dev->CCA)) + channel) = absolute;
irq_restore(irq_state);
set_oneshot(tim, channel);
/* Compare or Capture Enable */
dev->INTCTRLB |= (timer_config[tim].int_lvl[channel] << (channel * 2));
return 0;
}
unsigned int timer_read(tim_t tim)
{
if (tim >= TIMER_NUMOF) {
return -1;
}
DEBUG("timer_read\n");
return (unsigned int)timer_config[tim].dev->CNT;
}
void timer_stop(tim_t tim)
{
if (tim >= TIMER_NUMOF) {
return;
}
DEBUG("timer_stop\n");
timer_config[tim].dev->CTRLA = 0;
timer_config[tim].dev->CTRLFSET = TC_CMD_RESTART_gc;
pm_periph_disable(timer_config[tim].pwr);
}
void timer_start(tim_t tim)
{
if (tim >= TIMER_NUMOF) {
return;
}
DEBUG("timer_start\n");
pm_periph_enable(timer_config[tim].pwr);
timer_config[tim].dev->CTRLA = ctx[tim].prescaler;
}
#ifdef TIMER_NUMOF
static inline void _isr(tim_t tim, int channel)
{
avr8_enter_isr();
DEBUG("timer %d _isr channel %d\n", tim, channel);
if (is_oneshot(tim, channel)) {
timer_config[tim].dev->INTCTRLB &= ~(TC0_CCAINTLVL_gm << (channel * 2));
}
if (ctx[tim].cb) {
ctx[tim].cb(ctx[tim].arg, channel);
}
avr8_exit_isr();
}
#endif
#ifdef TIMER_0_ISRA
ISR(TIMER_0_ISRA, ISR_BLOCK)
{
_isr(0, 0);
}
#endif
#ifdef TIMER_0_ISRB
ISR(TIMER_0_ISRB, ISR_BLOCK)
{
_isr(0, 1);
}
#endif
#ifdef TIMER_0_ISRC
ISR(TIMER_0_ISRC, ISR_BLOCK)
{
_isr(0, 2);
}
#endif
#ifdef TIMER_0_ISRD
ISR(TIMER_0_ISRD, ISR_BLOCK)
{
_isr(0, 3);
}
#endif /* TIMER_0 */
#ifdef TIMER_1_ISRA
ISR(TIMER_1_ISRA, ISR_BLOCK)
{
_isr(1, 0);
}
#endif
#ifdef TIMER_1_ISRB
ISR(TIMER_1_ISRB, ISR_BLOCK)
{
_isr(1, 1);
}
#endif
#ifdef TIMER_1_ISRC
ISR(TIMER_1_ISRC, ISR_BLOCK)
{
_isr(1, 2);
}
#endif
#ifdef TIMER_1_ISRD
ISR(TIMER_1_ISRD, ISR_BLOCK)
{
_isr(1, 3);
}
#endif /* TIMER_1 */
#ifdef TIMER_2_ISRA
ISR(TIMER_2_ISRA, ISR_BLOCK)
{
_isr(2, 0);
}
#endif
#ifdef TIMER_2_ISRB
ISR(TIMER_2_ISRB, ISR_BLOCK)
{
_isr(2, 1);
}
#endif
#ifdef TIMER_2_ISRC
ISR(TIMER_2_ISRC, ISR_BLOCK)
{
_isr(2, 2);
}
#endif
#ifdef TIMER_2_ISRD
ISR(TIMER_2_ISRD, ISR_BLOCK)
{
_isr(2, 3);
}
#endif /* TIMER_2 */
#ifdef TIMER_3_ISRA
ISR(TIMER_3_ISRA, ISR_BLOCK)
{
_isr(3, 0);
}
#endif
#ifdef TIMER_3_ISRB
ISR(TIMER_3_ISRB, ISR_BLOCK)
{
_isr(3, 1);
}
#endif
#ifdef TIMER_3_ISRC
ISR(TIMER_3_ISRC, ISR_BLOCK)
{
_isr(3, 2);
}
#endif
#ifdef TIMER_3_ISRD
ISR(TIMER_3_ISRD, ISR_BLOCK)
{
_isr(3, 3);
}
#endif /* TIMER_3 */
#ifdef TIMER_4_ISRA
ISR(TIMER_4_ISRA, ISR_BLOCK)
{
_isr(4, 0);
}
#endif
#ifdef TIMER_4_ISRB
ISR(TIMER_4_ISRB, ISR_BLOCK)
{
_isr(4, 1);
}
#endif
#ifdef TIMER_4_ISRC
ISR(TIMER_4_ISRC, ISR_BLOCK)
{
_isr(4, 2);
}
#endif
#ifdef TIMER_4_ISRD
ISR(TIMER_4_ISRD, ISR_BLOCK)
{
_isr(4, 3);
}
#endif /* TIMER_4 */
#ifdef TIMER_5_ISRA
ISR(TIMER_5_ISRA, ISR_BLOCK)
{
_isr(5, 0);
}
#endif
#ifdef TIMER_5_ISRB
ISR(TIMER_5_ISRB, ISR_BLOCK)
{
_isr(5, 1);
}
#endif
#ifdef TIMER_5_ISRC
ISR(TIMER_5_ISRC, ISR_BLOCK)
{
_isr(5, 2);
}
#endif
#ifdef TIMER_5_ISRD
ISR(TIMER_5_ISRD, ISR_BLOCK)
{
_isr(5, 3);
}
#endif /* TIMER_5 */
#ifdef TIMER_6_ISRA
ISR(TIMER_6_ISRA, ISR_BLOCK)
{
_isr(6, 0);
}
#endif
#ifdef TIMER_6_ISRB
ISR(TIMER_6_ISRB, ISR_BLOCK)
{
_isr(6, 1);
}
#endif
#ifdef TIMER_6_ISRC
ISR(TIMER_6_ISRC, ISR_BLOCK)
{
_isr(6, 2);
}
#endif
#ifdef TIMER_6_ISRD
ISR(TIMER_6_ISRD, ISR_BLOCK)
{
_isr(6, 3);
}
#endif /* TIMER_6 */
#ifdef TIMER_7_ISRA
ISR(TIMER_7_ISRA, ISR_BLOCK)
{
_isr(7, 0);
}
#endif
#ifdef TIMER_7_ISRB
ISR(TIMER_7_ISRB, ISR_BLOCK)
{
_isr(7, 1);
}
#endif
#ifdef TIMER_7_ISRC
ISR(TIMER_7_ISRC, ISR_BLOCK)
{
_isr(7, 2);
}
#endif
#ifdef TIMER_7_ISRB
ISR(TIMER_7_ISRD, ISR_BLOCK)
{
_isr(7, 3);
}
#endif /* TIMER_7 */