1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2024-12-29 04:50:03 +01:00
RIOT/cpu/mips32r2_common/periph/timer.c
Gaëtan Harter d6e47461ba mips32r2_common/periph/timer: fix return values
Fix 'timer_set', 'timer_set_absolute' and 'timer_clear' return value to 1 on
success as documented in the API.
2018-03-22 14:21:03 +01:00

284 lines
6.7 KiB
C

/*
* Copyright 2016, Imagination Technologies Limited and/or its
* affiliated group companies.
* 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_mips32r2_common
* @ingroup drivers_periph_timer
* @{
*
* @file
* @brief Implementation of the low-level timer driver
*
* @}
*/
#include <mips/cpu.h>
#include <mips/m32c0.h>
#include <mips/regdef.h>
#include <mips/asm.h>
#include <string.h>
#include <periph/timer.h>
#include "cpu_conf.h"
#include <stdio.h>
#include "sched.h"
#include "thread.h"
#include "board.h"
#include "irq.h"
#include "timex.h"
#include "div.h"
#include <sys/time.h>
#ifdef EIC_IRQ
#include "eic_irq.h"
#endif
/*
* setting TIMER_ACCURACY_SHIFT lower will improve accuracy
* at the cost of more regular interrupts (hence less power efficient).
* */
#define TIMER_ACCURACY_SHIFT (10)
#define TIMER_ACCURACY (1 << TIMER_ACCURACY_SHIFT)
#define CHANNELS (3)
/* TICKS_PER_US must be set in the board file */
#ifndef TICKS_PER_US
#error "Please set TICK_PER_US in your board file"
#endif
/*
* The base MIPS count / compare timer is fixed frequency at core clock / 2
* and is pretty basic This timer is currently only supported in Vectored
* Interrupt Mode (VI), EIC mode is partially supported in non-vectored mode
* only.
*
* RIOT's xtimer expects the timer to operate at 1MHZ or any 2^n multiple or
* factor of this, thus we maintain a software timer which counts at 1MHz.
* This is not particularly power efficient and may add latency too.
*
* If other SoC specific timers are available which are more flexible then
* it is advised to use them, this timer is a fallback version
* that should work on all MIPS implementations.
*
*/
static timer_isr_ctx_t timer_isr_ctx;
volatile unsigned int counter;
volatile unsigned int compares[CHANNELS];
static volatile int spurious_int;
/*
* The mips toolchain C library does not implement gettimeofday()
*
* implement it here using the timer.
*
*/
int gettimeofday(struct timeval *__restrict __p, void *__restrict __tz)
{
(void)__tz;
uint64_t now = counter * US_PER_MS;
__p->tv_sec = div_u64_by_1000000(now);
__p->tv_usec = now - (__p->tv_sec * US_PER_SEC);
return 0;
}
int timer_init(tim_t dev, unsigned long freq, timer_cb_t cb, void *arg)
{
assert(dev == 0);
(void)dev;
(void)freq; /* Cannot adjust Frequency */
timer_isr_ctx.cb = cb;
timer_isr_ctx.arg = arg;
/* Clear down soft counters */
memset((void *)compares, 0, sizeof(compares));
counter = (1 << TIMER_ACCURACY_SHIFT);
/* Set compare up */
mips_setcompare(mips_getcount() + TICKS_PER_US * TIMER_ACCURACY);
/* Start the timer if stopped */
mips32_bc_c0(C0_CAUSE, CR_DC);
/* Enable Timer Interrupts */
#ifdef EIC_IRQ
eic_irq_configure(EIC_IRQ_TIMER);
#else
mips32_bs_c0(C0_STATUS, SR_HINT5);
#endif
return 0;
}
int timer_set(tim_t dev, int channel, unsigned int timeout)
{
assert(dev == 0);
assert(channel < CHANNELS);
(void)dev;
timeout >>= TIMER_ACCURACY_SHIFT;
timeout <<= TIMER_ACCURACY_SHIFT;
uint32_t status = irq_disable();
compares[channel] = counter + timeout;
irq_restore(status);
return 1;
}
int timer_set_absolute(tim_t dev, int channel, unsigned int value)
{
assert(dev == 0);
assert(channel < CHANNELS);
(void)dev;
value >>= TIMER_ACCURACY_SHIFT;
value <<= TIMER_ACCURACY_SHIFT;
uint32_t status = irq_disable();
compares[channel] = value;
irq_restore(status);
return 1;
}
int timer_clear(tim_t dev, int channel)
{
assert(dev == 0);
assert(channel < CHANNELS);
(void)dev;
uint32_t status = irq_disable();
compares[channel] = 0;
irq_restore(status);
return 1;
}
unsigned int timer_read(tim_t dev)
{
assert(dev == 0);
(void)dev;
return counter;
}
void timer_start(tim_t dev)
{
(void)dev;
mips32_bc_c0(C0_CAUSE, CR_DC);
}
void timer_stop(tim_t dev)
{
(void)dev;
mips32_bs_c0(C0_CAUSE, CR_DC);
}
void timer_irq_enable(tim_t dev)
{
(void)dev;
#ifdef EIC_IRQ
eic_irq_enable(EIC_IRQ_TIMER);
#else
mips32_bs_c0(C0_STATUS, SR_HINT5);
#endif
}
void timer_irq_disable(tim_t dev)
{
(void)dev;
#ifdef EIC_IRQ
eic_irq_disable(EIC_IRQ_TIMER);
#else
mips32_bc_c0(C0_STATUS, SR_HINT5);
#endif
}
/* note Compiler inserts GP context save + restore code (to current stack). */
#ifdef EIC_IRQ
/*
* This is a hack - currently the toolchain does not support correct placement
* of EIC mode vectors (it is coming though) But we can support non-vectored EIC
* mode and note the default PIC32 interrupt controller (which uses EIC +
* MCU-ASE) defaults to non vectored mode anyway with all interrupts coming via
* vector 0 which is equivalent to 'sw0' in 'VI' mode.
*
* Thus all EIC interrupts should be decoded here (currently only Timer is
* used)
*
* When toolchain support is available we could move to full vector mode but
* this does take up significant space (MCU-ASE provides 256 vectors at 32B
* spacing (the default) thats 8KB of vector space!), So a single entry point
* may be better anyway.
*
*/
void __attribute__ ((interrupt("vector=sw0"), keep_interrupts_masked)) _mips_isr_sw0(void)
#else
void __attribute__ ((interrupt("vector=hw5"))) _mips_isr_hw5(void)
#endif
{
register int cr = mips_getcr();
if (cr & CR_TI) {
#ifdef EIC_IRQ
eic_irq_ack(EIC_IRQ_TIMER);
#endif
uint32_t status = irq_disable();
counter += TIMER_ACCURACY;
irq_restore(status);
if (counter == compares[0]) {
/*
* The Xtimer code expects the ISR to take some time
* but our counter is a fake software one, so bump it a
* bit to give the impression some time elapsed in the ISR.
* Without this the callback ( _shoot(timer) on xtimer_core.c )
* never fires.
*/
counter += TIMER_ACCURACY;
timer_isr_ctx.cb(timer_isr_ctx.arg, 0);
if (sched_context_switch_request) {
thread_yield();
}
}
if (counter == compares[1]) {
timer_isr_ctx.cb(timer_isr_ctx.arg, 1);
if (sched_context_switch_request) {
thread_yield();
}
}
if (counter == compares[2]) {
timer_isr_ctx.cb(timer_isr_ctx.arg, 2);
if (sched_context_switch_request) {
thread_yield();
}
}
mips_setcompare(mips_getcount() + TICKS_PER_US * TIMER_ACCURACY);
}
else {
spurious_int++;
}
}