/** @file usart.c
 * Module handling Universal Synchronous/Asynchronous Receiver/Transmitter
 *
 * The module provides functions to configure the usarts and read/write from/to
 * it
 */

//--includes--------------------------------------------------------------------

#include "usart.h"
#include "usart_regs.h"

#include "rcc.h"
#include "dma.h"

#include "stddef.h"


//--local definitions-----------------------------------------------------------

static void configure_usart(volatile struct USART* regs,
		enum UsartConfig config);
static void configure_baudrate(volatile struct USART* regs, uint32_t clock,
		uint32_t baudrate);

#define DMA_CONFIG (DMA_CONFIG_PSIZE_8BITS)


//--local variables-------------------------------------------------------------

static volatile struct USART* const usarts[] = {
	(struct USART*)USART1_BASE_ADDRESS,
	(struct USART*)USART2_BASE_ADDRESS,
	(struct USART*)USART3_BASE_ADDRESS,
};

static const struct DmaParam usarts_rx_param[] = {
	{
		(void*)&usarts[USART_PERIPH_1]->DR,
		DMA_CONFIG,
		DMA_PERIPH_1,
		DMA_CHANNEL_5,
	},
	{
		(void*)&usarts[USART_PERIPH_2]->DR,
		DMA_CONFIG,
		DMA_PERIPH_1,
		DMA_CHANNEL_6,
	},
	{
		(void*)&usarts[USART_PERIPH_3]->DR,
		DMA_CONFIG,
		DMA_PERIPH_1,
		DMA_CHANNEL_3,
	},
};

static const struct DmaParam usarts_tx_param[] = {
	{
		(void*)&usarts[USART_PERIPH_1]->DR,
		DMA_CONFIG,
		DMA_PERIPH_1,
		DMA_CHANNEL_4,
	},
	{
		(void*)&usarts[USART_PERIPH_2]->DR,
		DMA_CONFIG,
		DMA_PERIPH_1,
		DMA_CHANNEL_7,
	},
	{
		(void*)&usarts[USART_PERIPH_3]->DR,
		DMA_CONFIG,
		DMA_PERIPH_1,
		DMA_CHANNEL_2,
	},
};


//--public functions------------------------------------------------------------

void usart_configure(enum UsartPeriph periph, enum UsartConfig config,
		uint32_t baudrate)
{
	struct RccClocks clocks;
	rcc_get_clocks(&clocks);

	switch (periph) {
		case USART_PERIPH_1:
			rcc_enable(RCC_AHB_NONE, RCC_APB1_NONE, RCC_APB2_USART);
			configure_baudrate(usarts[USART_PERIPH_1], clocks.apb2_freq,
					baudrate);
			configure_usart(usarts[USART_PERIPH_1], config);
			break;
		case USART_PERIPH_2:
			rcc_enable(RCC_AHB_NONE, RCC_APB1_USART2, RCC_APB2_NONE);
			configure_baudrate(usarts[USART_PERIPH_2], clocks.apb1_freq,
					baudrate);
			configure_usart(usarts[USART_PERIPH_2], config);
			break;
		case USART_PERIPH_3:
			rcc_enable(RCC_AHB_NONE, RCC_APB1_USART3, RCC_APB2_NONE);
			configure_baudrate(usarts[USART_PERIPH_3], clocks.apb1_freq,
					baudrate);
			configure_usart(usarts[USART_PERIPH_3], config);
			break;
		default:
			break;
	}
}

uint32_t usart_read_byte(enum UsartPeriph periph, uint8_t* byte)
{
	if (periph > USART_PERIPH_3) {
		return 1;
	}

	if (usarts[periph]->SR.RXNE) {
		*byte = usarts[periph]->DR.DR;
		return 0;
	} else {
		return 1;
	}
}

uint32_t usart_write_byte(enum UsartPeriph periph, uint8_t byte)
{
	if (periph > USART_PERIPH_3) {
		return 1;
	}

	//only write data if the tx register it empty, give up otherwise
	if (usarts[periph]->SR.TXE) {
		usarts[periph]->DR.DR = byte;
		return 0;
	} else {
		return 1;
	}
}

const struct DmaParam* usart_configure_rx_dma(enum UsartPeriph periph)
{
	if (periph > USART_PERIPH_3) {
		return nullptr;
	}

	usarts[periph]->CR3.DMAR = 1;
	return &usarts_rx_param[periph];
}

const struct DmaParam* usart_configure_tx_dma(enum UsartPeriph periph)
{
	if (periph > USART_PERIPH_3) {
		return nullptr;
	}

	usarts[periph]->CR3.DMAT = 1;
	return &usarts_tx_param[periph];
}


//--local functions-------------------------------------------------------------

/**
 * Apply the given configuration to the given registers. Generic version of
 * usart_configure()
 */
static void configure_usart(volatile struct USART* regs,
		enum UsartConfig config)
{
	//configure parity
	switch (config)
	{
		case USART_CONFIG_7E1:
		case USART_CONFIG_8E1:
		case USART_CONFIG_7E2:
		case USART_CONFIG_8E2:
			regs->CR1.PCE = 1;
			regs->CR1.PS = 0;
			break;
		case USART_CONFIG_7O1:
		case USART_CONFIG_7O2:
		case USART_CONFIG_8O1:
		case USART_CONFIG_8O2:
			regs->CR1.PCE = 1;
			regs->CR1.PS = 1;
			break;
		case USART_CONFIG_8N1:
		case USART_CONFIG_8N2:
			regs->CR1.PCE = 0;
			break;
		default:
			break;
	}

	//configure bit number
	switch (config)
	{
		case USART_CONFIG_7E1:
		case USART_CONFIG_7E2:
		case USART_CONFIG_7O1:
		case USART_CONFIG_7O2:
		case USART_CONFIG_8N1:
		case USART_CONFIG_8N2:
			regs->CR1.M = 0;
			break;
		case USART_CONFIG_8E2:
		case USART_CONFIG_8E1:
		case USART_CONFIG_8O1:
		case USART_CONFIG_8O2:
			regs->CR1.M = 1;
			break;
		default:
			break;
	}

	//configure stop bits
	switch (config)
	{
		case USART_CONFIG_7E1:
		case USART_CONFIG_7O1:
		case USART_CONFIG_8N1:
		case USART_CONFIG_8E1:
		case USART_CONFIG_8O1:
			regs->CR2.STOP = 0;
			break;
		case USART_CONFIG_7E2:
		case USART_CONFIG_7O2:
		case USART_CONFIG_8N2:
		case USART_CONFIG_8E2:
		case USART_CONFIG_8O2:
			regs->CR2.STOP = 2;
			break;
		default:
			break;
	}

	//enable Rx/Tx
	regs->CR1.TE = 1;
	regs->CR1.RE = 1;
	regs->CR1.UE = 1;
}

/**
 * Configure the given registers with the given baudrate. Baudrate is dependant
 * on the peripheral's clock and may not be exact due to precision errors (see
 * table 192 in documentation)
 */
static void configure_baudrate(volatile struct USART* regs, uint32_t clock,
		uint32_t baudrate)
{
	uint32_t mantissa = clock / (baudrate * 16);
	uint32_t factor = clock / baudrate;
	volatile uint32_t divider = factor - (mantissa * 16);

	regs->BRR.DIV_Mantissa = mantissa & 0xFFF;
	regs->BRR.DIV_Fraction =  divider & 0xF;
}

