/******************************************************************************
 * The MIT License
 *
 * Copyright (c) 2011 LeafLabs, LLC.
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use, copy,
 * modify, merge, publish, distribute, sublicense, and/or sell copies
 * of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *****************************************************************************/

/**
 * @file   timer.c
 * @author Marti Bolivar <mbolivar@leaflabs.com>
 * @brief  New-style timer interface
 */

#include "timer.h"

/* Just like the corresponding DIER bits:
 * [0] = Update handler;
 * [1,2,3,4] = capture/compare 1,2,3,4 handlers, respectively;
 * [5] = COM;
 * [6] = TRG;
 * [7] = BRK. */
#define NR_ADV_HANDLERS                 8
/* Update, capture/compare 1,2,3,4; <junk>; trigger. */
#define NR_GEN_HANDLERS                 6
/* Update only. */
#define NR_BAS_HANDLERS                 1

static timer_dev timer1 = {
    .regs         = { .adv = TIMER1_BASE },
    .clk_id       = RCC_TIMER1,
    .type         = TIMER_ADVANCED,
    .handlers     = { [NR_ADV_HANDLERS - 1] = 0 },
};
/** Timer 1 device (advanced) */
timer_dev *TIMER1 = &timer1;

static timer_dev timer2 = {
    .regs         = { .gen = TIMER2_BASE },
    .clk_id       = RCC_TIMER2,
    .type         = TIMER_GENERAL,
    .handlers     = { [NR_GEN_HANDLERS - 1] = 0 },
};
/** Timer 2 device (general-purpose) */
timer_dev *TIMER2 = &timer2;

static timer_dev timer3 = {
    .regs         = { .gen = TIMER3_BASE },
    .clk_id       = RCC_TIMER3,
    .type         = TIMER_GENERAL,
    .handlers     = { [NR_GEN_HANDLERS - 1] = 0 },
};
/** Timer 3 device (general-purpose) */
timer_dev *TIMER3 = &timer3;

static timer_dev timer4 = {
    .regs         = { .gen = TIMER4_BASE },
    .clk_id       = RCC_TIMER4,
    .type         = TIMER_GENERAL,
    .handlers     = { [NR_GEN_HANDLERS - 1] = 0 },
};
/** Timer 4 device (general-purpose) */
timer_dev *TIMER4 = &timer4;

#ifdef STM32_HIGH_DENSITY
static timer_dev timer5 = {
    .regs         = { .gen = TIMER5_BASE },
    .clk_id       = RCC_TIMER5,
    .type         = TIMER_GENERAL,
    .handlers     = { [NR_GEN_HANDLERS - 1] = 0 },
};
/** Timer 5 device (general-purpose) */
timer_dev *TIMER5 = &timer5;

static timer_dev timer6 = {
    .regs         = { .bas = TIMER6_BASE },
    .clk_id       = RCC_TIMER6,
    .type         = TIMER_BASIC,
    .handlers     = { [NR_BAS_HANDLERS - 1] = 0 },
};
/** Timer 6 device (basic) */
timer_dev *TIMER6 = &timer6;

static timer_dev timer7 = {
    .regs         = { .bas = TIMER7_BASE },
    .clk_id       = RCC_TIMER7,
    .type         = TIMER_BASIC,
    .handlers     = { [NR_BAS_HANDLERS - 1] = 0 },
};
/** Timer 7 device (basic) */
timer_dev *TIMER7 = &timer7;

static timer_dev timer8 = {
    .regs         = { .adv = TIMER8_BASE },
    .clk_id       = RCC_TIMER8,
    .type         = TIMER_ADVANCED,
    .handlers     = { [NR_ADV_HANDLERS - 1] = 0 },
};
/** Timer 8 device (advanced) */
timer_dev *TIMER8 = &timer8;
#endif

/*
 * Convenience routines
 */

static void disable_channel(timer_dev *dev, uint8 channel);
static void pwm_mode(timer_dev *dev, uint8 channel);
static void output_compare_mode(timer_dev *dev, uint8 channel);

static inline void enable_irq(timer_dev *dev, uint8 interrupt);

/**
 * Initialize a timer, and reset its register map.
 * @param dev Timer to initialize
 */
void timer_init(timer_dev *dev) {
    rcc_clk_enable(dev->clk_id);
    rcc_reset_dev(dev->clk_id);
}

/**
 * @brief Disable a timer.
 *
 * The timer will stop counting, all DMA requests and interrupts will
 * be disabled, and no state changes will be output.
 *
 * @param dev Timer to disable.
 */
void timer_disable(timer_dev *dev) {
    (dev->regs).bas->CR1 = 0;
    (dev->regs).bas->DIER = 0;
    switch (dev->type) {
    case TIMER_ADVANCED:        /* fall-through */
    case TIMER_GENERAL:
        (dev->regs).gen->CCER = 0;
        break;
    case TIMER_BASIC:
        break;
    }
}

/**
 * Sets the mode of an individual timer channel.
 *
 * Note that not all timers can be configured in every mode.  For
 * example, basic timers cannot be configured to output compare mode.
 * Be sure to use a timer which is appropriate for the mode you want.
 *
 * @param dev Timer whose channel mode to set
 * @param channel Relevant channel
 * @param mode New timer mode for channel
 */
void timer_set_mode(timer_dev *dev, uint8 channel, timer_mode mode) {
    ASSERT_FAULT(channel > 0 && channel <= 4);

    /* TODO decide about the basic timers */
    ASSERT(dev->type != TIMER_BASIC);
    if (dev->type == TIMER_BASIC)
        return;

    switch (mode) {
    case TIMER_DISABLED:
        disable_channel(dev, channel);
        break;
    case TIMER_PWM:
        pwm_mode(dev, channel);
        break;
    case TIMER_OUTPUT_COMPARE:
        output_compare_mode(dev, channel);
        break;
    }
}

/**
 * @brief Call a function on timer devices.
 * @param fn Function to call on each timer device.
 */
void timer_foreach(void (*fn)(timer_dev*)) {
    fn(TIMER1);
    fn(TIMER2);
    fn(TIMER3);
    fn(TIMER4);
#ifdef STM32_HIGH_DENSITY
    fn(TIMER5);
    fn(TIMER6);
    fn(TIMER7);
    fn(TIMER8);
#endif
}

/**
 * @brief Attach a timer interrupt.
 * @param dev Timer device
 * @param interrupt Interrupt number to attach to; this may be any
 *                  timer_interrupt_id or timer_channel value appropriate
 *                  for the timer.
 * @param handler Handler to attach to the given interrupt.
 * @see timer_interrupt_id
 * @see timer_channel
 */
void timer_attach_interrupt(timer_dev *dev,
                            uint8 interrupt,
                            voidFuncPtr handler) {
    dev->handlers[interrupt] = handler;
    timer_enable_irq(dev, interrupt);
    enable_irq(dev, interrupt);
}

/**
 * @brief Detach a timer interrupt.
 * @param dev Timer device
 * @param interrupt Interrupt number to detach; this may be any
 *                  timer_interrupt_id or timer_channel value appropriate
 *                  for the timer.
 * @see timer_interrupt_id
 * @see timer_channel
 */
void timer_detach_interrupt(timer_dev *dev, uint8 interrupt) {
    timer_disable_irq(dev, interrupt);
    dev->handlers[interrupt] = NULL;
}

/*
 * IRQ handlers
 */

static inline void dispatch_adv_brk(timer_dev *dev);
static inline void dispatch_adv_up(timer_dev *dev);
static inline void dispatch_adv_trg_com(timer_dev *dev);
static inline void dispatch_adv_cc(timer_dev *dev);
static inline void dispatch_general(timer_dev *dev);
static inline void dispatch_basic(timer_dev *dev);

void __irq_tim1_brk(void) {
    dispatch_adv_brk(TIMER1);
}

void __irq_tim1_up(void) {
    dispatch_adv_up(TIMER1);
}

void __irq_tim1_trg_com(void) {
    dispatch_adv_trg_com(TIMER1);
}

void __irq_tim1_cc(void) {
    dispatch_adv_cc(TIMER1);
}

void __irq_tim2(void) {
    dispatch_general(TIMER2);
}

void __irq_tim3(void) {
    dispatch_general(TIMER3);
}

void __irq_tim4(void) {
    dispatch_general(TIMER4);
}

#if defined(STM32_HIGH_DENSITY) || defined(STM32_XL_DENSITY)

void __irq_tim5(void) {
    dispatch_general(TIMER5);
}

void __irq_tim6(void) {
    dispatch_basic(TIMER6);
}

void __irq_tim7(void) {
    dispatch_basic(TIMER7);
}

void __irq_tim8_brk(void) {
    dispatch_adv_brk(TIMER8);
}

void __irq_tim8_up(void) {
    dispatch_adv_up(TIMER8);
}

void __irq_tim8_trg_com(void) {
    dispatch_adv_trg_com(TIMER8);
}

void __irq_tim8_cc(void) {
    dispatch_adv_cc(TIMER8);
}
#endif

static inline void dispatch_irq(timer_dev *dev, uint8 iid, uint8 sr_bit);
static inline void dispatch_cc_irqs(timer_dev *dev);

static inline void dispatch_adv_brk(timer_dev *dev) {
    dispatch_irq(dev, TIMER_BREAK_INTERRUPT, TIMER_SR_BIF_BIT);
}

static inline void dispatch_adv_up(timer_dev *dev) {
    dispatch_irq(dev, TIMER_UPDATE_INTERRUPT, TIMER_SR_UIF_BIT);
}

static inline void dispatch_adv_trg_com(timer_dev *dev) {
    dispatch_irq(dev, TIMER_TRG_INTERRUPT, TIMER_SR_TIF_BIT);
    dispatch_irq(dev, TIMER_COM_INTERRUPT, TIMER_SR_COMIF_BIT);
}

static inline void dispatch_adv_cc(timer_dev *dev) {
    dispatch_cc_irqs(dev);
}

static inline void dispatch_general(timer_dev *dev) {
    dispatch_irq(dev, TIMER_TRG_INTERRUPT, TIMER_SR_TIF_BIT);
    dispatch_cc_irqs(dev);
    dispatch_irq(dev, TIMER_UPDATE_INTERRUPT, TIMER_SR_UIF_BIT);
}

static inline void dispatch_basic(timer_dev *dev) {
    dispatch_irq(dev, TIMER_UPDATE_INTERRUPT, TIMER_SR_UIF_BIT);
}

static inline void dispatch_irq(timer_dev *dev, uint8 iid, uint8 sr_bit) {
    __io uint32 *sr = &(dev->regs).bas->SR;
    if (bb_peri_get_bit(sr, sr_bit)) {
        if (dev->handlers[iid])
            (dev->handlers[iid])();
        bb_peri_set_bit(sr, sr_bit, 0);
    }
}

static inline void dispatch_cc_irqs(timer_dev *dev) {
    uint32 sr = (dev->regs).gen->SR;
    uint32 sr_clear = 0;
    uint32 b;

    ASSERT_FAULT(sr & (TIMER_SR_CC1IF | TIMER_SR_CC2IF |
                       TIMER_SR_CC3IF | TIMER_SR_CC4IF));

    for (b = TIMER_SR_CC1IF_BIT; b <= TIMER_SR_CC4IF_BIT; b++) {
        uint32 mask = BIT(b);
        if (sr & mask) {
            if (dev->handlers[b])
                (dev->handlers[b])();
            sr_clear |= mask;
        }
    }

    (dev->regs).gen->SR &= ~sr_clear;
}

/*
 * Utilities
 */

static void disable_channel(timer_dev *dev, uint8 channel) {
    timer_detach_interrupt(dev, channel);
    timer_cc_disable(dev, channel);
}

static void pwm_mode(timer_dev *dev, uint8 channel) {
    timer_disable_irq(dev, channel);
    timer_oc_set_mode(dev, channel, TIMER_OC_MODE_PWM_1, TIMER_OC_PE);
    timer_cc_enable(dev, channel);
}

static void output_compare_mode(timer_dev *dev, uint8 channel) {
    timer_oc_set_mode(dev, channel, TIMER_OC_MODE_ACTIVE_ON_MATCH, 0);
    timer_cc_enable(dev, channel);
}

static void enable_advanced_irq(timer_dev *dev, timer_interrupt_id id);
static void enable_nonmuxed_irq(timer_dev *dev);

static inline void enable_irq(timer_dev *dev, timer_interrupt_id iid) {
    if (dev->type == TIMER_ADVANCED) {
        enable_advanced_irq(dev, iid);
    } else {
        enable_nonmuxed_irq(dev);
    }
}

static void enable_advanced_irq(timer_dev *dev, timer_interrupt_id id) {
    uint8 is_timer1 = dev->clk_id == RCC_TIMER1;

    switch (id) {
    case TIMER_UPDATE_INTERRUPT:
        nvic_irq_enable(is_timer1 ? NVIC_TIMER1_UP : NVIC_TIMER8_UP);
        break;
    case TIMER_CC1_INTERRUPT:
    case TIMER_CC2_INTERRUPT:
    case TIMER_CC3_INTERRUPT:
    case TIMER_CC4_INTERRUPT:
        nvic_irq_enable(is_timer1 ? NVIC_TIMER1_CC : NVIC_TIMER8_CC);
        break;
    case TIMER_COM_INTERRUPT:
    case TIMER_TRG_INTERRUPT:
        nvic_irq_enable(is_timer1 ? NVIC_TIMER1_TRG_COM : NVIC_TIMER8_TRG_COM);
        break;
    case TIMER_BREAK_INTERRUPT:
        nvic_irq_enable(is_timer1 ? NVIC_TIMER1_BRK : NVIC_TIMER8_BRK);
        break;
    }
}

static void enable_nonmuxed_irq(timer_dev *dev) {
    switch (dev->clk_id) {
    case RCC_TIMER2:
        nvic_irq_enable(NVIC_TIMER2);
        break;
    case RCC_TIMER3:
        nvic_irq_enable(NVIC_TIMER3);
        break;
    case RCC_TIMER4:
        nvic_irq_enable(NVIC_TIMER4);
        break;
#ifdef STM32_HIGH_DENSITY
    case RCC_TIMER5:
        nvic_irq_enable(NVIC_TIMER5);
        break;
    case RCC_TIMER6:
        nvic_irq_enable(NVIC_TIMER6);
        break;
    case RCC_TIMER7:
        nvic_irq_enable(NVIC_TIMER7);
        break;
#endif
    default:
        ASSERT_FAULT(0);
        break;
    }
}
