/* mbed Microcontroller Library
 * Copyright (c) 2006-2015 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 <stddef.h>
#include "us_ticker_api.h"
#include "cmsis.h"
#include "mbed_assert.h"
#include "compiler.h"
#include "sysclk.h"
#include "tc.h"

uint8_t us_ticker_inited = 0;
extern uint8_t g_sys_init;
volatile uint16_t us_ticker_16bit_counter;
volatile uint16_t us_ticker_interrupt_counter;
volatile uint16_t us_ticker_interrupt_offset;
volatile uint32_t overflow32bitcounter = 0;

#define TICKER_COUNTER_uS        TC1

#define TICKER_COUNTER_CLK0      ID_TC3
#define TICKER_COUNTER_CLK1      ID_TC4

#define TICKER_COUNTER_CHANNEL0  0
#define TICKER_COUNTER_IRQn0     TC3_IRQn
#define TICKER_COUNTER_Handlr0   TC3_Handler

#define TICKER_COUNTER_CHANNEL1  1
#define TICKER_COUNTER_IRQn1     TC4_IRQn
#define TICKER_COUNTER_Handlr1   TC4_Handler

#define OVERFLOW_16bit_VALUE    0xFFFF


void TICKER_COUNTER_Handlr1(void)
{
    uint32_t status=tc_get_status(TICKER_COUNTER_uS, TICKER_COUNTER_CHANNEL1);
    uint32_t interrupmask=tc_get_interrupt_mask(TICKER_COUNTER_uS, TICKER_COUNTER_CHANNEL1);

    if (((status & interrupmask)  & TC_IER_CPCS)) {
        if(us_ticker_interrupt_counter) {
            us_ticker_interrupt_counter--;
        } else {
            if(us_ticker_interrupt_offset) {
                us_ticker_interrupt_offset=0;
                tc_stop(TICKER_COUNTER_uS, TICKER_COUNTER_CHANNEL1);
                tc_write_rc(TICKER_COUNTER_uS, TICKER_COUNTER_CHANNEL1, (uint32_t)us_ticker_interrupt_offset);
                tc_start(TICKER_COUNTER_uS, TICKER_COUNTER_CHANNEL1);
            } else
                us_ticker_irq_handler();
        }
    }
}

void TICKER_COUNTER_Handlr0(void)
{
    uint32_t status=tc_get_status(TICKER_COUNTER_uS, TICKER_COUNTER_CHANNEL0);
    uint32_t interrupmask=tc_get_interrupt_mask(TICKER_COUNTER_uS, TICKER_COUNTER_CHANNEL0);

    if (((status & interrupmask)  & TC_IER_COVFS)) {
        us_ticker_16bit_counter++;
        if(us_ticker_16bit_counter == 0xFFFF)
            overflow32bitcounter++;
    }
}

void us_ticker_init(void)
{
    if (us_ticker_inited) return;
    us_ticker_inited = 1;

    us_ticker_16bit_counter=0;
    us_ticker_interrupt_counter=0;
    us_ticker_interrupt_offset=0;

    if (g_sys_init == 0) {
        sysclk_init();
        system_board_init();
        g_sys_init = 1;
    }

    /* Configure the PMC to enable the TC module. */
    sysclk_enable_peripheral_clock(TICKER_COUNTER_CLK0);
    sysclk_enable_peripheral_clock(TICKER_COUNTER_CLK1);

#if SAMG55
    /* Enable PCK output */
    pmc_disable_pck(PMC_PCK_3);
    pmc_switch_pck_to_mck(PMC_PCK_3, PMC_PCK_PRES_CLK_1);
    pmc_enable_pck(PMC_PCK_3);
#endif

    /* Init TC to Counter mode. */
    tc_init(TICKER_COUNTER_uS, TICKER_COUNTER_CHANNEL0, TC_CMR_TCCLKS_TIMER_CLOCK4);
    tc_init(TICKER_COUNTER_uS, TICKER_COUNTER_CHANNEL1, TC_CMR_TCCLKS_TIMER_CLOCK4);


    NVIC_DisableIRQ(TICKER_COUNTER_IRQn0);

    NVIC_ClearPendingIRQ(TICKER_COUNTER_IRQn0);
    NVIC_SetPriority(TICKER_COUNTER_IRQn0, 0);
    NVIC_EnableIRQ(TICKER_COUNTER_IRQn0);
    tc_enable_interrupt(TICKER_COUNTER_uS, TICKER_COUNTER_CHANNEL0, TC_IER_COVFS);

    tc_start(TICKER_COUNTER_uS, TICKER_COUNTER_CHANNEL0);
}


uint32_t us_ticker_read()
{
    if (!us_ticker_inited)
        us_ticker_init();

    uint32_t counter_value=0;
    uint16_t tickerbefore=0;
    do {
        tickerbefore=us_ticker_16bit_counter;
        counter_value=tc_read_cv(TICKER_COUNTER_uS, TICKER_COUNTER_CHANNEL0);
    } while(tickerbefore!=us_ticker_16bit_counter);

    return counter_value+(OVERFLOW_16bit_VALUE*us_ticker_16bit_counter);
}

void us_ticker_set_interrupt(timestamp_t timestamp)
{
    uint32_t cur_time;
    int32_t delta;

    cur_time = us_ticker_read();
    delta = (int32_t)((uint32_t)timestamp - cur_time);
    if (delta < 0) {
        /* Event already occurred in past */
        us_ticker_irq_handler();
        return;
    }

    uint16_t interruptat=0;

    if(delta > OVERFLOW_16bit_VALUE) {
        us_ticker_interrupt_counter= (delta/OVERFLOW_16bit_VALUE) -1;
        us_ticker_interrupt_offset=delta%OVERFLOW_16bit_VALUE;
        interruptat=OVERFLOW_16bit_VALUE;
    } else {
        us_ticker_interrupt_counter=0;
        us_ticker_interrupt_offset=0;
        interruptat=delta;
    }

    NVIC_DisableIRQ(TICKER_COUNTER_IRQn1);

    tc_write_rc(TICKER_COUNTER_uS, TICKER_COUNTER_CHANNEL1, (uint32_t)interruptat);

    NVIC_ClearPendingIRQ(TICKER_COUNTER_IRQn1);
    NVIC_SetPriority(TICKER_COUNTER_IRQn1, 0);
    NVIC_EnableIRQ(TICKER_COUNTER_IRQn1);
    tc_enable_interrupt(TICKER_COUNTER_uS, TICKER_COUNTER_CHANNEL1, TC_IDR_CPCS );

    tc_start(TICKER_COUNTER_uS, TICKER_COUNTER_CHANNEL1);
}

void us_ticker_disable_interrupt(void)
{
    tc_stop(TICKER_COUNTER_uS, TICKER_COUNTER_CHANNEL1);
    tc_disable_interrupt(TICKER_COUNTER_uS, TICKER_COUNTER_CHANNEL1, TC_IDR_CPCS);
    NVIC_DisableIRQ(TICKER_COUNTER_IRQn1);
}

void us_ticker_clear_interrupt(void)
{
    NVIC_ClearPendingIRQ(TICKER_COUNTER_IRQn1);
}
