/** 
 * \file uart.c
 * \brief uart source file
 * \details File contains functions definitions for UART module
 *
 * \author Juraj Golej
 * \version 1.0
 */

#include "uart.h"

static struct UART_module* gUART_registered[UART_INST_NUM] = { NULL };	///< List of registered modules

/**
 * \brief General UART interrupt handler function
 * \param[in] perID module ID
 */
static inline void _General_irq(uint8_t perID)
{
	Assert(gUART_registered[perID]);
	struct UART_module* module = gUART_registered[perID];
	uint32_t SRreg = module->periphery->UART_SR;

	if(SRreg & UART_SR_RXRDY_Msk) {
		uint8_t data = module->periphery->UART_RHR;
		circbuff_push(&(module->read), &data);
		if((data == '\n' || data == '\r') && module->callback_rxLine) {
			module->callback_rxLine(module);
		}
		else if(module->callback_rxByte) {
			module->callback_rxByte(module);
		}
	}
	if(SRreg & UART_SR_TXRDY_Msk) {
		if(module->writeIndex < module->writeLen) {
			module->periphery->UART_THR = module->writeBuff[module->writeIndex];
			++module->writeIndex;
		}
		else {
			module->periphery->UART_CR = UART_CR_TXDIS_Msk;
			module->writeIndex = 0;
			module->writeLen = 0;
			if(module->callback_tx) {
				module->callback_tx(module);
			}
		}
	}
}

/**
 * \brief UART0 Interrupt handler function
 */
void UART0_Handler()
{
	_General_irq(0);
}

/**
 * \brief UART1 Interrupt handler function
 */
void UART1_Handler()
{
	_General_irq(1);
}

/**
 * \brief UART2 Interrupt handler function
 */
void UART2_Handler()
{
	_General_irq(2);
}

/**
 * \brief UART3 Interrupt handler function
 */
void UART3_Handler()
{
	_General_irq(3);
}

/**
 * \brief UART4 Interrupt handler function
 */
void UART4_Handler()
{
	_General_irq(4);
}

enum retCode UART_init(struct UART_module* const module, Uart* periphery)
{
	/* Check NULL pointers */
	Assert(module && periphery);

	/* Get peripheral hardware ID */
	int16_t tmpPerID =  UART_getPeripheralHWID(periphery);
	Try(tmpPerID < 0, EInvalidParameter);

	/* Register UART module */
	Uart* tmpInsta[UART_INST_NUM] = UART_INSTS;
	for(uint8_t i = 0; i < UART_INST_NUM; ++i) {
		if(tmpInsta[i] == periphery) {
			if(!gUART_registered[i]) {
				gUART_registered[i] = module;
				module->ID = i + 1;
				break;
			}
			else {
				return EDenied;
			}
		}
	}

	/* Init UART module */
	module->periphery = periphery;
	circbuff_deinit(&(module->read));
	module->writeBuff = NULL;
	module->writeLen = 0;
	module->writeIndex = 0;
	module->callback_rxByte = NULL;
	module->callback_rxLine = NULL;
	module->callback_tx = NULL;

	/* Enable peripheral clock and interrupt */
	PMC_peripheral_clock_enable(tmpPerID);
	NVIC_ClearPendingIRQ(tmpPerID);
	NVIC_EnableIRQ(tmpPerID);

	/* Disable receiver and transmitter and reset to default state */
	periphery->UART_CR = UART_CR_RSTSTA_Msk | UART_CR_TXDIS_Msk | UART_CR_RXDIS_Msk | UART_CR_RSTTX_Msk | UART_CR_RSTRX_Msk;

	/* Disable interrupts */
	periphery->UART_IDR = 0xFFUL;

	/* Set parity */
	periphery->UART_MR = UART_MR_PAR((uint8_t)UART_PARITY_NONE);

	return ENone;
}

enum retCode UART_deinit(struct UART_module* const module)
{
	/* Check NULL pointers */
	Assert(module);

	/* Check, if module is initialized */
	Try(!module->ID, EDenied);

	/* Disable interrupts */
	module->periphery->UART_IDR = 0xFFUL;

	/* Disable receiver and transmitter and reset to default state */
	module->periphery->UART_CR = UART_CR_RSTSTA_Msk | UART_CR_TXDIS_Msk | UART_CR_RXDIS_Msk | UART_CR_RSTTX_Msk | UART_CR_RSTRX_Msk;

	/* Erase UART registers */
	module->periphery->UART_MR = 0;
	module->periphery->UART_BRGR = 0;

	/* Disable peripheral clock and interrupt */
	int16_t tmpPerID =  UART_getPeripheralHWID(module->periphery);
	PMC_peripheral_clock_disable(tmpPerID);
	NVIC_DisableIRQ(tmpPerID);
	NVIC_ClearPendingIRQ(tmpPerID);

	/* Remove module from registered modules */
	gUART_registered[module->ID - 1] = NULL;
	module->ID = 0;

	return ENone;
}

enum retCode UART_enable_receiver(struct UART_module* const module, uint8_t* buffer, const uint16_t buffSize, uart_rx_byte_cb byte_cb, uart_rx_line_cb line_cb)
{
	/* Check NULL pointers */
	Assert(module && buffer);

	/* Check, if module is initialized */
	Try(!module->ID, EDenied);

	/* Init circbuffer and callback functions */
	Try(circbuff_init(&(module->read), buffer, sizeof(uint8_t), buffSize), EInvalidParameter);
	module->callback_rxByte = byte_cb;
	module->callback_rxLine = line_cb;

	/* Enable receiver */
	module->periphery->UART_CR = UART_CR_RXEN_Msk;

	/* Enable interrupt when receiver register have data */
	module->periphery->UART_IER = UART_IER_RXRDY_Msk;

	return ENone;
}

enum retCode UART_stop_transmit(struct UART_module* const module)
{
	/* Check NULL pointers */
	Assert(module);

	/* Check, if module is initialized */
	Try(!module->ID, EDenied);

	/* Disable interrupt when transmitter register is empty */
	module->periphery->UART_IDR = UART_IER_TXRDY_Msk;

	/* Disable transmitter */
	module->periphery->UART_CR = UART_CR_TXDIS_Msk | UART_CR_RSTTX_Msk;

	/* Deinit buffer and callback functions */
	module->writeBuff = NULL;
	module->writeLen = 0;
	module->writeIndex = 0;
	module->callback_tx = NULL;

	return ENone;
}

enum retCode UART_disable_receiver(struct UART_module* const module)
{
	/* Check NULL pointers */
	Assert(module);

	/* Check, if module is initialized */
	Try(!module->ID, EDenied);

	/* Disable interrupt when receiver register is empty */
	module->periphery->UART_IDR = UART_IER_RXRDY_Msk;

	/* Disable receiver */
	module->periphery->UART_CR = UART_CR_RXDIS_Msk | UART_CR_RSTRX_Msk;

	/* Deinit circbuffer and callback functions */
	circbuff_deinit(&(module->read));
	module->callback_rxByte = NULL;
	module->callback_rxLine = NULL;

	return ENone;
}

enum retCode UART_write(struct UART_module* const module, const uint8_t* const data, const uint16_t len, uart_tx_cb cb)
{
	/* Check NULL pointers */
	Assert(module);

	/* Check, if module is initialized */
	Try(!module->ID, EDenied);
	
	/* Check, if transmitter isn't busy */
	Try(module->writeIndex || module->writeLen, EBusy);

	module->writeBuff = data;
	module->writeLen = len;

	/* Set callback function */
	module->callback_tx = cb;

	/* Enable transmitter */
	module->periphery->UART_CR = UART_CR_TXEN_Msk;

	/* Enable interrupt when transmitter register is empty */
	module->periphery->UART_IER = UART_IER_TXRDY_Msk;

	return ENone;
}

uint16_t UART_read(struct UART_module* const module, uint8_t* const data, const uint16_t maxlen)
{
	/* Check NULL pointers */
	Assert(module && data);

	/* Check, if module is initialized */
	Try(!module->ID, EDenied);

	/* Check, if receiver is enabled */
	Try(!(module->periphery->UART_IMR & (1 << UART_IMR_RXRDY_Msk)), 0);

	uint16_t i;
	for(i = 0; i < maxlen; ++i) {
		if(circbuff_pull(&(module->read), data + i)) {
			break;
		}
	}

	return i;
}

uint16_t UART_readLine(struct UART_module* const module, uint8_t* const data, const uint16_t maxlen)
{
	/* Check NULL pointers */
	Assert(module && data);

	/* Check, if module is initialized */
	Try(!module->ID, EDenied);

	/* Check, if receiver is enabled */
	Try(!(module->periphery->UART_IMR & (1 << UART_IMR_RXRDY_Msk)), 0);

	uint16_t i;
	for(i = 0; i < maxlen; ++i) {
		if(circbuff_pull(&(module->read), data + i) || data[i] == '\n' || data[i] == '\r') {
			break;
		}
	}

	return i;
}

enum retCode UART_setBaudRate(struct UART_module* const module, const enum UART_BAUD baud)
{
	/* Check NULL pointers */
	Assert(module);

	/* Check, if module is initialized */
	Try(!module->ID, EDenied);

	/* Calculate Baud rate register */
	uint32_t BRreg = PMC_get_mck_freq() / (16UL * (uint32_t)baud);
	Try(BRreg > 0xFFFFUL, EInvalidParameter);

	/* Set baud rate */
	module->periphery->UART_BRGR = (uint16_t)BRreg;

	return ENone;
}

enum retCode UART_setParity(struct UART_module* const module, const enum UART_PARITY parity)
{
	/* Check NULL pointers */
	Assert(module);

	/* Check, if module is initialized */
	Try(!module->ID, EDenied);

	/* Set parity */
	module->periphery->UART_MR = (module->periphery->UART_MR & ~UART_MR_PAR_Msk) | UART_MR_PAR((uint8_t)parity);

	return ENone;
}

enum retCode UART_setTest(struct UART_module* const module, const enum UART_TEST test)
{
	/* Check NULL pointers */
	Assert(module);

	/* Check, if module is initialized */
	Try(!module->ID, EDenied);

	/* Set parity */
	module->periphery->UART_MR = (module->periphery->UART_MR & ~UART_MR_CHMODE_Msk) | UART_MR_CHMODE((uint8_t)test);

	return ENone;
}

enum retCode UART_setFilter(struct UART_module* const module, const bool filter)
{
	/* Check NULL pointers */
	Assert(module);

	/* Check, if module is initialized */
	Try(!module->ID, EDenied);

	/* Enable/disable filter */
	if(filter) {
		module->periphery->UART_MR |= UART_MR_FILTER_Msk;
	}
	else {
		module->periphery->UART_MR &= ~UART_MR_FILTER_Msk;
	}

	return ENone;
}