/*
 * uart16550_drv.cpp
 *
 * See Xilinx pg143-axi-uart16550.pdf
 *
 * Register Address Map
 * LCR(7) 	Address Offset Register Name Access Type Description
 * 0 	0	0x1000 RBR RO Receiver Buffer Register
 * 0 	0	0x1000 THR WO Transmitter Holding Register
 * 0 	1	0x1004 IER R/W Interrupt Enable Register
 * x 	2	0x1008 IIR RO Interrupt Identification Register
 * x 	2	0x1008 FCR WO FIFO Control Register
 * 1 	2	0x1008 FCR RO FIFO Control Register
 * x 	3	0x100C LCR R/W Line Control Register
 * x 	4	0x1010 MCR R/W Modem Control Register
 * x 	5	0x1014 LSR R/W Line Status Register
 * x 	6	0x1018 MSR R/W Modem Status Register
 * x 	7	0x101C SCR R/W Scratch Register
 * 1 	9	0x1000 DLL R/W Divisor Latch (Least Significant Byte) Register
 * 1 	1	0x1004 DLM R/W Divisor Latch (Most Significant Byte) Register
 */

#include "xuart_driver.h"

#include "bsp_platform.h"

#include <stdint.h>

#ifndef __MINGW32__


typedef uint32_t u16650_reg_t;

enum lcr_t
{
	lcr_stop_bit=2,

	lcr_parity_enable=3,
	lcr_parity_even=4,

	lcr_dlab=7
};


struct u16550_t
{
	u16650_reg_t rbr_thr;	//0
	u16650_reg_t ier;		//1
	//u16650_reg_t iir;
	u16650_reg_t fcr;		//2
	u16650_reg_t lcr;		//3
	u16650_reg_t mcr;		//4
	u16650_reg_t lsr;		//5
	u16650_reg_t msr;		//6
	u16650_reg_t scratch;	//7
};

//static u16550_t dummy_uart;

static void u16550_initialize(volatile u16550_t* const u)
{
	u->lcr=
			3|	//8 bits, 1 stop
			(1<<lcr_parity_even) | //parity: EVEN
			(1<<lcr_parity_enable);

	u->fcr=
			(3<<6)|	//RX FIFO Trigger = 14 bytes
			(1<<2)| //TX FIFO Reset
			(1<<1)| //RX FIFO Reset
			(1<<0); //FIFO Enable

	u->ier=0;	//interrupt disabled

	u->mcr=0x0F;	//All pin low

	u->fcr=
			(3<<6)|	//RX FIFO Trigger = 14 bytes
			(1<<0); //FIFO Enable


	//Speed: use default = 9600 baud, that is 0x28B (clk = 100Mhz)
}

static int u16550_lcr_get(volatile u16550_t* const u, unsigned int bit)
{
	u16650_reg_t lsr=u->lsr;
	u16650_reg_t mask=1<<bit;
	int ready=lsr & mask;
	return ready;
}

static int u16550_data_ready(volatile u16550_t* const u)
{
	return u16550_lcr_get(u, 0);
}

static unsigned int u16550_data_ready_or_error(volatile u16550_t* const u)
{
	u16650_reg_t lsr=u->lsr;
	u16650_reg_t mask=(1<<7) | (3);	//Overrun, DR, or error in the Rx fifo
	unsigned int ready=lsr & mask;
	return ready;
}

//static int u16550_tx_ready(volatile u16550_t* const u) -- with this device no way to know is there are some free slot in the FIFO
static int u16550_tx_fifo_empty(volatile u16550_t* const u) //Can only know if the FIFo is emptu
{
	return u16550_lcr_get(u, 5);
}

#if 0
static int u16550_tx_idle(volatile u16550_t* const u)
{
	return u16550_lcr_get(u, 6);
}

static void u16550_loopback_enable(volatile u16550_t* const u, int ena)
{
	u->mcr=ena ? (1<<4) : 0;
}

static void u16550_isr(volatile u16550_t* const u)
{
}
#endif

enum { uart_num = 3};

static volatile u16550_t* const uarts[uart_num]=
{
		reinterpret_cast<u16550_t*>(0x50090000),
		reinterpret_cast<u16550_t*>(0x500A0000),
		reinterpret_cast<u16550_t*>(0x500B0000)
};

static volatile u16550_t* uartn(unsigned int ch)
{
	if (bsp_is_evm())
		return 0;

	if (ch>=uart_num)
	{
		return 0;
	}
	return uarts[ch];
}

int xuart_initialize(unsigned int ch, unsigned int /*speed*/, int /*parity*/)
{
	volatile u16550_t* u=uartn(ch);
	if (!u)
		return 1;

	u16550_initialize(u);
	return 0;
}

int xuart_send(unsigned int ch, const void* data, unsigned int len/*==0 -> "\0" terminated*/)
{
	volatile u16550_t* u=uartn(ch);
	if (!u)
		return 1;

	//TODO: add interrupt management, so far use the FIFO
	const unsigned char* const d=reinterpret_cast<const unsigned char*>(data);


	const int fifo_depth=16;
	int fifo_left=0;

	if (len==0)
	{
		for(int i=0; d[i]; ++i)
		{
			if (fifo_left<1)
			{
				while(!u16550_tx_fifo_empty(u))
				{
				}
				fifo_left=fifo_depth;
			}
			u->rbr_thr=d[i];
			--fifo_left;
		}
	}
	else
	{
		for(unsigned int i=0; i<len; ++i)
		{
			if (fifo_left<1)
			{
				while(!u16550_tx_fifo_empty(u))
				{
				}
				fifo_left=fifo_depth;
			}
			u->rbr_thr=d[i];
			--fifo_left;
		}
	}
	return 0;
}

int xuart_data_ready(unsigned int ch)
{
	volatile u16550_t* u=uartn(ch);
	if (!u)
		return 0;
	return u16550_data_ready(u);
}

int xuart_receive(unsigned int ch, char* received_char)
{
	volatile u16550_t* u=uartn(ch);
	if (!u)
		return xuart_rx_nodata;

	unsigned int rx_sts=u16550_data_ready_or_error(u);
	if (rx_sts)
	{
		if (received_char)
			*received_char=u->rbr_thr;
		if ((rx_sts & 0x0FE)==0)	//only DR bit set?
			return xuart_rx_data_ready;
		if ((rx_sts & 0x0D)==0)	//overrun?
			return xuart_rx_overrun;
		return xuart_rx_error;
	}
#if 0
	if (u16550_data_ready(u))
	{
		if (received_char)
			*received_char=u->rbr_thr;

		return 1;
	}
#endif
	return xuart_rx_nodata;
}

#else

void b16550_drv_zynq_stub()
{
}

#if 0
#include <windows.h>

static void printf(const char*, ...)
{
}

static const int stdout=0;

static void fflush(int)
{
}

//#include "xlru_log.h"

//#include <stdio.h>

#define DEBUG		//use to debug with no serial messages
#define	BOARD		//use to connect to board

// GLOBAL DEFINE

#define DELAY_SEND_CHAR 			10		//mmsec delay between 2 char on com
#define SEND_WITH_DELAY				true	//send single char on com

#ifdef BOARD
#define COM_MASTER 	"\\\\.\\COM1"		//serial port for EIF master, board Nucleo
#define COM_BAUDRATE CBR_9600
#else
#define COM_MASTER 	"\\\\.\\COM3"		//serial port for EIF master, local test with qtterminal
#define COM_BAUDRATE CBR_9600
#endif

#define COM_SLAVE 	"\\\\.\\COM4"		//serial port for LCU slave

// COMMON PARAMETERS

#define COM_BYTESIZE 8;
#define COM_STOPBITS ONESTOPBIT;
#define COM_PARITY NOPARITY;

#ifdef DEBUG
#define SERIAL_TIMEOUT 			2
#define SERIAL_MULTIPLIER  		2
#else
#define SERIAL_TIMEOUT 			50
#define SERIAL_MULTIPLIER  		10
#endif

//#define SERIAL_LOG	//enable log for serial comunication
//#define MESSAGE_LOG		//enable print all char arrived from serial into correct message
#define PRINT_ALL_CHAR	//enable print all char from serial

static HANDLE hSerial;
static DCB dcbSerialParams;
static COMMTIMEOUTS timeouts;
//static DWORD dwBytesRead=0;
//static DWORD dwBytesWrite=0;
static DWORD Status;

static int serial_set_parameter(uint32_t baudrate)
{
	// Set device parameters (9600 baud, 1 start bit, 1 stop bit, no parity)
	dcbSerialParams.DCBlength = sizeof(dcbSerialParams);
	if (GetCommState(hSerial, &dcbSerialParams) == 0)
	{
		CloseHandle(hSerial);
		return 1;
	}

	dcbSerialParams.BaudRate = baudrate; 				//CBR_9600;
	dcbSerialParams.ByteSize = COM_BYTESIZE;			//8;
	dcbSerialParams.StopBits = COM_STOPBITS;			//ONESTOPBIT;
	dcbSerialParams.Parity = baudrate==CBR_9600 ? EVENPARITY : COM_PARITY;				//NOPARITY;
	if(SetCommState(hSerial, &dcbSerialParams) == 0)
	{
		CloseHandle(hSerial);
		return 2;
	}

	return 0;
}

static int serial_set_timeouts(int timeout, int multiplier)
{
	// Set COM port timeout settings
	timeouts.ReadIntervalTimeout = timeout;
	timeouts.ReadTotalTimeoutConstant = timeout;
	timeouts.ReadTotalTimeoutMultiplier = multiplier;
	timeouts.WriteTotalTimeoutConstant = timeout;
	timeouts.WriteTotalTimeoutMultiplier = multiplier;

	timeouts.ReadIntervalTimeout = MAXDWORD;
	timeouts.ReadTotalTimeoutConstant = 0;
	timeouts.ReadTotalTimeoutMultiplier = 0;
	timeouts.WriteTotalTimeoutConstant = 0;
	timeouts.WriteTotalTimeoutMultiplier = 0;

	if(SetCommTimeouts(hSerial, &timeouts) == 0)
	{
		CloseHandle(hSerial);
		return 1;
	}
	return 0;
}

static int serial_open(const char *comNum, uint32_t baudrate)
{
	memset(&dcbSerialParams, 0, sizeof(dcbSerialParams));
	memset(&timeouts, 0, sizeof(timeouts));

	// Open serial port number 1
	//fprintf(stderr, "Opening serial port...");
	hSerial = CreateFile(comNum, GENERIC_READ|GENERIC_WRITE, 0, NULL,
		           	   	     OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL );
	if (hSerial == INVALID_HANDLE_VALUE)
	{
		//fprintf(stderr, "Error\n");
		return 1;	//invalid handler
	}
	else
	{
		//fprintf(stderr, "OK\n");
	}

	int ret = serial_set_parameter(baudrate);

	switch (ret)
	{
		case 0: 	//ok
			break;
		case 1:		//error GetComState
			printf("Error getting device state\n");
#ifdef SERIAL_LOG
			log_error("Error getting device state\n");
#endif
			return 2;
			break;
		case 2:		//error SetComState
			printf("Error setting device parameters\n");
#ifdef SERIAL_LOG
			log_error("Error setting device parameters\n");
#endif
			return 3;
			break;
	}

	if (serial_set_timeouts(SERIAL_TIMEOUT, SERIAL_MULTIPLIER)==1)
	{
		//error SetTimeOuts
		printf("Error setting timeouts\n");
#ifdef SERIAL_LOG
		log_error("Error setting timeouts\n");
#endif
		return 4;
	}

	//EV_TXTEMPTY|EV_RXCHAR
	//Status = SetCommMask(hSerial, EV_RXCHAR);
	Status = SetCommMask(hSerial, EV_TXEMPTY|EV_RXCHAR);

	//serial open ok
	return 0;
}

#if 0
static int serial_close()
{
	// Close serial port
	//printf("Closing serial port...");
#ifdef SERIAL_LOG
	log_info("Closing serial port...");
#endif
	if (CloseHandle(hSerial) == 0)
	{
		printf("Error\n");
		return 1;
	}
	//printf("Serial port closed.\n");
#ifdef SERIAL_LOG
	log_info("Serial port closed.\n");
#endif

	return 0;
}
#endif

static int serial_send(const char * pString)
{
	// Send specified text
	DWORD dwBytesWrite=0;
	printf("Sending bytes...");
#ifdef SERIAL_LOG
	log_info("Sending bytes...");
#endif
	if(!WriteFile(hSerial, pString, strlen(pString), &dwBytesWrite, NULL))
	{
		printf("Error sending bytes.\n");
#ifdef SERIAL_LOG
		log_error("Error sending bytes.\n");
#endif
		CloseHandle(hSerial);
		return 1;
	}
	printf("%d bytes written\n", (int)dwBytesWrite);
#ifdef SERIAL_LOG
	log_info("%d bytes written\n", (int)dwBytesWrite);
#endif
	fflush(stdout);
	return 0;
}

static int serial_send_delay(const char * pString)
{
	// Send specified text
	DWORD dwBytesWrite=0;
	int count_sended = 0;
	printf("Sending bytes...");
#ifdef SERIAL_LOG
	log_info("Sending bytes...");
#endif

	char send_char;
	int len = strlen(pString);
	for (int i = 0; i<len; i++)
	{
		send_char = pString[i];
		if(!WriteFile(hSerial, &send_char, 1, &dwBytesWrite, NULL))
		{
			printf("Error sending bytes.\n");
#ifdef SERIAL_LOG
			log_error("Error sending bytes.\n");
#endif
			CloseHandle(hSerial);
			return 1;
		}
		Sleep(DELAY_SEND_CHAR);
		count_sended = count_sended + dwBytesWrite;
	}
	printf("%d bytes written\n", (int)count_sended);
#ifdef SERIAL_LOG
	log_info("%d bytes written\n", (int)count_sended);
#endif
	fflush(stdout);
	return 0;
}

static int baud_rate;

int xuart_initialize(unsigned int /*ch*/, unsigned int /*speed*/, int /*parity*/)
{
	if (baud_rate==0)
	{
		baud_rate=COM_BAUDRATE;
		serial_open(COM_MASTER, baud_rate);
	}
	return 0;
}

int xuart_send(unsigned int /*ch*/, const void* data, unsigned int /*len*//*==0 -> "\0" terminated*/)
{
	if (baud_rate==CBR_9600)
		return serial_send(reinterpret_cast<const char*>(data));
	else
	return serial_send_delay(reinterpret_cast<const char*>(data));
}

static unsigned int pendingChar;

int xuart_data_ready(unsigned int /*ch*/)
{
	if (pendingChar!=0xF00000)
		return 1;

	char TempChar=0;
	DWORD dwBytesRead=0;

	BOOL ok=ReadFile( hSerial,           	//Handle of the Serial port
             &TempChar,       		//Temporary character
             sizeof(TempChar),		//Size of TempChar
             &dwBytesRead,    		//Number of bytes read
             NULL);

	if (!ok || (dwBytesRead==0))
		return 0;
	pendingChar=TempChar;
    return 1;
}

int xuart_receive(unsigned int /*ch*/, char* received_char)
{
	char TempChar=0;
	DWORD dwBytesRead=0;

	if (pendingChar!=0xF00000)
	{
		TempChar=pendingChar;
		pendingChar=0xF00000;

	}
	else
	{
		BOOL ok=ReadFile( hSerial,           	//Handle of the Serial port
             &TempChar,       		//Temporary character
             sizeof(TempChar),		//Size of TempChar
             &dwBytesRead,    		//Number of bytes read
             NULL);

		if ((!ok) || (dwBytesRead==0))
				return 0;
	}
	if (received_char)
		*received_char=TempChar;
	return 1;
}


#endif

#endif

