/* mbed Microcontroller Library
 * Copyright (c) 2006-2018 ARM Limited
 *
 * Licensed 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 "us_ticker_api.h"
#include "rda_ccfg_api.h"
#include "mbed_critical.h"

#define US_TICKER_TIMER             (RDA_TIM0)
#define rTIMER0_CURVAL              (RDA_TIM0->CVAL)
#define TIMER0_PRESCALE             (8)
#define TIMER0_SHIFTBITS            (3)
#define TIMER0_LDCNT_INIT_VAL       (0xFFFFFFFF)
#define TIMER0_MAX_COUNT            (0x1FFFFFFF)

#define TIMER0_CONTROL_ENABLE       (0x01)
#define TIMER0_CONTROL_MODE         (0x02)
#define TIMER0_CONTROL_INT_MSK      (0x04)


volatile uint32_t us_ticker_clrInt = 0;

static uint32_t us_ticker_inited = 0;
uint32_t us_ticker_soft_int_flag;
static uint32_t us_ticker_timestamp;
static uint32_t us_ticker_interruptCount;

extern void rda_timer_irq_set(void);

void us_ticker_init(void)
{
    if (us_ticker_inited) {
        return;
    }

    /* Enable apb timer clock */
    RDA_SCU->CLKGATE1 |= (0x01UL << 3);

    /* Set timer mode */
    US_TICKER_TIMER->TCTRL |= (TIMER0_CONTROL_MODE);

    /* Set period mode */
    RDA_GPIO->REVID |= (0x01UL << 25);

    /* Set timer count */
    US_TICKER_TIMER->LDCNT = TIMER0_LDCNT_INIT_VAL;

    /* Enable timer */
    US_TICKER_TIMER->TCTRL |= (TIMER0_CONTROL_ENABLE);

    /* mask timer, disable an overflow int */
    US_TICKER_TIMER->TCTRL |= (TIMER0_CONTROL_INT_MSK);

    rda_timer_irq_set();

    /* Set us_ticker_inited true, after all settings done */
    us_ticker_inited = 1U;
    us_ticker_soft_int_flag = 0;
    us_ticker_timestamp = 0;
    us_ticker_interruptCount = TIMER0_MAX_COUNT;
}

uint32_t us_ticker_read(void)
{
    if (!us_ticker_inited) {
        return 0 ;
    }

    uint32_t tick_readout = 0 ;

    core_util_critical_section_enter();
    uint32_t ticker = rTIMER0_CURVAL >> TIMER0_SHIFTBITS ;

    if (us_ticker_interruptCount > ticker)
        tick_readout = (us_ticker_timestamp + us_ticker_interruptCount - ticker) % TIMER0_MAX_COUNT ;
    else
        tick_readout = (us_ticker_timestamp + TIMER0_MAX_COUNT + us_ticker_interruptCount - ticker) % TIMER0_MAX_COUNT ;
    core_util_critical_section_exit();

    return tick_readout;
}

void us_ticker_set_interrupt(timestamp_t timestamp)
{
    if (!us_ticker_inited) {
        return ;
    }

    uint32_t tmp_stamp = timestamp % TIMER0_MAX_COUNT ;

    core_util_critical_section_enter();
    us_ticker_timestamp = us_ticker_read() ;
    us_ticker_interruptCount = (tmp_stamp > us_ticker_timestamp) ? (tmp_stamp - us_ticker_timestamp):(tmp_stamp + TIMER0_MAX_COUNT - us_ticker_timestamp) ;

    /* Disable timer */
    US_TICKER_TIMER->TCTRL &= (~TIMER0_CONTROL_ENABLE);

    US_TICKER_TIMER->LDCNT = us_ticker_interruptCount << TIMER0_SHIFTBITS ;

    /* Enable timer */
    US_TICKER_TIMER->TCTRL |= (TIMER0_CONTROL_ENABLE);

    /* Unmask timer, enable an overflow int */
    US_TICKER_TIMER->TCTRL &= (~(TIMER0_CONTROL_INT_MSK));
    core_util_critical_section_exit();

    return ;
}

void us_ticker_fire_interrupt(void)
{
    if (!us_ticker_inited) {
        return ;
    }

    core_util_critical_section_enter();
    us_ticker_soft_int_flag = 1 ;
    NVIC_SetPendingIRQ(TIMER_IRQn);
    core_util_critical_section_exit();
}

void us_ticker_disable_interrupt_help(void)
{
    if (!us_ticker_inited) {
        return ;
    }

    /* Mask timer, disable an overflow int */
    US_TICKER_TIMER->TCTRL |= (TIMER0_CONTROL_INT_MSK);
}

void us_ticker_disable_interrupt(void)
{
    if (!us_ticker_inited) {
        return ;
    }

    core_util_critical_section_enter();
    /* Mask timer, disable an overflow int */
    US_TICKER_TIMER->TCTRL |= (TIMER0_CONTROL_INT_MSK);

    us_ticker_timestamp = us_ticker_read();
    us_ticker_interruptCount = TIMER0_MAX_COUNT;

    /* Disable timer */
    US_TICKER_TIMER->TCTRL &= (~TIMER0_CONTROL_ENABLE);

    /* Set timer count */
    US_TICKER_TIMER->LDCNT = TIMER0_LDCNT_INIT_VAL;

    /* Enable timer */
    US_TICKER_TIMER->TCTRL |= (TIMER0_CONTROL_ENABLE);

    /* mask timer, disable an overflow int */
    US_TICKER_TIMER->TCTRL |= (TIMER0_CONTROL_INT_MSK);
    core_util_critical_section_exit();
}

void us_ticker_clear_interrupt(void)
{
    if (!us_ticker_inited) {
        return ;
    }

    us_ticker_clrInt = US_TICKER_TIMER->INTCLR;
}

const ticker_info_t* us_ticker_get_info()
{
    static const ticker_info_t info =
    {
        5000000,      // 5MHZ
        29            // 29 bit counter
    };
    return &info;
}

void us_ticker_free(void)
{
    if (!us_ticker_inited) {
        return ;
    }

    core_util_critical_section_enter();
    us_ticker_disable_interrupt_help();
    us_ticker_clear_interrupt();

    /* Disable timer */
    US_TICKER_TIMER->TCTRL &= (~TIMER0_CONTROL_ENABLE);

    us_ticker_inited = 0;
    us_ticker_timestamp = 0 ;
    us_ticker_interruptCount = 0 ;
    core_util_critical_section_exit();

    return;
}

void us_ticker_irq_callback()
{

    us_ticker_clear_interrupt () ;

    if (us_ticker_soft_int_flag == 1) {
        us_ticker_soft_int_flag = 0 ;
        return ;
    }

    core_util_critical_section_enter();
    /* Check the flag firstly, because following hanlder can change it */
    us_ticker_disable_interrupt_help();

    /* Disable timer */
    US_TICKER_TIMER->TCTRL &= (~TIMER0_CONTROL_ENABLE);

    US_TICKER_TIMER->LDCNT = TIMER0_LDCNT_INIT_VAL ;

    /* Enable timer */
    US_TICKER_TIMER->TCTRL |= (TIMER0_CONTROL_ENABLE);

    /* mask timer, disable an overflow int */
    US_TICKER_TIMER->TCTRL |= (TIMER0_CONTROL_INT_MSK);

    core_util_critical_section_exit();
}
