/*
 * FT232H SPI Test Program
 * - Created for the Eyebot M8 Board
 *
 * By John Hodge (20518201)
 * - Eyebot M8 by Andrew Adamson
 * Uses the linux version of ftd2xx.h from http://www.ftdichip.com/Drivers/D2XX.htm
 * You may need to run 'sudo rmmod ftdi_sio usbserial' before running this to remove any drivers that linux has automatically loaded 
 * The above driver command is not necessary with the angstrom distribution currently on the beagleboard
 */
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>
#include <string.h>
#include <stdbool.h>
#include <ftd2xx.h>

// Buffer sizes, might want to tweak for production
#define OUTBUF_SIZE	512
#define INBUF_SIZE	512

// Some helpful macros
#define MIN(a,b)	((a) < (b) ? (a) : (b))
#define Sleep(ms)	usleep(ms*1000)

// === GLOBALS ===
FT_HANDLE	gDeviceHandle;
size_t	giOutBytes = 0;
uint8_t	gsOutBuffer[OUTBUF_SIZE];
size_t	giInBytes = 0;
uint8_t	gsInBuffer[INBUF_SIZE];

// === CODE ===
// 
// Append data to the outbound queue
// 
void AppendBuf(const void *Data, size_t Length)
{
	if( Length > OUTBUF_SIZE - giOutBytes ) {
		fprintf(stderr, "Out of space in output buffer\n");
		exit(EXIT_FAILURE);
	}
	Length = MIN(OUTBUF_SIZE - giOutBytes, Length);
	memcpy(gsOutBuffer + giOutBytes, Data, Length);
	giOutBytes += Length;
}

void AppendByte(uint8_t Byte)
{
	AppendBuf(&Byte, 1);
}

// 
// Send the outbound queue to the device
// 
FT_STATUS SendBuf(FT_HANDLE Handle)
{
	FT_STATUS	fts;
	DWORD	bytes_sent;
	fts = FT_Write(Handle, gsOutBuffer, giOutBytes, &bytes_sent);
	if( fts != FT_OK ) {
		fprintf(stderr, "FT_Write failed (%i)\n", fts);
		giOutBytes = 0;
		return fts;
	}

	if( giOutBytes != bytes_sent ) {
		fprintf(stderr, "%i bytes queued, but only %i were sent.",
			(int)giOutBytes, (int)bytes_sent);
	}

	//printf("Send %i bytes\n", giOutBytes);
	giOutBytes = 0;
	return FT_OK;
}

//
// Wait for input and then read
//
FT_STATUS ReadInput(FT_HANDLE Handle)
{
	FT_STATUS	fts;
	 int	timeout = 1000;
	DWORD	queue_bytes, bytes_read;
	giInBytes = 0;
	// Wait for data
	do {
		fts = FT_GetQueueStatus(Handle, &queue_bytes);
		if( !queue_bytes )
			usleep(1000);
	} while( fts == FT_OK && queue_bytes == 0 && --timeout );
	// - error (timeout included)
	if( fts != FT_OK )
		return fts;

	// Read as much as possible
	giInBytes = MIN(queue_bytes, INBUF_SIZE);
	fts = FT_Read(Handle, gsInBuffer, giInBytes, &bytes_read);
	if( fts != FT_OK )
		return fts;
	if( giInBytes != bytes_read ) {
		fprintf(stderr, "%i bytes in read queue, but only %i were read\n",
			(int)giInBytes, (int)bytes_read);
	}
	giInBytes = bytes_read;
	return FT_OK;
}

//
// (Internal)
// Sends a byte to the MPSSE and checks for an error response.
//
FT_STATUS InitSPI_BadCmd(FT_HANDLE Handle, uint8_t cmdbyte)
{
	FT_STATUS	fts;

	// Ensure MPSSE is synchronized (send a bad command)
	AppendByte(cmdbyte);
	fts = SendBuf(Handle);
	if( fts != FT_OK )	return fts;
	fts = ReadInput(Handle);
	if( fts != FT_OK )	return fts;

	// Make sure the "Bad Command" response was recieved
	bool bad_command_found = false;
	if( giInBytes > 0 )
		printf("%02X ", gsInBuffer[0]);
	for( size_t i = 0; i < giInBytes - 1; i ++ )
	{
		printf("%02X ", gsInBuffer[i+1]);
		if( gsInBuffer[i] == 0xFA && gsInBuffer[i+1] == cmdbyte ) {
			bad_command_found = true;
			break ;
		}
	}
	printf("\n");
	if( !bad_command_found ) {
		fprintf(stderr, "MPSSE sync (0x%02X) failed. No 'Bad Command' resp in %i bytes.\n",
			cmdbyte, (int)giInBytes);
		return FT_OTHER_ERROR;	// Mind if I use this?
	}
	
	return FT_OK;
}

//
// Initialise the FT232H for MPSSE mode and configure for SPI
// - Also turns on an LED, just for debugging
//
FT_STATUS InitSPI(FT_HANDLE Handle)
{
	FT_STATUS	fts;

	fts = FT_ResetDevice(Handle);
	if(fts != FT_OK )	return fts;

	// Clean out stale data in the buffer
	DWORD	stale_buf_size;
	fts = FT_GetQueueStatus(Handle, &stale_buf_size);
	if( fts != FT_OK )	return fts;
	if(stale_buf_size > 0) {
		char	tmpbuf[stale_buf_size];
		fts = FT_Read(Handle, tmpbuf, stale_buf_size, &stale_buf_size);
		if( fts != FT_OK )	return fts;
	}
	
	// Set some useful values
	fts = FT_SetUSBParameters(Handle, 0xFFFF, 0xFFFF);	// set USB Req Transfer Size to 64k
	if( fts != FT_OK )	return fts;
	fts = FT_SetChars(Handle, false, 0, false, 0);	// Disable event/error chars
	if( fts != FT_OK )	return fts;
	fts = FT_SetTimeouts(Handle, 3000, 3000);	// set read/write timeouts to 3s
	if( fts != FT_OK )	return fts;
	fts = FT_SetLatencyTimer(Handle, 1);	// Set latency timer to 1ms (default is 16ms)
	if( fts != FT_OK )	return fts;
	fts = FT_SetFlowControl(Handle, FT_FLOW_RTS_CTS, 0x00, 0x00);	//Turn on flow control to synchronize IN requests (added by Andrew)
        if( fts != FT_OK )      return fts;
	fts = FT_SetBitMode(Handle, 0, 0x00);	// Reset.
	if( fts != FT_OK )	return fts;
	Sleep(50);	// quick nap
	fts = FT_SetBitMode(Handle, 0, 0x02);	// Enable MPSSE mode
	if( fts != FT_OK )	return fts;

	// Sleep for a bit for USB to catch up
	Sleep(50);

	// Sync (send 0xAA - bad command)
	fts = InitSPI_BadCmd(Handle, 0xAA);
	if( fts != FT_OK )	return fts;
	// Second sync (0xAB)
	fts = InitSPI_BadCmd(Handle, 0xAB);
	if( fts != FT_OK )	return fts;
	
	// Base clock is 60MHz
	// Caution: high and low periods must be longer than 2 CPU clock cycles
	uint16_t	divisor = 1200;	// 50KHz
	//uint16_t	divisor = 60;	// 1MHz (cannot run at this speed with a 2MHz system clock)
	AppendByte(0x8A);	// Disable clock front stage divisor (/5)
	AppendByte(0x97);	// Disable adaptive clocking
	AppendByte(0x8D);	// Disable 3-phase data clock
	SendBuf(Handle);
	AppendByte(0x80);	// "Set Data Bits Low Byte"
	AppendByte(0x00);
	AppendByte(0x0B);
	// NOTE: The below is for the debug LED
	AppendByte(0x82);	// "Set Data Bits High Byte (AC Bus)"
	AppendByte(0x01);	// (ACBUS0 High - Status LED)
	AppendByte(0x01);
	// Clock divisor
	AppendByte(0x86);
	AppendByte((divisor/2-1) & 0xFF);	// Low
	AppendByte((divisor/2-1) >> 8 );	// High
	SendBuf(Handle);
	// Wait a bit to ensure the chip is ready
	Sleep(30);

	// Turn off loopback on TDI/TDO connection
	AppendByte(0x85);
	SendBuf(Handle);

	// All done!
	return FT_OK;
}

// 
// Raise the CS line
// 
FT_STATUS SPI_CSRaise(FT_HANDLE Handle)
{
	// 5 repeats for 1us
	// - each command takes 0.2us to perform, so ensures that the
	//   line is high for at least 1us before data is sent
	for( int i = 0; i < 5; i ++ )
	{
		AppendByte(0x80);	// GPIO ADBUS
		AppendByte(0x08);	// CS line High
		AppendByte(0x0B);	// (output mode, shouldn't change)
	}
	return FT_OK;
}

// Lower the CS line
FT_STATUS SPI_CSLower(FT_HANDLE Handle)
{
	// - 5 repeats for 1us (each command aparently raises for 0.2us)?
	for( int i = 0; i < 5; i ++ )
	{
		AppendByte(0x80);	// GPIO ADBUS
		AppendByte(0x00);	// CS line low
		AppendByte(0x0B);	// (output mode, shoudn't change)
	}
	return FT_OK;
}

//
// Write a sequence of bytes to the SPI bus
//
FT_STATUS SPIWriteBytes(FT_HANDLE Handle, size_t Len, const void *Data)
{
	const uint8_t	*bdata = Data;

	if( Len == 0 )
		return FT_OK;

	// 0 = 1 byte, 0xFFFF = 2^16 bytes
	// - So, subtract one
	Len -= 1;

	// NOTE: CS is active-low
	SPI_CSLower(Handle);
	AppendByte(0x11);	// MSB Falling Edge Change Clock - out on -ve clock edge
	AppendByte(Len & 0xFF);	// LSB
	AppendByte(Len >> 8);	// MSB
	// Append data from input buffer
	for( size_t i = 0; i < Len+1; i ++ )
		AppendByte(bdata[i]);
	SPI_CSRaise(Handle);

	return SendBuf(Handle);
}

int main(int argc, char *argv[])
{
	FT_STATUS fts;

	// Check for correct usage
	if( argc != 2 )
	{
		fprintf(stderr, "Usage: %s ID\n", argv[0]);

		DWORD ndev;
		fts = FT_CreateDeviceInfoList(&ndev);
		if( fts != FT_OK ) {
			fprintf(stderr, "Enum failed, FT_CreateDeviceInfoList, fts = %i\n", fts);
			return 1;
		}

		printf("%i devices\n", ndev);

		return -1;
	}

	// Open the device
	int port = atoi(argv[1]);
	fts = FT_Open(port, &gDeviceHandle);
	if( fts != FT_OK ) {
		fprintf(stderr, "FT_Open(%i) failed, fts = %i\n", port, fts);
		return 1;
	}

	// Initialise SPI
	fts = InitSPI(gDeviceHandle);
	if( fts != FT_OK ) {
		fprintf(stderr, "InitSPI failed, fts = %i\n", fts);
		return 1;
	}

	// Continuously send test data to the device
	while( 1 )
	{
		uint8_t	data[] = {0x37};
		fts = SPIWriteBytes(gDeviceHandle, sizeof(data), data);
		sleep(10);
	}

	// Clean up, like a good program
	FT_Close(gDeviceHandle);

	return 0;
}

