/* libnexys.c
 * Main libusb driver for libnexys
 * libnexys, a free driver for Digilent NEXYS2 boards
 *
 * Copyright (c) 2007-2008 Joshua Wise
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License.
 *
 * Commercial licenses are available by request.
 */

#ifdef WIN32_NATIVE
#include "libnexys.win32.c"
#else
#define LIBNEXYS_INTERNAL
#include "libnexys.h"
#include <stdio.h>
#include <usb.h>

#define NEXYS2_VENDOR 0x1443
#define NEXYS2_DEVICE 0x0005

nexys2_t nexys2_init(void) {
	struct usb_bus *bus;
	struct usb_device *dev;
	usb_dev_handle *udev;
	int dev_found, ret;
	unsigned char buf[15];
	
	usb_init();

	usb_find_busses();
	usb_find_devices();

	udev = NULL;
	for (bus = usb_get_busses(); bus && !udev; bus = bus->next)
		for (dev = bus->devices; dev && !udev; dev = dev->next)
			if ((dev->descriptor.idVendor == NEXYS2_VENDOR) && (dev->descriptor.idProduct == NEXYS2_DEVICE))
				udev = usb_open(dev);

	if (!udev)
	{
		fprintf(stderr, "nexys2_init: no device found or device open failed\n");
		return NULL;
	}
	
	if (usb_set_configuration(udev, 1) < 0)
	{
		fprintf(stderr, "nexys2_init: failed to set configuration 1 -- check your permissions\n");
		return NULL;
	}
	
	if (usb_claim_interface(udev, 0) < 0)	/* Needed for libusb on win32.  This code somehow crashes win32 libusb/kernel anyway, but... */
	{
		fprintf(stderr, "nexys2_init: failed to claim interface 0 -- check your permissions\n");
		return NULL;
	}
	
	if (usb_reset(udev) < 0)	/* Sometimes the board locks up when you close it, so send this. */
	{
		fprintf(stderr, "nexys2_init: failed to reset the USB (%s)\n", usb_strerror());
		return NULL;
	}
	
	ret = usb_control_msg(udev, 0x00, 0x09, 1, 0, NULL, 0, 1000);
	if (ret < 0)
	{
		fprintf(stderr, "nexys2_init: failed to send setup control message\n");
		return NULL;
	}
	
	ret = usb_control_msg(udev, 0xC0, 0xE4, 0, 0, buf, 14, 1000);
	if (ret < 0)
	{
		fprintf(stderr, "nexys2_init: failed to send serial number control message\n");
		return NULL;
	}
	
	buf[ret] = 0;
	fprintf(stderr, "nexys2_init: got serial number \"%s\" (%d bytes)\n", buf, ret);
	
	ret = usb_control_msg(udev, 0xC0, 0xE6, 0, 0, buf, 4, 1000);
	if (ret < 0)
	{
		fprintf(stderr, "nexys2_init: failed to send magic goo message\n");
		return NULL;
	}
	if (memcmp(buf, "\x01\x00\x02\x00", 4))
	{
		fprintf(stderr, "nexys2_init: magic goo did not return what we expected (%d, %02x %02x %02x %02x)\n", ret, buf[0], buf[1], buf[2], buf[3]);
		return NULL;
	}
	
	return udev;
}

static unsigned char common_response[64] =
	{ 0x00, 0x03, 0xf3, 0x77, 0x9f, 0xf5, 0xdf, 0xdd,
	  0xdf, 0xfe, 0x5f, 0xac, 0xff, 0xad, 0xf3, 0x34,
	  0xaf, 0xf5, 0xac, 0xf7, 0xca, 0xb7, 0xf7, 0x6f,
	  0xff, 0x5e, 0x5e, 0xf7, 0xfb, 0xfd, 0xfb, 0x37,
	  0x81, 0x53, 0xbf, 0x64, 0x59, 0x47, 0x59, 0x2d,
	  0xe8, 0x30, 0x77, 0xda, 0x4f, 0xaf, 0x7f, 0xd7,
	  0x5f, 0xee, 0xc7, 0x3b, 0xdf, 0x3e, 0xbf, 0x35,
	  0xd1, 0xdf, 0xef, 0x3f, 0x9f, 0xde, 0xfa, 0xe2
	};

int nexys2_jtag_enable(usb_dev_handle *udev)
{
	int ret;
	unsigned char message[64] =
		{ 0x07, 0x01, 0x00, 0x00, 0xdc, 0xff, 0xd3, 0x00,
		  0xf3, 0x99, 0x83, 0x7c, 0x08, 0x26, 0x80, 0x7c,
		  0xff, 0xff, 0xff, 0xff, 0x00, 0x26, 0x80, 0x7c,
		  0xf1, 0xe2, 0x90, 0x7c, 0xb7, 0x24, 0x80, 0x7c,
		  0x14, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
		  0x2a, 0x95, 0x80, 0x7c, 0x5b, 0x3d, 0x00, 0x11,
		  0x14, 0x07, 0x00, 0x00, 0xe8, 0x22, 0xa0, 0x00,
		  0x02, 0x00, 0x00, 0x00, 0xb3, 0x9e, 0x01, 0x11	/* XXX does this need to be sequenced? */
		};
	unsigned char resp[64];
	
	if ((ret = usb_bulk_write(udev, 0x01, message, 64, 1000)) < 64)
	{
		fprintf(stderr, "nexys2_jtag_enable: write failed: %d (%s)\n", ret, usb_strerror());
		return -1;
	}
	
	if ((ret = usb_bulk_read(udev, 0x81, resp, 64, 1000)) < 64)
	{
		fprintf(stderr, "nexys2_jtag_enable: short read on response: %d\n", ret);
		return -1;
	}
	
	if (memcmp(resp, common_response, 64))
	{
		fprintf(stderr, "nexys2_jtag_enable: incorrect response?\n");
//		return -1;
	}
	
	return 0;
}

int nexys2_jtag_disable(usb_dev_handle *udev)
{
	int ret;
	unsigned char message[64] =
		{ 0x07, 0x00, 0xf3, 0x76, 0xdc, 0xff, 0xd3, 0x00,
		  0xf3, 0x99, 0x83, 0x7c, 0x08, 0x26, 0x80, 0x7c,
		  0xff, 0xff, 0xff, 0xff, 0x00, 0x26, 0x80, 0x7c,
		  0xf1, 0xe2, 0x90, 0x7c, 0xb7, 0x24, 0x80, 0x7c,
		  0x14, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
		  0x2a, 0x95, 0x80, 0x7c, 0x5b, 0x3d, 0x00, 0x11,
		  0x14, 0x07, 0x00, 0x00, 0xe8, 0x22, 0xa0, 0x00,
		  0x05, 0x00, 0x00, 0x00, 0xb3, 0x9e, 0x01, 0x11	/* XXX does this need to be sequenced? */
		};
	unsigned char resp[64];
	
	if ((ret = usb_bulk_write(udev, 0x01, message, 64, 1000)) < 64)
	{
		fprintf(stderr, "nexys2_jtag_disable: write failed: %d (%s)\n", ret, usb_strerror());
		return -1;
	}
	
	if ((ret = usb_bulk_read(udev, 0x81, resp, 64, 1000)) < 64)
	{
		fprintf(stderr, "nexys2_jtag_disable: short read on response: %d\n", ret);
		return -1;
	}
	
	if (memcmp(resp, common_response, 64))
	{
		fprintf(stderr, "nexys2_jtag_disable: incorrect response?\n");
//		return -1;
	}
	
	return 0;
}

int nexys2_jtag_setbits(usb_dev_handle *udev, int tms, int tdi, int tck)
{
	int ret;
	unsigned char message[64] =
		{ 0x08, !!tms, !!tdi, !!tck, 0xdc, 0xff, 0xd3, 0x00,
		  0xf3, 0x99, 0x83, 0x7c, 0x08, 0x26, 0x80, 0x7c,
		  0xff, 0xff, 0xff, 0xff, 0x00, 0x26, 0x80, 0x7c,
		  0xf1, 0xe2, 0x90, 0x7c, 0xb7, 0x24, 0x80, 0x7c,
		  0x14, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
		  0x2a, 0x95, 0x80, 0x7c, 0x5b, 0x3d, 0x00, 0x11,
		  0x14, 0x07, 0x00, 0x00, 0xe8, 0x22, 0xa0, 0x00,
		  0x03, 0x00, 0x00, 0x00, 0xb3, 0x9e, 0x01, 0x11	/* XXX does this need to be sequenced? */
		};
	unsigned char resp[64];
	
	if ((ret = usb_bulk_write(udev, 0x01, message, 64, 1000)) < 64)
	{
		fprintf(stderr, "nexys2_jtag_setbits: write failed: %d (%s)\n", ret, usb_strerror());
		return -1;
	}
	
/*	if ((ret = usb_bulk_read(udev, 0x81, resp, 64, 1000)) < 64)
	{
		fprintf(stderr, "nexys2_jtag_setbits: short read on response: %d\n", ret);
		return -1;
	}
	
	if (memcmp(resp, common_response, 64))
	{
		fprintf(stderr, "nexys2_jtag_setbits: incorrect response?\n");
		return -1;
	}*/
	
	return 0;
}

int nexys2_jtag_puttdi(usb_dev_handle *udev, int tms, int nbits, unsigned char *bits, unsigned char *obits)
{
	int wanttdo = obits ? 1 : 0;
	int bytesrem;
	int ret;
	
	unsigned char message[64] =
		{ 0x09, wanttdo, !!tms, 0x00, 0x00, (nbits >> 8) & 0xFF, nbits & 0xFF, 0x7C,
		  0xeb, 0x06, 0x91, 0x7c, 0x28, 0xfa, 0xd3, 0x00,
		  0xec, 0xfc, 0xd3, 0x00, 0x00, 0x00, 0x00, 0x00,
		  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
		  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
		  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
		  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
		  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
		};
	
	if (nbits > 0xFFFF)
	{
		fprintf(stderr, "nexys2_jtag_puttdi: too many bits!\n");
		return -1;
	}
	
	if ((ret = usb_bulk_write(udev, 0x01, message, 64, 1000)) < 64)
	{
		fprintf(stderr, "nexys2_jtag_puttdi: write failed: %d (%s)\n", ret, usb_strerror());
		return -1;
	}
	
	bytesrem = nbits / 8 + ((nbits % 8) != 0);
	while (bytesrem)
	{
		int nbytes = (bytesrem > 512) ? 512 : bytesrem;
		if ((ret = usb_bulk_write(udev, 0x02, bits, nbytes, 1000)) < nbytes)
		{
			fprintf(stderr, "nexys2_jtag_puttdi: data write failed: %d (%s)\n", ret, usb_strerror());
			return -1;
		}
		bits += nbytes;
		bytesrem -= nbytes;
	}
	
	if (obits)
	{
		bytesrem = nbits / 8 + ((nbits % 8) != 0);
		while (bytesrem)
		{
			int nbytes = (bytesrem > 512) ? 512 : bytesrem;
			if ((ret = usb_bulk_read(udev, 0x86, obits, nbytes, 1000)) < nbytes)
			{
				fprintf(stderr, "nexys2_jtag_puttdi: data read failed: %d (%s)\n", ret, usb_strerror());
				return -1;
			}
			obits += nbytes;
			bytesrem -= nbytes;
		}
	}
	
	return 0;
}

int nexys2_jtag_puttmstdi(usb_dev_handle *udev, int nbits, unsigned char *bits, unsigned char *obits)
{
	int wanttdo = obits ? 1 : 0;
	int bytesrem;
	int ret;
	
	unsigned char message[64] =
		{ 0x0a, wanttdo, 0x00, 0x00, (nbits >> 8) & 0xFF, nbits & 0xFF, 0x91, 0x7C,
		  0xeb, 0x06, 0x91, 0x7c, 0x28, 0xfa, 0xd3, 0x00,
		  0xec, 0xfc, 0xd3, 0x00, 0x00, 0x00, 0x00, 0x00,
		  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
		  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
		  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
		  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
		  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
		};
	
	if (nbits > 0xFFFF)
	{
		fprintf(stderr, "nexys2_jtag_puttmstdi: too many bits!\n");
		return -1;
	}
	
	if ((ret = usb_bulk_write(udev, 0x01, message, 64, 1000)) < 64)
	{
		fprintf(stderr, "nexys2_jtag_puttmstdi: write failed: %d (%s)\n", ret, usb_strerror());
		return -1;
	}
	
	bytesrem = nbits / 4 + ((nbits % 4) != 0);
	while (bytesrem)
	{
		int nbytes = (bytesrem > 512) ? 512 : bytesrem;
		if ((ret = usb_bulk_write(udev, 0x02, bits, nbytes, 1000)) < nbytes)
		{
			fprintf(stderr, "nexys2_jtag_puttmstdi: data write failed: %d (%s)\n", ret, usb_strerror());
			return -1;
		}
		if (obits)
		{
			int nrbytes = nbytes/2 + (nbytes % 2);
			if ((ret = usb_bulk_read(udev, 0x86, obits, nrbytes, 1000)) < nrbytes)
			{
				fprintf(stderr, "nexys2_jtag_puttmstdi: data read failed: %d (%s)\n", ret, usb_strerror());
				return -1;
			}
			obits += nrbytes;
		}
		bits += nbytes;
		bytesrem -= nbytes;
	}
	
	return 0;
}

int nexys2_jtag_gettdo(usb_dev_handle *udev, int tdi, int tms, int nbits, unsigned char *obits)
{
	int wanttdo = obits ? 1 : 0;
	int bytesrem;
	int ret;
	
	unsigned char message[64] =
		{ 0x0B, !!tdi, !!tms, 0x00, 0x00, (nbits >> 8) & 0xFF, nbits & 0xFF, 0x7C,
		  0xeb, 0x06, 0x91, 0x7c, 0x28, 0xfa, 0xd3, 0x00,
		  0xec, 0xfc, 0xd3, 0x00, 0x00, 0x00, 0x00, 0x00,
		  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
		  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
		  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
		  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
		  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
		};
	
	if (nbits > 0xFFFF)
	{
		fprintf(stderr, "nexys2_jtag_puttdo: too many bits!\n");
		return -1;
	}
	
	if ((ret = usb_bulk_write(udev, 0x01, message, 64, 1000)) < 64)
	{
		fprintf(stderr, "nexys2_jtag_gettdo: write failed: %d (%s)\n", ret, usb_strerror());
		return -1;
	}
	
	bytesrem = nbits / 8 + ((nbits % 8) != 0);
	while (bytesrem)
	{
		int nbytes = (bytesrem > 512) ? 512 : bytesrem;
		if ((ret = usb_bulk_read(udev, 0x86, obits, nbytes, 1000)) < nbytes)
		{
			fprintf(stderr, "nexys2_jtag_gettdo: data read failed: %d (%s)\n", ret, usb_strerror());
			return -1;
		}
		obits += nbytes;
		bytesrem -= nbytes;
	}
	
	return 0;
}

#endif

#ifdef TEST_DRIVER

void poke_idcode(nexys2_t nexys)
{
	unsigned char seq1[] = { 0xaa, 0x22};
	unsigned char seq2[] = { 0xaa, 0x02};
	unsigned int idcode = 0;
	
//	while(1)
	{
		while (nexys2_jtag_enable(nexys) < 0)
			;
		if (nexys2_jtag_puttmstdi(nexys, 8, seq1, NULL) < 0)
			return;
		do
		{
			if (nexys2_jtag_gettdo(nexys, 0, 0, 32, &idcode) < 0)
				return;
			printf("IDCODE: %08x\n", idcode);
		} while(idcode && (idcode != 0xFFFFFFFF));
		if (nexys2_jtag_puttmstdi(nexys, 8, seq2, NULL) < 0)
			return;
		if (nexys2_jtag_disable(nexys) < 0)
			return;
	}
}

void main()
{
	nexys2_t nexys;
	nexys = nexys2_init();
	if (!nexys)
		return;
	poke_idcode(nexys);
}

#endif

