/*
 * 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 imx_uart.c
 * @brief UART driver.

 * @ingroup diag_uart
 */

#include "sdk.h"
#include "registers/regsuart.h"
#include "imx_uart.h"
#include "ccm_pll.h"
#include "interrupt.h"

#define UART_UFCR_RFDIV    BF_UART_UFCR_RFDIV(4) 
//#define UART_UFCR_RFDIV     UART_UFCR_RFDIV_4
//#define UART_UFCR_RFDIV     UART_UFCR_RFDIV_7

uint32_t uart_get_reffreq(uint32_t instance)
{
    uint32_t div = UART_UFCR_RFDIV;
    uint32_t ret = 0;
    uint32_t freq = get_peri_clock(UART_MODULE_CLK(instance));

    if (div == BF_UART_UFCR_RFDIV(4))
        ret = freq / 2;
    else if (div == BF_UART_UFCR_RFDIV(2))
        ret = freq / 4;
    else if (div == BF_UART_UFCR_RFDIV(6))
        ret = freq / 7;

    return ret;
}

uint8_t uart_putchar(uint32_t instance, uint8_t * ch)
{
    /* Wait for Tx FIFO not full */
    while (HW_UART_UTS(instance).B.TXFULL);

    HW_UART_UTXD_WR(instance, *ch);

    return *ch;
}

uint8_t uart_getchar(uint32_t instance)
{
    uint32_t read_data;

    /* If Rx FIFO has no data ready */
    if (!(HW_UART_USR2(instance).B.RDR))
        return NONE_CHAR;

    read_data = HW_UART_URXD_RD(instance); 

    /* If error are detected */
    if (read_data & 0x7C00)
        return NONE_CHAR;

    return (uint8_t) read_data;
}

void uart_set_FIFO_mode(uint32_t instance, uint8_t fifo, uint8_t trigger_level,
                        uint8_t service_mode)
{
    if (fifo == TX_FIFO) {
        /* Configure the TX_FIFO trigger level */
        HW_UART_UFCR_CLR(instance,BM_UART_UFCR_TXTL);
        HW_UART_UFCR_SET(instance, BF_UART_UFCR_TXTL(trigger_level)); 
        /* Configure the TX_FIFO service mode */
        /* Default mode is polling: IRQ and DMA requests are disabled */
        HW_UART_UCR1_CLR(instance,(BM_UART_UCR1_TRDYEN | BM_UART_UCR1_TXDMAEN));
        if (service_mode == DMA_MODE)
           HW_UART_UCR1_SET(instance,BM_UART_UCR1_TXDMAEN);
        else if (service_mode == IRQ_MODE)
            HW_UART_UCR1_SET(instance,BM_UART_UCR1_TRDYEN); 
    } else {                    /* fifo = RX_FIFO */
        /* Configure the RX_FIFO trigger level */
         HW_UART_UFCR_CLR(instance,BM_UART_UFCR_RXTL);
        HW_UART_UFCR_SET(instance,BF_UART_UFCR_RXTL(trigger_level)); 
        /* Configure the RX_FIFO service mode */
        /* Default mode is polling: IRQ and DMA requests are disabled */
        HW_UART_UCR1_CLR(instance,(BM_UART_UCR1_RRDYEN | BM_UART_UCR1_RXDMAEN)); 
        if (service_mode == DMA_MODE)
            HW_UART_UCR1_SET(instance,BM_UART_UCR1_RXDMAEN); 
        else if (service_mode == IRQ_MODE)
            HW_UART_UCR1_SET(instance,BM_UART_UCR1_RRDYEN); 
    }
}

void uart_set_loopback_mode(uint32_t instance, uint8_t state)
{
    if (state == TRUE)
	HW_UART_UTS_SET(instance, BM_UART_UTS_LOOP);
    else
        HW_UART_UTS_CLR(instance, BM_UART_UTS_LOOP);
}

void uart_setup_interrupt(uint32_t instance, void (*irq_subroutine)(void), uint8_t state)
{
    uint32_t irq_id = UART_IRQS(instance);

    if (state == TRUE) {
        /* 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 uart_init(uint32_t instance, uint32_t baudrate, uint8_t parity,
               uint8_t stopbits, uint8_t datasize, uint8_t flowcontrol)
{
    uint32_t base = REGS_UART_BASE(instance);

   /* configure the I/O for the port */
    uart_iomux_config(instance);

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

    /* Wait for UART to finish transmitting before changing the configuration */
    while (!(HW_UART_UTS(instance).B.TXEMPTY)) ;

    /* Disable UART */
    HW_UART_UCR1_CLR(instance,BM_UART_UCR1_UARTEN );

    /* Configure FIFOs trigger level to half-full and half-empty */
    HW_UART_UFCR_WR(instance, BF_UART_UFCR_RXTL(16) | UART_UFCR_RFDIV | BF_UART_UFCR_TXTL(16)); 

    /* Setup One Millisecond timer */
    HW_UART_ONEMS_WR(instance, uart_get_reffreq(instance) / 1000);

    /* Set parity */
    if (parity == PARITY_NONE)
        HW_UART_UCR2_CLR(instance,(BM_UART_UCR2_PREN| BM_UART_UCR2_PROE)); 
    else if (parity == PARITY_ODD)
        HW_UART_UCR2_SET(instance,(BM_UART_UCR2_PREN| BM_UART_UCR2_PROE));
    else {                      /* parity == PARITY_EVEN */
        HW_UART_UCR2_SET(instance, BM_UART_UCR2_PREN);
        HW_UART_UCR2_CLR(instance, BM_UART_UCR2_PROE);
    }

    /* Set stop bit */
    if (stopbits == STOPBITS_ONE)
        HW_UART_UCR2_CLR(instance, BM_UART_UCR2_STPB);
    else                        /* stopbits == STOPBITS_TWO */
        HW_UART_UCR2_SET(instance, BM_UART_UCR2_STPB);

    /* Set data size */
    if (datasize == EIGHTBITS)
        HW_UART_UCR2_SET(instance, BM_UART_UCR2_WS);
    else                        /* stopbits == STOPBITS_TWO */
        HW_UART_UCR2_CLR(instance, BM_UART_UCR2_WS);

    /* Configure the flow control */
    if (flowcontrol == FLOWCTRL_ON) {
        /* transmit done when RTS asserted */
        HW_UART_UCR2_CLR(instance, BM_UART_UCR2_IRTS );
        /* CTS controlled by the receiver */
        HW_UART_UCR2_SET(instance, BM_UART_UCR2_CTSC );
    } else {                    /* flowcontrol == FLOWCTRL_OFF */
        /* Ignore RTS */
        HW_UART_UCR2_SET(instance,  BM_UART_UCR2_IRTS);
        /* CTS controlled by the CTS bit */
        HW_UART_UCR2_CLR(instance,  BM_UART_UCR2_CTSC);
    }

    /* the reference manual says that this bit must always be set */
    HW_UART_UCR3_SET(instance,  BM_UART_UCR3_RXDMUXSEL);

    /* Enable UART */
    HW_UART_UCR1_SET(instance, BM_UART_UCR1_UARTEN);

    /* Enable FIFOs and does software reset to clear status flags, reset
       the transmit and receive state machine, and reset the FIFOs */
    HW_UART_UCR2_SET(instance, BM_UART_UCR2_TXEN | BM_UART_UCR2_RXEN | BM_UART_UCR2_SRST);

    /* Set the numerator value minus one of the BRM ratio */
    HW_UART_UBIR_WR(instance, (baudrate / 100) - 1);

    /* Set the denominator value minus one of the BRM ratio */
    HW_UART_UBMR_WR(instance,  ((uart_get_reffreq(instance) / 1600) - 1));

    /* Optional: prevent the UART to enter debug state. Useful when debugging
       the code with a JTAG and without active IRQ */
    HW_UART_UTS_SET(instance, BM_UART_UTS_DBGEN);
}