#include <asf.h>
#include "Serial.h"
#include "FIFO.h"

/* initialize USART on SERCOM2 with TX on pad 2 (pin PA10) and RX on pad 3 (pin PA11) */
void Serial_init(void)
{
	/* initialize FIFO buffers */
	FIFO_init();
	
	/* enable peripheral clock */
	PM->APBCMASK.bit.SERCOM2_ = 1;
	
	/* enable generic clock */
	GCLK->CLKCTRL.bit.ID = GCLK_CLKCTRL_ID_SERCOM2_CORE_Val;                         // configure generic clock for SERCOM2
	GCLK->CLKCTRL.bit.GEN = GCLK_CLKCTRL_GEN_GCLK0_Val;                              // source is generic clock generator 0
	GCLK->CLKCTRL.bit.CLKEN = 1;                                                     // enable generic clock
	
	/* enable GPIO pins alternative function */
	PORT->Group[0].PINCFG[10].bit.PMUXEN = 1;                                        // enable alternative function for pins PA10 and PA11
	PORT->Group[0].PINCFG[11].bit.PMUXEN = 1;
	PORT->Group[0].DIRCLR.reg = PORT_PA11;
	PORT->Group[0].PINCFG[11].bit.INEN = 1;
	PORT->Group[0].PMUX[5].bit.PMUXE = MUX_PA10D_SERCOM2_PAD2;                       // PA10 alternative function D (SERCOM2 pad 2)
	PORT->Group[0].PMUX[5].bit.PMUXO = MUX_PA11D_SERCOM2_PAD3;                       // PA11 alternative function D (SERCOM2 pad 3)
	
	/* configure peripheral */
	SERCOM2->USART.CTRLA.bit.MODE = SERCOM_USART_CTRLA_MODE_USART_INT_CLK_Val;       // USART internal clock operation
	SERCOM2->USART.CTRLA.bit.RXPO = 0x03;                                            // RX on pad 3 (pin PA11)
	SERCOM2->USART.CTRLA.bit.TXPO = 0x01;                                            // TX on pad 2 (pin PA10)
	SERCOM2->USART.CTRLA.bit.DORD = 1;                                               // LSB first
	SERCOM2->USART.CTRLA.bit.CMODE = 0x00;                                           // asynchronous communication (UART)
	SERCOM2->USART.CTRLA.bit.FORM = 0x00;                                            // no parity
	SERCOM2->USART.CTRLA.bit.SAMPR = 0x00;                                           // 16 bit oversampling with arithmetic baud rate generation
	SERCOM2->USART.CTRLB.bit.CHSIZE = 0x00;                                          // 8 bit data 
	SERCOM2->USART.CTRLB.bit.SBMODE = 0x00;                                          // one stop bit
	SERCOM2->USART.BAUD.reg = 63018;                                                 // BAUD = 65535 * (1 - num_samples * f_baud/f_clck)
	SERCOM2->USART.CTRLB.bit.RXEN = 1;                                               // enable receiver
	SERCOM2->USART.CTRLB.bit.TXEN = 1;                                               // enable transmitter
	
	/* enable interrupt */
	SERCOM2->USART.INTENSET.reg = SERCOM_USART_INTENSET_RXC;                         // enable Reception Complete interrupt
	NVIC_EnableIRQ(SERCOM2_IRQn);                                                    // enable interrupts in NVIC
	
	/* enable peripheral */	
	SERCOM2->USART.CTRLA.reg |= SERCOM_USART_CTRLA_ENABLE;	
	
	/* avoid corrupted bytes at start */
	delay_ms(100);
}

/* blocking implementation */
void Serial_transmit(uint8_t data)
{
	while (!TX_FIFO_put(data))
		;
	/* TX buffer has new data: re enable DRE interrupt */
	if (!(SERCOM2->USART.INTENSET.reg & SERCOM_USART_INTENSET_DRE))
		SERCOM2->USART.INTENSET.reg = SERCOM_USART_INTENSET_DRE;
	SERCOM2->USART.INTENSET.reg = SERCOM_USART_INTENSET_RXC;
}

/* blocking implementation */
uint8_t Serial_receive(void)
{
	uint8_t data;
	while (!RX_FIFO_get(&data))
		;
	/* RX buffer has space: re enable RXC interrupt */
	if (!(SERCOM2->USART.INTENSET.reg & SERCOM_USART_INTENSET_RXC))
		SERCOM2->USART.INTENSET.reg = SERCOM_USART_INTENSET_RXC;
	return data;
}

int Serial_receive_timeout(uint8_t *data, int timeout)
{
	int count = 0;
	while (!RX_FIFO_get(data))
	{
		count++;
		if (count == timeout)
			return 0;
	}
	return 1;
}

void Serial_write(const char *string)
{
	while (*string)
		Serial_transmit(*string++);
}

void Serial_writeln(const char *string)
{
	Serial_write(string);
	Serial_write("\r\n");
}

void Serial_read(char *string, int length)
{
	while (length--)
		*string++ = Serial_receive();
	*string = '\0';
}

void Serial_read_string(char *string)
{
	while ((*string++ = Serial_receive()) != '\n')
		;
	*string = '\0';
}

int Serial_available(void)
{
	return RX_FIFO_count();
}

/* find a string in RX buffer (blocking until found) */
void Serial_find(const char *string)
{
	const char *p = string;
	char c;
	while (*p != '\0')
		if ((c = Serial_receive()) == *p)
			p++;
		else if (p != string)
		{
			const char *q = p;
			while (p > string)
			{
				p--;
				if (*p == c)
				{
					int diff = q - p;
					const char *i;
					for (i = string ; i < p ; i++)
						if (*i != *(i + diff))
							break; // for loop
					if (i == p)
					{
						p++;
						break; // while loop
					}
				}
			}
		}
}

/* find a string in RX buffer (returns 0 if timed out) */
int Serial_find_timeout(const char *string, int timeout)
{
	const char *p = string;
	char c;
	while (*p != '\0')
	{
		if (!Serial_receive_timeout(&c, timeout))
			return 0;
		else if (c == *p)
			p++;
		else if (p != string)
		{
			const char *q = p;
			while (p > string)
			{
				p--;
				if (*p == c)
				{
					int diff = q - p;
					const char *i;
					for (i = string ; i < p ; i++)
						if (*i != *(i + diff))
						break; // for loop
					if (i == p)
					{
						p++;
						break; // while loop
					}
				}
			}
		}
	}
	return 1;
}

void SERCOM2_Handler(void)
{
	/* handle RX interrupt */
	if (SERCOM2->USART.INTFLAG.bit.RXC && SERCOM2->USART.INTENSET.bit.RXC)    // if RXC interrupt is triggered and enabled
		if (!RX_FIFO_put(SERCOM2->USART.DATA.reg))
			SERCOM2->USART.INTENCLR.reg = SERCOM_USART_INTENCLR_RXC;          // if RX buffer is full disable RXC interrupt
	/* handle TX interrupt */
	if (SERCOM2->USART.INTFLAG.bit.DRE && SERCOM2->USART.INTENSET.bit.DRE)    // if DRE interrupt is triggered and enabled
	{
		uint8_t data;
		if (!TX_FIFO_get(&data))
			SERCOM2->USART.INTENCLR.reg = SERCOM_USART_INTENCLR_DRE;          // if TX buffer is empty disable DRE interrupt
		else
			SERCOM2->USART.DATA.reg = data;		
	}
}