#include "lprintf.h"

#include "lprintf_config.h"
#include "LPC17xx.h"
#include "core_cm3.h"
#include "iolpc17XX.h"
#include "lpc17xx_pinfunc.h"
#include "lpc_uart_baudrate.inc"
#include "lbitfield.h"

#define UART_FIFO_SIZE 16

#if (LPRINTF_UART_NUM == 0)
    #define LPRINTF_UART_PCLKSEL_REGNUM 0
    #define LPRINTF_UART_TX_PINSEL_REGNUM 0
    #define LPRINTF_UART_TX_PINSEL_Msk  LPC_PINCON_PINSEL_PX2_Msk
    #define LPRINTF_UART_TX_PINSEL_Val  LPC_PINFUNC_P0_2_TXD0
#elif (LPRINTF_UART_NUM == 1)
    #define LPRINTF_UART_PCLKSEL_REGNUM 0
    #define LPRINTF_UART_TX_PINSEL_REGNUM 4
    #define LPRINTF_UART_TX_PINSEL_Msk  LPC_PINCON_PINSEL_PX0_Msk
    #define LPRINTF_UART_TX_PINSEL_Val  LPC_PINFUNC_P2_0_TXD1
#elif (LPRINTF_UART_NUM == 2)
    #define LPRINTF_UART_PCLKSEL_REGNUM 1
    #warning pinsel not defined for UART2
#elif (LPRINTF_UART_NUM == 3)
    #define LPRINTF_UART_PCLKSEL_REGNUM 1
    #warning pinsel not defined for UART2
#endif



#define LPRINTF_UART                        LPRINTF_UART_(LPRINTF_UART_NUM)
#define LPRINTF_UART_(num)                  LPRINTF_UART__(num)
#define LPRINTF_UART__(num)                 LPC_UART ## num

#define LPRINTF_UART_IRQ                    LPRINTF_UART_IRQ_(LPRINTF_UART_NUM)
#define LPRINTF_UART_IRQ_(num)              LPRINTF_UART_IRQ__(num)
#define LPRINTF_UART_IRQ__(num)             UART ## num ## _IRQn

#define LPRINTF_UART_PCONP                  LPRINTF_UART_PCONP_(LPRINTF_UART_NUM)
#define LPRINTF_UART_PCONP_(num)            LPRINTF_UART_PCONP__(num)
#define LPRINTF_UART_PCONP__(num)           LPC_SC_PCONP_PCUART ## num ## _Msk

#define LPRINTF_UART_PCLKSEL                LPRINTF_UART_PCLKSEL_(LPRINTF_UART_PCLKSEL_REGNUM, LPRINTF_UART_NUM)
#define LPRINTF_UART_PCLKSEL_(reg, num)     LPRINTF_UART_PCLKSEL__(reg, num)
#define LPRINTF_UART_PCLKSEL__(reg, num)    LPC_SC_PCLKSEL ## reg ## _PCLK_UART ## num ## _Msk

#define LPRINTF_UART_PCLKSEL_REG            LPRINTF_UART_PCLKSEL_REG_(LPRINTF_UART_PCLKSEL_REGNUM)
#define LPRINTF_UART_PCLKSEL_REG_(regnum)   LPRINTF_UART_PCLKSEL_REG__(regnum)
#define LPRINTF_UART_PCLKSEL_REG__(regnum)  LPC_SC->PCLKSEL ## regnum

#define LPRINTF_UART_TX_PINSEL_REG          LPRINTF_UART_TX_PINSEL_REG_(LPRINTF_UART_TX_PINSEL_REGNUM)
#define LPRINTF_UART_TX_PINSEL_REG_(regnum) LPRINTF_UART_TX_PINSEL_REG__(regnum)
#define LPRINTF_UART_TX_PINSEL_REG__(regnum) LPC_PINCON->PINSEL ## regnum


static const unsigned char clk_sel_div[] = {4, 1, 2, 8};
extern uint32_t SystemCoreClock;


int lprintf_uart_init(uint32_t baud, uint8_t databits, t_lprintf_uart_parity parity) {
    unsigned long cfg = 0;
    int err = 0;

    LPC_SC->PCONP |= LPRINTF_UART_PCONP;
#ifdef LPRINTF_UART_TX_PINSEL_Msk
    /** @note для LBITFIELD_UPDATE LPRINTF_UART_TX_PINSEL_Val должен быть без смещения, пожтому
     *        используем явно. в будущих версиях возможно следует сделать определение
     *        пинов как в lpc43xx */
    LPRINTF_UART_TX_PINSEL_REG = (LPRINTF_UART_TX_PINSEL_REG & ~(LPRINTF_UART_TX_PINSEL_Msk))
                            | LPRINTF_UART_TX_PINSEL_Val;
#endif
    if (!err) {
        switch (databits) {
            case 5:
                break;
            case 6:
                cfg |= 0x01 << LPC_UART_LCR_WordLengthSelect_Pos;
                break;
            case 7:
                cfg |= 0x02 << LPC_UART_LCR_WordLengthSelect_Pos;
                break;
            case 8:
                cfg |= 0x03 << LPC_UART_LCR_WordLengthSelect_Pos;
                break;
            default:
                err = -1;
        }
    }

    if (!err) {
        switch(parity) {
            case LPRINTF_UART_PARITY_NONE:
                break;
            case LPRINTF_UART_PARITY_ODD:
                cfg |= LPC_UART_LCR_ParityEnable_Msk;
                break;
            case LPRINTF_UART_PARITY_EVEN:
                cfg |= LPC_UART_LCR_ParityEnable_Msk | (0x01 << LPC_UART_LCR_ParitySelect_Pos);
                break;
            default:
                err = -1;
        }
    }





    if (!err) {
        LPRINTF_UART->LCR = cfg;
        LPRINTF_UART->IER = 0;


#if 0
        LPC_UART1->RS485CTRL = LPC_UART1_RS485CTRL_SEL_Msk | LPC_UART1_RS485CTRL_DCTRL_Msk;
#ifdef MB_PORT_RS485_DIR_INVERSE
        LPC_UART1->RS485CTRL |= LPC_UART1_RS485CTRL_OINV_Msk;
#endif
#endif

        unsigned dl;
        unsigned fract_div;
        unsigned fract_mul;

        unsigned uartclk = SystemCoreClock;
        unsigned char clk_sel = LBITFIELD_GET(LPRINTF_UART_PCLKSEL_REG, LPRINTF_UART_PCLKSEL);
        uartclk/=clk_sel_div[clk_sel];

        f_get_baudrate_settings(uartclk, baud, &dl, &fract_div, &fract_mul );

        if (fract_mul != 0) {
            LPRINTF_UART->LCR |= LPC_UART_LCR_DLAB_Msk;
            LPRINTF_UART->DLL = dl & 0xFF;
            LPRINTF_UART->DLM = (dl >> 8) & 0xFF;
            LPRINTF_UART->FDR =((fract_div << LPC_UART_FDR_DIVADDVAL_Pos)
                    & LPC_UART_FDR_DIVADDVAL_Msk) |
                    ((fract_mul << LPC_UART_FDR_MULVAL_Pos) & LPC_UART_FDR_MULVAL_Msk);
            LPRINTF_UART->LCR &= ~LPC_UART_LCR_DLAB_Msk;
            LPRINTF_UART->FCR = LPC_UART_FCR_FIFOEnable_Msk | LPC_UART_FCR_RXFIFOReset_Msk |
                LPC_UART_FCR_TXFIFOReset_Msk;
            LPRINTF_UART->FCR = LPC_UART_FCR_FIFOEnable_Msk;

            (void)LPRINTF_UART->IIR;
        } else {
            err = -1;
        }
    }

    return err;
}

void lprintf_close(void) {
    lprintf("lboot: uart close!\n");
    /* ждем передачи всех данных */
    while (!(LPRINTF_UART->LSR & LPC_UART_LSR_TEMT_Msk)) {
        continue;
    }

#ifdef LPRINTF_UART_TX_PINSEL_Msk
    LPRINTF_UART_TX_PINSEL_REG = (LPRINTF_UART_TX_PINSEL_REG & ~(LPRINTF_UART_TX_PINSEL_Msk));
#endif

    /* возвращаем значения по умолчанию */
    LPRINTF_UART->LCR = LPC_UART_LCR_DLAB_Msk;
    LPRINTF_UART->DLL = 1;
    LPRINTF_UART->DLM = 0;
    LPRINTF_UART->FDR = 0x10;
    LPRINTF_UART->LCR = 0;
    LPRINTF_UART->IER = 0;
    LPRINTF_UART->FCR = 0;

    LPC_SC->PCONP &= ~LPRINTF_UART_PCONP;
}



void lprintf_putchar(char c) {
    static uint8_t rdy_size = UART_FIFO_SIZE;
    static uint8_t empty;

    do {
        empty = LPRINTF_UART->LSR & LPC_UART_LSR_THRE_Msk;
    } while (!empty && (rdy_size==0));


    if (empty)
        rdy_size = UART_FIFO_SIZE;

    LPRINTF_UART->THR = c;
    rdy_size--;
}
