/*
 * File      : drv_uart.c
 * This file is part of RT-Thread RTOS
 * COPYRIGHT (C) 2009-2014 RT-Thread Develop Team
 *
 * The license and distribution terms for this file may be
 * found in the file LICENSE in this distribution or at
 * http://www.rt-thread.org/license/LICENSE
 *
 * Change Logs:
 * Date           Author       Notes
 * 2013-05-18     Bernard      The first version for LPC40xx
 * 2014-12-16     RT_learning  The first version for LPC5410x
 */


#include <rthw.h>
#include <rtthread.h>
#include <rtdevice.h>

#include "chip.h"

static uint32_t _UART_DivClk(uint32_t pclk, uint32_t m);
static uint32_t _UART_GetHighDiv(uint32_t val, uint8_t strict);
static int32_t _CalcErr(uint32_t n, uint32_t d, uint32_t *prev);
static ErrorCode_t _UART_CalcDiv(UART_BAUD_T *ub);
static void _UART_CalcMul(UART_BAUD_T *ub);

struct lpc_uart
{
    LPC_USART_T *UART;
    IRQn_Type UART_IRQn;
};

static rt_err_t lpc_configure(struct rt_serial_device *serial, struct serial_configure *cfg)
{
    struct lpc_uart *uart;

    UART_BAUD_T baud;
    UART_CFG_T UART_cfg;

    RT_ASSERT(serial != RT_NULL);
    uart = (struct lpc_uart *)serial->parent.user_data;

    /* Initialize UART Configuration parameter structure to default state:
     * Baudrate = 115200 b
     * 8 data bit
     * 1 Stop bit
     * None parity
     */

    /* Set up baudrate parameters */
    baud.clk = Chip_Clock_GetAsyncSyscon_ClockRate();   /* Clock frequency */
    baud.baud = cfg->baud_rate; /* Required baud rate */
    baud.ovr = 0;   /* Set the oversampling to the recommended rate */
    baud.mul = baud.div = 0;

    if(!baud.mul)
    {
        _UART_CalcMul(&baud);
    }
    _UART_CalcDiv(&baud);

    /* Set fractional control register */
    LPC_ASYNC_SYSCON->FRGCTRL = ((uint32_t) baud.mul << 8) | 0xFF;

    /* Configure the UART */
    UART_cfg.cfg = UART_CFG_8BIT;
    UART_cfg.div = baud.div;    /* Use the calculated div value */
    UART_cfg.ovr = baud.ovr;    /* Use oversampling rate from baud */
    UART_cfg.res = UART_BIT_DLY(cfg->baud_rate);

    /* P254,255,246 */
    uart->UART->OSR = (UART_cfg.ovr - 1) & 0x0F;
    uart->UART->BRG = (UART_cfg.div - 1) & 0xFFFF;
    uart->UART->CFG = UART_CFG_ENABLE | (UART_cfg.cfg & ~UART_CFG_RES);

    return RT_EOK;
}

static rt_err_t lpc_control(struct rt_serial_device *serial, int cmd, void *arg)
{
    struct lpc_uart *uart;

    RT_ASSERT(serial != RT_NULL);
    uart = (struct lpc_uart *)serial->parent.user_data;

    switch (cmd)
    {
    case RT_DEVICE_CTRL_CLR_INT:
        /* disable rx irq */
        uart->UART->INTENCLR &= ~0x01;
        break;
    case RT_DEVICE_CTRL_SET_INT:
        /* enable rx irq */
        uart->UART->INTENSET |= 0x01;
        break;
    }

    return RT_EOK;
}

static int lpc_putc(struct rt_serial_device *serial, char c)
{
    struct lpc_uart *uart;

    uart = (struct lpc_uart *)serial->parent.user_data;
    while(!(uart->UART->STAT & (0x01<<2)));

    uart->UART->TXDAT = c ;


    return 1;
}



static int lpc_getc(struct rt_serial_device *serial)
{
    struct lpc_uart *uart;

    uart = (struct lpc_uart *)serial->parent.user_data;
    if (uart->UART->STAT & 0x01)
        return (uart->UART->RXDAT);
    else
        return -1;
}

static const struct rt_uart_ops lpc_uart_ops =
{
    lpc_configure,
    lpc_control,
    lpc_putc,
    lpc_getc,
};


/* UART0 device driver structure */
struct lpc_uart uart0 =
{
    LPC_USART0,
    UART0_IRQn,
};
struct rt_serial_device serial0;



void UART0_IRQHandler(void)
{
    volatile  uint32_t  INTSTAT, tmp;
    /* enter interrupt */
    rt_interrupt_enter();

    INTSTAT = LPC_USART0->INTSTAT;

    INTSTAT &= 0x01;
    switch (INTSTAT)
    {
    case 0x01:
        rt_hw_serial_isr(&serial0, RT_SERIAL_EVENT_RX_IND);
        break;
    default :
        tmp = LPC_USART0->INTSTAT;
        break;
    }
    /* leave interrupt */
    rt_interrupt_leave();
}

void rt_hw_uart_init(void)
{
    struct lpc_uart *uart;
    struct serial_configure config = RT_SERIAL_CONFIG_DEFAULT;

    uart = &uart0;

    serial0.ops    = &lpc_uart_ops;
    serial0.config = config;
    serial0.parent.user_data = uart;

    /* Enable IOCON clock  Then your cfg will effective P38 */
    LPC_SYSCON->AHBCLKCTRLSET[0] = (1UL << 13);

    /* Setup UART TX,RX Pin configuration  cfg Pin as Tx, Rx */
    /*  P63,P77
            Selects pin function 1      IOCON_FUNC1
            No addition pin function    IOCON_MODE_INACT
            Enables digital function by setting 1 to bit 7(default) IOCON_DIGITAL_EN
    */
    LPC_IOCON->PIO[0][0] = (0x1 | (0x0 << 3) | (0x1 << 7));
    LPC_IOCON->PIO[0][1] = (0x1 | (0x0 << 3) | (0x1 << 7));


    /* Enable asynchronous APB bridge and subsystem P30 */
    LPC_SYSCON->ASYNCAPBCTRL = 0x01;

    /* The UART clock rate is the main system clock divided by this value P59 */
    LPC_ASYNC_SYSCON->ASYNCCLKDIV = 1;                                      /* Set Async clock divider to 1 */

      /* Enable peripheral clock(asynchronous APB) to UART0  P57*/
    LPC_ASYNC_SYSCON->ASYNCAPBCLKCTRLSET = (1 << 0x01);

    /* Controls the clock for the Fractional Rate Generator used with the USARTs P57*/
    LPC_ASYNC_SYSCON->ASYNCAPBCLKCTRLSET = (1 << 0x0F);     /* Enable clock to Fractional divider */

    /* preemption = 1, sub-priority = 1 */
    NVIC_SetPriority(uart->UART_IRQn, ((0x01 << 3) | 0x01));

    /* Enable Interrupt for UART channel */
    NVIC_EnableIRQ(uart->UART_IRQn);

    /* register UART0 device */
    rt_hw_serial_register(&serial0, "uart0",
                          RT_DEVICE_FLAG_RDWR | RT_DEVICE_FLAG_INT_RX | RT_DEVICE_FLAG_STREAM,
                          uart);
}


/* PRIVATE: Division logic to divide without integer overflow */
static uint32_t _UART_DivClk(uint32_t pclk, uint32_t m)
{
    uint32_t q, r, u = pclk >> 24, l = pclk << 8;
    m = m + 256;
    q = (1 << 24) / m;
    r = (1 << 24) - (q * m);
    return ((q * u) << 8) + (((r * u) << 8) + l) / m;
}

/* PRIVATE: Get highest Over sampling value */
static uint32_t _UART_GetHighDiv(uint32_t val, uint8_t strict)
{
    int32_t i, max = strict ? 16 : 5;
    for (i = 16; i >= max; i--)
    {
        if (!(val % i))
        {
            return i;
        }
    }
    return 0;
}

/* Calculate error difference */
static int32_t _CalcErr(uint32_t n, uint32_t d, uint32_t *prev)
{
    uint32_t err = n - (n / d) * d;
    uint32_t herr = ((n / d) + 1) * d - n;
    if (herr < err) {
        err = herr;
    }

    if (*prev <= err) {
        return 0;
    }
    *prev = err;
    return (herr == err) + 1;
}

/* Calculate the base DIV value */
static ErrorCode_t _UART_CalcDiv(UART_BAUD_T *ub)
{
    int32_t i = 0;
    uint32_t perr = ~0UL;

    if (!ub->div) {
        i = ub->ovr ? ub->ovr : 16;
    }

    for (; i > 4; i--) {
        int32_t tmp = _CalcErr(ub->clk, ub->baud * i, &perr);

        /* Continue when no improvement seen in err value */
        if (!tmp) {
            continue;
        }

        ub->div = tmp - 1;
        if (ub->ovr == i) {
            break;
        }
        ub->ovr = i;
    }

    if (!ub->ovr) {
        return ERR_UART_BAUDRATE;
    }

    ub->div += ub->clk / (ub->baud * ub->ovr);
    if (!ub->div) {
        return ERR_UART_BAUDRATE;
    }

    ub->baud = ub->clk / (ub->div * ub->ovr);
    return LPC_OK;
}

/* Calculate the best MUL value */
static void _UART_CalcMul(UART_BAUD_T *ub)
{
    uint32_t m, perr = ~0UL, pclk = ub->clk, ovr = ub->ovr;

    /* If clock is UART's base clock calculate only the divider */
    for (m = 0; m < 256; m++) {
        uint32_t ov = ovr, x, v, tmp;

        /* Get clock and calculate error */
        x = _UART_DivClk(pclk, m);
        tmp = _CalcErr(x, ub->baud, &perr);
        v = (x / ub->baud) + tmp - 1;

        /* Update if new error is better than previous best */
        if (!tmp || (ovr && (v % ovr)) ||
            (!ovr && ((ov = _UART_GetHighDiv(v, ovr)) == 0))) {
            continue;
        }

        ub->ovr = ov;
        ub->mul = m;
        ub->clk = x;
        ub->div = tmp - 1;
    }
}