/**
 * Kneron Peripheral API - UART
 *
 * Copyright (C) 2019 Kneron, Inc. All rights reserved.
 *
 */

//#define UART_DEBUG
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include "kdrv_uart.h"
#include "kdrv_scu_ext.h"
#include "kdrv_clock.h" // for delay_us()

static kdrv_uart_handle_t handle0 = 0;

#define SERIAL_THR 0x00 /*  Transmitter Holding Register(Write).*/
#define SERIAL_RBR 0x00 /*  Receive Buffer register (Read).*/
#define SERIAL_IER 0x04 /*  Interrupt Enable register.*/
#define SERIAL_IIR 0x08 /*  Interrupt Identification register(Read).*/
#define SERIAL_FCR 0x08 /*  FIFO control register(Write).*/
#define SERIAL_LCR 0x0C /*  Line Control register.*/
#define SERIAL_MCR 0x10 /*  Modem Control Register.*/
#define SERIAL_LSR 0x14 /*  Line status register(Read) .*/
#define SERIAL_MSR 0x18 /*  Modem Status register (Read).*/
#define SERIAL_SPR 0x1C /*  Scratch pad register */
#define SERIAL_DLL 0x0  /*  Divisor Register LSB */
#define SERIAL_DLM 0x4  /*  Divisor Register MSB */
#define SERIAL_PSR 0x8  /* Prescale Divison Factor */

#define SERIAL_MDR 0x20
#define SERIAL_ACR 0x24
#define SERIAL_TXLENL 0x28
#define SERIAL_TXLENH 0x2C
#define SERIAL_MRXLENL 0x30
#define SERIAL_MRXLENH 0x34
#define SERIAL_PLR 0x38
#define SERIAL_FMIIR_PIO 0x3C
#define SERIAL_FEATURE 0x68

/* IER Register */
#define SERIAL_IER_DR 0x1  /* Data ready Enable */
#define SERIAL_IER_TE 0x2  /* THR Empty Enable */
#define SERIAL_IER_RLS 0x4 /* Receive Line Status Enable */
#define SERIAL_IER_MS 0x8  /* Modem Staus Enable */

/* IIR Register */
#define SERIAL_IIR_NONE 0x1    /* No interrupt pending */
#define SERIAL_IIR_RLS 0x6     /* Receive Line Status */
#define SERIAL_IIR_DR 0x4      /* Receive Data Ready */
#define SERIAL_IIR_TIMEOUT 0xc /* Receive Time Out */
#define SERIAL_IIR_TE 0x2      /* THR Empty */
#define SERIAL_IIR_MODEM 0x0   /* Modem Status */

/* FCR Register */
#define SERIAL_FCR_FE 0x1   /* FIFO Enable */
#define SERIAL_FCR_RXFR 0x2 /* Rx FIFO Reset */
#define SERIAL_FCR_TXFR 0x4 /* Tx FIFO Reset */

/* LCR Register */
#define SERIAL_LCR_LEN5 0x0
#define SERIAL_LCR_LEN6 0x1
#define SERIAL_LCR_LEN7 0x2
#define SERIAL_LCR_LEN8 0x3

#define SERIAL_LCR_STOP 0x4
#define SERIAL_LCR_EVEN 0x18        /* Even Parity */
#define SERIAL_LCR_ODD 0x8          /* Odd Parity */
#define SERIAL_LCR_PE 0x8           /* Parity Enable */
#define SERIAL_LCR_SETBREAK 0x40    /* Set Break condition */
#define SERIAL_LCR_STICKPARITY 0x20 /* Stick Parity Enable */
#define SERIAL_LCR_DLAB 0x80        /* Divisor Latch Access Bit */

/* LSR Register */
#define SERIAL_LSR_DR 0x1    /* Data Ready */
#define SERIAL_LSR_OE 0x2    /* Overrun Error */
#define SERIAL_LSR_PE 0x4    /* Parity Error */
#define SERIAL_LSR_FE 0x8    /* Framing Error */
#define SERIAL_LSR_BI 0x10   /* Break Interrupt */
#define SERIAL_LSR_THRE 0x20 /* THR Empty */
#define SERIAL_LSR_TE 0x40   /* Transmitte Empty */
#define SERIAL_LSR_DE 0x80   /* FIFO Data Error */

/* MCR Register */
#define SERIAL_MCR_DTR 0x1   /* Data Terminal Ready */
#define SERIAL_MCR_RTS 0x2   /* Request to Send */
#define SERIAL_MCR_OUT1 0x4  /* output    1 */
#define SERIAL_MCR_OUT2 0x8  /* output2 or global interrupt enable */
#define SERIAL_MCR_LPBK 0x10 /* loopback mode */

/* MSR Register */
#define SERIAL_MSR_DELTACTS 0x1 /* Delta CTS */
#define SERIAL_MSR_DELTADSR 0x2 /* Delta DSR */
#define SERIAL_MSR_TERI 0x4     /* Trailing Edge RI */
#define SERIAL_MSR_DELTACD 0x8  /* Delta CD */
#define SERIAL_MSR_CTS 0x10     /* Clear To Send */
#define SERIAL_MSR_DSR 0x20     /* Data Set Ready */
#define SERIAL_MSR_RI 0x40      /* Ring Indicator */
#define SERIAL_MSR_DCD 0x80     /* Data Carrier Detect */

/* MDR register */
#define SERIAL_MDR_MODE_SEL 0x03
#define SERIAL_MDR_UART 0x0
#define SERIAL_MDR_SIR 0x1
#define SERIAL_MDR_FIR 0x2

/* ACR register */
#define SERIAL_ACR_TXENABLE 0x1
#define SERIAL_ACR_RXENABLE 0x2
#define SERIAL_ACR_SET_EOT 0x4

#define IIR_CODE_MASK 0xf
#define SERIAL_IIR_TX_FIFO_FULL 0x10

#define UART_INITIALIZED (1 << 0)
#define UART_BASIC_CONFIGURED (1 << 2)
#define UART_FIFO_RX_CONFIGURED (1 << 3)
#define UART_FIFO_TX_CONFIGURED (1 << 4)
#define UART_TX_ENABLED (1 << 5)
#define UART_RX_ENABLED (1 << 6)
#define UART_LOOPBACK_ENABLED (1 << 7)

#define SERIAL_FIFO_DEPTH_REG 0x68

#define SERIAL_FIFO_DEPTH_16B 0x1
#define SERIAL_FIFO_DEPTH_32B 0x2
#define SERIAL_FIFO_DEPTH_64B 0x4
#define SERIAL_FIFO_DEPTH_128B 0x8

#define SERIAL_FIFO_TRIG_LVEL_1 0x0
#define SERIAL_FIFO_TRIG_LVEL_4 0x1
#define SERIAL_FIFO_TRIG_LVEL_8 0x2
#define SERIAL_FIFO_TRIG_LVEL_14 0x3

#define DEFAULT_SYNC_TIMEOUT_CHARS_TIME 0 //default is no timeout

#define UART_IRQ_CNT_DBG 0

/***********************************************************************************
              Global variables
************************************************************************************/
void UART0_ISR(void);
void UART1_ISR(void);
void UART2_ISR(void);
void UART3_ISR(void);
void UART4_ISR(void);
uart_drv_ctx_t gDrvCtx;
uint32_t UART_PORT[5] = {UART_FTUART010_0_PA_BASE, UART_FTUART010_1_PA_BASE,
                                UART_FTUART010_1_1_PA_BASE, UART_FTUART010_1_2_PA_BASE, UART_FTUART010_1_3_PA_BASE};
uint32_t uart_baud_rate_map[11] = {BAUD_1200,BAUD_2400,BAUD_4800,BAUD_9600,BAUD_14400 ,BAUD_19200 ,BAUD_38400 ,BAUD_57600 ,BAUD_115200,BAUD_460800,BAUD_921600};

/***********************************************************************************
              local variables
************************************************************************************/

IRQn_Type gUartIRQTbl[5] = {
    UART_FTUART010_0_IRQ,   //UART0
    UART_FTUART010_1_IRQ,   //UART1
    UART_FTUART010_1_1_IRQ, //UART2
    UART_FTUART010_1_2_IRQ, //UART3
    UART_FTUART010_1_3_IRQ  //UART4
};

uart_isr_t gUartISRs[5] = {
    UART0_ISR,
    UART1_ISR,
    UART2_ISR,
    UART3_ISR,
    UART4_ISR};

uint32_t gUartClk[5] = {
    UART_CLOCK,
    UART_CLOCK_2,
    UART_CLOCK_2,
    UART_CLOCK,
    UART_CLOCK};

static bool gDriverInitialized = false;

#if (defined(UART_IRQ_CNT_DBG) && UART_IRQ_CNT_DBG == 1)
volatile uint32_t tx_isr_cnt[TOTAL_UART_DEV]={0};
volatile uint32_t rx_isr_cnt[TOTAL_UART_DEV]={0};
#endif
/***********************************************************************************
              local functions
************************************************************************************/
static void uart_serial_init(DRVUART_PORT port_no, uint32_t baudrate, uint32_t parity, uint32_t num, uint32_t len, uint32_t interruptMode)
{
    uint32_t lcr;

    lcr = inw(UART_PORT[port_no] + SERIAL_LCR) & ~SERIAL_LCR_DLAB;
    /* Set DLAB=1 */
    outw(UART_PORT[port_no] + SERIAL_LCR, SERIAL_LCR_DLAB);
    /* Set baud rate */
    outw(UART_PORT[port_no] + SERIAL_DLM, ((baudrate & 0xff00) >> 8)); //ycmo090930
    outw(UART_PORT[port_no] + SERIAL_DLL, (baudrate & 0xff));
    lcr &= 0xc0;

    switch (parity)
    {
    case PARITY_NONE:
        //do nothing
        break;
    case PARITY_ODD:
        lcr |= SERIAL_LCR_ODD;
        break;
    case PARITY_EVEN:
        lcr |= SERIAL_LCR_EVEN;
        break;
    case PARITY_MARK:
        lcr |= (SERIAL_LCR_STICKPARITY | SERIAL_LCR_ODD);
        break;
    case PARITY_SPACE:
        lcr |= (SERIAL_LCR_STICKPARITY | SERIAL_LCR_EVEN);
        break;

    default:
        break;
    }

    if (num == 2)
        lcr |= SERIAL_LCR_STOP;

    len -= 5;

    lcr |= len;

    outw(UART_PORT[port_no] + SERIAL_LCR, lcr);
    if (1 == interruptMode)
        outw(UART_PORT[port_no] + SERIAL_FCR, SERIAL_FCR_FE);
}

static char uart_get_serial_char(DRVUART_PORT port_no)
{
    char Ch;
    uint32_t status;

    do
    {
        status = inw(UART_PORT[port_no] + SERIAL_LSR);
    } while (!((status & SERIAL_LSR_DR) == SERIAL_LSR_DR)); // wait until Rx ready
    Ch = inw(UART_PORT[port_no] + SERIAL_RBR);
    return (Ch);
}

uint32_t uart_get_status(DRVUART_PORT port_no)
{
    uint32_t status;
    status = inw(UART_PORT[port_no] + SERIAL_LSR);
    return status;
}

#if 0
static int32_t kdp_uart_get_default_timeout(uint32_t baud)
{
    int32_t timeout;
    switch (baud)
    {
    case BAUD_921600:
    {
        timeout = (DEFAULT_SYNC_TIMEOUT_CHARS_TIME * 100) / 9216;
        break;
    }
    case BAUD_460800:
    {
        timeout = (DEFAULT_SYNC_TIMEOUT_CHARS_TIME * 100) / 4608;
        break;
    }
    case BAUD_115200:
    {
        timeout = (DEFAULT_SYNC_TIMEOUT_CHARS_TIME * 100) / 1152;
        break;
    }
    case BAUD_57600:
    {
        timeout = (DEFAULT_SYNC_TIMEOUT_CHARS_TIME * 100) / 576;
        break;
    }
    case BAUD_38400:
    {
        timeout = (DEFAULT_SYNC_TIMEOUT_CHARS_TIME * 100) / 384;
        break;
    }
    case BAUD_19200:
    {
        timeout = (DEFAULT_SYNC_TIMEOUT_CHARS_TIME * 100) / 192;
        break;
    }
    case BAUD_14400:
    {
        timeout = (DEFAULT_SYNC_TIMEOUT_CHARS_TIME * 100) / 144;
        break;
    }
    case BAUD_9600:
    {
        timeout = (DEFAULT_SYNC_TIMEOUT_CHARS_TIME * 100) / 96;
        break;
    }
    case BAUD_4800:
    {
        timeout = (DEFAULT_SYNC_TIMEOUT_CHARS_TIME * 100) / 48;
        break;
    }
    case BAUD_2400:
    {
        timeout = (DEFAULT_SYNC_TIMEOUT_CHARS_TIME * 100) / 240;
        break;
    }
    case BAUD_1200:
    {
        timeout = (DEFAULT_SYNC_TIMEOUT_CHARS_TIME * 100) / 12;
        break;
    }
    default:
        timeout = DEFAULT_SYNC_TIMEOUT_CHARS_TIME;
    }
    return timeout;
}
#endif

static uart_driver_handle_t *uart_get_drv_hdl(uint16_t port)
{
#ifdef UART_DEBUG
    if (port >= MAX_UART_INST)
    {

        dbg_msg("Error: invalid port number\n");

        return NULL;
    }
#endif
    return gDrvCtx.uart_dev[port];
}

/* calculate fifo config: depth and trigger level
   pCfg->bEnFifo and pCfg->fifo_depth was set by client before calling in
*/
static kdrv_status_t kdp_calculate_fifo_cfg(uart_driver_handle_t *const pDrv, uint32_t val, kdrv_uart_fifo_config_t *pCfg)
{
    int32_t depth;

    if (pCfg->bEnFifo == false)
    {
        pCfg->fifo_trig_level = SERIAL_FIFO_TRIG_LVEL_1;
        return KDRV_STATUS_OK;
    }

    depth = pDrv->res.fifo_depth;

    if (val <= depth * 4)
    {
        pCfg->fifo_trig_level = SERIAL_FIFO_TRIG_LVEL_1;
    }
    else if ((val > depth * 4) && (val <= depth * 8))
    {
        pCfg->fifo_trig_level = SERIAL_FIFO_TRIG_LVEL_4;
    }
    else if ((val > depth * 8) && (val <= depth * 14))
    {
        pCfg->fifo_trig_level = SERIAL_FIFO_TRIG_LVEL_8;
    }
    else if (val > depth * 14)
    {
        pCfg->fifo_trig_level = SERIAL_FIFO_TRIG_LVEL_14;
    }
    return KDRV_STATUS_OK;
}

/**
  \fn          void UART_IRQHandler (UART_RESOURCES  *uart)
  \brief       UART Interrupt handler.
  \param[in]   uart  Pointer to UART resources
*/
static void UART_TX_ISR(uart_driver_handle_t *const uart)
{
    uint32_t status;
    uint32_t ww;
    uint16_t port_no;
    bool bFifo;

    ww = uart->iir;
    bFifo = ((ww & 0xc0) != 0) ? true : false;
    port_no = uart->uart_port;

    uart->info.xfer.tx_cnt += uart->info.xfer.batch_tx_num;
    if (uart->info.xfer.tx_num > uart->info.xfer.tx_cnt)
    {
        if (bFifo == false) //NO FIFO
        {
            do
            {
                status = inw(UART_PORT[port_no] + SERIAL_LSR);
            } while (!((status & SERIAL_LSR_THRE) == SERIAL_LSR_THRE)); // wait until Tx ready
            outw(UART_PORT[port_no] + SERIAL_THR, *(uart->info.xfer.tx_buf++));
        }
        else //FIFO mode
        {
            uint32_t tx_num = MAX_FIFO_TX;
            if (uart->info.xfer.tx_num < uart->info.xfer.tx_cnt+MAX_FIFO_TX)
            {
                tx_num = uart->info.xfer.tx_num - uart->info.xfer.tx_cnt;
            }
            for(uint32_t i = 0; i < tx_num ; i++)
            {
                outw(UART_PORT[port_no] + SERIAL_THR, *uart->info.xfer.tx_buf);
                uart->info.xfer.tx_buf++;
            }
            uart->info.xfer.batch_tx_num = tx_num;
        }
    }

    if (uart->info.xfer.tx_num <= uart->info.xfer.tx_cnt)
    {
        // need to add: determine if TX fifo is empty
        status = inw(UART_PORT[port_no] + SERIAL_LSR);
        if ((status & SERIAL_LSR_THRE) == SERIAL_LSR_THRE) //TX empty to make sure FIFO data to tranmit shift
        {
            uint32_t ww = inw(UART_PORT[port_no] + SERIAL_IER);
            ww &= ~SERIAL_IER_TE; // disable Tx empty interrupt
            outw(UART_PORT[port_no] + SERIAL_IER, ww);
            if (uart->info.cb_event)
                uart->info.cb_event(UART_TX_DONE);
            uart->info.status.tx_busy = 0;
        }
    }
#if (defined(UART_IRQ_CNT_DBG) && UART_IRQ_CNT_DBG == 1)
    tx_isr_cnt[uart->uart_port]++;
#endif
}
static void UART_RX_ISR(uart_driver_handle_t *const uart)
{
    uint32_t ww;
    uint16_t port_no;

    ww = uart->iir;

    port_no = uart->uart_port;

    if (((ww & SERIAL_IIR_DR) == SERIAL_IIR_DR) || ((ww & SERIAL_IIR_TIMEOUT) == SERIAL_IIR_TIMEOUT))
    {

        if ((ww & 0xc0) == 0) //non FIFO mode
        {
            *(uart->info.xfer.rx_buf++) = (uint8_t)uart_get_serial_char((DRVUART_PORT)port_no);
            uart->info.xfer.rx_cnt++;

            if (uart->info.xfer.rx_cnt == uart->info.xfer.rx_num)
            {
                ww = inw(UART_PORT[port_no] + SERIAL_IER);
                ww &= ~SERIAL_IER_DR; // disable Rx empty interrupt
                outw(UART_PORT[port_no] + SERIAL_IER, ww);
                if (uart->info.cb_event)
                    uart->info.cb_event(UART_RX_DONE);
            }
        }
        else //FIFO mode
        {
            /* Read the data from FIFO buffer */

            ww = inw(UART_PORT[port_no] + SERIAL_LSR);
            if ((ww & SERIAL_LSR_DR) == SERIAL_LSR_DR)
            {
                while((inw(UART_PORT[port_no] + SERIAL_LSR) & SERIAL_LSR_DR) == SERIAL_LSR_DR)
                {
                    *(uart->info.xfer.rx_buf++) = (uint8_t)inw(UART_PORT[port_no] + SERIAL_RBR);
                    uart->info.xfer.rx_cnt++;
                }

                if (uart->info.xfer.rx_num <= uart->info.xfer.rx_cnt)
                {
                    ww = inw(UART_PORT[port_no] + SERIAL_IER);
                    ww &= ~SERIAL_IER_DR; // disable Rx empty interrupt
                    outw(UART_PORT[port_no] + SERIAL_IER, ww);
                    if (uart->info.cb_event)
                        uart->info.cb_event(UART_RX_DONE);
                }

            }
            else
            {
                ww = uart->iir;
                if ((ww & SERIAL_IIR_TIMEOUT) == SERIAL_IIR_TIMEOUT) // means end of frame
                {

                    while((inw(UART_PORT[port_no] + SERIAL_LSR) & SERIAL_LSR_DR) == SERIAL_LSR_DR)
                    {
                        *(uart->info.xfer.rx_buf++) = (uint8_t)inw(UART_PORT[port_no] + SERIAL_RBR);
                        uart->info.xfer.rx_cnt++;
                    }

                    if (uart->info.xfer.rx_num <= uart->info.xfer.rx_cnt)
                    {
                        ww = inw(UART_PORT[port_no] + SERIAL_IER);
                        ww &= ~SERIAL_IER_DR; // disable Rx empty interrupt
                        outw(UART_PORT[port_no] + SERIAL_IER, ww);
                        if (uart->info.cb_event)
                            uart->info.cb_event(UART_RX_TIMEOUT);
                    }
                }
            }
        }
        uart->info.status.rx_busy = 0;
#if (defined(UART_IRQ_CNT_DBG) && UART_IRQ_CNT_DBG == 1)
        rx_isr_cnt[uart->uart_port]++;
#endif
    }
}

/***********************************************************************************
              global functions
************************************************************************************/

void UART_ISR(uint8_t port_no)
{
    uart_driver_handle_t *pHdl = uart_get_drv_hdl(port_no);

    uint32_t ww = inw(UART_PORT[port_no] + SERIAL_IIR);

    pHdl->iir = ww;

    if ((ww & IIR_CODE_MASK) == SERIAL_IIR_RLS) // errors: overrun/parity/framing/break
    {
        ww = inw(UART_PORT[port_no] + SERIAL_LSR); //Read LSR to reset interrupt

        if (ww & SERIAL_LSR_OE)
        {
            pHdl->info.status.rx_overflow = 1;
        }

        if (ww & SERIAL_LSR_PE)
        {
            pHdl->info.status.rx_parity_error = 1;
        }

        if (ww & SERIAL_LSR_BI)
        {
            pHdl->info.status.rx_break = 1;
        }

        if (ww & SERIAL_LSR_FE)
        {
            pHdl->info.status.rx_framing_error = 1;
        }
    }

    if (((ww & IIR_CODE_MASK) == SERIAL_IIR_DR)          // Rx data ready in FIFO
             || ((ww & IIR_CODE_MASK) == SERIAL_IIR_TIMEOUT)) // Character Reception Timeout
    {
        if (pHdl->info.status.rx_busy)
        {
            UART_RX_ISR(pHdl);
        }
        else
        {
            ww = inw(UART_PORT[port_no] + SERIAL_RBR); //Read RBR to reset interrupt
        }
    }
    if ((ww & IIR_CODE_MASK) == SERIAL_IIR_TE) // Transmitter Holding Register Empty
    {
        if (pHdl->info.status.tx_busy)
        {
            UART_TX_ISR(pHdl);
        }
    }

    NVIC_ClearPendingIRQ((IRQn_Type)pHdl->res.irq_num);
    NVIC_EnableIRQ((IRQn_Type)pHdl->res.irq_num);
}

void UART0_ISR(void)
{
    UART_ISR(0);
}

void UART1_ISR(void)
{
    UART_ISR(1);
}

void UART2_ISR(void)
{
    UART_ISR(2);
}

void UART3_ISR(void)
{
    UART_ISR(3);
}

void UART4_ISR(void)
{
    UART_ISR(4);
}

/* Init the UART device driver, it shall be called once in lifecycle
*/
kdrv_status_t kdrv_uart_initialize(void)
{
    if (gDriverInitialized == false)
    {
        memset(&gDrvCtx, 0, sizeof(gDrvCtx));
        gDriverInitialized = true;
    }

    return KDRV_STATUS_OK;
}

kdrv_status_t kdrv_uart_console_init(uint8_t uart_dev, uint32_t baudrate, kdrv_uart_callback_t cb)
{
    kdrv_uart_handle_t handle;
    kdrv_status_t sts = kdrv_uart_open(&handle, uart_dev,
                                       UART_MODE_SYNC_RX | UART_MODE_SYNC_TX, cb);
    if (sts != KDRV_STATUS_OK)
        return sts;

    kdrv_uart_config_t cfg;
    cfg.baudrate = uart_baud_rate_map[baudrate];
    cfg.data_bits = 8;
    cfg.frame_length = 0;
    cfg.stop_bits = 1;
    cfg.parity_mode = PARITY_NONE;
    cfg.fifo_en = false;
#ifdef UART_RX_IRQ
    cfg.irq_en = false;
#endif

    sts = kdrv_uart_configure(handle, UART_CTRL_CONFIG, (void *)&cfg);
    if (sts != KDRV_STATUS_OK)
        return sts;

    return KDRV_STATUS_OK;
}

kdrv_status_t kdrv_uart_uninitialize(void)
{
    return KDRV_STATUS_OK;
}

/*
  Open one UART port
Input:
  com_port: UART port id
  mode: that is a combination of Tx mode|Rx mode, make sure set both, such as UART_MODE_ASYN_RX|UART_MODE_SYNC_TX
  cb: callback function

Output:
  return device handle: >=0 means success; -1 means open fail
*/
kdrv_status_t kdrv_uart_open(kdrv_uart_handle_t *handle, uint8_t com_port, uint32_t mode, kdrv_uart_callback_t cb)
{
    uint32_t ww;

#ifdef UART_DEBUG
    if (com_port >= TOTAL_UART_DEV)
    {
        dbg_msg("Invalid com_port\n");
        return KDRV_STATUS_ERROR;
    }
#endif

    if (gDrvCtx.active_dev[com_port] == true)
    {
#ifdef UART_DEBUG
        dbg_msg("This UART device has been opened\n");
#endif
        return KDRV_STATUS_ERROR;
    }

    uart_driver_handle_t *pDrv = (uart_driver_handle_t *)malloc(sizeof(uart_driver_handle_t));
    if (pDrv == NULL)
    {
#ifdef UART_DEBUG
        err_msg("Error: memory alloc failed\n");
#endif
        return KDRV_STATUS_ERROR;
    }

    if (((mode & UART_MODE_ASYN_TX) || (mode & UART_MODE_ASYN_RX)) && (cb == NULL))
    {
#ifdef UART_DEBUG
        err_msg("Error: Async mode needs callback function\n");
#endif
        return KDRV_STATUS_ERROR;
    }

    gDrvCtx.uart_dev[com_port] = pDrv;

    pDrv->uart_port = com_port;
    pDrv->state = UART_UNINIT;
    // now use a common capbilities for all UARTs, may introduce an array if diff UART has diff capbilities
    pDrv->info.cb_event = cb;
    pDrv->info.mode = mode;
    pDrv->info.status.tx_busy = 0;
    pDrv->info.status.rx_busy = 0;
    pDrv->info.status.tx_underflow = 0;
    pDrv->info.status.rx_overflow = 0;
    pDrv->info.status.rx_break = 0;
    pDrv->info.status.rx_framing_error = 0;
    pDrv->info.status.rx_parity_error = 0;

    pDrv->info.xfer.tx_num = 0;
    pDrv->info.xfer.rx_num = 0;
    pDrv->info.xfer.tx_cnt = 0;
    pDrv->info.xfer.rx_cnt = 0;

    pDrv->res.irq_num = gUartIRQTbl[com_port];
    pDrv->res.isr = gUartISRs[com_port];

    ww = inw(UART_PORT[com_port] + SERIAL_FIFO_DEPTH_REG);
    ww &= 0x0f;
    pDrv->res.fifo_depth = ww;

    pDrv->res.fifo_len = 16 * ww;

    pDrv->res.hw_base = UART_PORT[com_port];
    pDrv->res.clock = gUartClk[com_port];

    pDrv->nTimeOutRx = DEFAULT_SYNC_TIMEOUT_CHARS_TIME; //suppose default baud = 115200
    pDrv->nTimeOutTx = DEFAULT_SYNC_TIMEOUT_CHARS_TIME; //suppose default baud = 115200

    pDrv->res.rx_fifo_threshold = SERIAL_FIFO_TRIG_LVEL_14;
    pDrv->res.tx_fifo_threshold = SERIAL_FIFO_TRIG_LVEL_14;
    pDrv->config.fifo_en = 0;

    gDrvCtx.total_open_uarts++;
    gDrvCtx.active_dev[com_port] = true;

    pDrv->info.flags |= UART_INITIALIZED;

    // enable clocks / power / irq
    {
        uint32_t clock_enable_reg2 = inw(SCU_EXTREG_PA_BASE + 0x1c);

        if (com_port == UART0_DEV)
            clock_enable_reg2 |= (0x1 << 8);
        else
            clock_enable_reg2 |= (0x1 << (11 + com_port));

        outw(SCU_EXTREG_PA_BASE + 0x1c, clock_enable_reg2);

        kdrv_uart_fifo_config_t cfg;

        /* config FIFO trigger value with default value or client set via control API*/
        cfg.fifo_trig_level = pDrv->res.rx_fifo_threshold;
        cfg.bEnFifo         = pDrv->config.fifo_en;
        kdrv_uart_configure(com_port, UART_CTRL_FIFO_RX, (void *)&cfg);

        cfg.fifo_trig_level = pDrv->res.tx_fifo_threshold;
        cfg.bEnFifo = (cfg.fifo_trig_level == 0) ? false : true;
        kdrv_uart_configure(com_port, UART_CTRL_FIFO_TX, (void *)&cfg);

        NVIC_ClearPendingIRQ((IRQn_Type)pDrv->res.irq_num);
    }

    *handle = (kdrv_uart_handle_t)com_port;

    return KDRV_STATUS_OK;
}

/* close the device
Input:
   handle: device handle
return:
   0 - success; -1 - failure
*/
kdrv_status_t kdrv_uart_close(kdrv_uart_handle_t handle)
{
    uint32_t com_port = handle;

#ifdef UART_DEBUG
    if (com_port >= TOTAL_UART_DEV)
    {
        dbg_msg("Invalid parameter\n");
        return KDRV_STATUS_ERROR;
    }

    if (gDrvCtx.active_dev[com_port] == false)
    {
        dbg_msg("This UART device has been closed\n");
        return KDRV_STATUS_ERROR;
    }
#endif

    uart_driver_handle_t *pDrv = gDrvCtx.uart_dev[com_port];

    if (pDrv->info.flags == 0)
    {
        // Driver not initialized
        return KDRV_STATUS_ERROR;
    }

    // Reset USART status flags
    pDrv->info.flags = 0;

    free(gDrvCtx.uart_dev[com_port]);
    gDrvCtx.uart_dev[com_port] = NULL;
    gDrvCtx.active_dev[com_port] = false;
    gDrvCtx.total_open_uarts--;

    return KDRV_STATUS_OK;
}

/*
Set control for the device
Input:
    handle: device handle
    prop: control enumeration
    pVal: pointer to control value/structure
return:
    None
*/
kdrv_status_t kdrv_uart_configure(kdrv_uart_handle_t handle, kdrv_uart_control_t prop, uint8_t *pVal)
{
    uint32_t com_port = handle;

#ifdef UART_DEBUG
    if (com_port >= TOTAL_UART_DEV)
    {
        dbg_msg("Invalid parameter\n");
        return KDRV_STATUS_ERROR;
    }

    if (gDrvCtx.active_dev[com_port] == false)
    {
        dbg_msg("This UART device has been closed\n");
        return KDRV_STATUS_ERROR;
    }
#endif

    uart_driver_handle_t *pDrv = gDrvCtx.uart_dev[com_port];

#ifdef UART_DEBUG
    if ((pDrv->info.flags & UART_INITIALIZED) == 0)
    {
        // Return error, if USART is not initialized
        return KDRV_STATUS_ERROR;
    }
#endif

    DRVUART_PORT port_no = (DRVUART_PORT)pDrv->uart_port;
    switch (prop)
    {
    case UART_CTRL_CONFIG:
    {
        kdrv_uart_config_t cfg = *(kdrv_uart_config_t *)pVal;
        uint32_t baudrate = cfg.baudrate;
        uint32_t parity = cfg.parity_mode;
        uint32_t num = cfg.stop_bits;
        uint32_t len = cfg.data_bits;

        uart_serial_init(port_no, baudrate, parity, num, len, 0);

        pDrv->config.baudrate = baudrate;
        pDrv->config.data_bits = cfg.data_bits;
        pDrv->config.stop_bits = num;
        pDrv->config.parity_mode = parity;
        pDrv->config.fifo_en = cfg.fifo_en;

        pDrv->res.tx_fifo_threshold = 0;
        pDrv->res.rx_fifo_threshold = 0;

        pDrv->nTimeOutTx = 0; //kdp_uart_get_default_timeout(baudrate);
        pDrv->nTimeOutRx = 0; //kdp_uart_get_default_timeout(baudrate);

        pDrv->info.flags |= UART_BASIC_CONFIGURED;

        pDrv->state = UART_INIT_DONE;
        break;
    }

    case UART_CTRL_TX_EN:
    {
        uint32_t ww = inw(UART_PORT[port_no] + SERIAL_IER);
        if (*pVal == 0)
        {
            ww &= ~SERIAL_IER_TE; // disable TX
            outw(UART_PORT[port_no] + SERIAL_IER, ww);
            pDrv->info.flags &= ~UART_TX_ENABLED;
        }
        else
        {
            ww |= SERIAL_IER_TE; // enable TX
            outw(UART_PORT[port_no] + SERIAL_IER, ww);
            pDrv->info.flags |= UART_TX_ENABLED;
        }

        break;
    }

    case UART_CTRL_RX_EN:
    {
        uint32_t ww = inw(UART_PORT[port_no] + SERIAL_IER);
        if (*pVal == 0)
        {
            ww &= ~SERIAL_IER_DR; // disable Rx
            outw(UART_PORT[port_no] + SERIAL_IER, ww);
            pDrv->info.flags &= ~UART_RX_ENABLED;
        }
        else
        {
            ww |= SERIAL_IER_DR; // enable Rx
            outw(UART_PORT[port_no] + SERIAL_IER, ww);
            pDrv->info.flags |= UART_RX_ENABLED;
        }

        break;
    }

    case UART_CTRL_ABORT_TX:
    {
        /* disable Tx interrupt */
        uint32_t ww = inw(UART_PORT[port_no] + SERIAL_IER);
        ww &= ~SERIAL_IER_TE;
        outw(UART_PORT[port_no] + SERIAL_IER, ww);

        /* reset Tx FIFO */
        ww = inw(UART_PORT[port_no] + SERIAL_FCR);
        ww |= (SERIAL_FCR_TXFR | SERIAL_FCR_FE);
        outw(UART_PORT[port_no] + SERIAL_FCR, ww);

        pDrv->info.status.tx_busy = 0;

        break;
    }

    case UART_CTRL_ABORT_RX:
    {
        /* disable Rx interrupt */
        uint32_t ww = inw(UART_PORT[port_no] + SERIAL_IER);
        ww &= ~SERIAL_IER_DR;
        outw(UART_PORT[port_no] + SERIAL_IER, ww);

        /* reset Rx FIFO */
        ww = inw(UART_PORT[port_no] + SERIAL_FCR);
        ww |= (SERIAL_FCR_RXFR | SERIAL_FCR_FE);
        outw(UART_PORT[port_no] + SERIAL_FCR, ww);

        pDrv->info.status.rx_busy = 0;

        break;
    }

    case UART_CTRL_FIFO_RX:
    {
        uint32_t ww;
        kdrv_uart_fifo_config_t *pCfg = (kdrv_uart_fifo_config_t *)pVal;
        uint8_t trig_lvl = 0x3 & pCfg->fifo_trig_level;

        if(pCfg->bEnFifo)
        {
            ww = inw(UART_PORT[port_no] + SERIAL_FCR);
            ww &= ~(0x3 << 6);
            ww |= (trig_lvl << 6) | SERIAL_FCR_RXFR | SERIAL_FCR_FE; // val + reset + enable
            outw(UART_PORT[port_no] + SERIAL_FCR, ww);
        }
        else
        {
            /*
              becasue RX/TX FIFO can only be enable/disabled simutanously,
              cannot be set individually, so set trigger val to 0 for FIFO disable
            */
            ww = inw(UART_PORT[port_no] + SERIAL_FCR);
            ww &= ~(0x3 << 6);
            ww |= (trig_lvl << 6) | SERIAL_FCR_RXFR | SERIAL_FCR_FE; // val + reset + enable
            outw(UART_PORT[port_no] + SERIAL_FCR, ww);
        }

        pDrv->info.flags |= UART_FIFO_RX_CONFIGURED;

        break;
    }

    case UART_CTRL_FIFO_TX:
    {
        uint32_t ww;
        kdrv_uart_fifo_config_t *pCfg = (kdrv_uart_fifo_config_t *)pVal;
        uint8_t trig_lvl = 0x3 & pCfg->fifo_trig_level;

        if (pCfg->bEnFifo)
        {
            ww = inw(UART_PORT[port_no] + SERIAL_FCR);
            ww &= ~(0x3 << 4);
            ww |= (trig_lvl << 4) | SERIAL_FCR_TXFR | SERIAL_FCR_FE; // val + reset + enable
            outw(UART_PORT[port_no] + SERIAL_FCR, ww);
        }
        else
        {
            /*
              becasue RX/TX FIFO can only be enable/disabled simutanously,
              cannot be set individually, so set trigger val to 0 for FIFO disable
            */
            ww = inw(UART_PORT[port_no] + SERIAL_FCR);
            ww &= ~(0x3 << 4);
                ww |= (trig_lvl << 4) | SERIAL_FCR_TXFR | SERIAL_FCR_FE; // val + reset + enable
            outw(UART_PORT[port_no] + SERIAL_FCR, ww);
        }

        pDrv->info.flags |= UART_FIFO_TX_CONFIGURED;

        break;
    }

    case UART_CTRL_LOOPBACK:
    {
        uint32_t ww = inw(UART_PORT[port_no] + SERIAL_MCR);
        if (*pVal == 0)
        {
            ww &= ~SERIAL_MCR_LPBK; // disable loopback
            outw(UART_PORT[port_no] + SERIAL_MCR, ww);
        }
        else
        {
            ww |= SERIAL_MCR_LPBK; // enable loopback
            outw(UART_PORT[port_no] + SERIAL_MCR, ww);
        }

        pDrv->info.flags |= UART_LOOPBACK_ENABLED;

        break;
    }
    case UART_CTRL_TIMEOUT_RX:
    {
        pDrv->nTimeOutRx = (int32_t)*pVal;

        break;
    }

    case UART_CTRL_TIMEOUT_TX:
    {
        pDrv->nTimeOutTx = (int32_t)*pVal;

        break;
    }

    default:
        break;
    }
    return KDRV_STATUS_OK;
}

/*
Write data to KL520 device, such as command, parameters, but not suitable for chunk data
Input:
    hdl: device handle
    buf: data buffer
    len: data buffer length
return:
    driver status
*/
kdrv_status_t kdrv_uart_write(kdrv_uart_handle_t handle, uint8_t *data, uint32_t len)
{
    uint32_t com_port = handle;

#ifdef UART_DEBUG
    if (com_port >= TOTAL_UART_DEV)
    {
        dbg_msg("Invalid parameter\n");
        return KDRV_STATUS_ERROR;
    }

    if (gDrvCtx.active_dev[com_port] == false)
    {
        dbg_msg("This UART device has been closed\n");
        return KDRV_STATUS_ERROR;
    }
#endif

    uart_driver_handle_t *pDrv = gDrvCtx.uart_dev[com_port];

#ifdef UART_DEBUG
    if ((data == NULL) || (len == 0))
    {
        // Invalid parameters
        return KDRV_STATUS_ERROR;
    }
#endif

    if (pDrv->info.status.tx_busy == 1)
    {
        // Send is not completed yet
        return KDRV_STATUS_UART_TX_RX_BUSY;
    }


    pDrv->info.status.tx_busy = 1;
    uint8_t enable = 1;
    uint32_t write_len;
    write_len = (len>MAX_FIFO_TX)? MAX_FIFO_TX : len;
    for(uint32_t i = 0; i<write_len; i++)
    {
        outw(UART_PORT[pDrv->uart_port] + SERIAL_THR, *data++);
    }
    // Save transmit buffer info
    pDrv->info.xfer.tx_buf          = (uint8_t *)data;
    pDrv->info.xfer.tx_num          = len;
    pDrv->info.xfer.batch_tx_num    = write_len;
    pDrv->info.xfer.tx_cnt          = 0;


    NVIC_SetVector((IRQn_Type)pDrv->res.irq_num, (uint32_t)pDrv->res.isr);
    NVIC_EnableIRQ((IRQn_Type)pDrv->res.irq_num);
    kdrv_uart_configure(pDrv->uart_port, UART_CTRL_TX_EN, &enable);

    if (pDrv->info.mode & UART_MODE_ASYN_TX)
    {
        /* setup TX FIFO
           re-calculate FIFO trigger value based on buffer length
        */
        if (pDrv->config.fifo_en == true)
        {
            kdrv_uart_fifo_config_t cfg;
            cfg.bEnFifo = true;
            cfg.fifo_trig_level = 0; // init with 0
            kdp_calculate_fifo_cfg(pDrv, len, &cfg);
            kdrv_uart_configure(com_port, UART_CTRL_FIFO_TX, (uint8_t *)&cfg);
        }
        return KDRV_STATUS_UART_TX_RX_BUSY;
    }
    else if (pDrv->info.mode & UART_MODE_SYNC_TX)
    {
        if (pDrv->info.cb_event)
                pDrv->info.cb_event(UART_TRANSFER_COMPLETE);
        else
        {
            while(pDrv->info.status.tx_busy)
            {
                __WFI();
            }
        }
        return KDRV_STATUS_OK;
    }
    else
    {
#ifdef UART_DEBUG
        dbg_msg("Error: Sync/Async mode was not set\n");
#endif
        return KDRV_STATUS_ERROR;
    }
}

/*
Read character data from KL520 device
Input:
    handle: device handle
return:
    character data
*/
kdrv_status_t kdrv_uart_get_char(kdrv_uart_handle_t handle, char *ch)
{
    uint32_t com_port = handle;

#ifdef UART_DEBUG
    if (com_port >= TOTAL_UART_DEV)
    {
        dbg_msg("Invalid parameter\n");
        return KDRV_STATUS_ERROR;
    }

    if (gDrvCtx.active_dev[com_port] == false)
    {
        dbg_msg("This UART device has been closed\n");
        return KDRV_STATUS_ERROR;
    }
#endif

    uart_driver_handle_t *pDrv = gDrvCtx.uart_dev[com_port];

    // Check if receiver is busy
    if (pDrv->info.status.rx_busy == 1)
    {
        return KDRV_STATUS_UART_TX_RX_BUSY;
    }

    pDrv->info.status.rx_busy = 1;

    // Clear RX status
    pDrv->info.status.rx_break = 0;
    pDrv->info.status.rx_framing_error = 0;
    pDrv->info.status.rx_overflow = 0;
    pDrv->info.status.rx_parity_error = 0;

    // Save receive buffer info
    pDrv->info.xfer.rx_buf = (uint8_t *)ch;
    pDrv->info.xfer.rx_cnt = 0;
    // Save number of data to be received
    pDrv->info.xfer.rx_num = 1;

    uint8_t enable = 1;
    kdrv_uart_configure(pDrv->uart_port, UART_CTRL_RX_EN, &enable);

    NVIC_SetVector((IRQn_Type)pDrv->res.irq_num, (uint32_t)pDrv->res.isr);
    NVIC_EnableIRQ((IRQn_Type)pDrv->res.irq_num);

    if (pDrv->info.mode & UART_MODE_ASYN_RX)
    {
        return KDRV_STATUS_UART_TX_RX_BUSY;
    }
    else if (pDrv->info.mode & UART_MODE_SYNC_RX)
    {
        if (pDrv->info.cb_event)
                pDrv->info.cb_event(UART_REVEIVE_COMPLETE);
        else
        {
            while(pDrv->info.status.rx_busy)
            {
                __WFI();
            }
        }
    }
    return KDRV_STATUS_OK;
}

/**
  Read data from UART receiver.
Input
  data:  buffer for receving data
  len:   size  Data buffer size in bytes
  handle:  Driver handle
return
    driver status
*/
kdrv_status_t kdrv_uart_read(kdrv_uart_handle_t handle, uint8_t *data, uint32_t len)
{
    uint32_t com_port = handle;

#ifdef UART_DEBUG
    if (com_port >= TOTAL_UART_DEV)
    {
        dbg_msg("Invalid parameter\n");
        return KDRV_STATUS_ERROR;
    }

    if (gDrvCtx.active_dev[com_port] == false)
    {
        dbg_msg("This UART device has been closed\n");
        return KDRV_STATUS_ERROR;
    }
#endif

    uart_driver_handle_t *pDrv = gDrvCtx.uart_dev[com_port];

#ifdef UART_DEBUG
    if ((data == NULL) || (len == 0))
    {
        // Invalid parameters
        return UART_API_INVALID_PARAM;
    }
#endif

    // Check if receiver is busy
    if (pDrv->info.status.rx_busy == 1)
    {
        return KDRV_STATUS_UART_TX_RX_BUSY;
    }

    pDrv->info.status.rx_busy = 1;

    // Clear RX status
    pDrv->info.status.rx_break = 0;
    pDrv->info.status.rx_framing_error = 0;
    pDrv->info.status.rx_overflow = 0;
    pDrv->info.status.rx_parity_error = 0;

    // Save receive buffer info
    pDrv->info.xfer.rx_buf = (uint8_t *)data;
    pDrv->info.xfer.rx_cnt = 0;
    // Save number of data to be received
    pDrv->info.xfer.rx_num = len;

    uint8_t enable = 1;
    kdrv_uart_configure(pDrv->uart_port, UART_CTRL_RX_EN, &enable);

    NVIC_SetVector((IRQn_Type)pDrv->res.irq_num, (uint32_t)pDrv->res.isr);
    NVIC_EnableIRQ((IRQn_Type)pDrv->res.irq_num);

    if (pDrv->info.mode & UART_MODE_ASYN_RX)
    {
        return KDRV_STATUS_UART_TX_RX_BUSY;
    }
    else if (pDrv->info.mode & UART_MODE_SYNC_RX)
    {
        if (pDrv->info.cb_event)
                pDrv->info.cb_event(UART_REVEIVE_COMPLETE);
        else
        {
            while(pDrv->info.status.rx_busy)
            {
                __WFI();
            }
        }
        return KDRV_STATUS_OK;
    }
    else
    {
#ifdef UART_DEBUG
        dbg_msg("Error: Sync/Async mode was not set\n");
#endif
        return KDRV_STATUS_ERROR;
    }
}

/* get char number in Rx buffer
Input:
    handle: device handle
Return:
    Received bytes
*/
uint32_t kdrv_uart_get_rx_count(kdrv_uart_handle_t handle)
{
    uint32_t data;
    uint32_t com_port = handle;

    uart_driver_handle_t *pDrv = gDrvCtx.uart_dev[com_port];
    data = pDrv->info.xfer.rx_cnt;
    return data;
}

uint32_t kdrv_uart_get_tx_count(kdrv_uart_handle_t handle)
{
    uint32_t data;
    uint32_t com_port = handle;

    uart_driver_handle_t *pDrv = gDrvCtx.uart_dev[com_port];
    data = pDrv->info.xfer.tx_cnt;
    return data;
}

int32_t fputc(int ch, FILE *f)
{
    char cc;
    if (ch != '\0')
    {
        cc = ch;
        kdrv_uart_write(handle0, (uint8_t *)&cc, 1);
    }

    if (ch == '\n')
    {
        cc = '\r';
        kdrv_uart_write(handle0, (uint8_t *)&cc, 1);
    }

    return ch;
}

int32_t fgetc(FILE *f)
{
    char c;
    kdrv_uart_read(handle0, (uint8_t *)&c, 1);
      return c;
}
