/***************************************************************************
 *   Copyright (C) 2005 by Dominic Rath                                    *
 *   Dominic.Rath@gmx.de                                                   *
 *                                                                         *
 *   Copyright (C) 2007,2008 Øyvind Harboe                                 *
 *   oyvind.harboe@zylin.com                                               *
 *                                                                         *
 *   Copyright (C) 2008 Rob Brown, Lou Deluxe                              *
 *   rob@cobbleware.com, lou.openocd012@fixit.nospammail.net               *
 *                                                                         *
 *   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, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.           *
 ***************************************************************************/

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

/* project specific includes */
#include <jtag/interface.h>
#include <jtag/commands.h>
#include "rlink.h"
#include "rlink_st7.h"
#include "rlink_ep1_cmd.h"
#include "rlink_dtc_cmd.h"
#include "usb_common.h"

/* This feature is made useless by running the DTC all the time.  When automatic, the LED is on
 *whenever the DTC is running.  Otherwise, USB messages are sent to turn it on and off. */
#undef AUTOMATIC_BUSY_LED

/* This feature may require derating the speed due to reduced hold time. */
#undef USE_HARDWARE_SHIFTER_FOR_TMS

#define INTERFACE_NAME          "RLink"

#define USB_IDVENDOR            (0x138e)
#define USB_IDPRODUCT           (0x9000)

#define USB_EP1OUT_ADDR         (0x01)
#define USB_EP1OUT_SIZE         (16)
#define USB_EP1IN_ADDR          (USB_EP1OUT_ADDR | 0x80)
#define USB_EP1IN_SIZE          (USB_EP1OUT_SIZE)

#define USB_EP2OUT_ADDR         (0x02)
#define USB_EP2OUT_SIZE         (64)
#define USB_EP2IN_ADDR          (USB_EP2OUT_ADDR | 0x80)
#define USB_EP2IN_SIZE          (USB_EP2OUT_SIZE)
#define USB_EP2BANK_SIZE        (512)

#define USB_TIMEOUT_MS          (3 * 1000)

#define DTC_STATUS_POLL_BYTE    (ST7_USB_BUF_EP0OUT + 0xff)

#define ST7_PD_NBUSY_LED                ST7_PD0
#define ST7_PD_NRUN_LED                 ST7_PD1
/* low enables VPP at adapter header, high connects it to GND instead */
#define ST7_PD_VPP_SEL                  ST7_PD6
/* low: VPP = 12v, high: VPP <= 5v */
#define ST7_PD_VPP_SHDN                 ST7_PD7

/* These pins are connected together */
#define ST7_PE_ADAPTER_SENSE_IN         ST7_PE3
#define ST7_PE_ADAPTER_SENSE_OUT        ST7_PE4

/* Symbolic mapping between port pins and numbered IO lines */
#define ST7_PA_IO1      ST7_PA1
#define ST7_PA_IO2      ST7_PA2
#define ST7_PA_IO4      ST7_PA4
#define ST7_PA_IO8      ST7_PA6
#define ST7_PA_IO10     ST7_PA7
#define ST7_PB_IO5      ST7_PB5
#define ST7_PC_IO9      ST7_PC1
#define ST7_PC_IO3      ST7_PC2
#define ST7_PC_IO7      ST7_PC3
#define ST7_PE_IO6      ST7_PE5

/* Symbolic mapping between numbered IO lines and adapter signals */
#define ST7_PA_RTCK     ST7_PA_IO0
#define ST7_PA_NTRST    ST7_PA_IO1
#define ST7_PC_TDI      ST7_PC_IO3
#define ST7_PA_DBGRQ    ST7_PA_IO4
#define ST7_PB_NSRST    ST7_PB_IO5
#define ST7_PE_TMS      ST7_PE_IO6
#define ST7_PC_TCK      ST7_PC_IO7
#define ST7_PC_TDO      ST7_PC_IO9
#define ST7_PA_DBGACK   ST7_PA_IO10

static usb_dev_handle *pHDev;

/*
 * ep1 commands are up to USB_EP1OUT_SIZE bytes in length.
 * This function takes care of zeroing the unused bytes before sending the packet.
 * Any reply packet is not handled by this function.
 */
static int ep1_generic_commandl(usb_dev_handle *pHDev_param, size_t length, ...)
{
	uint8_t usb_buffer[USB_EP1OUT_SIZE];
	uint8_t *usb_buffer_p;
	va_list ap;
	int usb_ret;

	if (length > sizeof(usb_buffer))
		length = sizeof(usb_buffer);

	usb_buffer_p = usb_buffer;

	va_start(ap, length);
	while (length > 0) {
		*usb_buffer_p++ = va_arg(ap, int);
		length--;
	}

	memset(
		usb_buffer_p,
		0,
		sizeof(usb_buffer) - (usb_buffer_p - usb_buffer)
		);

	usb_ret = usb_bulk_write(
			pHDev_param,
			USB_EP1OUT_ADDR,
			(char *)usb_buffer, sizeof(usb_buffer),
			USB_TIMEOUT_MS
			);

	return usb_ret;
}

#if 0
static ssize_t ep1_memory_read(
	usb_dev_handle *pHDev, uint16_t addr,
	size_t length, uint8_t *buffer)
{
	uint8_t usb_buffer[USB_EP1OUT_SIZE];
	int usb_ret;
	size_t remain;
	ssize_t count;

	usb_buffer[0] = EP1_CMD_MEMORY_READ;
	memset(
		usb_buffer + 4,
		0,
		sizeof(usb_buffer) - 4
		);

	remain = length;
	count = 0;

	while (remain) {
		if (remain > sizeof(usb_buffer))
			length = sizeof(usb_buffer);
		else
			length = remain;

		usb_buffer[1] = addr >> 8;
		usb_buffer[2] = addr;
		usb_buffer[3] = length;

		usb_ret = usb_bulk_write(
				pHDev, USB_EP1OUT_ADDR,
				usb_buffer, sizeof(usb_buffer),
				USB_TIMEOUT_MS
				);

		if (usb_ret < sizeof(usb_buffer))
			break;

		usb_ret = usb_bulk_read(
				pHDev, USB_EP1IN_ADDR,
				buffer, length,
				USB_TIMEOUT_MS
				);

		if (usb_ret < length)
			break;

		addr += length;
		buffer += length;
		count += length;
		remain -= length;
	}

	return count;
}
#endif

static ssize_t ep1_memory_write(usb_dev_handle *pHDev_param, uint16_t addr,
	size_t length, uint8_t const *buffer)
{
	uint8_t usb_buffer[USB_EP1OUT_SIZE];
	int usb_ret;
	size_t remain;
	ssize_t count;

	usb_buffer[0] = EP1_CMD_MEMORY_WRITE;

	remain = length;
	count = 0;

	while (remain) {
		if (remain > (sizeof(usb_buffer) - 4))
			length = (sizeof(usb_buffer) - 4);
		else
			length = remain;

		usb_buffer[1] = addr >> 8;
		usb_buffer[2] = addr;
		usb_buffer[3] = length;
		memcpy(
			usb_buffer + 4,
			buffer,
			length
			);
		memset(
			usb_buffer + 4 + length,
			0,
			sizeof(usb_buffer) - 4 - length
			);

		usb_ret = usb_bulk_write(
				pHDev_param, USB_EP1OUT_ADDR,
				(char *)usb_buffer, sizeof(usb_buffer),
				USB_TIMEOUT_MS
				);

		if ((size_t)usb_ret < sizeof(usb_buffer))
			break;

		addr += length;
		buffer += length;
		count += length;
		remain -= length;
	}

	return count;
}


#if 0
static ssize_t ep1_memory_writel(usb_dev_handle *pHDev, uint16_t addr,
	size_t length, ...)
{
	uint8_t buffer[USB_EP1OUT_SIZE - 4];
	uint8_t *buffer_p;
	va_list ap;
	size_t remain;

	if (length > sizeof(buffer))
		length = sizeof(buffer);

	remain = length;
	buffer_p = buffer;

	va_start(ap, length);
	while (remain > 0) {
		*buffer_p++ = va_arg(ap, int);
		remain--;
	}

	return ep1_memory_write(pHDev, addr, length, buffer);
}
#endif

#define DTCLOAD_COMMENT         (0)
#define DTCLOAD_ENTRY           (1)
#define DTCLOAD_LOAD            (2)
#define DTCLOAD_RUN                     (3)
#define DTCLOAD_LUT_START       (4)
#define DTCLOAD_LUT                     (5)

#define DTC_LOAD_BUFFER         ST7_USB_BUF_EP2UIDO

/* This gets set by the DTC loader */
static uint8_t dtc_entry_download;

/* The buffer is specially formatted to represent a valid image to load into the DTC. */
static int dtc_load_from_buffer(usb_dev_handle *pHDev_param, const uint8_t *buffer,
		size_t length)
{
	struct header_s {
		uint8_t type;
		uint8_t length;
	};

	int usb_err;
	struct header_s *header;
	uint8_t lut_start = 0xc0;

	dtc_entry_download = 0;

	/* Stop the DTC before loading anything. */
	usb_err = ep1_generic_commandl(
			pHDev_param, 1,
			EP1_CMD_DTC_STOP
			);
	if (usb_err < 0)
		return usb_err;

	while (length) {
		if (length < sizeof(*header)) {
			LOG_ERROR("Malformed DTC image");
			exit(1);
		}

		header = (struct header_s *)buffer;
		buffer += sizeof(*header);
		length -= sizeof(*header);

		if (length < (size_t)header->length + 1) {
			LOG_ERROR("Malformed DTC image");
			exit(1);
		}

		switch (header->type) {
			case DTCLOAD_COMMENT:
				break;

			case DTCLOAD_ENTRY:
				/* store entry addresses somewhere */
				if (!strncmp("download", (char *)buffer + 1, 8))
					dtc_entry_download = buffer[0];
				break;

			case DTCLOAD_LOAD:
				/* Send the DTC program to ST7 RAM. */
				usb_err = ep1_memory_write(
						pHDev_param,
						DTC_LOAD_BUFFER,
						header->length + 1, buffer
					);
				if (usb_err < 0)
					return usb_err;

				/* Load it into the DTC. */
				usb_err = ep1_generic_commandl(
						pHDev_param, 3,
						EP1_CMD_DTC_LOAD,
						(DTC_LOAD_BUFFER >> 8),
						DTC_LOAD_BUFFER
					);
				if (usb_err < 0)
					return usb_err;

				break;

			case DTCLOAD_RUN:
				usb_err = ep1_generic_commandl(
						pHDev_param, 3,
						EP1_CMD_DTC_CALL,
						buffer[0],
						EP1_CMD_DTC_WAIT
					);
				if (usb_err < 0)
					return usb_err;

				break;

			case DTCLOAD_LUT_START:
				lut_start = buffer[0];
				break;

			case DTCLOAD_LUT:
				usb_err = ep1_memory_write(
						pHDev_param,
						ST7_USB_BUF_EP0OUT + lut_start,
						header->length + 1, buffer
					);
				if (usb_err < 0)
					return usb_err;
				break;

			default:
				LOG_ERROR("Invalid DTC image record type: 0x%02x", header->type);
				exit(1);
				break;
		}

		buffer += (header->length + 1);
		length -= (header->length + 1);
	}

	return 0;
}

/*
 * Start the DTC running in download mode (waiting for 512 byte command packets on ep2).
 */
static int dtc_start_download(void)
{
	int usb_err;
	uint8_t ep2txr;

	/* set up for download mode and make sure EP2 is set up to transmit */
	usb_err = ep1_generic_commandl(
			pHDev, 7,

			EP1_CMD_DTC_STOP,
			EP1_CMD_SET_UPLOAD,
			EP1_CMD_SET_DOWNLOAD,
			EP1_CMD_MEMORY_READ,	/* read EP2TXR for its data toggle */
			ST7_EP2TXR >> 8,
			ST7_EP2TXR,
			1
			);
	if (usb_err < 0)
		return usb_err;

	/* read back ep2txr */
	usb_err = usb_bulk_read(
			pHDev, USB_EP1IN_ADDR,
			(char *)&ep2txr, 1,
			USB_TIMEOUT_MS
			);
	if (usb_err < 0)
		return usb_err;

	usb_err = ep1_generic_commandl(
			pHDev, 13,

			EP1_CMD_MEMORY_WRITE,	/* preinitialize poll byte */
			DTC_STATUS_POLL_BYTE >> 8,
			DTC_STATUS_POLL_BYTE,
			1,
			0x00,
			EP1_CMD_MEMORY_WRITE,	/* set EP2IN to return data */
			ST7_EP2TXR >> 8,
			ST7_EP2TXR,
			1,
			(ep2txr & ST7_EP2TXR_DTOG_TX) | ST7_EP2TXR_STAT_VALID,
			EP1_CMD_DTC_CALL,	/* start running the DTC */
			dtc_entry_download,
			EP1_CMD_DTC_GET_CACHED_STATUS
			);
	if (usb_err < 0)
		return usb_err;

	/* wait for completion */
	usb_err = usb_bulk_read(
			pHDev, USB_EP1IN_ADDR,
			(char *)&ep2txr, 1,
			USB_TIMEOUT_MS
			);

	return usb_err;
}

static int dtc_run_download(
	usb_dev_handle *pHDev_param,
	uint8_t *command_buffer,
	int command_buffer_size,
	uint8_t *reply_buffer,
	int reply_buffer_size
	)
{
	char dtc_status;
	int usb_err;
	int i;

	LOG_DEBUG("%d/%d", command_buffer_size, reply_buffer_size);

	usb_err = usb_bulk_write(
			pHDev_param,
			USB_EP2OUT_ADDR,
			(char *)command_buffer, USB_EP2BANK_SIZE,
			USB_TIMEOUT_MS
			);
	if (usb_err < 0)
		return usb_err;


	/* Wait for DTC to finish running command buffer */
	for (i = 50;; ) {
		usb_err = ep1_generic_commandl(
				pHDev_param, 4,

				EP1_CMD_MEMORY_READ,
				DTC_STATUS_POLL_BYTE >> 8,
				DTC_STATUS_POLL_BYTE,
				1
				);
		if (usb_err < 0)
			return usb_err;

		usb_err = usb_bulk_read(
				pHDev_param,
				USB_EP1IN_ADDR,
				&dtc_status, 1,
				USB_TIMEOUT_MS
				);
		if (usb_err < 0)
			return usb_err;

		if (dtc_status & 0x01)
			break;

		if (!--i) {
			LOG_ERROR("too many retries waiting for DTC status");
			return -ETIMEDOUT;
		}
	}


	if (reply_buffer && reply_buffer_size) {
		usb_err = usb_bulk_read(
				pHDev_param,
				USB_EP2IN_ADDR,
				(char *)reply_buffer, reply_buffer_size,
				USB_TIMEOUT_MS
				);

		if (usb_err < reply_buffer_size) {
			LOG_ERROR("Read of endpoint 2 returned %d, expected %d",
				usb_err, reply_buffer_size
				);
			return usb_err;
		}
	}

	return usb_err;
}

/*
 * The dtc reply queue is a singly linked list that describes what to do
 * with the reply packet that comes from the DTC.  Only SCAN_IN and SCAN_IO generate
 * these entries.
 */

struct dtc_reply_queue_entry {
	struct dtc_reply_queue_entry *next;
	struct jtag_command *cmd;	/* the command that resulted in this entry */

	struct {
		uint8_t *buffer;		/* the scan buffer */
		int size;			/* size of the scan buffer in bits */
		int offset;			/* how many bits were already done before this? */
		int length;			/* how many bits are processed in this operation? */
		enum scan_type type;		/* SCAN_IN/SCAN_OUT/SCAN_IO */
	} scan;
};


/*
 * The dtc_queue consists of a buffer of pending commands and a reply queue.
 * rlink_scan and tap_state_run add to the command buffer and maybe to the reply queue.
 */

static struct {
	struct dtc_reply_queue_entry *rq_head;
	struct dtc_reply_queue_entry *rq_tail;
	uint32_t cmd_index;
	uint32_t reply_index;
	uint8_t cmd_buffer[USB_EP2BANK_SIZE];
} dtc_queue;

/*
 * The tap state queue is for accumulating TAP state changes wiithout needlessly
 * flushing the dtc_queue.  When it fills or is run, it adds the accumulated bytes to
 * the dtc_queue.
 */

static struct {
	uint32_t length;
	uint32_t buffer;
} tap_state_queue;

static int dtc_queue_init(void)
{
	dtc_queue.rq_head = NULL;
	dtc_queue.rq_tail = NULL;
	dtc_queue.cmd_index = 0;
	dtc_queue.reply_index = 0;
	return 0;
}

static inline struct dtc_reply_queue_entry *dtc_queue_enqueue_reply(
	enum scan_type type, uint8_t *buffer, int size, int offset,
	int length, struct jtag_command *cmd)
{
	struct dtc_reply_queue_entry *rq_entry;

	rq_entry = malloc(sizeof(struct dtc_reply_queue_entry));
	if (rq_entry != NULL) {
		rq_entry->scan.type = type;
		rq_entry->scan.buffer = buffer;
		rq_entry->scan.size = size;
		rq_entry->scan.offset = offset;
		rq_entry->scan.length = length;
		rq_entry->cmd = cmd;
		rq_entry->next = NULL;

		if (dtc_queue.rq_head == NULL)
			dtc_queue.rq_head = rq_entry;
		else
			dtc_queue.rq_tail->next = rq_entry;

		dtc_queue.rq_tail = rq_entry;
	}

	return rq_entry;
}

/*
 * Running the queue means that any pending command buffer is run
 * and any reply data dealt with.  The command buffer is then cleared for subsequent processing.
 * The queue is automatically run by append when it is necessary to get space for the append.
 */

static int dtc_queue_run(void)
{
	struct dtc_reply_queue_entry *rq_p, *rq_next;
	int retval;
	int usb_err;
	int bit_cnt;
	int x;
	uint8_t *dtc_p, *tdo_p;
	uint8_t dtc_mask, tdo_mask;
	uint8_t reply_buffer[USB_EP2IN_SIZE];

	assert((dtc_queue.rq_head != 0) == (dtc_queue.reply_index > 0));
	assert(dtc_queue.cmd_index < USB_EP2BANK_SIZE);
	assert(dtc_queue.reply_index <= USB_EP2IN_SIZE);

	retval = ERROR_OK;

	if (dtc_queue.cmd_index < 1)
		return retval;

	dtc_queue.cmd_buffer[dtc_queue.cmd_index++] = DTC_CMD_STOP;

	usb_err = dtc_run_download(pHDev,
			dtc_queue.cmd_buffer, dtc_queue.cmd_index,
			reply_buffer, sizeof(reply_buffer)
			);
	if (usb_err < 0) {
		LOG_ERROR("dtc_run_download: %s", usb_strerror());
		exit(1);
	}

	if (dtc_queue.rq_head != NULL) {
		/* process the reply, which empties the reply queue and frees its entries */
		dtc_p = reply_buffer;

		/* The rigamarole with the masks and doing it bit-by-bit is due to the fact that the
		 *scan buffer is LSb-first and the DTC code is MSb-first for hardware reasons.   It
		 *was that or craft a function to do the reversal, and that wouldn't work with
		 *bit-stuffing (supplying extra bits to use mostly byte operations), or any other
		 *scheme which would throw the byte alignment off. */

		for (
			rq_p = dtc_queue.rq_head;
			rq_p != NULL;
			rq_p = rq_next
			) {
			tdo_p = rq_p->scan.buffer + (rq_p->scan.offset / 8);
			tdo_mask = 1 << (rq_p->scan.offset % 8);


			bit_cnt = rq_p->scan.length;
			if (bit_cnt >= 8) {
				/* bytes */

				dtc_mask = 1 << (8 - 1);

				for (
					;
					bit_cnt;
					bit_cnt--
					) {
					if (*dtc_p & dtc_mask)
						*tdo_p |= tdo_mask;
					else
						*tdo_p &= ~tdo_mask;

					dtc_mask >>= 1;
					if (dtc_mask == 0) {
						dtc_p++;
						dtc_mask = 1 << (8 - 1);
					}

					tdo_mask <<= 1;
					if (tdo_mask == 0) {
						tdo_p++;
						tdo_mask = 1;
					}
				}
			} else {
				/*  extra bits or last bit */

				x = *dtc_p++;
				if ((rq_p->scan.type == SCAN_IN) && (
						rq_p->scan.offset != rq_p->scan.size - 1
					)) {
					/* extra bits were sent as a full byte with padding on the
					 *end */
					dtc_mask = 1 << (8 - 1);
				} else
					dtc_mask = 1 << (bit_cnt - 1);

				for (
					;
					bit_cnt;
					bit_cnt--
					) {
					if (x & dtc_mask)
						*tdo_p |= tdo_mask;
					else
						*tdo_p &= ~tdo_mask;

					dtc_mask >>= 1;

					tdo_mask <<= 1;
					if (tdo_mask == 0) {
						tdo_p++;
						tdo_mask = 1;
					}

				}
			}

			if ((rq_p->scan.offset + rq_p->scan.length) >= rq_p->scan.size) {
				/* feed scan buffer back into openocd and free it */
				if (jtag_read_buffer(rq_p->scan.buffer,
						rq_p->cmd->cmd.scan) != ERROR_OK)
					retval = ERROR_JTAG_QUEUE_FAILED;
				free(rq_p->scan.buffer);
			}

			rq_next = rq_p->next;
			free(rq_p);
		}
		dtc_queue.rq_head = NULL;
		dtc_queue.rq_tail = NULL;
	}

	/* reset state for new appends */
	dtc_queue.cmd_index = 0;
	dtc_queue.reply_index = 0;

	return retval;
}

/* runs the queue if it cannot take reserved_cmd bytes of command data
 * or reserved_reply bytes of reply data */
static int dtc_queue_run_if_full(int reserved_cmd, int reserved_reply)
{
	/* reserve one additional byte for the STOP cmd appended during run */
	if (dtc_queue.cmd_index + reserved_cmd + 1 > USB_EP2BANK_SIZE)
		return dtc_queue_run();

	if (dtc_queue.reply_index + reserved_reply > USB_EP2IN_SIZE)
		return dtc_queue_run();

	return ERROR_OK;
}

static int tap_state_queue_init(void)
{
	tap_state_queue.length = 0;
	tap_state_queue.buffer = 0;
	return 0;
}

static int tap_state_queue_run(void)
{
	int i;
	int bits;
	uint8_t byte_param;
	int retval;

	retval = 0;
	if (!tap_state_queue.length)
		return retval;
	bits = 1;
	byte_param = 0;
	for (i = tap_state_queue.length; i--; ) {

		byte_param <<= 1;
		if (tap_state_queue.buffer & 1)
			byte_param |= 1;
		if ((bits >= 8) || !i) {
			byte_param <<= (8 - bits);

			/* make sure there's room for two cmd bytes */
			dtc_queue_run_if_full(2, 0);

#ifdef USE_HARDWARE_SHIFTER_FOR_TMS
			if (bits == 8) {
				dtc_queue.cmd_buffer[dtc_queue.cmd_index++] =
					DTC_CMD_SHIFT_TMS_BYTES(1);
			} else {
#endif
			dtc_queue.cmd_buffer[dtc_queue.cmd_index++] =
				DTC_CMD_SHIFT_TMS_BITS(bits);
#ifdef USE_HARDWARE_SHIFTER_FOR_TMS
		}
#endif

			dtc_queue.cmd_buffer[dtc_queue.cmd_index++] =
				byte_param;

			byte_param = 0;
			bits = 1;
		} else
			bits++;

		tap_state_queue.buffer >>= 1;
	}
	retval = tap_state_queue_init();
	return retval;
}

static int tap_state_queue_append(uint8_t tms)
{
	int retval;

	if (tap_state_queue.length >= sizeof(tap_state_queue.buffer) * 8) {
		retval = tap_state_queue_run();
		if (retval != 0)
			return retval;
	}

	if (tms)
		tap_state_queue.buffer |= (1 << tap_state_queue.length);
	tap_state_queue.length++;

	return 0;
}

static void rlink_end_state(tap_state_t state)
{
	if (tap_is_state_stable(state))
		tap_set_end_state(state);
	else {
		LOG_ERROR("BUG: %i is not a valid end state", state);
		exit(-1);
	}
}

static void rlink_state_move(void)
{

	int i = 0, tms = 0;
	uint8_t tms_scan = tap_get_tms_path(tap_get_state(), tap_get_end_state());
	int tms_count = tap_get_tms_path_len(tap_get_state(), tap_get_end_state());

	for (i = 0; i < tms_count; i++) {
		tms = (tms_scan >> i) & 1;
		tap_state_queue_append(tms);
	}

	tap_set_state(tap_get_end_state());
}

static void rlink_path_move(struct pathmove_command *cmd)
{
	int num_states = cmd->num_states;
	int state_count;
	int tms = 0;

	state_count = 0;
	while (num_states) {
		if (tap_state_transition(tap_get_state(), false) == cmd->path[state_count])
			tms = 0;
		else if (tap_state_transition(tap_get_state(), true) == cmd->path[state_count])
			tms = 1;
		else {
			LOG_ERROR("BUG: %s -> %s isn't a valid TAP transition",
				tap_state_name(tap_get_state()),
				tap_state_name(cmd->path[state_count]));
			exit(-1);
		}

		tap_state_queue_append(tms);

		tap_set_state(cmd->path[state_count]);
		state_count++;
		num_states--;
	}

	tap_set_end_state(tap_get_state());
}

static void rlink_runtest(int num_cycles)
{
	int i;

	tap_state_t saved_end_state = tap_get_end_state();

	/* only do a state_move when we're not already in RTI */
	if (tap_get_state() != TAP_IDLE) {
		rlink_end_state(TAP_IDLE);
		rlink_state_move();
	}

	/* execute num_cycles */
	for (i = 0; i < num_cycles; i++)
		tap_state_queue_append(0);

	/* finish in end_state */
	rlink_end_state(saved_end_state);
	if (tap_get_state() != tap_get_end_state())
		rlink_state_move();
}

/* (1) assert or (0) deassert reset lines */
static void rlink_reset(int trst, int srst)
{
	uint8_t bitmap;
	int usb_err;

	/* Read port A for bit op */
	usb_err = ep1_generic_commandl(
			pHDev, 4,
			EP1_CMD_MEMORY_READ,
			ST7_PADR >> 8,
			ST7_PADR,
			1
			);
	if (usb_err < 0) {
		LOG_ERROR("%s", usb_strerror());
		exit(1);
	}

	usb_err = usb_bulk_read(
			pHDev, USB_EP1IN_ADDR,
			(char *)&bitmap, 1,
			USB_TIMEOUT_MS
			);
	if (usb_err < 1) {
		LOG_ERROR("%s", usb_strerror());
		exit(1);
	}

	if (trst)
		bitmap &= ~ST7_PA_NTRST;
	else
		bitmap |= ST7_PA_NTRST;

	/* Write port A and read port B for bit op
	 * port B has no OR, and we want to emulate open drain on NSRST, so we initialize DR to 0
	 *and assert NSRST by setting DDR to 1. */
	usb_err = ep1_generic_commandl(
			pHDev, 9,
			EP1_CMD_MEMORY_WRITE,
			ST7_PADR >> 8,
			ST7_PADR,
			1,
			bitmap,
			EP1_CMD_MEMORY_READ,
			ST7_PBDDR >> 8,
			ST7_PBDDR,
			1
			);
	if (usb_err < 0) {
		LOG_ERROR("%s", usb_strerror());
		exit(1);
	}

	usb_err = usb_bulk_read(
			pHDev, USB_EP1IN_ADDR,
			(char *)&bitmap, 1,
			USB_TIMEOUT_MS
			);
	if (usb_err < 1) {
		LOG_ERROR("%s", usb_strerror());
		exit(1);
	}

	if (srst)
		bitmap |= ST7_PB_NSRST;
	else
		bitmap &= ~ST7_PB_NSRST;

	/* write port B and read dummy to ensure completion before returning */
	usb_err = ep1_generic_commandl(
			pHDev, 6,
			EP1_CMD_MEMORY_WRITE,
			ST7_PBDDR >> 8,
			ST7_PBDDR,
			1,
			bitmap,
			EP1_CMD_DTC_GET_CACHED_STATUS
			);
	if (usb_err < 0) {
		LOG_ERROR("%s", usb_strerror());
		exit(1);
	}

	usb_err = usb_bulk_read(
			pHDev, USB_EP1IN_ADDR,
			(char *)&bitmap, 1,
			USB_TIMEOUT_MS
			);
	if (usb_err < 1) {
		LOG_ERROR("%s", usb_strerror());
		exit(1);
	}
}

static int rlink_scan(struct jtag_command *cmd, enum scan_type type,
		uint8_t *buffer, int scan_size)
{
	bool ir_scan;
	tap_state_t saved_end_state;
	int byte_bits;
	int extra_bits;
	int chunk_bits;
	int chunk_bytes;
	int x;

	int tdi_bit_offset;
	uint8_t tdi_mask, *tdi_p;
	uint8_t dtc_mask;

	if (scan_size < 1) {
		LOG_ERROR("scan_size cannot be less than 1 bit");
		exit(1);
	}

	ir_scan = cmd->cmd.scan->ir_scan;

	/* Move to the proper state before starting to shift TDI/TDO. */
	if (!((!ir_scan && (tap_get_state() == TAP_DRSHIFT)) ||
			(ir_scan && (tap_get_state() == TAP_IRSHIFT)))) {
		saved_end_state = tap_get_end_state();
		rlink_end_state(ir_scan ? TAP_IRSHIFT : TAP_DRSHIFT);
		rlink_state_move();
		rlink_end_state(saved_end_state);
	}

	tap_state_queue_run();


#if 0
	printf("scan_size = %d, type = 0x%x\n", scan_size, type);
	{
		int i;

		/* clear unused bits in scan buffer for ease of debugging
		 * (it makes diffing output easier) */
		buffer[scan_size / 8] &= ((1 << ((scan_size - 1) % 8) + 1) - 1);

		printf("before scan:");
		for (i = 0; i < (scan_size + 7) / 8; i++)
			printf(" %02x", buffer[i]);
		printf("\n");
	}
#endif

	/* The number of bits that can be shifted as complete bytes */
	byte_bits = (int)(scan_size - 1) / 8 * 8;
	/* The number of bits left over, not counting the last bit */
	extra_bits = (scan_size - 1) - byte_bits;

	tdi_bit_offset = 0;
	tdi_p = buffer;
	tdi_mask = 1;

	if (extra_bits && (type == SCAN_OUT)) {
		/* Schedule any extra bits into the DTC command buffer, padding as needed
		 * For SCAN_OUT, this comes before the full bytes so the (leading) padding bits will
		 *fall off the end */

		/* make sure there's room for two cmd bytes */
		dtc_queue_run_if_full(2, 0);

		x = 0;
		dtc_mask = 1 << (extra_bits - 1);

		while (extra_bits--) {
			if (*tdi_p & tdi_mask)
				x |= dtc_mask;

			dtc_mask >>= 1;

			tdi_mask <<= 1;
			if (tdi_mask == 0) {
				tdi_p++;
				tdi_mask = 1;
			}
		}

		dtc_queue.cmd_buffer[dtc_queue.cmd_index++] =
			DTC_CMD_SHIFT_TDI_BYTES(1);

		dtc_queue.cmd_buffer[dtc_queue.cmd_index++] = x;
	}

	/* Loop scheduling full bytes into the DTC command buffer */
	while (byte_bits) {
		/* make sure there's room for one (for in scans) or two cmd bytes and
		 * at least one reply byte for in or inout scans*/
		dtc_queue_run_if_full(type == SCAN_IN ? 1 : 2, type != SCAN_OUT ? 1 : 0);

		chunk_bits = byte_bits;
		/* we can only use up to 16 bytes at a time */
		if (chunk_bits > (16 * 8))
			chunk_bits = (16 * 8);

		if (type != SCAN_IN) {
			/* how much is there room for, considering stop and byte op? */
			x = (sizeof(dtc_queue.cmd_buffer) - (dtc_queue.cmd_index + 1 + 1)) * 8;
			if (chunk_bits > x)
				chunk_bits = x;
		}

		if (type != SCAN_OUT) {
			/* how much is there room for in the reply buffer? */
			x = (USB_EP2IN_SIZE - dtc_queue.reply_index) * 8;
			if (chunk_bits > x)
				chunk_bits = x;
		}

		/* so the loop will end */
		byte_bits -= chunk_bits;

		if (type != SCAN_OUT) {
			if (dtc_queue_enqueue_reply(
					type, buffer, scan_size, tdi_bit_offset,
					chunk_bits,
					cmd
				) == NULL) {
				LOG_ERROR("enqueuing DTC reply entry: %s", strerror(errno));
				exit(1);
			}
			dtc_queue.reply_index += (chunk_bits + 7) / 8;

			tdi_bit_offset += chunk_bits;
		}

		/* chunk_bits is a multiple of 8, so there are no rounding issues. */
		chunk_bytes = chunk_bits / 8;

		switch (type) {
			case SCAN_IN:
				x = DTC_CMD_SHIFT_TDO_BYTES(chunk_bytes);
				break;
			case SCAN_OUT:
				x = DTC_CMD_SHIFT_TDI_BYTES(chunk_bytes);
				break;
			default:
				x = DTC_CMD_SHIFT_TDIO_BYTES(chunk_bytes);
				break;
		}
		dtc_queue.cmd_buffer[dtc_queue.cmd_index++] = x;

		if (type != SCAN_IN) {
			x = 0;
			dtc_mask = 1 << (8 - 1);

			while (chunk_bits--) {
				if (*tdi_p & tdi_mask)
					x |= dtc_mask;

				dtc_mask >>= 1;
				if (dtc_mask == 0) {
					dtc_queue.cmd_buffer[dtc_queue.cmd_index++] = x;
					x = 0;
					dtc_mask = 1 << (8 - 1);
				}

				tdi_mask <<= 1;
				if (tdi_mask == 0) {
					tdi_p++;
					tdi_mask = 1;
				}
			}
		}
	}

	if (extra_bits && (type != SCAN_OUT)) {
		/* Schedule any extra bits into the DTC command buffer */

		/* make sure there's room for one (for in scans) or two cmd bytes
		 * and one reply byte */
		dtc_queue_run_if_full(type == SCAN_IN ? 1 : 2, 1);

		if (dtc_queue_enqueue_reply(
				type, buffer, scan_size, tdi_bit_offset,
				extra_bits,
				cmd
			) == NULL) {
			LOG_ERROR("enqueuing DTC reply entry: %s", strerror(errno));
			exit(1);
		}

		dtc_queue.reply_index++;

		tdi_bit_offset += extra_bits;

		if (type == SCAN_IN) {
			dtc_queue.cmd_buffer[dtc_queue.cmd_index++] =
				DTC_CMD_SHIFT_TDO_BYTES(1);

		} else {
			dtc_queue.cmd_buffer[dtc_queue.cmd_index++] =
				DTC_CMD_SHIFT_TDIO_BITS(extra_bits);

			x = 0;
			dtc_mask = 1 << (8 - 1);

			while (extra_bits--) {
				if (*tdi_p & tdi_mask)
					x |= dtc_mask;

				dtc_mask >>= 1;

				tdi_mask <<= 1;
				if (tdi_mask == 0) {
					tdi_p++;
					tdi_mask = 1;
				}
			}

			dtc_queue.cmd_buffer[dtc_queue.cmd_index++] = x;
		}
	}

	/* Schedule the last bit into the DTC command buffer */

	/* make sure there's room for one cmd byte and one reply byte
	 * for in or inout scans*/
	dtc_queue_run_if_full(1, type == SCAN_OUT ? 0 : 1);

	if (type == SCAN_OUT) {
		dtc_queue.cmd_buffer[dtc_queue.cmd_index++] =
			DTC_CMD_SHIFT_TMS_TDI_BIT_PAIR(1, (*tdi_p & tdi_mask), 0);

	} else {
		if (dtc_queue_enqueue_reply(
				type, buffer, scan_size, tdi_bit_offset,
				1,
				cmd
				) == NULL) {
			LOG_ERROR("enqueuing DTC reply entry: %s", strerror(errno));
			exit(1);
		}

		dtc_queue.reply_index++;

		dtc_queue.cmd_buffer[dtc_queue.cmd_index++] =
			DTC_CMD_SHIFT_TMS_TDI_BIT_PAIR(1, (*tdi_p & tdi_mask), 1);
	}

	/* Move to pause state */
	tap_state_queue_append(0);
	tap_set_state(ir_scan ? TAP_IRPAUSE : TAP_DRPAUSE);
	if (tap_get_state() != tap_get_end_state())
		rlink_state_move();

	return 0;
}

static int rlink_execute_queue(void)
{
	struct jtag_command *cmd = jtag_command_queue;	/* currently processed command */
	int scan_size;
	enum scan_type type;
	uint8_t *buffer;
	int retval, tmp_retval;

	/* return ERROR_OK, unless something goes wrong */
	retval = ERROR_OK;

#ifndef AUTOMATIC_BUSY_LED
	/* turn LED on */
	ep1_generic_commandl(pHDev, 2,
		EP1_CMD_SET_PORTD_LEDS,
		~(ST7_PD_NBUSY_LED)
		);
#endif

	while (cmd) {
		switch (cmd->type) {
			case JTAG_RUNTEST:
			case JTAG_TLR_RESET:
			case JTAG_PATHMOVE:
			case JTAG_SCAN:
				break;

			default:
				/* some events, such as resets, need a queue flush to ensure
				 *consistency */
				tap_state_queue_run();
				dtc_queue_run();
				break;
		}

		switch (cmd->type) {
			case JTAG_RESET:
#ifdef _DEBUG_JTAG_IO_
				LOG_DEBUG("reset trst: %i srst %i",
						cmd->cmd.reset->trst,
						cmd->cmd.reset->srst);
#endif
				if ((cmd->cmd.reset->trst == 1) ||
						(cmd->cmd.reset->srst &&
								(jtag_get_reset_config() & RESET_SRST_PULLS_TRST)))
					tap_set_state(TAP_RESET);
				rlink_reset(cmd->cmd.reset->trst, cmd->cmd.reset->srst);
				break;
			case JTAG_RUNTEST:
#ifdef _DEBUG_JTAG_IO_
				LOG_DEBUG("runtest %i cycles, end in %i",
						cmd->cmd.runtest->num_cycles,
						cmd->cmd.runtest->end_state);
#endif
				if (cmd->cmd.runtest->end_state != -1)
					rlink_end_state(cmd->cmd.runtest->end_state);
				rlink_runtest(cmd->cmd.runtest->num_cycles);
				break;
			case JTAG_TLR_RESET:
#ifdef _DEBUG_JTAG_IO_
				LOG_DEBUG("statemove end in %i", cmd->cmd.statemove->end_state);
#endif
				if (cmd->cmd.statemove->end_state != -1)
					rlink_end_state(cmd->cmd.statemove->end_state);
				rlink_state_move();
				break;
			case JTAG_PATHMOVE:
#ifdef _DEBUG_JTAG_IO_
				LOG_DEBUG("pathmove: %i states, end in %i",
				cmd->cmd.pathmove->num_states,
				cmd->cmd.pathmove->path[cmd->cmd.pathmove->num_states - 1]);
#endif
				rlink_path_move(cmd->cmd.pathmove);
				break;
			case JTAG_SCAN:
#ifdef _DEBUG_JTAG_IO_
				LOG_DEBUG("%s scan end in %i",
				(cmd->cmd.scan->ir_scan) ? "IR" : "DR",
				cmd->cmd.scan->end_state);
#endif
				if (cmd->cmd.scan->end_state != -1)
					rlink_end_state(cmd->cmd.scan->end_state);
				scan_size = jtag_build_buffer(cmd->cmd.scan, &buffer);
				type = jtag_scan_type(cmd->cmd.scan);
				if (rlink_scan(cmd, type, buffer, scan_size) != ERROR_OK)
					retval = ERROR_FAIL;
				break;
			case JTAG_SLEEP:
#ifdef _DEBUG_JTAG_IO_
				LOG_DEBUG("sleep %i", cmd->cmd.sleep->us);
#endif
				jtag_sleep(cmd->cmd.sleep->us);
				break;
			default:
				LOG_ERROR("BUG: unknown JTAG command type encountered");
				exit(-1);
		}
		cmd = cmd->next;
	}

	/* Flush the DTC queue to make sure any pending reads have been done before exiting this
	 *function */
	tap_state_queue_run();
	tmp_retval = dtc_queue_run();
	if (tmp_retval != ERROR_OK)
		retval = tmp_retval;

#ifndef AUTOMATIC_BUSY_LED
	/* turn LED onff */
	ep1_generic_commandl(pHDev, 2,
		EP1_CMD_SET_PORTD_LEDS,
		~0
		);
#endif

	return retval;
}

/* Using an unindexed table because it is infrequently accessed and it is short.  The table must be
 *in order of ascending speed (and descending prescaler), as it is scanned in reverse. */

static int rlink_speed(int speed)
{
	int i;

	if (speed == 0) {
		/* fastest speed */
		speed = rlink_speed_table[rlink_speed_table_size - 1].prescaler;
	}

	for (i = rlink_speed_table_size; i--; ) {
		if (rlink_speed_table[i].prescaler == speed) {
			if (dtc_load_from_buffer(pHDev, rlink_speed_table[i].dtc,
					rlink_speed_table[i].dtc_size) != 0) {
				LOG_ERROR(
					"An error occurred while trying to load DTC code for speed \"%d\".",
					speed);
				exit(1);
			}

			if (dtc_start_download() < 0) {
				LOG_ERROR("starting DTC: %s", usb_strerror());
				exit(1);
			}

			return ERROR_OK;
		}
	}

	LOG_ERROR("%d is not a supported speed", speed);
	return ERROR_FAIL;
}

static int rlink_speed_div(int speed, int *khz)
{
	int i;

	for (i = rlink_speed_table_size; i--; ) {
		if (rlink_speed_table[i].prescaler == speed) {
			*khz = rlink_speed_table[i].khz;
			return ERROR_OK;
		}
	}

	LOG_ERROR("%d is not a supported speed", speed);
	return ERROR_FAIL;
}

static int rlink_khz(int khz, int *speed)
{
	int i;

	if (khz == 0) {
		LOG_ERROR("RCLK not supported");
		return ERROR_FAIL;
	}

	for (i = rlink_speed_table_size; i--; ) {
		if (rlink_speed_table[i].khz <= khz) {
			*speed = rlink_speed_table[i].prescaler;
			return ERROR_OK;
		}
	}

	LOG_WARNING("The lowest supported JTAG speed is %d KHz", rlink_speed_table[0].khz);
	*speed = rlink_speed_table[0].prescaler;
	return ERROR_OK;
}

static int rlink_init(void)
{
	int i, j, retries;
	uint8_t reply_buffer[USB_EP1IN_SIZE];

	usb_init();
	const uint16_t vids[] = { USB_IDVENDOR, 0 };
	const uint16_t pids[] = { USB_IDPRODUCT, 0 };
	if (jtag_usb_open(vids, pids, &pHDev) != ERROR_OK)
		return ERROR_FAIL;

	struct usb_device *dev = usb_device(pHDev);
	if (dev->descriptor.bNumConfigurations > 1) {
		LOG_ERROR("Whoops! NumConfigurations is not 1, don't know what to do...");
		return ERROR_FAIL;
	}
	if (dev->config->bNumInterfaces > 1) {
		LOG_ERROR("Whoops! NumInterfaces is not 1, don't know what to do...");
		return ERROR_FAIL;
	}

	LOG_DEBUG("Opened device, pHDev = %p", pHDev);

	/* usb_set_configuration required under win32 */
	usb_set_configuration(pHDev, dev->config[0].bConfigurationValue);

	retries = 3;
	do {
		i = usb_claim_interface(pHDev, 0);
		if (i) {
			LOG_ERROR("usb_claim_interface: %s", usb_strerror());
#ifdef LIBUSB_HAS_DETACH_KERNEL_DRIVER_NP
			j = usb_detach_kernel_driver_np(pHDev, 0);
			if (j)
				LOG_ERROR("detach kernel driver: %s", usb_strerror());
#endif
		} else {
			LOG_DEBUG("interface claimed!");
			break;
		}
	} while (--retries);

	if (i) {
		LOG_ERROR("Initialisation failed.");
		return ERROR_FAIL;
	}
	if (usb_set_altinterface(pHDev, 0) != 0) {
		LOG_ERROR("Failed to set interface.");
		return ERROR_FAIL;
	}

	/* The device starts out in an unknown state on open.  As such,
	 * result reads time out, and it's not even known whether the
	 * command was accepted.  So, for this first command, we issue
	 * it repeatedly until its response doesn't time out.  Also, if
	 * sending a command is going to time out, we find that out here.
	 *
	 * It must be possible to open the device in such a way that
	 * this special magic isn't needed, but, so far, it escapes us.
	 */
	for (i = 0; i < 5; i++) {
		j = ep1_generic_commandl(
				pHDev, 1,
				EP1_CMD_GET_FWREV
				);
		if (j < USB_EP1OUT_SIZE) {
			LOG_ERROR("USB write error: %s", usb_strerror());
			return ERROR_FAIL;
		}
		j = usb_bulk_read(
				pHDev, USB_EP1IN_ADDR,
				(char *)reply_buffer, sizeof(reply_buffer),
				200
				);
		if (j != -ETIMEDOUT)
			break;
	}

	if (j < (int)sizeof(reply_buffer)) {
		LOG_ERROR("USB read error: %s", usb_strerror());
		return ERROR_FAIL;
	}
	LOG_DEBUG(INTERFACE_NAME " firmware version: %d.%d.%d",
		reply_buffer[0],
		reply_buffer[1],
		reply_buffer[2]);

	if ((reply_buffer[0] != 0) || (reply_buffer[1] != 0) || (reply_buffer[2] != 3))
		LOG_WARNING(
			"The rlink device is not of the version that the developers have played with.  It may or may not work.");

	/* Probe port E for adapter presence */
	ep1_generic_commandl(
		pHDev, 16,
		EP1_CMD_MEMORY_WRITE,		/* Drive sense pin with 0 */
		ST7_PEDR >> 8,
		ST7_PEDR,
		3,
		0x00,							/* DR */
		ST7_PE_ADAPTER_SENSE_OUT,		/* DDR */
		ST7_PE_ADAPTER_SENSE_OUT,		/* OR */
		EP1_CMD_MEMORY_READ,		/* Read back */
		ST7_PEDR >> 8,
		ST7_PEDR,
		1,
		EP1_CMD_MEMORY_WRITE,		/* Drive sense pin with 1 */
		ST7_PEDR >> 8,
		ST7_PEDR,
		1,
		ST7_PE_ADAPTER_SENSE_OUT
		);

	usb_bulk_read(
		pHDev, USB_EP1IN_ADDR,
		(char *)reply_buffer, 1,
		USB_TIMEOUT_MS
		);

	if ((reply_buffer[0] & ST7_PE_ADAPTER_SENSE_IN) != 0)
		LOG_WARNING("target detection problem");

	ep1_generic_commandl(
		pHDev, 11,
		EP1_CMD_MEMORY_READ,		/* Read back */
		ST7_PEDR >> 8,
		ST7_PEDR,
		1,
		EP1_CMD_MEMORY_WRITE,		/* float port E */
		ST7_PEDR >> 8,
		ST7_PEDR,
		3,
		0x00,		/* DR */
		0x00,		/* DDR */
		0x00		/* OR */
		);

	usb_bulk_read(
		pHDev, USB_EP1IN_ADDR,
		(char *)reply_buffer, 1,
		USB_TIMEOUT_MS
		);


	if ((reply_buffer[0] & ST7_PE_ADAPTER_SENSE_IN) == 0)
		LOG_WARNING("target not plugged in");

	/* float ports A and B */
	ep1_generic_commandl(
		pHDev, 11,
		EP1_CMD_MEMORY_WRITE,
		ST7_PADDR >> 8,
		ST7_PADDR,
		2,
		0x00,
		0x00,
		EP1_CMD_MEMORY_WRITE,
		ST7_PBDDR >> 8,
		ST7_PBDDR,
		1,
		0x00
		);

	/* make sure DTC is stopped, set VPP control, set up ports A and B */
	ep1_generic_commandl(
		pHDev, 14,
		EP1_CMD_DTC_STOP,
		EP1_CMD_SET_PORTD_VPP,
		~(ST7_PD_VPP_SHDN),
		EP1_CMD_MEMORY_WRITE,
		ST7_PADR >> 8,
		ST7_PADR,
		2,
		((~(0)) & (ST7_PA_NTRST)),
		(ST7_PA_NTRST),
		/* port B has no OR, and we want to emulate open drain on NSRST, so we set DR to 0
		 *here and later assert NSRST by setting DDR bit to 1. */
		EP1_CMD_MEMORY_WRITE,
		ST7_PBDR >> 8,
		ST7_PBDR,
		1,
		0x00
		);

	/* set LED updating mode and make sure they're unlit */
	ep1_generic_commandl(
		pHDev, 3,
#ifdef AUTOMATIC_BUSY_LED
		EP1_CMD_LEDUE_BUSY,
#else
		EP1_CMD_LEDUE_NONE,
#endif
		EP1_CMD_SET_PORTD_LEDS,
		~0
		);

	tap_state_queue_init();
	dtc_queue_init();
	rlink_reset(0, 0);

	return ERROR_OK;
}

static int rlink_quit(void)
{
	/* stop DTC and make sure LEDs are off */
	ep1_generic_commandl(
		pHDev, 6,
		EP1_CMD_DTC_STOP,
		EP1_CMD_LEDUE_NONE,
		EP1_CMD_SET_PORTD_LEDS,
		~0,
		EP1_CMD_SET_PORTD_VPP,
		~0
		);

	usb_release_interface(pHDev, 0);
	usb_close(pHDev);

	return ERROR_OK;
}

struct jtag_interface rlink_interface = {
	.name = "rlink",
	.init = rlink_init,
	.quit = rlink_quit,
	.speed = rlink_speed,
	.speed_div = rlink_speed_div,
	.khz = rlink_khz,
	.execute_queue = rlink_execute_queue,
};
