/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

#include <string.h>
#include <inttypes.h>
#include <assert.h>

#include "os/mynewt.h"

#include <mcu/cmsis_nvic.h>
#include <hal/hal_timer.h>
#include "mcu/stm32_hal.h"
#include "stm32_common/stm32_hal.h"

#if MYNEWT_VAL(MCU_STM32L0) || MYNEWT_VAL(MCU_STM32L1)
void TIM_Base_SetConfig(TIM_TypeDef *TIMx, TIM_Base_InitTypeDef *Structure);
void TIM_CCxChannelCmd(TIM_TypeDef *TIMx, uint32_t Channel, uint32_t ChannelState);
#endif

#define STM32_OFLOW_VALUE       (0x10000UL)
#define STM32_NSEC_PER_SEC      (1000000000UL)

struct stm32_hal_tmr {
    TIM_TypeDef *sht_regs;   /* Pointer to timer registers */
    uint32_t sht_oflow;      /* 16 bits of overflow to make timer 32bits */
    TAILQ_HEAD(hal_timer_qhead, hal_timer) sht_timers;
#if MYNEWT_VAL(STM32_TIMER_AUTO_OFF_COUNT)
    uint8_t sht_empty_run;
#endif
};

#if MYNEWT_VAL(TIMER_0)
struct stm32_hal_tmr stm32_tmr0;
#endif
#if MYNEWT_VAL(TIMER_1)
struct stm32_hal_tmr stm32_tmr1;
#endif
#if MYNEWT_VAL(TIMER_2)
struct stm32_hal_tmr stm32_tmr2;
#endif

static struct stm32_hal_tmr *stm32_tmr_devs[STM32_HAL_TIMER_MAX] = {
#if MYNEWT_VAL(TIMER_0)
    &stm32_tmr0,
#else
    NULL,
#endif
#if MYNEWT_VAL(TIMER_1)
    &stm32_tmr1,
#else
    NULL,
#endif
#if MYNEWT_VAL(TIMER_2)
    &stm32_tmr2,
#else
    NULL,
#endif
};

static uint32_t hal_timer_cnt(struct stm32_hal_tmr *tmr);

#if (MYNEWT_VAL(TIMER_0) || MYNEWT_VAL(TIMER_1) || MYNEWT_VAL(TIMER_2))
/*
 * Call expired timer callbacks, and reprogram timer with new expiry time.
 */
static void
stm32_tmr_cbs(struct stm32_hal_tmr *tmr)
{
    uint32_t cnt;
    struct hal_timer *ht;

    while ((ht = TAILQ_FIRST(&tmr->sht_timers)) != NULL) {
        cnt = hal_timer_cnt(tmr);
        if (((int32_t)(cnt - ht->expiry)) >= 0) {
            TAILQ_REMOVE(&tmr->sht_timers, ht, link);
            ht->link.tqe_prev = NULL;
            ht->cb_func(ht->cb_arg);
        } else {
            break;
        }
    }
    ht = TAILQ_FIRST(&tmr->sht_timers);
    if (ht) {
#if MYNEWT_VAL(STM32_TIMER_AUTO_OFF_COUNT)
        tmr->sht_empty_run = 0;
#endif
        tmr->sht_regs->CCR1 = ht->expiry & 0xFFFFU;
    } else {
        tmr->sht_regs->DIER &= ~TIM_DIER_CC1IE;
    }
}

/*
 * timer irq handler
 *
 * Generic HAL timer irq handler.
 *
 * @param tmr
 */
static void
stm32_tmr_irq(struct stm32_hal_tmr *tmr)
{
    uint32_t sr;
    uint32_t clr = 0;

    sr = tmr->sht_regs->SR;
    if (sr & TIM_SR_UIF) {
        /*
         * Overflow interrupt
         */
        tmr->sht_oflow += STM32_OFLOW_VALUE;
        clr |= TIM_SR_UIF;
#if MYNEWT_VAL(STM32_TIMER_AUTO_OFF_COUNT)
        tmr->sht_empty_run++;
#endif
    }
    if (sr & TIM_SR_CC1IF) {
        /*
         * Capture event
         */
        clr |= TIM_SR_CC1IF;
        stm32_tmr_cbs(tmr);
    }

    tmr->sht_regs->SR = ~clr;

#if MYNEWT_VAL(STM32_TIMER_AUTO_OFF_COUNT)
    if (tmr->sht_empty_run >= MYNEWT_VAL(STM32_TIMER_AUTO_OFF_COUNT)) {
        /* Timer oveflowed few times without any usage, disable it till it used again */
        tmr->sht_regs->CR1 &= ~TIM_CR1_CEN;
    }
#endif
}
#endif

#if MYNEWT_VAL(TIMER_0)
static void
stm32_tmr0_irq(void)
{
    stm32_tmr_irq(&stm32_tmr0);
}
#endif

#if MYNEWT_VAL(TIMER_1)
static void
stm32_tmr1_irq(void)
{
    stm32_tmr_irq(&stm32_tmr1);
}
#endif

#if MYNEWT_VAL(TIMER_2)
static void
stm32_tmr2_irq(void)
{
    stm32_tmr_irq(&stm32_tmr2);
}
#endif

static void
stm32_tmr_reg_irq(IRQn_Type irqn, uint32_t func)
{
    NVIC_SetPriority(irqn, (1 << __NVIC_PRIO_BITS) - 1);
    NVIC_SetVector(irqn, func);
    NVIC_EnableIRQ(irqn);
}

static void
stm32_hw_setup(int num, TIM_TypeDef *regs)
{
    uint32_t func;

    switch (num) {
#if MYNEWT_VAL(TIMER_0)
    case 0:
        func = (uint32_t)stm32_tmr0_irq;
        break;
#endif
#if MYNEWT_VAL(TIMER_1)
    case 1:
        func = (uint32_t)stm32_tmr1_irq;
        break;
#endif
#if MYNEWT_VAL(TIMER_2)
    case 2:
        func = (uint32_t)stm32_tmr2_irq;
        break;
#endif
    default:
        assert(0);
        return;
    }

#ifdef TIM1
    if (regs == TIM1) {
        stm32_tmr_reg_irq(STM32_HAL_TIMER_TIM1_IRQ, func);
        __HAL_RCC_TIM1_CLK_ENABLE();
    }
#endif
#ifdef TIM2
    if (regs == TIM2) {
        stm32_tmr_reg_irq(STM32_HAL_TIMER_TIM2_IRQ, func);
        __HAL_RCC_TIM2_CLK_ENABLE();
    }
#endif
#ifdef TIM3
    if (regs == TIM3) {
        stm32_tmr_reg_irq(STM32_HAL_TIMER_TIM3_IRQ, func);
        __HAL_RCC_TIM3_CLK_ENABLE();
    }
#endif
#ifdef TIM4
    if (regs == TIM4) {
        stm32_tmr_reg_irq(STM32_HAL_TIMER_TIM4_IRQ, func);
        __HAL_RCC_TIM4_CLK_ENABLE();
    }
#endif
#ifdef TIM6
    if (regs == TIM6) {
        stm32_tmr_reg_irq(STM32_HAL_TIMER_TIM6_IRQ, func);
        __HAL_RCC_TIM6_CLK_ENABLE();
    }
#endif
#ifdef TIM7
    if (regs == TIM7) {
        stm32_tmr_reg_irq(STM32_HAL_TIMER_TIM7_IRQ, func);
        __HAL_RCC_TIM7_CLK_ENABLE();
    }
#endif
#ifdef TIM8
    if (regs == TIM8) {
        stm32_tmr_reg_irq(STM32_HAL_TIMER_TIM8_IRQ, func);
        __HAL_RCC_TIM8_CLK_ENABLE();
    }
#endif
#ifdef TIM9
    if (regs == TIM9) {
        stm32_tmr_reg_irq(STM32_HAL_TIMER_TIM9_IRQ, func);
        __HAL_RCC_TIM9_CLK_ENABLE();
    }
#endif
#ifdef TIM10
    if (regs == TIM10) {
        stm32_tmr_reg_irq(STM32_HAL_TIMER_TIM10_IRQ, func);
        __HAL_RCC_TIM10_CLK_ENABLE();
    }
#endif
#ifdef TIM11
    if (regs == TIM11) {
        stm32_tmr_reg_irq(STM32_HAL_TIMER_TIM11_IRQ, func);
        __HAL_RCC_TIM11_CLK_ENABLE();
    }
#endif
#ifdef TIM12
    if (regs == TIM12) {
        stm32_tmr_reg_irq(STM32_HAL_TIMER_TIM12_IRQ, func);
        __HAL_RCC_TIM12_CLK_ENABLE();
    }
#endif
#ifdef TIM13
    if (regs == TIM13) {
        stm32_tmr_reg_irq(STM32_HAL_TIMER_TIM13_IRQ, func);
        __HAL_RCC_TIM13_CLK_ENABLE();
    }
#endif
#ifdef TIM14
    if (regs == TIM14) {
        stm32_tmr_reg_irq(STM32_HAL_TIMER_TIM14_IRQ, func);
        __HAL_RCC_TIM14_CLK_ENABLE();
    }
#endif
#ifdef TIM15
    if (regs == TIM15) {
        stm32_tmr_reg_irq(STM32_HAL_TIMER_TIM15_IRQ, func);
        __HAL_RCC_TIM15_CLK_ENABLE();
    }
#endif
#ifdef TIM16
    if (regs == TIM16) {
        stm32_tmr_reg_irq(STM32_HAL_TIMER_TIM16_IRQ, func);
        __HAL_RCC_TIM16_CLK_ENABLE();
    }
#endif
#ifdef TIM17
    if (regs == TIM17) {
        stm32_tmr_reg_irq(STM32_HAL_TIMER_TIM17_IRQ, func);
        __HAL_RCC_TIM17_CLK_ENABLE();
    }
#endif
#ifdef TIM21
    if (regs == TIM21) {
        stm32_tmr_reg_irq(STM32_HAL_TIMER_TIM21_IRQ, func);
        __HAL_RCC_TIM21_CLK_ENABLE();
    }
#endif
#ifdef TIM22
    if (regs == TIM22) {
        stm32_tmr_reg_irq(STM32_HAL_TIMER_TIM22_IRQ, func);
        __HAL_RCC_TIM22_CLK_ENABLE();
    }
#endif
}

static void
stm32_hw_setdown(TIM_TypeDef *regs)
{
#ifdef TIM1
    if (regs == TIM1) {
        __HAL_RCC_TIM1_CLK_DISABLE();
    }
#endif
#ifdef TIM2
    if (regs == TIM2) {
        __HAL_RCC_TIM2_CLK_DISABLE();
    }
#endif
#ifdef TIM3
    if (regs == TIM3) {
        __HAL_RCC_TIM3_CLK_DISABLE();
    }
#endif
#ifdef TIM4
    if (regs == TIM4) {
        __HAL_RCC_TIM4_CLK_DISABLE();
    }
#endif
#ifdef TIM8
    if (regs == TIM8) {
        __HAL_RCC_TIM8_CLK_DISABLE();
    }
#endif
#ifdef TIM9
    if (regs == TIM9) {
        __HAL_RCC_TIM9_CLK_DISABLE();
    }
#endif
#ifdef TIM10
    if (regs == TIM10) {
        __HAL_RCC_TIM10_CLK_DISABLE();
    }
#endif
#ifdef TIM11
    if (regs == TIM11) {
        __HAL_RCC_TIM11_CLK_DISABLE();
    }
#endif
#ifdef TIM15
    if (regs == TIM15) {
        __HAL_RCC_TIM15_CLK_DISABLE();
    }
#endif
#ifdef TIM16
    if (regs == TIM16) {
        __HAL_RCC_TIM16_CLK_DISABLE();
    }
#endif
#ifdef TIM17
    if (regs == TIM17) {
        __HAL_RCC_TIM17_CLK_DISABLE();
    }
#endif
#ifdef TIM21
    if (regs == TIM21) {
        __HAL_RCC_TIM21_CLK_DISABLE();
    }
#endif
#ifdef TIM22
    if (regs == TIM22) {
        __HAL_RCC_TIM22_CLK_DISABLE();
    }
#endif
}

/**
 * hal timer init
 *
 * Initialize platform specific timer items
 *
 * @param timer_num     Timer number to initialize
 * @param cfg           Pointer to platform specific configuration
 *
 * @return int          0: success; error code otherwise
 */
int
hal_timer_init(int num, void *cfg)
{
    struct stm32_hal_tmr *tmr;
    TIM_TypeDef *regs;

    if (num >= STM32_HAL_TIMER_MAX || !(tmr = stm32_tmr_devs[num]) ||
        (cfg == NULL)) {
        return -1;
    }

    regs = tmr->sht_regs = (TIM_TypeDef *)cfg;

    if (!IS_TIM_CC1_INSTANCE(regs)) {
        return -1;
    }

    stm32_hw_setup(num, regs);

    /*
     * Stop the timers at debugger.
     */
#ifdef TIM1
    if (regs == TIM1) {
        __HAL_DBGMCU_FREEZE_TIM1();
    }
#endif
#ifdef TIM2
    if (regs == TIM2) {
        __HAL_DBGMCU_FREEZE_TIM2();
    }
#endif
#ifdef TIM3
    if (regs == TIM3) {
        __HAL_DBGMCU_FREEZE_TIM3();
    }
#endif
#ifdef TIM4
    if (regs == TIM4) {
        __HAL_DBGMCU_FREEZE_TIM4();
    }
#endif
#ifdef TIM8
    if (regs == TIM8) {
        __HAL_DBGMCU_FREEZE_TIM8();
    }
#endif
#ifdef TIM9
    if (regs == TIM9) {
        __HAL_DBGMCU_FREEZE_TIM9();
    }
#endif
#ifdef TIM10
    if (regs == TIM10) {
        __HAL_DBGMCU_FREEZE_TIM10();
    }
#endif
#ifdef TIM11
    if (regs == TIM11) {
        __HAL_DBGMCU_FREEZE_TIM11();
    }
#endif
#ifdef TIM15
    if (regs == TIM15) {
        __HAL_DBGMCU_FREEZE_TIM15();
    }
#endif
#ifdef TIM16
    if (regs == TIM16) {
        __HAL_DBGMCU_FREEZE_TIM16();
    }
#endif
#ifdef TIM17
    if (regs == TIM17) {
        __HAL_DBGMCU_FREEZE_TIM17();
    }
#endif
#ifdef TIM21
    if (regs == TIM21) {
        __HAL_DBGMCU_FREEZE_TIM21();
    }
#endif
#ifdef TIM22
    if (regs == TIM22) {
        __HAL_DBGMCU_FREEZE_TIM22();
    }
#endif

    return 0;
}

/**
 * hal timer config
 *
 * Configure a timer to run at the desired frequency. This starts the timer.
 *
 * @param timer_num
 * @param freq_hz
 *
 * @return int
 */
int
hal_timer_config(int num, uint32_t freq_hz)
{
    struct stm32_hal_tmr *tmr;
    TIM_Base_InitTypeDef init;
    uint32_t prescaler;

    if (num >= STM32_HAL_TIMER_MAX || !(tmr = stm32_tmr_devs[num])) {
        return -1;
    }
    if (!IS_TIM_CC1_INSTANCE(tmr->sht_regs)) {
        return -1;
    }

    prescaler = stm32_hal_timer_get_freq(tmr->sht_regs) / freq_hz;
    if (prescaler > 0xffff) {
        return -1;
    }

    memset(&init, 0, sizeof(init));
    init.Period = 0xffff;
    init.Prescaler = prescaler - 1;
    init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
    init.CounterMode = TIM_COUNTERMODE_UP;

    /*
     * Set up to count overflow interrupts.
     */
    tmr->sht_regs->CR1 = TIM_CR1_URS;
    tmr->sht_regs->DIER = TIM_DIER_UIE;

    TIM_Base_SetConfig(tmr->sht_regs, &init);

    tmr->sht_regs->SR = 0;
    tmr->sht_regs->CR1 |= TIM_CR1_CEN;

    return 0;
}

/**
 * hal timer deinit
 *
 * De-initialize a HW timer.
 *
 * @param timer_num
 *
 * @return int
 */
int
hal_timer_deinit(int num)
{
    struct stm32_hal_tmr *tmr;
    int sr;

    if (num >= STM32_HAL_TIMER_MAX || !(tmr = stm32_tmr_devs[num])) {
        return -1;
    }

    __HAL_DISABLE_INTERRUPTS(sr);
    /*
     * Turn off CC1, and then turn off the timer.
     */
    tmr->sht_regs->CR1 &= ~TIM_CR1_CEN;
    tmr->sht_regs->DIER &= ~TIM_DIER_CC1IE;
    TIM_CCxChannelCmd(tmr->sht_regs, TIM_CHANNEL_1, TIM_CCx_DISABLE);
    __HAL_ENABLE_INTERRUPTS(sr);
    stm32_hw_setdown(tmr->sht_regs);

    return 0;
}

/**
 * hal timer get resolution
 *
 * Get the resolution of the timer. This is the timer period, in nanoseconds
 *
 * @param timer_num
 *
 * @return uint32_t
 */
uint32_t
hal_timer_get_resolution(int num)
{
    struct stm32_hal_tmr *tmr;

    if (num >= STM32_HAL_TIMER_MAX || !(tmr = stm32_tmr_devs[num])) {
        return -1;
    }
    return (STM32_NSEC_PER_SEC / (SystemCoreClock / (1 + tmr->sht_regs->PSC)));
}

static uint32_t
hal_timer_cnt(struct stm32_hal_tmr *tmr)
{
    uint32_t cnt;
    int sr;

#if MYNEWT_VAL(STM32_TIMER_AUTO_OFF_COUNT)
    tmr->sht_empty_run = 0;
#endif

    __HAL_DISABLE_INTERRUPTS(sr);
    if (tmr->sht_regs->SR & TIM_SR_UIF) {
        /*
         * Just overflowed
         */
        tmr->sht_oflow += STM32_OFLOW_VALUE;
        tmr->sht_regs->SR &= ~TIM_SR_UIF;
    }
    cnt = tmr->sht_oflow + tmr->sht_regs->CNT;
    __HAL_ENABLE_INTERRUPTS(sr);

#if MYNEWT_VAL(STM32_TIMER_AUTO_OFF_COUNT)
    /* Timer could be turned off, so turn it on */
    tmr->sht_regs->CR1 |= TIM_CR1_CEN;
#endif

    return cnt;
}

/**
 * hal timer read
 *
 * Returns the timer counter. NOTE: if the timer is a 16-bit timer, only
 * the lower 16 bits are valid. If the timer is a 64-bit timer, only the
 * low 32-bits are returned.
 *
 * @return uint32_t The timer counter register.
 */
uint32_t
hal_timer_read(int num)
{
    struct stm32_hal_tmr *tmr;

    if (num >= STM32_HAL_TIMER_MAX || !(tmr = stm32_tmr_devs[num])) {
        return -1;
    }
    return hal_timer_cnt(tmr);
}

/**
 * hal timer delay
 *
 * Blocking delay for n ticks
 *
 * @param timer_num
 * @param ticks
 *
 * @return int 0 on success; error code otherwise.
 */
int
hal_timer_delay(int num, uint32_t ticks)
{
    struct stm32_hal_tmr *tmr;
    uint32_t until;

    if (num >= STM32_HAL_TIMER_MAX || !(tmr = stm32_tmr_devs[num])) {
        return -1;
    }

    until = hal_timer_cnt(tmr) + ticks;
    while ((int32_t)(hal_timer_cnt(tmr) - until) <= 0) {
        ;
    }
    return 0;
}

/**
 *
 * Initialize the HAL timer structure with the callback and the callback
 * argument. Also initializes the HW specific timer pointer.
 *
 * @param cb_func
 *
 * @return int
 */
int
hal_timer_set_cb(int num, struct hal_timer *timer, hal_timer_cb cb_func,
                 void *arg)
{
    struct stm32_hal_tmr *tmr;

    if (num >= STM32_HAL_TIMER_MAX || !(tmr = stm32_tmr_devs[num])) {
        return -1;
    }
    timer->cb_func = cb_func;
    timer->cb_arg = arg;
    timer->bsp_timer = tmr;
    timer->link.tqe_prev = NULL;

    return 0;
}

/**
 * hal_timer_start()
 *
 * Start a timer. Timer fires 'ticks' ticks from now.
 *
 * @param timer
 * @param ticks
 *
 * @return int
 */
int
hal_timer_start(struct hal_timer *timer, uint32_t ticks)
{
    struct stm32_hal_tmr *tmr;
    uint32_t tick;

    tmr = (struct stm32_hal_tmr *)timer->bsp_timer;

    tick = ticks + hal_timer_cnt(tmr);
    return hal_timer_start_at(timer, tick);
}

/**
 * hal_timer_start_at()
 *
 * Start a timer. Timer fires at tick 'tick'.
 *
 * @param timer
 * @param tick
 *
 * @return int
 */
int
hal_timer_start_at(struct hal_timer *timer, uint32_t tick)
{
    struct stm32_hal_tmr *tmr;
    struct hal_timer *ht;
    int sr;

    tmr = (struct stm32_hal_tmr *)timer->bsp_timer;

    timer->expiry = tick;

    __HAL_DISABLE_INTERRUPTS(sr);

    if (TAILQ_EMPTY(&tmr->sht_timers)) {
        TAILQ_INSERT_HEAD(&tmr->sht_timers, timer, link);
    } else {
        TAILQ_FOREACH(ht, &tmr->sht_timers, link) {
            if ((int32_t)(timer->expiry - ht->expiry) < 0) {
                TAILQ_INSERT_BEFORE(ht, timer, link);
                break;
            }
        }
        if (!ht) {
            TAILQ_INSERT_TAIL(&tmr->sht_timers, timer, link);
        }
    }

    if ((int32_t)(tick - hal_timer_cnt(tmr)) <= 0) {
        /*
         * Event in the past (should be the case if it was just inserted).
         */
        tmr->sht_regs->EGR |= TIM_EGR_CC1G;
        tmr->sht_regs->DIER |= TIM_DIER_CC1IE;
    } else {
        if (timer == TAILQ_FIRST(&tmr->sht_timers)) {
            tmr->sht_regs->CCR1 = timer->expiry & 0xFFFFU;
            tmr->sht_regs->DIER |= TIM_DIER_CC1IE;
        }
    }
    __HAL_ENABLE_INTERRUPTS(sr);

    return 0;
}

/**
 * hal_timer_stop()
 *
 * Cancels the timer.
 *
 * @param timer
 *
 * @return int
 */
int
hal_timer_stop(struct hal_timer *timer)
{
    struct stm32_hal_tmr *tmr;
    struct hal_timer *ht = NULL;
    int sr;
    int reset_ocmp;

    __HAL_DISABLE_INTERRUPTS(sr);

    tmr = (struct stm32_hal_tmr *)timer->bsp_timer;
    if (timer->link.tqe_prev != NULL) {
        reset_ocmp = 0;
        if (timer == TAILQ_FIRST(&tmr->sht_timers)) {
            /* If first on queue, we will need to reset OCMP */
            ht = TAILQ_NEXT(timer, link);
            reset_ocmp = 1;
        }
        TAILQ_REMOVE(&tmr->sht_timers, timer, link);
        timer->link.tqe_prev = NULL;
        if (reset_ocmp) {
            if (ht) {
                tmr->sht_regs->CCR1 = ht->expiry & 0xFFFFU;
            } else {
                tmr->sht_regs->DIER &= ~TIM_DIER_CC1IE;
            }
        }
    }
    __HAL_ENABLE_INTERRUPTS(sr);

    return 0;
}
