/*
 *
 * Copyright MBARI 2016 
 */

#include "uart.h"
#include "iap_driver.h"
#include "utils.h"

extern SERIAL_CFG serial0_cfg;
extern SERIAL_CFG serial3_cfg;

uint8_t rx0buff[UART_RRB_SIZE];
uint8_t tx0buff[UART_SRB_SIZE];
uint8_t rx3buff[UART_RRB_SIZE];
uint8_t tx3buff[UART_SRB_SIZE];

static void uart_hw_init(SERIAL_CFG *cfg);


/* flash writes are done in 256 byte segments */
UART_FLASH_CONFIG uart_flash_cfg[256 / sizeof(UART_FLASH_CONFIG)];

void handle_uart_int(SERIAL_CFG *cfg)
{
	uint32_t goal;
	uint8_t purgebuf[16];
	
	/* If we're getting close to filling the buffer, throw out some stuff. */
	if (RingBuffer_GetFree(&cfg->rxring) * 10 / UART_RRB_SIZE == 0) {
		goal = cfg->rx_lost + UART_RRB_SIZE / 10;
		while (cfg->rx_lost < goal) {
			cfg->rx_lost += RingBuffer_PopMult(&cfg->rxring, purgebuf,
			  sizeof(purgebuf));
		}
	}
	Chip_UART_IRQRBHandler(cfg->uart, &cfg->rxring, &cfg->txring);
}	

void UART0_IRQHandler(void)
{
	handle_uart_int(&serial0_cfg);
}

void UART3_IRQHandler(void)
{
	handle_uart_int(&serial3_cfg);
}

void
send_break(int uart_num)
{
	
	uint8_t port, pin;
	
	switch(uart_num) {
	case 0:
		port = UART0_PORT;
		pin = UART0_TX_PIN;
		break;
	case 3:
		port = UART3_PORT;
		pin = UART3_TX_PIN;
		break;
	default:
		return;
	}
	
	/*
	 * The break signal is a 0 on the TX line for at least one symbol time.
	 * The LPCOpen 2.0 library doesn't seem to support this directly even
	 * though v1.0 seemed to.
	 * So we switch the TX port bit back to GPIO, set it to 0 and set the
	 * port back to UART mode.
	 */
	Chip_IOCON_PinMux(LPC_IOCON, port, pin, IOCON_MODE_INACT, IOCON_FUNC0);
	Chip_GPIO_SetPinDIROutput(LPC_GPIO, port, pin);
	Chip_GPIO_SetPinState(LPC_GPIO, port, pin, false);
	utils_task_sleep(1500);
	Chip_GPIO_SetPinState(LPC_GPIO, port, pin, true);
	Chip_IOCON_PinMux(LPC_IOCON, port, pin, IOCON_MODE_INACT, IOCON_FUNC1);
}

void setup_uart0(SERIAL_CFG *cfg, uint32_t baud, char *mode)
{
	memset(cfg, 0, sizeof(SERIAL_CFG));
	cfg->irq = UART0_IRQn;
	cfg->uart = LPC_UART0;
	cfg->uart_num = 0;
	cfg->rxbuff = rx0buff;
	cfg->txbuff = tx0buff;
	uart_hw_init(cfg);
	set_uart_config(cfg, baud, mode, NULL, true);
}

void setup_uart3(SERIAL_CFG *cfg, uint32_t baud, char *mode)
{
	memset(cfg, 0, sizeof(SERIAL_CFG));
	cfg->irq = UART3_IRQn;
	cfg->uart = LPC_UART3;
	cfg->uart_num = 3;
	cfg->rxbuff = rx3buff;
	cfg->txbuff = tx3buff;
	uart_hw_init(cfg);
	set_uart_config(cfg, baud, mode, NULL, true);
}


static void
uart_hw_init(SERIAL_CFG *cfg)
{
	Chip_UART_Init(cfg->uart);

	Chip_UART_SetupFIFOS(cfg->uart, (UART_FCR_FIFO_EN | UART_FCR_TRG_LEV2));

	/* Enable UART Transmit */
	Chip_UART_TXEnable(cfg->uart);
	
	/* Before using the ring buffers, initialize them using the ring
	   buffer init function */
	RingBuffer_Init(&cfg->rxring, cfg->rxbuff, 1, UART_RRB_SIZE);
	RingBuffer_Init(&cfg->txring, cfg->txbuff, 1, UART_SRB_SIZE);

	/* Reset and enable FIFOs, FIFO trigger level 3 (14 chars) */
	Chip_UART_SetupFIFOS(cfg->uart, (UART_FCR_FIFO_EN | UART_FCR_RX_RS | 
							UART_FCR_TX_RS | UART_FCR_TRG_LEV3));

	/* Enable receive data and line status interrupt */
	Chip_UART_IntEnable(cfg->uart, (UART_IER_RBRINT | UART_IER_RLSINT));

	/* preemption = 1, sub-priority = 1 */
	NVIC_SetPriority(cfg->irq, 1);
	NVIC_EnableIRQ(cfg->irq);

}


int
set_uart_config(SERIAL_CFG *cfg, int baud, char* mode, char *error, bool nofail)
{
	int i, ret = 0;
	char wlen, stop;
	uint32_t flags = 0;
	
	/* in nofail mode we put in default values for invalid parameters */
	uint32_t default_baud = 9600;
	char default_wlen = 8;
	char default_parity = 'N';
	char default_parity_flag = UART_LCR_PARITY_DIS;
	char default_stop = 1;
	char default_mode[4] = {0,0,0,0};

	default_mode[0] = default_wlen + 48;
	default_mode[1] = default_parity;
	default_mode[2] = default_stop + 48;

	/* baud of -1 means don't set */
	if (baud > 0 || nofail) {
		switch (baud) {
		case 115200:
		case 57600:
		case 38400:
		case 19200:
		case 14400:
		case 9600:
		case 4800:
		case 2400:
		case 1200:
		case 600:
		case 300:
		case 110:
			cfg->baud = baud;
			break;
		default:
			if (nofail) {
				cfg->baud = default_baud;
				break;
			}
			sprintf(error, "Invalid baud rate!\r\n  acceptable values: "
			  "115200, 57600, 38400, 19200, 14400, 9600, 4800, 2400, 1200, 600, 300, 110\r\n");
			error += strlen(error);
			ret = 1;
		}
	}
	
	/* mode = NULL means don't set */
	if (mode || nofail) {

		/* filter out trailing newline characters */
		for (i = 0; i < strlen(mode); i++) {
			if (mode[i] <= ' ') {
				mode[i] = 0;
				break;
			}
		}
		if (strlen(mode) < 3) {
			if (nofail) {
				mode = default_mode;				
			} else {
				error += sprintf(error, "Invalid mode value!\r\n  example: "
				  "8N1\r\n");
				return (1);
			}
		}
	
		wlen = mode[0] - 48;
	
		if (wlen > 4 && wlen < 9) {
			cfg->mode[0] = wlen + 48;
			flags |= wlen - 5; /* WLEN5 is 0x0, WLEN8 is 0x3 */ 
		} else if (nofail) {
			cfg->mode[0] = default_wlen + 48;
			flags |= default_wlen - 5;
		} else {
			error += sprintf(error, "Invalid word length!\r\n  acceptable "
			  "values: 5, 6, 7, 8\r\n");
			ret = 1;
		}
	
		switch (mode[1]) {
		case 'n':
		case 'N':
			cfg->mode[1] = 'N';
			flags |= UART_LCR_PARITY_DIS;
			break;
		case 'e':
		case 'E':
			cfg->mode[1] = 'E';
			flags |= UART_LCR_PARITY_EN | UART_LCR_PARITY_EVEN;
			break;
		case 'o':
		case 'O':
			cfg->mode[1] = 'O';
			flags |= UART_LCR_PARITY_EN | UART_LCR_PARITY_ODD;
			break;
		default:
			if (nofail) {
				cfg->mode[1] = default_parity;
				flags |= default_parity_flag;
				break;
			}
			error += sprintf(error, "Invalid parity!\r\n  acceptable values: "
			  "n, e, o\r\n");
			ret = 1;
		}
	
		stop = mode[2] - 48;
		if (stop > 0 && stop < 3) {
			cfg->mode[2] = stop + 48;
		} else if (nofail) {
			cfg->mode[2] = default_stop + 48;
		} else {
			error += sprintf(error, "Invalid amount of stop bits!\r\n"
			  "acceptable values: 1, 2\r\n");
			ret = 1;
		}
		switch (cfg->mode[2]) {
		case '1':
			flags |= UART_LCR_SBS_1BIT;
			break;
		case '2':
			flags |= UART_LCR_SBS_2BIT;
			break;
		}

	}
	if (ret) return (ret);

	if (baud > 0 || nofail) Chip_UART_SetBaud(cfg->uart, cfg->baud);
	if (mode || nofail) Chip_UART_ConfigData(cfg->uart, flags);

	return (0);
}
