/*
* This file is part of the libopencm3 project.
*
* Copyright (C) 2012 Benjamin Vernoux <titanmkd@gmail.com>
*
* This library is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library.  If not, see <http://www.gnu.org/licenses/>.
*/

#include <libopencm3/lpc43xx/uart.h>
#include <libopencm3/lpc43xx/cgu.h>

#define UART_SRC_32K             0x00
#define UART_SRC_IRC             0x01
#define UART_SRC_ENET_RX         0x02
#define UART_SRC_ENET_TX         0x03
#define UART_SRC_GP_CLKIN        0x04
#define UART_SRC_XTAL            0x06
#define UART_SRC_PLL0USB         0x07
#define UART_SRC_PLL0AUDIO       0x08
#define UART_SRC_PLL1            0x09
#define UART_SRC_IDIVA           0x0C
#define UART_SRC_IDIVB           0x0D
#define UART_SRC_IDIVC           0x0E
#define UART_SRC_IDIVD           0x0F
#define UART_SRC_IDIVE           0x10

#define UART_CGU_AUTOBLOCK_CLOCK_BIT     11
/* clock source selection (5 bits) */
#define UART_CGU_BASE_CLK_SEL_SHIFT      24

uint32_t dummy_read;

/*
* UART Init function
*/
void uart_init(uart_num_t uart_num, uart_databit_t data_nb_bits,
	    uart_stopbit_t data_nb_stop, uart_parity_t data_parity,
	    uint16_t uart_divisor, uint8_t uart_divaddval, uint8_t uart_mulval)
{
	uint32_t lcr_config;
	uint32_t uart_port;

	uart_port = uart_num;

	switch (uart_num) {
	case UART0_NUM:
		/* use PLL1 as clock source for UART0 */
		CGU_BASE_UART0_CLK = (1<<UART_CGU_AUTOBLOCK_CLOCK_BIT) |
			(CGU_SRC_PLL1<<UART_CGU_BASE_CLK_SEL_SHIFT);
		break;

	case UART1_NUM:
		/* use PLL1 as clock source for UART1 */
		CGU_BASE_UART1_CLK = (1<<UART_CGU_AUTOBLOCK_CLOCK_BIT) |
			(CGU_SRC_PLL1<<UART_CGU_BASE_CLK_SEL_SHIFT);
		break;

	case UART2_NUM:
		/* use PLL1 as clock source for UART2 */
		CGU_BASE_UART2_CLK = (1<<UART_CGU_AUTOBLOCK_CLOCK_BIT) |
			(CGU_SRC_PLL1<<UART_CGU_BASE_CLK_SEL_SHIFT);
		break;

	case UART3_NUM:
		/* use PLL1 as clock source for UART3 */
		CGU_BASE_UART3_CLK = (1<<UART_CGU_AUTOBLOCK_CLOCK_BIT) |
			(CGU_SRC_PLL1<<UART_CGU_BASE_CLK_SEL_SHIFT);
		break;

	default:
		return; /* error */
	}

	/* FIFOs RX/TX Enabled and Reset RX/TX FIFO (DMA Mode is also cleared)*/
	UART_FCR(uart_port) = (UART_FCR_FIFO_EN | UART_FCR_RX_RS |
				UART_FCR_TX_RS);
	/* Disable FIFO */
	UART_FCR(uart_port) = 0;

	/* Dummy read (to clear existing data) */
	while (UART_LSR(uart_port) & UART_LSR_RDR) {
		dummy_read = UART_RBR(uart_port);
	}

	/* Wait end of TX & disable TX */
	UART_TER(uart_port) = UART_TER_TXEN;

	/* Wait for current transmit complete */
	while (!(UART_LSR(uart_port) & UART_LSR_THRE));

	/* Disable Tx */
	UART_TER(uart_port) = 0;

	/* Disable interrupt */
	UART_IER(uart_port) = 0;

	/* Set LCR to default state */
	UART_LCR(uart_port) = 0;

	/* Set ACR to default state */
	UART_ACR(uart_port) = 0;

	/* Dummy Read to Clear Status */
	dummy_read = UART_LSR(uart_port);

	/*
	Table 835. USART Fractional Divider Register:
	UARTbaudrate = PCLK / ( 16* (((256*DLM)+ DLL)*(1+(DivAddVal/MulVal))) )
	The value of MULVAL and DIVADDVAL should comply to the following
	conditions:
	1. 1 <= MULVAL <= 15
	2. 0 <= DIVADDVAL <= 14
	3. DIVADDVAL < MULVAL
	*/

	/* Set DLAB Bit */
	UART_LCR(uart_port) |= UART_LCR_DLAB_EN;
	UART_DLM(uart_port) = UART_LOAD_DLM(uart_divisor);
	UART_DLL(uart_port) = UART_LOAD_DLL(uart_divisor);
	/* Clear DLAB Bit */
	UART_LCR(uart_port) &= (~UART_LCR_DLAB_EN) & UART_LCR_BITMASK;
	UART_FDR(uart_port) = UART_FDR_BITMASK &
	    (UART_FDR_MULVAL(uart_mulval) | UART_FDR_DIVADDVAL(uart_divaddval));

	/* Read LCR config & Force Enable of Divisor Latches Access */
	lcr_config = (UART_LCR(uart_port) & UART_LCR_DLAB_EN) &
			UART_LCR_BITMASK;
	lcr_config |= data_nb_bits; /* Set Nb Data Bits */
	lcr_config |= data_nb_stop; /* Set Nb Stop Bits */
	lcr_config |= data_parity; /* Set Data Parity */

	/* Write LCR (only 8bits) */
	UART_LCR(uart_port) = (lcr_config & UART_LCR_BITMASK);

	/* Enable TX */
	UART_TER(uart_port) = UART_TER_TXEN;
}

/*
* This Function return if data are received or not received.
*/
uart_rx_data_ready_t uart_rx_data_ready(uart_num_t uart_num)
{
	uint32_t uart_port;
	uint8_t uart_status;
	uart_rx_data_ready_t data_ready;

	uart_port = uart_num;

	uart_status = UART_LSR(uart_port) & 0xFF;

	/* Check Error */
	if ((uart_status & UART_LSR_ERROR_MASK) == 0) {
		/* No errors check if data is ready */
		if ((uart_status & UART_LSR_RDR) == 0) {
			data_ready = UART_RX_NO_DATA;
		} else {
			data_ready = UART_RX_DATA_READY;
		}
	} else {
		/* UART Error */
		data_ready = UART_RX_DATA_ERROR;
	}

	return data_ready;
}

/*
* This Function Wait until Data RX Ready, and return Data Read from UART.
*/
uint8_t uart_read(uart_num_t uart_num)
{
	uint32_t uart_port;
	uint8_t uart_val;

	uart_port = uart_num;

	/* Wait Until Data Received (Rx Data Not Ready) */
	while ((UART_LSR(uart_port) & UART_LSR_RDR) == 0);

	uart_val = (UART_RBR(uart_port) & UART_RBR_MASKBIT);

	return uart_val;
}

/*
* This Function Wait until Data RX Ready, and return Data Read from UART.
*/
uint8_t uart_read_timeout(uart_num_t uart_num, uint32_t rx_timeout_nb_cycles,
			    uart_error_t *error)
{
	uint32_t uart_port;
	uint8_t uart_val;
	uint32_t counter;

	uart_port = uart_num;

	/* Wait Until Data Received (Rx Data Not Ready) */
	counter = 0;
	while ((UART_LSR(uart_port) & UART_LSR_RDR) == 0) {
		if (rx_timeout_nb_cycles > 0) {
			counter++;
			if (counter >= rx_timeout_nb_cycles) {
				*error = UART_TIMEOUT_ERROR;
				return 0;
			}
		}
	}

	uart_val = (UART_RBR(uart_port) & UART_RBR_MASKBIT);

	/* Clear error */
	*error = UART_NO_ERROR;

	return uart_val;
}

/* This Function Wait Data TX Ready, and Write Data to UART
	if rx_timeout_nb_cycles = 0 Infinite wait
*/
void uart_write(uart_num_t uart_num, uint8_t data)
{
	uint32_t uart_port;

	uart_port = uart_num;

	/* Wait Until FIFO not full  */
	while ((UART_LSR(uart_port) & UART_LSR_THRE) == 0);

	UART_THR(uart_port) = data;
}