/*
 * Copyright (c) 2011-2012, Freescale Semiconductor, Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 * o Redistributions of source code must retain the above copyright notice, this list
 *   of conditions and the following disclaimer.
 *
 * o Redistributions in binary form must reproduce the above copyright notice, this
 *   list of conditions and the following disclaimer in the documentation and/or
 *   other materials provided with the distribution.
 *
 * o Neither the name of Freescale Semiconductor, Inc. nor the names of its
 *   contributors may be used to endorse or promote products derived from this
 *   software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/*!
 * @file gpt.c
 * @brief  GPT driver source file.
 *
 * @ingroup diag_timer
 */

#include "sdk.h"
#include "gpt.h"

#include "imx_timer.h"
#include "registers/regsgpt.h"
#include "interrupt.h"
#include "ccm_pll.h"

////////////////////////////////////////////////////////////////////////////////
// Code
////////////////////////////////////////////////////////////////////////////////

static inline void gpt_clear_all_events(void)
{
    HW_GPT_SR_WR(kGPTAllEvents);
}

uint32_t gpt_get_rollover_event(void)
{
    // clear it if found set 
    if (HW_GPT_SR.B.ROV)
    {
        HW_GPT_SR_WR(BM_GPT_SR_ROV);
        return kGPTRollover;
    }

    // return the read value before the bit was cleared 
    return 0;
}

uint32_t gpt_get_capture_event(uint8_t flag, uint32_t * capture_val)
{
    // get the capture status bit 
    flag &= kGPTInputCapture1 | kGPTInputCapture2;
    uint32_t status_register = HW_GPT_SR_RD() & flag;
    
    // Read the captured timer value.
    if (capture_val)
    {
        if (status_register == kGPTInputCapture1)
        {
            *(uint32_t *) capture_val = HW_GPT_ICR1.B.CAPT;
        }
        else if (status_register == kGPTInputCapture2)
        {
            *(uint32_t *) capture_val = HW_GPT_ICR2.B.CAPT;
        }
    }
    
    // Clear the flag.
    HW_GPT_SR_WR(status_register);

    // return the read value before the bit was cleared 
    return status_register;
}

void gpt_set_capture_event(uint8_t cap_input, uint8_t cap_input_mode)
{
    // set the new input mode 
    switch (cap_input)
    {
        case kGPTInputCapture1:
            HW_GPT_CR.B.IM1 = cap_input_mode;
            break;
            
        case kGPTInputCapture2:
            HW_GPT_CR.B.IM2 = cap_input_mode;
            break;
    }
}

uint32_t gpt_get_compare_event(uint8_t flag)
{
    // get the active compare flags 
    flag &= kGPTOutputCompare1 | kGPTOutputCompare2 | kGPTOutputCompare3;
    uint32_t status_register = HW_GPT_SR_RD() & flag;
    
    // clear flags which are active 
    if (status_register)
    {
        HW_GPT_SR_WR(status_register);
    }

    // return the read value before the flags were cleared
    return status_register;
}

void gpt_set_compare_event(uint8_t cmp_output, uint8_t cmp_output_mode, uint32_t cmp_value)
{
    // set the value to compare with 
    switch (cmp_output)
    {
        case kGPTOutputCompare1:
            BW_GPT_CR_OM1(cmp_output_mode);
            HW_GPT_OCR1_WR(cmp_value);
            break;

        case kGPTOutputCompare2:
            BW_GPT_CR_OM2(cmp_output_mode);
            HW_GPT_OCR2_WR(cmp_value);
            break;

        case kGPTOutputCompare3:
            BW_GPT_CR_OM3(cmp_output_mode);
            HW_GPT_OCR3_WR(cmp_value);
            break;
    }
}

void gpt_counter_disable(void)
{
    // disable the counter 
    HW_GPT_CR.B.EN = 0;

    // ensure to leave the counter in a proper state by disabling the interrupt sources
    HW_GPT_IR_WR(0);
    
    // and by clearing possible remaining events 
    gpt_clear_all_events();
}

void gpt_counter_enable(uint32_t irq_mode)
{
    // ensure to start the counter in a proper state by clearing possible remaining events
    gpt_clear_all_events();

    // enable the interrupts or clear the register for polling 
    HW_GPT_IR_WR(irq_mode & kGPTAllEvents);

    // finally, enable the counter 
    HW_GPT_CR.B.EN = 1;
}

void gpt_setup_interrupt(void (*irq_subroutine)(void), bool enableIt)
{
    uint32_t irq_id = IMX_INT_GPT;

    if (enableIt)
    {    
        // register the IRQ sub-routine 
        register_interrupt_routine(irq_id, irq_subroutine);
        
        // enable the IRQ 
        enable_interrupt(irq_id, CPU_0, 0);
    }
    else
    {
        // disable the IRQ 
        disable_interrupt(irq_id, CPU_0);
    }
}

void gpt_init(uint32_t clock_src, uint32_t prescaler, uint32_t counter_mode, uint32_t low_power_mode)
{
    uint32_t control_reg_tmp = 0;
    uint32_t base = GPT_BASE_ADDR;

    // enable the source clocks to the GPT port 
    clock_gating_config(base, CLOCK_ON);

    // start with a known state by disabling and reseting the module 
    HW_GPT_CR_WR(BM_GPT_CR_SWR);
    
    // wait for the reset to complete 
    while (HW_GPT_CR.B.SWR != 0) ;

    // set the reference source clock for the counter 
    if (clock_src == CLKSRC_CKIL)
    {
        // CKIL source is 0x4 for GPT but 0x3 for EPIT
        clock_src++;
    }
    control_reg_tmp |= BF_GPT_CR_CLKSRC(clock_src);

    // the prescaler can be changed at any time, and 
    // this affects the output clock immediately
    HW_GPT_PR_WR(BF_GPT_PR_PRESCALER(prescaler - 1));

    // set the counter mode 
    control_reg_tmp |= BF_GPT_CR_FRR(counter_mode);

    // set behavior for low power mode 
    if (low_power_mode & WAIT_MODE_EN)
    {
        control_reg_tmp |= BM_GPT_CR_WAITEN;
    }
    if (low_power_mode & STOP_MODE_EN)
    {
        control_reg_tmp |= BM_GPT_CR_STOPEN;
    }

    // specify from where the counter starts to count when enabled 
    // this code makes it start from 0 
    control_reg_tmp |= BM_GPT_CR_ENMOD;

    // finally write the control register 
    HW_GPT_CR_WR(control_reg_tmp);
}

////////////////////////////////////////////////////////////////////////////////
// EOF
////////////////////////////////////////////////////////////////////////////////