/*******************************************************************************
 *   Driver for OpenJTAG Project (www.openjtag.org)                            *
 *   Compatible with libftdi and ftd2xx drivers.                               *
 *                                                                             *
 *   Copyright (C) 2010 by Ivan Meleca <mileca@gmail.com>                      *
 *                                                                             *
 *   Copyright (C) 2013 by Ryan Corbin, GlueLogix Inc. <corbin.ryan@gmail.com> *
 *   Updated to work with OpenOCD v0.7.0. Fixed libftdi read speed issue.      *
 *                                                                             *
 *   Based on usb_blaster.c                                                    *
 *   Copyright (C) 2009 Catalin Patulea                                        *
 *   Copyright (C) 2006 Kolja Waschk                                           *
 *                                                                             *
 *   And jlink.c                                                               *
 *   Copyright (C) 2008 by Spencer Oliver                                      *
 *   spen@spen-soft.co.uk                                                      *
 *                                                                             *
 *   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, see <http://www.gnu.org/licenses/>.     *
 ***************************************************************************/

/***************************************************************************
 * Version 1.0  Tested on a MCBSTM32 board using a Cortex-M3 (stm32f103x), *
 *              GDB and Eclipse under Linux (Ubuntu 10.04)                 *
 *                                                                         *
 ***************************************************************************/

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

#include <jtag/interface.h>
#include <jtag/commands.h>
#include "usb_common.h"

/*
 * OpenJTAG-OpenOCD state conversion
 */
typedef enum openjtag_tap_state {
	OPENJTAG_TAP_INVALID    = -1,
	OPENJTAG_TAP_RESET  = 0,
	OPENJTAG_TAP_IDLE   = 1,
	OPENJTAG_TAP_SELECT_DR  = 2,
	OPENJTAG_TAP_CAPTURE_DR = 3,
	OPENJTAG_TAP_SHIFT_DR   = 4,
	OPENJTAG_TAP_EXIT1_DR   = 5,
	OPENJTAG_TAP_PAUSE_DR   = 6,
	OPENJTAG_TAP_EXIT2_DR   = 7,
	OPENJTAG_TAP_UPDATE_DR  = 8,
	OPENJTAG_TAP_SELECT_IR  = 9,
	OPENJTAG_TAP_CAPURE_IR  = 10,
	OPENJTAG_TAP_SHIFT_IR   = 11,
	OPENJTAG_TAP_EXIT1_IR   = 12,
	OPENJTAG_TAP_PAUSE_IR   = 13,
	OPENJTAG_TAP_EXIT2_IR   = 14,
	OPENJTAG_TAP_UPDATE_IR  = 15,
} openjtag_tap_state_t;

#if (BUILD_OPENJTAG_FTD2XX == 1 && BUILD_OPENJTAG_LIBFTDI == 1)
#error "BUILD_OPENJTAG_FTD2XX && BUILD_OPENJTAG_LIBFTDI "
	   "are mutually exclusive"
#elif (BUILD_OPENJTAG_FTD2XX != 1 && BUILD_OPENJTAG_LIBFTDI != 1)
#error "BUILD_OPENJTAG_FTD2XX || BUILD_OPENJTAG_LIBFTDI must be chosen"
#endif

/* OPENJTAG access library includes */
#if BUILD_OPENJTAG_FTD2XX == 1
#include <ftd2xx.h>
#elif BUILD_OPENJTAG_LIBFTDI == 1
#include <ftdi.h>
#endif

/* OpenJTAG vid/pid */
static uint16_t openjtag_vid = 0x0403;
static uint16_t openjtag_pid = 0x6001;

static char *openjtag_device_desc;

#if BUILD_OPENJTAG_FTD2XX == 1
static FT_HANDLE ftdih;

#elif BUILD_OPENJTAG_LIBFTDI == 1
static struct ftdi_context ftdic;
#endif

#define OPENJTAG_BUFFER_SIZE        504
#define OPENJTAG_MAX_PENDING_RESULTS    256

struct openjtag_scan_result {
	uint32_t bits;          /* Length in bits*/
	struct scan_command *command;   /* Corresponding scan command */
	uint8_t *buffer;
};

/* USB RX/TX buffers */
static int usb_tx_buf_offs;
static uint8_t usb_tx_buf[OPENJTAG_BUFFER_SIZE];
static uint32_t usb_rx_buf_len;
static uint8_t usb_rx_buf[OPENJTAG_BUFFER_SIZE];

/* Pending readings */
static struct openjtag_scan_result openjtag_scan_result_buffer[OPENJTAG_MAX_PENDING_RESULTS];
static int openjtag_scan_result_count;

/* Openocd usb handler */
struct openocd {
	struct usb_dev_handle *usb_handle;
};

#ifdef _DEBUG_USB_COMMS_

#define DEBUG_TYPE_READ     0
#define DEBUG_TYPE_WRITE    1
#define DEBUG_TYPE_OCD_READ 2
#define DEBUG_TYPE_BUFFER   3

#define LINE_LEN  16
static void openjtag_debug_buffer(uint8_t *buffer, int length, uint8_t type)
{
	char line[128];
	char s[4];
	int i;
	int j;

	switch (type) {
		case DEBUG_TYPE_READ:
			sprintf(line, "USB READ %d bytes", length);
			break;
		case DEBUG_TYPE_WRITE:
			sprintf(line, "USB WRITE %d bytes", length);
			break;
		case DEBUG_TYPE_OCD_READ:
			sprintf(line, "TO OpenOCD %d bytes", length);
			break;
		case DEBUG_TYPE_BUFFER:
			sprintf(line, "Buffer %d bytes", length);
			break;
	}

	LOG_DEBUG("%s", line);

	for (i = 0; i < length; i += LINE_LEN) {
		switch (type) {
			case DEBUG_TYPE_READ:
				sprintf(line, "USB READ: %04x", i);
				break;
			case DEBUG_TYPE_WRITE:
				sprintf(line, "USB WRITE: %04x", i);
				break;
			case DEBUG_TYPE_OCD_READ:
				sprintf(line, "TO OpenOCD: %04x", i);
				break;
			case DEBUG_TYPE_BUFFER:
				sprintf(line, "BUFFER: %04x", i);
				break;
		}

		for (j = i; j < i + LINE_LEN && j < length; j++) {
			sprintf(s, " %02x", buffer[j]);
			strcat(line, s);
		}
		LOG_DEBUG("%s", line);
	}

}

#endif

static int8_t openjtag_get_tap_state(int8_t state)
{

	switch (state) {
		case TAP_DREXIT2:   return OPENJTAG_TAP_EXIT2_DR;
		case TAP_DREXIT1:   return OPENJTAG_TAP_EXIT1_DR;
		case TAP_DRSHIFT:   return OPENJTAG_TAP_SHIFT_DR;
		case TAP_DRPAUSE:   return OPENJTAG_TAP_PAUSE_DR;
		case TAP_IRSELECT:  return OPENJTAG_TAP_SELECT_IR;
		case TAP_DRUPDATE:  return OPENJTAG_TAP_UPDATE_DR;
		case TAP_DRCAPTURE: return OPENJTAG_TAP_CAPTURE_DR;
		case TAP_DRSELECT:  return OPENJTAG_TAP_SELECT_DR;
		case TAP_IREXIT2:   return OPENJTAG_TAP_EXIT2_IR;
		case TAP_IREXIT1:   return OPENJTAG_TAP_EXIT1_IR;
		case TAP_IRSHIFT:   return OPENJTAG_TAP_SHIFT_IR;
		case TAP_IRPAUSE:   return OPENJTAG_TAP_PAUSE_IR;
		case TAP_IDLE:      return OPENJTAG_TAP_IDLE;
		case TAP_IRUPDATE:  return OPENJTAG_TAP_UPDATE_IR;
		case TAP_IRCAPTURE: return OPENJTAG_TAP_CAPURE_IR;
		case TAP_RESET:     return OPENJTAG_TAP_RESET;
		case TAP_INVALID:
		default:            return OPENJTAG_TAP_INVALID;
	}
}

static int openjtag_buf_write(
	uint8_t *buf, int size, uint32_t *bytes_written)
{
#if BUILD_OPENJTAG_FTD2XX == 1
	FT_STATUS status;
	DWORD dw_bytes_written;

#ifdef _DEBUG_USB_COMMS_
	openjtag_debug_buffer(buf, size, DEBUG_TYPE_WRITE);
#endif

	status = FT_Write(ftdih, buf, size, &dw_bytes_written);
	if (status != FT_OK) {
		*bytes_written = dw_bytes_written;
		LOG_ERROR("FT_Write returned: %u", status);
		return ERROR_JTAG_DEVICE_ERROR;
	}
	*bytes_written = dw_bytes_written;
	return ERROR_OK;
#elif BUILD_OPENJTAG_LIBFTDI == 1
	int retval;
#ifdef _DEBUG_USB_COMMS_
	openjtag_debug_buffer(buf, size, DEBUG_TYPE_WRITE);
#endif

	retval = ftdi_write_data(&ftdic, buf, size);
	if (retval < 0) {
		*bytes_written = 0;
		LOG_ERROR("ftdi_write_data: %s", ftdi_get_error_string(&ftdic));
		return ERROR_JTAG_DEVICE_ERROR;
	}

	*bytes_written += retval;

	return ERROR_OK;
#endif
}

static int openjtag_buf_read(uint8_t *buf, uint32_t qty, uint32_t *bytes_read)
{

#if BUILD_OPENJTAG_FTD2XX == 1
	DWORD dw_bytes_read;
	FT_STATUS status;
	int timeout = 50;

	*bytes_read = 0;
	while (qty && (*bytes_read < qty) && timeout--) {

		status = FT_Read(ftdih, buf + *bytes_read,
				qty - *bytes_read, &dw_bytes_read);
		if (status != FT_OK) {
			*bytes_read = dw_bytes_read;
			LOG_ERROR("FT_Read returned: %u", status);
			return ERROR_JTAG_DEVICE_ERROR;
		}
		*bytes_read += dw_bytes_read;
	}

#ifdef _DEBUG_USB_COMMS_
	openjtag_debug_buffer(buf, *bytes_read, DEBUG_TYPE_READ);
#endif

	return ERROR_OK;
#elif BUILD_OPENJTAG_LIBFTDI == 1
	int retval;
	int timeout = 5;

	*bytes_read = 0;

	while ((*bytes_read < qty) && timeout--) {
		retval = ftdi_read_data(&ftdic, buf + *bytes_read,
				qty - *bytes_read);
		if (retval < 0) {
			*bytes_read = 0;
			DEBUG_JTAG_IO("ftdi_read_data: %s",
					ftdi_get_error_string(&ftdic));
			return ERROR_JTAG_DEVICE_ERROR;
		}
		*bytes_read += retval;
	}

#ifdef _DEBUG_USB_COMMS_
	openjtag_debug_buffer(buf, *bytes_read, DEBUG_TYPE_READ);
#endif

#endif
	return ERROR_OK;
}

static int openjtag_sendcommand(uint8_t cmd)
{
	uint32_t written;
	return openjtag_buf_write(&cmd, 1, &written);
}

static int openjtag_speed(int speed)
{
	int clockcmd;
	switch (speed) {
		case 48000:
			clockcmd = 0x00;
			break;
		case 24000:
			clockcmd = 0x20;
			break;
		case 12000:
			clockcmd = 0x40;
			break;
		case 6000:
			clockcmd = 0x60;
			break;
		case 3000:
			clockcmd = 0x80;
			break;
		case 1500:
			clockcmd = 0xA0;
			break;
		case 750:
			clockcmd = 0xC0;
			break;
		case 375:
			clockcmd = 0xE0;
			break;
		default:
			clockcmd = 0xE0;
			LOG_WARNING("adapter speed not recognized, reverting to 375 kHz");
			break;
	}
	openjtag_sendcommand(clockcmd);

	return ERROR_OK;
}

static int openjtag_init(void)
{
	uint8_t latency_timer;

#if BUILD_OPENJTAG_FTD2XX == 1
	FT_STATUS status;
#endif

usb_tx_buf_offs = 0;
usb_rx_buf_len = 0;
openjtag_scan_result_count = 0;

#if BUILD_OPENJTAG_FTD2XX == 1
	LOG_DEBUG("'openjtag' interface using FTD2XX");
#elif BUILD_OPENJTAG_LIBFTDI == 1
	LOG_DEBUG("'openjtag' interface using libftdi");
#endif

/* Open by device description */
if (openjtag_device_desc == NULL) {
	LOG_WARNING("no openjtag device description specified, "
				"using default 'Open JTAG Project'");
	openjtag_device_desc = "Open JTAG Project";
}

#if BUILD_OPENJTAG_FTD2XX == 1

#if IS_WIN32 == 0
	/* Add non-standard Vid/Pid to the linux driver */
	status = FT_SetVIDPID(openjtag_vid, openjtag_pid);
	if (status != FT_OK) {
		LOG_WARNING("couldn't add %4.4x:%4.4x",
			openjtag_vid, openjtag_pid);
	}
#endif

	status = FT_OpenEx(openjtag_device_desc, FT_OPEN_BY_DESCRIPTION,
			&ftdih);
	if (status != FT_OK) {
		DWORD num_devices;

		LOG_ERROR("unable to open ftdi device: %u", status);
		status = FT_ListDevices(&num_devices, NULL,
				FT_LIST_NUMBER_ONLY);
		if (status == FT_OK) {
			char **desc_array = malloc(sizeof(char *)
						* (num_devices + 1));
			unsigned int i;

			for (i = 0; i < num_devices; i++)
				desc_array[i] = malloc(64);
			desc_array[num_devices] = NULL;

			status = FT_ListDevices(desc_array, &num_devices,
				FT_LIST_ALL | FT_OPEN_BY_DESCRIPTION);

			if (status == FT_OK) {
				LOG_ERROR("ListDevices: %u\n", num_devices);
				for (i = 0; i < num_devices; i++)
					LOG_ERROR("%i: %s", i, desc_array[i]);
			}

			for (i = 0; i < num_devices; i++)
				free(desc_array[i]);
			free(desc_array);
		} else {
			LOG_ERROR("ListDevices: NONE\n");
		}
		return ERROR_JTAG_INIT_FAILED;
	}

	status = FT_SetLatencyTimer(ftdih, 2);
	if (status != FT_OK) {
		LOG_ERROR("unable to set latency timer: %u", status);
		return ERROR_JTAG_INIT_FAILED;
	}

	status = FT_GetLatencyTimer(ftdih, &latency_timer);
	if (status != FT_OK) {
		LOG_ERROR("unable to get latency timer: %u", status);
		return ERROR_JTAG_INIT_FAILED;
	}
	LOG_DEBUG("current latency timer: %i", latency_timer);

	status = FT_SetBitMode(ftdih, 0x00, 0x40);
	if (status != FT_OK) {
		LOG_ERROR("unable to disable bit i/o mode: %u", status);
		return ERROR_JTAG_INIT_FAILED;
	}

	status = FT_SetTimeouts(ftdih, 50, 0);
	if (status != FT_OK) {
		LOG_ERROR("unable to set timeouts: %u", status);
		return ERROR_JTAG_INIT_FAILED;
	}

	status = FT_Purge(ftdih, FT_PURGE_RX | FT_PURGE_TX);
	if (status != FT_OK) {
		LOG_ERROR("unable to FT_Purge() %u", status);
		return ERROR_JTAG_INIT_FAILED;
	}

#elif BUILD_OPENJTAG_LIBFTDI == 1
	if (ftdi_init(&ftdic) < 0)
		return ERROR_JTAG_INIT_FAILED;

	/* context, vendor id, product id, description, serial id */
	if (ftdi_usb_open_desc(&ftdic, openjtag_vid, openjtag_pid, openjtag_device_desc, NULL) < 0) {
		LOG_ERROR("unable to open ftdi device: %s", ftdic.error_str);
		return ERROR_JTAG_INIT_FAILED;
	}

	if (ftdi_usb_reset(&ftdic) < 0) {
		LOG_ERROR("unable to reset ftdi device");
		return ERROR_JTAG_INIT_FAILED;
	}

	if (ftdi_set_latency_timer(&ftdic, 2) < 0) {
		LOG_ERROR("unable to set latency timer");
		return ERROR_JTAG_INIT_FAILED;
	}

	if (ftdi_get_latency_timer(&ftdic, &latency_timer) < 0) {
		LOG_ERROR("unable to get latency timer");
		return ERROR_JTAG_INIT_FAILED;
	}
	LOG_DEBUG("current latency timer: %u", latency_timer);

	ftdi_disable_bitbang(&ftdic);
	/* was (3000000 / 4) with a comment about a bug in libftdi when using high baudrate */
	if (ftdi_set_baudrate(&ftdic, 3000000) < 0) {
		LOG_ERROR("Can't set baud rate to max: %s",
			ftdi_get_error_string(&ftdic));
		return ERROR_JTAG_DEVICE_ERROR;
	}
#endif

#if BUILD_OPENJTAG_FTD2XX == 1
	status = FT_Purge(ftdih, FT_PURGE_RX | FT_PURGE_TX);
	if (status != FT_OK)
		return ERROR_JTAG_INIT_FAILED;
#elif BUILD_OPENJTAG_LIBFTDI == 1
	if (ftdi_usb_purge_buffers(&ftdic) < 0) {
		LOG_ERROR("ftdi_purge_buffers: %s", ftdic.error_str);
		return ERROR_JTAG_INIT_FAILED;
	}
#endif

	/* OpenJTAG speed */
	openjtag_sendcommand(0xE0); /*Start at slowest adapter speed*/

	/* MSB */
	openjtag_sendcommand(0x75);

	return ERROR_OK;
}

static int openjtag_quit(void)
{
#if BUILD_OPENJTAG_FTD2XX == 1
	FT_Close(ftdih);
#elif BUILD_OPENJTAG_LIBFTDI == 1
	ftdi_usb_close(&ftdic);
	ftdi_deinit(&ftdic);
#endif

	return ERROR_OK;
}

static void openjtag_write_tap_buffer(void)
{
	uint32_t written;

	openjtag_buf_write(usb_tx_buf, usb_tx_buf_offs, &written);
	openjtag_buf_read(usb_rx_buf, usb_tx_buf_offs, &usb_rx_buf_len);

	usb_tx_buf_offs = 0;
}

static int openjtag_execute_tap_queue(void)
{
	openjtag_write_tap_buffer();

	int res_count = 0;

	if (openjtag_scan_result_count && usb_rx_buf_len) {

		int count;
		int rx_offs = 0;
		int len;

		/* for every pending result */
		while (res_count < openjtag_scan_result_count) {

			/* get sent bits */
			len = openjtag_scan_result_buffer[res_count].bits;

			count = 0;

			uint8_t *buffer = openjtag_scan_result_buffer[res_count].buffer;

			while (len) {
				if (len <= 8) {
					DEBUG_JTAG_IO("bits < 8 buf = 0x%X, will be 0x%X",
						usb_rx_buf[rx_offs], usb_rx_buf[rx_offs] >> (8 - len));
					buffer[count] = usb_rx_buf[rx_offs] >> (8 - len);
					len = 0;
				} else {
					buffer[count] = usb_rx_buf[rx_offs];
					len -= 8;
				}

				rx_offs++;
				count++;
			}

#ifdef _DEBUG_USB_COMMS_
			openjtag_debug_buffer(buffer,
				DIV_ROUND_UP(openjtag_scan_result_buffer[res_count].bits, 8), DEBUG_TYPE_OCD_READ);
#endif
			jtag_read_buffer(buffer, openjtag_scan_result_buffer[res_count].command);

			if (openjtag_scan_result_buffer[res_count].buffer)
				free(openjtag_scan_result_buffer[res_count].buffer);

			res_count++;
		}
	}

	openjtag_scan_result_count = 0;

	return ERROR_OK;
}

static void openjtag_add_byte(char buf)
{

	if (usb_tx_buf_offs == OPENJTAG_BUFFER_SIZE) {
		DEBUG_JTAG_IO("Forcing execute_tap_queue");
		DEBUG_JTAG_IO("TX Buff offs=%d", usb_tx_buf_offs);
		openjtag_execute_tap_queue();
	}

	usb_tx_buf[usb_tx_buf_offs] = buf;
	usb_tx_buf_offs++;
}

static void openjtag_add_scan(uint8_t *buffer, int length, struct scan_command *scan_cmd)
{

	/* Ensure space to send long chains */
	/* We add two byte for each eight (or less) bits, one for command, one for data */
	if (usb_tx_buf_offs + (DIV_ROUND_UP(length, 8) * 2) >= OPENJTAG_BUFFER_SIZE) {
		DEBUG_JTAG_IO("Forcing execute_tap_queue from scan");
		DEBUG_JTAG_IO("TX Buff offs=%d len=%d", usb_tx_buf_offs, DIV_ROUND_UP(length, 8) * 2);
		openjtag_execute_tap_queue();
	}

	openjtag_scan_result_buffer[openjtag_scan_result_count].bits = length;
	openjtag_scan_result_buffer[openjtag_scan_result_count].command = scan_cmd;
	openjtag_scan_result_buffer[openjtag_scan_result_count].buffer = buffer;

	uint8_t command;
	uint8_t bits;
	int count = 0;
	while (length) {

		/* write command */
		command = 6;

		/* last bits? */
		if (length <= 8) {
			/* tms high */
			command |= (1 << 4);

			/* bits to transfer */
			bits = (length - 1);
			command |= bits << 5;
			length = 0;
		} else {
			/* whole byte */

			/* bits to transfer */
			bits = 7;
			command |= (7 << 5);
			length -= 8;
		}

		openjtag_add_byte(command);
		openjtag_add_byte(buffer[count]);
		count++;
	}

	openjtag_scan_result_count++;
}

static void openjtag_execute_reset(struct jtag_command *cmd)
{

	DEBUG_JTAG_IO("reset trst: %i srst %i",
			cmd->cmd.reset->trst, cmd->cmd.reset->srst);

	uint8_t buf = 0x00;

	if (cmd->cmd.reset->trst) {
		buf = 0x03;
	} else {
		buf |= 0x04;
		buf |= 0x05 << 4;
	}

	openjtag_add_byte(buf);
}

static void openjtag_execute_sleep(struct jtag_command *cmd)
{
	jtag_sleep(cmd->cmd.sleep->us);
}

static void openjtag_set_state(uint8_t openocd_state)
{
	int8_t state = openjtag_get_tap_state(openocd_state);

	uint8_t buf = 0;
	buf = 0x01;
	buf |= state << 4;

	openjtag_add_byte(buf);
}

static void openjtag_execute_statemove(struct jtag_command *cmd)
{
	DEBUG_JTAG_IO("state move to %i", cmd->cmd.statemove->end_state);

	tap_set_end_state(cmd->cmd.statemove->end_state);

	openjtag_set_state(cmd->cmd.statemove->end_state);

	tap_set_state(tap_get_end_state());
}


static void openjtag_execute_scan(struct jtag_command *cmd)
{

	int scan_size, old_state;
	uint8_t *buffer;

	DEBUG_JTAG_IO("scan ends in %s", tap_state_name(cmd->cmd.scan->end_state));

	/* get scan info */
	tap_set_end_state(cmd->cmd.scan->end_state);
	scan_size = jtag_build_buffer(cmd->cmd.scan, &buffer);

#ifdef _DEBUG_USB_COMMS_
	openjtag_debug_buffer(buffer, (scan_size + 7) / 8, DEBUG_TYPE_BUFFER);
#endif
	/* set state */
	old_state = tap_get_end_state();
	openjtag_set_state(cmd->cmd.scan->ir_scan ? TAP_IRSHIFT : TAP_DRSHIFT);
	tap_set_state(cmd->cmd.scan->ir_scan ? TAP_IRSHIFT : TAP_DRSHIFT);
	tap_set_end_state(old_state);

	openjtag_add_scan(buffer, scan_size, cmd->cmd.scan);

	openjtag_set_state(cmd->cmd.scan->ir_scan ? TAP_IRPAUSE : TAP_DRPAUSE);
	tap_set_state(cmd->cmd.scan->ir_scan ? TAP_IRPAUSE : TAP_DRPAUSE);

	if (tap_get_state() != tap_get_end_state()) {
		openjtag_set_state(tap_get_end_state());
		tap_set_state(tap_get_end_state());
	}
}

static void openjtag_execute_runtest(struct jtag_command *cmd)
{

	tap_state_t end_state = cmd->cmd.runtest->end_state;
	tap_set_end_state(end_state);

	/* only do a state_move when we're not already in IDLE */
	if (tap_get_state() != TAP_IDLE) {
		openjtag_set_state(TAP_IDLE);
		tap_set_state(TAP_IDLE);
	}

	if (cmd->cmd.runtest->num_cycles > 16)
		LOG_WARNING("num_cycles > 16 on run test");

	uint8_t command;
	command = 7;
	command |= ((cmd->cmd.runtest->num_cycles - 1) & 0x0F) << 4;

	openjtag_add_byte(command);

	tap_set_end_state(end_state);
	if (tap_get_end_state() != tap_get_state()) {
		openjtag_set_state(end_state);
		tap_set_state(end_state);
	}
}

static void openjtag_execute_command(struct jtag_command *cmd)
{
	DEBUG_JTAG_IO("openjtag_execute_command %i", cmd->type);
	switch (cmd->type) {
	case JTAG_RESET:
			openjtag_execute_reset(cmd);
			break;
	case JTAG_SLEEP:
			openjtag_execute_sleep(cmd);
			break;
	case JTAG_TLR_RESET:
			openjtag_execute_statemove(cmd);
			break;
	case JTAG_SCAN:
			openjtag_execute_scan(cmd);
			break;
	case JTAG_RUNTEST:
			openjtag_execute_runtest(cmd);
			break;
	case JTAG_PATHMOVE:
		/* jlink_execute_pathmove(cmd); break; */
	default:
		LOG_ERROR("BUG: unknown Open JTAG command type encountered");
		exit(-1);
	}
}

static int openjtag_execute_queue(void)
{
	struct jtag_command *cmd = jtag_command_queue;

	while (cmd != NULL) {
		openjtag_execute_command(cmd);
		cmd = cmd->next;
	}

	return openjtag_execute_tap_queue();
}

static int openjtag_speed_div(int speed, int *khz)
{
	*khz = speed;

	return ERROR_OK;
}

static int openjtag_khz(int khz, int *jtag_speed)
{

	if (khz >= 48000)
		*jtag_speed = 48000;
	else if (khz >= 24000)
		*jtag_speed = 24000;
	else if (khz >= 12000)
		*jtag_speed = 12000;
	else if (khz >= 6000)
		*jtag_speed = 6000;
	else if (khz >= 3000)
		*jtag_speed = 3000;
	else if (khz >= 1500)
		*jtag_speed = 1500;
	else if (khz >= 750)
		*jtag_speed = 750;
	else
		*jtag_speed = 375;

	return ERROR_OK;
}

COMMAND_HANDLER(openjtag_handle_device_desc_command)
{
	if (CMD_ARGC == 1)
		openjtag_device_desc = strdup(CMD_ARGV[0]);
	else
		LOG_ERROR("require exactly one argument to "
				  "openjtag_device_desc <description>");
	return ERROR_OK;
}


static const struct command_registration openjtag_command_handlers[] = {
	{
		.name = "openjtag_device_desc",
		.handler = openjtag_handle_device_desc_command,
		.mode = COMMAND_CONFIG,
		.help = "set the USB device description of the OpenJTAG",
		.usage = "description-string",
	},
	COMMAND_REGISTRATION_DONE
};

struct jtag_interface openjtag_interface = {
	.name = "openjtag",
	.commands = openjtag_command_handlers,

	.execute_queue = openjtag_execute_queue,
	.speed = openjtag_speed,
	.speed_div = openjtag_speed_div,
	.khz = openjtag_khz,
	.init = openjtag_init,
	.quit = openjtag_quit,
};


