/* This file contains routines that talk to the FPGA board over the usb link */

#include <stdlib.h>
#include <stdio.h>
#include <libusb-1.0/libusb.h>

#include "hardware.h"


static char Rcsid[] = "$Header: /jayar/i/tm4/tmu/src/tmumon/RCS/hardware.c,v 1.6 2013/05/02 14:32:55 drg Exp drg $";


/* Constants */

#define MAX_PORTS 128
#define DEV_VID 0x09fb
#define DEV_PID 0x1337
#define HEADER_MAGIC 0xBEADED00
#define HEADER_DIR_READ 0x00
#define HEADER_DIR_WRITE 0x80
#define TIMEOUT_HEADER 0

#define EP_READ (1 | LIBUSB_ENDPOINT_IN)
#define EP_WRITE (1 | LIBUSB_ENDPOINT_OUT)

#define MAX_TRANSFER_SIZE	(32 * 1024)

/* Private globals */

static struct libusb_device_handle* g_usb_devh = NULL;
static struct libusb_context *g_usb_context = NULL;


/* Private functions */


static int send_header(int port, unsigned char dir, int nbytes)
{
	unsigned int header[2];
	int transferred;
	int result;

	if(!g_usb_devh) {
		if(hardware_open() == -1) {
			return(-1);
		}
	}

	header[0] = HEADER_MAGIC | dir | ((unsigned char)port);
	header[1] = nbytes;

	result = libusb_bulk_transfer(g_usb_devh, EP_WRITE, (unsigned char*)header, sizeof(header), &transferred, TIMEOUT_HEADER);
	if (result < 0)
	{
		// fprintf(stderr, "send_header: error on transfer\n");
		return -1;
	}
	else if (transferred < sizeof(header))
	{
		// fprintf(stderr, "send_header: short write %d/%d bytes\n", transferred, sizeof(header));
		return -1;
	}

	return 0;
}



/* Public functions */

int hardware_open(void) {
	int r;

	r = libusb_init(&g_usb_context);
	if (r < 0) {
		fprintf(stderr, "hardware_open: failed to init libusb\n");
		return -1;
	}

	libusb_set_debug(g_usb_context, 3);

	g_usb_devh = libusb_open_device_with_vid_pid(g_usb_context, DEV_VID, DEV_PID);
	if (!g_usb_devh) {
		fprintf(stderr, "hardware_open: could not find/open DE3/DE4 USB link\n");
		libusb_exit(g_usb_context);
		return -1;
	}

	if(libusb_kernel_driver_active(g_usb_devh, 0)) {
		fprintf(stderr, "hardware_open: driver was active\n");
		if(!libusb_detach_kernel_driver(g_usb_devh, 0)) {
			fprintf(stderr, "hardware_open: driver detached\n");
		}
	}

	r = libusb_claim_interface(g_usb_devh, 0);
	if (r < 0) {
		fprintf(stderr, "hardware_open: failed to claim interface\n");
		libusb_close(g_usb_devh);
		libusb_exit(g_usb_context);
		return -1;
	}

	/* Success */
	return 1;
}


void hardware_close(void) {

	libusb_release_interface(g_usb_devh, 0);
	libusb_close(g_usb_devh);
	libusb_exit(g_usb_context);

	g_usb_devh = 0;
}



int hardware_read(int port, char* buf, int nbytes)
{
	int result, request_size;
	int transferred, total_transferred, round_up_to4;
	unsigned int header[8];

	/* Send transaction header */
	result = send_header(port, HEADER_DIR_READ, nbytes);
	if (result < 0) {
		/* Try re-opening, in case the FPGAs have been reprogrammed */
		hardware_close();
		hardware_open();
		result = send_header(port, HEADER_DIR_READ, nbytes);
	}
	if (result < 0) {
		fprintf(stderr, "hardware_read: error sending header\n");
		return -1;
	}

	total_transferred = 0;
	/* Receive data */
	while(1) {
		request_size = nbytes;
		if(request_size > MAX_TRANSFER_SIZE) {
			request_size = MAX_TRANSFER_SIZE;
		}

		/* The USB circuitry can only handle 32 bit word
		 * transfers.  Round up the request to the next word,
		 * and then throw any extra received data away.
		 */
		round_up_to4 = (request_size + 3)/4*4;
		result = libusb_bulk_transfer(
			g_usb_devh, EP_READ, &buf[total_transferred],
			 round_up_to4, &transferred, 0);
		if (result < 0) {
			fprintf(stderr, "hardware_read: error on transfer request_size %d transferred %d\n", request_size, transferred);
			return -1;
		}
		if(transferred == round_up_to4) {
			transferred = request_size;
		}
		total_transferred += transferred;
		nbytes -= transferred;
		if(nbytes <= 0) {
			break;
		}
	}

	return total_transferred;
}


int hardware_write(int port, char* buf, int nbytes)
{
	int result, request_size;
	int transferred, total_transferred;
	unsigned int header[8];

	/* Send transaction header */
	result = send_header(port, HEADER_DIR_WRITE, nbytes);
	if (result < 0) {
		/* Try re-opening, in case the FPGAs have been reprogrammed */
		hardware_close();
		hardware_open();
		result = send_header(port, HEADER_DIR_WRITE, nbytes);
	}
	if (result < 0)
	{
		fprintf(stderr, "hardware_write: error sending header\n");
		return -1;
	}

	total_transferred = 0;
	/* Send data */
	while(1) {
		request_size = nbytes;
		if(request_size > MAX_TRANSFER_SIZE) {
			request_size = MAX_TRANSFER_SIZE;
		}
		result = libusb_bulk_transfer(
			g_usb_devh, EP_WRITE, &buf[total_transferred],
			 request_size, &transferred, 0);
		if (result < 0) {
			fprintf(stderr, "hardware_write: error on transfer\n");
			return -1;
		}
		total_transferred += transferred;
		nbytes -= transferred;
		if(nbytes <= 0) {
			break;
		}
	}

	return total_transferred;
}
