/***************************************************************************
 *
 * Copyright 2015-2019 BES.
 * All rights reserved. All unpublished rights reserved.
 *
 * No part of this work may be used or reproduced in any form or by any
 * means, or stored in a database or retrieval system, without prior written
 * permission of BES.
 *
 * Use of this work is governed by a license granted by BES.
 * This work contains confidential and proprietary information of
 * BES. which is protected by copyright, trade secret,
 * trademark and other intellectual property rights.
 *
 ****************************************************************************/
#include "plat_addr_map.h"
#include "hal_timer.h"
#define IGNORE_HAL_TIMER_RAW_API_CHECK
#include "hal_timer_raw.h"
#include "reg_timer.h"
#include "hal_location.h"
#include "hal_cmu.h"
#include "cmsis_nvic.h"

//#define ELAPSED_TIMER_ENABLED

#if defined(CHIP_BEST3001) || defined(CHIP_BEST3003) || defined(CHIP_BEST3005) || defined(CHIP_BEST1400) || defined(CHIP_BEST1402)
#define CLOCK_SYNC_WORKAROUND
#endif

#ifdef LOW_SYS_FREQ
#if defined(CHIP_BEST1305) || defined(CHIP_BEST1501) || \
        defined(CHIP_BEST2300) || defined(CHIP_BEST2300P) || defined(CHIP_BEST2300A)
#define FAST_TIMER_WORKAROUND
#endif
#endif

#if defined(__FPU_USED) && (__FPU_USED == 1)
//#define TIMER_USE_FPU
#endif

#define SLOW_TIMER_VAL_DELTA        1
#define SLOW_TIMER_VAL_DELTA_SLEEP  10
#define FAST_TIMER_VAL_DELTA        20

#ifdef CALIB_SLOW_TIMER
#define MAX_CALIB_SYSTICK_HZ        (CONFIG_SYSTICK_HZ_NOMINAL * 2)

#define MIN_CALIB_TICKS             (10 * (CONFIG_SYSTICK_HZ_NOMINAL / 1000))

#define MAX_CALIB_TICKS             (30 * CONFIG_SYSTICK_HZ_NOMINAL)

static uint32_t BOOT_DATA_LOC sys_tick_hz = CONFIG_SYSTICK_HZ_NOMINAL;
static uint32_t BOOT_BSS_LOC slow_val;
static uint32_t BOOT_BSS_LOC fast_val;
#endif

static struct DUAL_TIMER_T * const BOOT_RODATA_SRAM_LOC dual_timer0 = (struct DUAL_TIMER_T *)TIMER0_BASE;
#ifdef TIMER1_BASE
static struct DUAL_TIMER_T * const BOOT_RODATA_SRAM_LOC dual_timer1 = (struct DUAL_TIMER_T *)TIMER1_BASE;
#endif

static HAL_TIMER_IRQ_HANDLER_T irq_handler = NULL;

//static uint32_t load_value = 0;
static uint32_t start_time;

static void POSSIBLY_UNUSED hal_timer00_irq_handler(void);
static void hal_timer01_irq_handler(void);

__STATIC_FORCEINLINE uint32_t get_timer_value(struct TIMER_T *timer, uint32_t delta)
{
#ifdef CLOCK_SYNC_WORKAROUND
    uint32_t lock;
    uint32_t v1, v2;

    lock = int_lock();
    do {
        v1 = timer->Value;
        v2 = timer->Value;
    } while ((v1 < v2) || (v1 > v2 + delta));
    int_unlock(lock);

    return v2;
#else
    return timer->Value;
#endif
}

__STATIC_FORCEINLINE void clear_timer_irq(struct TIMER_T *timer)
{
#ifdef CLOCK_SYNC_WORKAROUND
    do {
        timer->IntClr = 1;
    } while (timer->RIS & TIMER_RIS_RIS);
#else
    timer->IntClr = 1;
#endif
}

__STATIC_FORCEINLINE void set_timer_load(struct TIMER_T *timer, uint32_t load, uint32_t delta)
{
#ifdef CLOCK_SYNC_WORKAROUND
    uint32_t lock;
    uint32_t val;

    lock = int_lock();
    do {
        timer->Load = load;
        val = timer->Value;
    } while ((load < val) || (load > val + delta));
    int_unlock(lock);
#else
    timer->Load = load;
#endif
}

__STATIC_FORCEINLINE void fast_timer_open(void)
{
#ifdef TIMER1_BASE
    hal_cmu_timer1_select_fast();
    dual_timer1->timer[0].Control = TIMER_CTRL_EN | TIMER_CTRL_PRESCALE_DIV_1 | TIMER_CTRL_SIZE_32_BIT;
#endif
}

void BOOT_TEXT_FLASH_LOC hal_sys_timer_open(void)
{
    hal_cmu_timer0_select_slow();
    dual_timer0->timer[0].Control = TIMER_CTRL_EN | TIMER_CTRL_PRESCALE_DIV_1 | TIMER_CTRL_SIZE_32_BIT;
    fast_timer_open();;
}

#ifdef CORE_SLEEP_POWER_DOWN
void SRAM_TEXT_LOC hal_sys_timer_wakeup(void)
{
    fast_timer_open();;
}
#endif

uint32_t BOOT_TEXT_SRAM_LOC hal_sys_timer_get(void)
{
    return -get_timer_value(&dual_timer0->timer[0], SLOW_TIMER_VAL_DELTA);
}

#ifdef CLOCK_SYNC_WORKAROUND
uint32_t SRAM_TEXT_LOC hal_sys_timer_get_in_sleep(void)
{
    return -get_timer_value(&dual_timer0->timer[0], SLOW_TIMER_VAL_DELTA_SLEEP);
}
#else
uint32_t hal_sys_timer_get_in_sleep(void) __attribute__((alias("hal_sys_timer_get")));
#endif

uint32_t BOOT_TEXT_FLASH_LOC flash_hal_sys_timer_get(void)
{
    return -get_timer_value(&dual_timer0->timer[0], SLOW_TIMER_VAL_DELTA);
}

uint32_t BOOT_TEXT_SRAM_LOC hal_sys_ms_get(void)
{
    return GET_CURRENT_MS();
}

uint32_t BOOT_TEXT_SRAM_LOC hal_fast_sys_timer_get(void)
{
#ifdef TIMER1_BASE
#ifdef FAST_TIMER_WORKAROUND
    if (hal_cmu_fast_timer_offline()) {
#ifdef TIMER_USE_FPU
        return (uint32_t)(hal_sys_timer_get() * ((float)CONFIG_FAST_SYSTICK_HZ / CONFIG_SYSTICK_HZ));
#else
        return (uint32_t)(hal_sys_timer_get() * (uint64_t)CONFIG_FAST_SYSTICK_HZ / CONFIG_SYSTICK_HZ);
#endif
    }
#endif // FAST_TIMER_WORKAROUND
    return -get_timer_value(&dual_timer1->timer[0], FAST_TIMER_VAL_DELTA);
#else
    return 0;
#endif
}

uint32_t hal_sys_timer_get_max(void)
{
    return 0xFFFFFFFF;
}

void BOOT_TEXT_SRAM_LOC hal_sys_timer_delay(uint32_t ticks)
{
    uint32_t start = hal_sys_timer_get();

    while (hal_sys_timer_get() - start < ticks);
}

#ifdef CLOCK_SYNC_WORKAROUND
void SRAM_TEXT_LOC hal_sys_timer_delay_in_sleep(uint32_t ticks)
{
    uint32_t start = hal_sys_timer_get_in_sleep();

    while (hal_sys_timer_get_in_sleep() - start < ticks);
}
#else
void hal_sys_timer_delay_in_sleep(uint32_t ticks) __attribute__((alias("hal_sys_timer_delay")));
#endif

void BOOT_TEXT_FLASH_LOC flash_hal_sys_timer_delay(uint32_t ticks)
{
    uint32_t start = flash_hal_sys_timer_get();

    while (flash_hal_sys_timer_get() - start < ticks);
}

void BOOT_TEXT_SRAM_LOC hal_sys_timer_delay_us(uint32_t us)
{
#ifdef TIMER1_BASE
#ifdef FAST_TIMER_WORKAROUND
    if (hal_cmu_fast_timer_offline()) {
        uint32_t start = hal_sys_timer_get();
        uint32_t ticks = US_TO_TICKS(us);

        while (hal_sys_timer_get() - start < ticks);
    }
#endif // FAST_TIMER_WORKAROUND

    uint32_t start = hal_fast_sys_timer_get();
    uint32_t ticks = US_TO_FAST_TICKS(us);

    while (hal_fast_sys_timer_get() - start < ticks);
#else
    enum HAL_CMU_FREQ_T freq = hal_cmu_sys_get_freq();
    uint32_t loop;
    uint32_t i;

    // Assuming:
    // 1) system clock uses audio PLL
    // 2) audio PLL is configured as 48K series, 196.608M
    // 3) crystal is 26M

    if (freq == HAL_CMU_FREQ_208M) {
        loop = 197;
    } else if (freq == HAL_CMU_FREQ_104M) {
        loop = 197 / 2;
    } else if (freq == HAL_CMU_FREQ_78M) {
        loop = 197 / 3;
    } else if (freq == HAL_CMU_FREQ_52M) {
        loop = 52;
    } else {
        loop = 26;
    }

    loop = loop * us / 5;
    for (i = 0; i < loop; i++) {
        asm volatile("nop");
    }
#endif
}

void SRAM_TEXT_LOC hal_sys_timer_delay_ns(uint32_t ns)
{
#ifdef TIMER1_BASE
#ifdef FAST_TIMER_WORKAROUND
    if (hal_cmu_fast_timer_offline()) {
        uint32_t start = hal_sys_timer_get();
        uint32_t ticks = US_TO_TICKS((ns + (1000 - 1)) / 1000);

        while (hal_sys_timer_get() - start < ticks);
    }
#endif // FAST_TIMER_WORKAROUND

    uint32_t start = hal_fast_sys_timer_get();
    uint32_t ticks = NS_TO_FAST_TICKS(ns);

    while (hal_fast_sys_timer_get() - start < ticks);
#else
    enum HAL_CMU_FREQ_T freq = hal_cmu_sys_get_freq();
    uint32_t loop;
    uint32_t i;

    // Assuming:
    // 1) system clock uses audio PLL
    // 2) audio PLL is configured as 48K series, 196.608M
    // 3) crystal is 26M

    if (freq == HAL_CMU_FREQ_208M) {
        loop = 197;
    } else if (freq == HAL_CMU_FREQ_104M) {
        loop = 197 / 2;
    } else if (freq == HAL_CMU_FREQ_78M) {
        loop = 197 / 3;
    } else if (freq == HAL_CMU_FREQ_52M) {
        loop = 52;
    } else {
        loop = 26;
    }

    loop = loop * ns / 5000;
    for (i = 0; i < loop; i++) {
        asm volatile("nop");
    }
#endif
}

static uint32_t NOINLINE SRAM_TEXT_DEF(measure_cpu_freq_interval)(uint32_t cnt)
{
    uint32_t start;
    struct DUAL_TIMER_T *t;
    uint32_t delta;

#ifdef TIMER1_BASE
    t = dual_timer1;
    delta = FAST_TIMER_VAL_DELTA;
#ifdef FAST_TIMER_WORKAROUND
    if (hal_cmu_fast_timer_offline()) {
        t = dual_timer0;
        delta = SLOW_TIMER_VAL_DELTA;
    }
#endif // FAST_TIMER_WORKAROUND
#else
    t = dual_timer0;
    delta = SLOW_TIMER_VAL_DELTA;
#endif

    start = get_timer_value(&t->timer[0], delta);

    asm volatile(
        "_loop:;"
#ifdef __ARM_ARCH_ISA_ARM
        "nop;"
        "nop;"
#endif
        "subs %0, #1;"
        "cmp %0, #0;"
        "bne _loop;"
        : : "r"(cnt));

    return start - get_timer_value(&t->timer[0], delta);
}

uint32_t hal_sys_timer_calc_cpu_freq(uint32_t interval_ms, int high_res)
{
    uint32_t ref_freq;
    uint32_t cnt;
    uint32_t one_sec;
    uint32_t lock;
    uint32_t run_interval;
    uint32_t base_interval;
    uint32_t freq;

    // Default measurement interval
    if (interval_ms == 0) {
#ifdef TIMER1_BASE
        interval_ms = 10;
#else
        interval_ms = 100;
#endif
    }

    ref_freq = hal_cmu_get_crystal_freq();
    // CPU loop cycle count
    cnt = ref_freq / 4 * interval_ms / 1000;

    // Timer ticks per second
#ifdef TIMER1_BASE
    one_sec = CONFIG_FAST_SYSTICK_HZ;
#ifdef FAST_TIMER_WORKAROUND
    if (hal_cmu_fast_timer_offline()) {
        one_sec = CONFIG_SYSTICK_HZ;
    }
#endif // FAST_TIMER_WORKAROUND
#else
    if (high_res) {
        one_sec = CONFIG_FAST_SYSTICK_HZ;
    } else {
        one_sec = CONFIG_SYSTICK_HZ;
    }
#endif
    // Timer ticks per measurement interval
    base_interval = one_sec * interval_ms / 1000;

    lock = int_lock();

#ifndef TIMER1_BASE
    if (high_res) {
        hal_cmu_timer0_select_fast();
    }
#endif

    run_interval = measure_cpu_freq_interval(cnt);

#ifndef TIMER1_BASE
    if (high_res) {
        hal_cmu_timer0_select_slow();
    }
#endif

    int_unlock(lock);

#ifdef TIMER_USE_FPU
    freq = (uint32_t)((float)ref_freq / run_interval * base_interval);
#else
    freq = (uint32_t)((uint64_t)ref_freq * base_interval / run_interval);
#endif

    if (high_res == 0) {
        freq = (freq + 500000) / 1000000 * 1000000;
    }

    return freq;
}

#ifdef CALIB_SLOW_TIMER
void hal_sys_timer_calib_start(void)
{
    uint32_t lock;
    uint32_t slow;
    uint32_t fast;

    lock = int_lock();
    slow = hal_sys_timer_get();
    while (hal_sys_timer_get() == slow);
    fast = hal_fast_sys_timer_get();
    int_unlock(lock);

    slow_val = slow + 1;
    fast_val = fast;
}

int hal_sys_timer_calib_end(void)
{
    uint32_t lock;
    uint32_t slow;
    uint32_t fast;
    uint32_t slow_diff;

    lock = int_lock();
    slow = hal_sys_timer_get();
    while (hal_sys_timer_get() == slow);
    fast = hal_fast_sys_timer_get();
    int_unlock(lock);

    slow += 1;
    slow_diff = slow - slow_val;

    // Avoid computation error
    if (slow_diff < MIN_CALIB_TICKS) {
        return 1;
    }
    // Avoid fast tick overflow
    if (slow_diff > MAX_CALIB_TICKS) {
        return 2;
    }

#ifdef TIMER_USE_FPU
    sys_tick_hz = (uint32_t)((float)CONFIG_FAST_SYSTICK_HZ / (fast - fast_val) * slow_diff);
#else
    uint64_t mul;

    mul = (uint64_t)CONFIG_FAST_SYSTICK_HZ * slow_diff;
    if ((mul >> 32) == 0) {
        sys_tick_hz = (uint32_t)mul / (fast - fast_val);
    } else {
        sys_tick_hz = mul / (fast - fast_val);
    }
#endif

    if (sys_tick_hz > MAX_CALIB_SYSTICK_HZ) {
        sys_tick_hz = MAX_CALIB_SYSTICK_HZ;
    }

    return 0;
}

void hal_sys_timer_calib(void)
{
    hal_sys_timer_calib_start();
    hal_sys_timer_delay(MIN_CALIB_TICKS);
    hal_sys_timer_calib_end();
}

uint32_t BOOT_TEXT_SRAM_LOC hal_sys_timer_systick_hz(void)
{
    return sys_tick_hz;
}

uint32_t BOOT_TEXT_SRAM_LOC hal_sys_timer_ms_to_ticks(uint32_t ms)
{
    if (ms <= (~0UL / MAX_CALIB_SYSTICK_HZ)) {
        return (ms * sys_tick_hz / 1000);
    } else {
#ifdef TIMER_USE_FPU
        return (uint32_t)((float)ms / 1000 * sys_tick_hz);
#else
        return ((uint64_t)ms * sys_tick_hz / 1000);
#endif
    }
}

uint32_t BOOT_TEXT_SRAM_LOC hal_sys_timer_us_to_ticks(uint32_t us)
{
    if (us <= (~0UL / MAX_CALIB_SYSTICK_HZ)) {
        return ((us * sys_tick_hz / 1000 + 1000 - 1) / 1000 + 1);
    } else {
#ifdef TIMER_USE_FPU
        return (uint32_t)((float)us / (1000 * 1000) * sys_tick_hz + 1 + 1);
#else
        return (((uint64_t)us * sys_tick_hz / 1000 + 1000 - 1) / 1000 + 1);
#endif
    }
}

uint32_t BOOT_TEXT_SRAM_LOC hal_sys_timer_ticks_to_ms(uint32_t tick)
{
    if (tick <= (~0UL / 1000)) {
        return tick * 1000 / CONFIG_SYSTICK_HZ;
    } else {
#ifdef TIMER_USE_FPU
        return (uint32_t)((float)tick / CONFIG_SYSTICK_HZ * 1000);
#else
        return (uint64_t)tick * 1000 / CONFIG_SYSTICK_HZ;
#endif
    }
}

uint32_t BOOT_TEXT_SRAM_LOC hal_sys_timer_ticks_to_us(uint32_t tick)
{
    if (tick <= (~0UL / (1000 * 1000))) {
        return tick * (1000 * 1000) / CONFIG_SYSTICK_HZ;
    } else {
#ifdef TIMER_USE_FPU
        return (uint32_t)((float)tick / CONFIG_SYSTICK_HZ * (1000 * 1000));
#else
        return (uint64_t)tick * (1000 * 1000) / CONFIG_SYSTICK_HZ;
#endif
    }
}
#endif

#ifndef RTOS
int osDelay(uint32_t ms)
{
    hal_sys_timer_delay(MS_TO_TICKS(ms));
    return 0;
}
#endif

static void hal_timer00_irq_handler(void)
{
    clear_timer_irq(&dual_timer0->timer[0]);
    dual_timer0->timer[0].Control &= ~TIMER_CTRL_INTEN;
}

void hal_timer_setup(enum HAL_TIMER_TYPE_T type, HAL_TIMER_IRQ_HANDLER_T handler)
{
    uint32_t mode;

    if (type == HAL_TIMER_TYPE_ONESHOT) {
        mode = TIMER_CTRL_ONESHOT;
    } else if (type == HAL_TIMER_TYPE_PERIODIC) {
        mode = TIMER_CTRL_MODE_PERIODIC;
    } else {
        mode = 0;
    }

    irq_handler = handler;

    clear_timer_irq(&dual_timer0->timer[1]);
#ifdef ELAPSED_TIMER_ENABLED
    dual_timer0->elapsed_timer[1].ElapsedCtrl = TIMER_ELAPSED_CTRL_CLR;
#endif

    if (handler) {
        NVIC_SetVector(TIMER01_IRQn, (uint32_t)hal_timer01_irq_handler);
        NVIC_SetPriority(TIMER01_IRQn, IRQ_PRIORITY_NORMAL);
        NVIC_ClearPendingIRQ(TIMER01_IRQn);
        NVIC_EnableIRQ(TIMER01_IRQn);
    }

    dual_timer0->timer[1].Control = mode |
                                   (handler ? TIMER_CTRL_INTEN : 0) |
                                   TIMER_CTRL_PRESCALE_DIV_1 |
                                   TIMER_CTRL_SIZE_32_BIT;
}

void hal_timer_start(uint32_t load)
{
    start_time = hal_sys_timer_get();
    hal_timer_reload(load);
    hal_timer_continue();
}

void hal_timer_stop(void)
{
    dual_timer0->timer[1].Control &= ~TIMER_CTRL_EN;
#ifdef ELAPSED_TIMER_ENABLED
    dual_timer0->elapsed_timer[1].ElapsedCtrl = TIMER_ELAPSED_CTRL_CLR;
#endif
    clear_timer_irq(&dual_timer0->timer[1]);
    NVIC_ClearPendingIRQ(TIMER01_IRQn);
}

void hal_timer_continue(void)
{
#ifdef ELAPSED_TIMER_ENABLED
    dual_timer0->elapsed_timer[1].ElapsedCtrl = TIMER_ELAPSED_CTRL_EN | TIMER_ELAPSED_CTRL_CLR;
#endif
    dual_timer0->timer[1].Control |= TIMER_CTRL_EN;
}

int hal_timer_is_enabled(void)
{
    return !!(dual_timer0->timer[1].Control & TIMER_CTRL_EN);
}

void hal_timer_reload(uint32_t load)
{
    if (load > HAL_TIMER_LOAD_DELTA) {
        //load_value = load;
        load -= HAL_TIMER_LOAD_DELTA;
    } else {
        //load_value = HAL_TIMER_LOAD_DELTA + 1;
        load = 1;
    }
    set_timer_load(&dual_timer0->timer[1], load, SLOW_TIMER_VAL_DELTA);
}

uint32_t hal_timer_get(void)
{
    return get_timer_value(&dual_timer0->timer[1], SLOW_TIMER_VAL_DELTA);
}

int hal_timer_irq_active(void)
{
    return NVIC_GetActive(TIMER01_IRQn);
}

int hal_timer_irq_pending(void)
{
    // Or NVIC_GetPendingIRQ(TIMER2_IRQn) ?
    return (dual_timer0->timer[1].MIS & TIMER_MIS_MIS);
}

uint32_t hal_timer_get_overrun_time(void)
{
#ifdef ELAPSED_TIMER_ENABLED
    uint32_t extra;

    if (dual_timer0->elapsed_timer[1].ElapsedCtrl & TIMER_ELAPSED_CTRL_EN) {
        extra = dual_timer0->elapsed_timer[1].ElapsedVal;
    } else {
        extra = 0;
    }

    return extra;
#else
    return 0;
#endif
}

uint32_t hal_timer_get_elapsed_time(void)
{
    //return load_value + hal_timer_get_overrun_time();
    return hal_sys_timer_get() - start_time;
}

static void hal_timer01_irq_handler(void)
{
    uint32_t elapsed;

    clear_timer_irq(&dual_timer0->timer[1]);
    if (irq_handler) {
        elapsed = hal_timer_get_elapsed_time();
        irq_handler(elapsed);
    } else {
        dual_timer0->timer[1].Control &= ~TIMER_CTRL_INTEN;
    }
}

uint32_t hal_timer_get_passed_ticks(uint32_t curr_ticks, uint32_t prev_ticks)
{
    if(curr_ticks < prev_ticks)
        return ((0xffffffff  - prev_ticks + 1) + curr_ticks);
    else
        return (curr_ticks - prev_ticks);
}

