timer.c 4.54 KB
/*
 * Copyright (C) 2015 Freie Universität Berlin
 *
 * 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_lpc2387
 * @ingroup     drivers_periph_timer
 * @{
 *
 * @file
 * @brief       Implementation of the low-level timer driver for the LPC2387
 *
 * @author      Hauke Petersen <hauke.petersen@fu-berlin.de>
 * @}
 */

#include <stdint.h>
#include <string.h>

#include "periph_conf.h"
#include "periph_cpu.h"
#include "periph/timer.h"

/**
 * @brief   Check the board config to make sure we do not exceed max number of
 *          timers
 */
#if TIMER_NUMOF > 3
#error "ERROR in timer configuration: too many timers defined"
#endif

/**
 * @brief   Interrupt context information for configured timers
 */
static timer_isr_ctx_t isr_ctx[TIMER_NUMOF];

/**
 * @brief   Forward declarations for interrupt functions
 * @{
 */
void tim_isr_0(void);
void tim_isr_1(void);
void tim_isr_2(void);
void tim_isr_3(void);
/** @} */

/**
 * @brief   Get the base pointer of a timer
 */
static inline lpc23xx_timer_t *get_dev(tim_t tim)
{
    switch (tim) {
        case 0:
            return TMR0;
#if TIMER_NUMOF > 1
        case 1:
            return TMR1;
#endif
#if TIMER_NUMOF > 2
        case 2:
            return TMR2;
#endif
#if TIMER_NUMOF > 3
        case 3:
            return TMR3;
#endif
        default:
            return NULL;
    }
}

static inline void pwr_clk_and_isr(tim_t tim)
{
    switch (tim) {
        case 0:
            PCONP |= (1 << 1);
            PCLKSEL0 &= ~(0x03 << 2);
            PCLKSEL0 |=  (0x01 << 2);
            install_irq(TIMER0_INT, &tim_isr_0, 1);
            break;
#if TIMER_NUMOF > 1
        case 1:
            PCONP |= (1 << 2);
            PCLKSEL0 &= ~(0x03 << 4);
            PCLKSEL0 |=  (0x01 << 4);
            install_irq(TIMER1_INT, &tim_isr_1, 1);
            break;
#endif
#if TIMER_NUMOF > 2
        case 2:
            PCONP |= (1 << 22);
            PCLKSEL1 &= ~(0x03 << 12);
            PCLKSEL1 |=  (0x01 << 12);
            install_irq(TIMER2_INT, &tim_isr_2, 1);
            break;
#endif
#if TIMER_NUMOF > 3
        case 3:
            PCONP |= (1 << 23);
            PCLKSEL1 &= ~(0x03 << 14);
            PCLKSEL1 |=  (0x01 << 14);
            install_irq(TIMER3_INT, &tim_isr_3, 1);
            break;
#endif
    }
}

int timer_init(tim_t tim, unsigned long freq, timer_cb_t cb, void *arg)
{
    /* get the timers base register */
    lpc23xx_timer_t *dev = get_dev(tim);

    /* make sure the timer device is valid */
    if (dev == NULL) {
        return -1;
    }

    /* save the callback */
    isr_ctx[tim].cb = cb;
    isr_ctx[tim].arg = arg;
    /* enable power, config periph clock and install ISR vector */
    pwr_clk_and_isr(tim);
    /* reset timer configuration (sets the timer to timer mode) */
    dev->TCR = 0;
    dev->CTCR = 0;
    /* configure the prescaler */
    dev->PR = (CLOCK_PCLK / freq) - 1;
    /* enable timer */
    dev->TCR = 1;
    return 0;
}

int timer_set_absolute(tim_t tim, int channel, unsigned int value)
{
    if (((unsigned) tim >= TIMER_NUMOF) || ((unsigned) channel >= TIMER_CHAN_NUMOF)) {
        return -1;
    }

    lpc23xx_timer_t *dev = get_dev(tim);
    dev->MR[channel] = value;
    dev->MCR |= (1 << (channel * 3));
    return 0;
}

int timer_clear(tim_t tim, int channel)
{
    if (((unsigned) tim >= TIMER_NUMOF) || ((unsigned) channel >= TIMER_CHAN_NUMOF)) {
        return -1;
    }
    get_dev(tim)->MCR &= ~(1 << (channel * 3));
    return 0;
}

unsigned int timer_read(tim_t tim)
{
    return (unsigned int)(get_dev(tim)->TC);
}

void timer_start(tim_t tim)
{
    get_dev(tim)->TCR = 1;
}

void timer_stop(tim_t tim)
{
    get_dev(tim)->TCR = 0;
}

static inline void isr_handler(lpc23xx_timer_t *dev, int tim_num)
{
    for (unsigned i = 0; i < TIMER_CHAN_NUMOF; i++) {
        if (dev->IR & (1 << i)) {
            dev->IR |= (1 << i);
            dev->MCR &= ~(1 << (i * 3));
            isr_ctx[tim_num].cb(isr_ctx[tim_num].arg, i);
        }
    }
    /* we must not forget to acknowledge the handling of the interrupt */
    VICVectAddr = 0;
}

void __attribute__((interrupt("IRQ"))) tim_isr_0(void)
{
    isr_handler(TMR0, 0);
}

#if TIMER_NUMOF > 1
void __attribute__((interrupt("IRQ"))) tim_isr_1(void)
{
    isr_handler(TMR1, 1);
}
#endif

#if TIMER_NUMOF > 2
void __attribute__((interrupt("IRQ"))) tim_isr_2(void)
{
    isr_handler(TMR2, 2);
}
#endif

#if TIMER_NUMOF > 3
void __attribute__((interrupt("IRQ"))) tim_isr_3(void)
{
    isr_handler(TMR3, 3);
}
#endif