From b598efb6135103ef2f2b451402b5a0aca1c87d22 Mon Sep 17 00:00:00 2001 From: Andreas Fritiofson Date: Mon, 30 Jan 2012 00:16:41 +0100 Subject: [PATCH] Add MPSSE communications layer for FTDI chips This is a higher-level libftdi replacement for use when implementing protocol drivers for FT2232, FT2232H or FT4232H. It takes care of device open/close and, unlike libftdi, also MPSSE command abstraction, command queueing, buffer handling and return data parsing. The FTDI device is accessed through libusb-1.0 in asynchronous mode. Change-Id: I051adb574dcc39f8ca9cd7f6dbe6ae4aeea5f4c8 Signed-off-by: Andreas Fritiofson Reviewed-on: http://openocd.zylin.com/451 Tested-by: jenkins Reviewed-by: Freddie Chopin Reviewed-by: Peter Stuge --- src/jtag/drivers/mpsse.c | 838 +++++++++++++++++++++++++++++++++++++++ src/jtag/drivers/mpsse.h | 79 ++++ 2 files changed, 917 insertions(+) create mode 100644 src/jtag/drivers/mpsse.c create mode 100644 src/jtag/drivers/mpsse.h diff --git a/src/jtag/drivers/mpsse.c b/src/jtag/drivers/mpsse.c new file mode 100644 index 0000000000..dbca535f91 --- /dev/null +++ b/src/jtag/drivers/mpsse.c @@ -0,0 +1,838 @@ +/************************************************************************** + * Copyright (C) 2012 by Andreas Fritiofson * + * andreas.fritiofson@gmail.com * + * * + * 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., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "mpsse.h" +#include "helper/log.h" +#include + +/* Compatibility define for older libusb-1.0 */ +#ifndef LIBUSB_CALL +#define LIBUSB_CALL +#endif + +#ifdef _DEBUG_JTAG_IO_ +#define DEBUG_IO(expr...) LOG_DEBUG(expr) +#define DEBUG_PRINT_BUF(buf, len) \ + do { \ + char buf_string[32 * 3 + 1]; \ + int buf_string_pos = 0; \ + for (int i = 0; i < len; i++) { \ + buf_string_pos += sprintf(buf_string + buf_string_pos, " %02x", buf[i]); \ + if (i % 32 == 32 - 1) { \ + LOG_DEBUG("%s", buf_string); \ + buf_string_pos = 0; \ + } \ + } \ + if (buf_string_pos > 0) \ + LOG_DEBUG("%s", buf_string);\ + } while (0) +#else +#define DEBUG_IO(expr...) do {} while (0) +#define DEBUG_PRINT_BUF(buf, len) do {} while (0) +#endif + +#define FTDI_DEVICE_OUT_REQTYPE (LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE) +#define FTDI_DEVICE_IN_REQTYPE (0x80 | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE) + +#define BITMODE_MPSSE 0x02 + +#define SIO_RESET_REQUEST 0x00 +#define SIO_SET_LATENCY_TIMER_REQUEST 0x09 +#define SIO_GET_LATENCY_TIMER_REQUEST 0x0A +#define SIO_SET_BITMODE_REQUEST 0x0B + +#define SIO_RESET_SIO 0 +#define SIO_RESET_PURGE_RX 1 +#define SIO_RESET_PURGE_TX 2 + +struct mpsse_ctx { + libusb_context *usb_ctx; + libusb_device_handle *usb_dev; + unsigned int usb_write_timeout; + unsigned int usb_read_timeout; + uint8_t in_ep; + uint8_t out_ep; + uint16_t max_packet_size; + uint16_t index; + uint8_t interface; + enum ftdi_chip_type type; + uint8_t *write_buffer; + unsigned write_size; + unsigned write_count; + uint8_t *read_buffer; + unsigned read_size; + unsigned read_count; + uint8_t *read_chunk; + unsigned read_chunk_size; + struct bit_copy_queue read_queue; +}; + +/* Returns true if the string descriptor indexed by str_index in device matches string */ +static bool string_descriptor_equal(libusb_device_handle *device, uint8_t str_index, + const char *string) +{ + int retval; + char desc_string[256]; /* Max size of string descriptor */ + retval = libusb_get_string_descriptor_ascii(device, str_index, (unsigned char *)desc_string, + sizeof(desc_string)); + if (retval < 0) { + LOG_ERROR("libusb_get_string_descriptor_ascii() failed with %d", retval); + return false; + } + return strncmp(string, desc_string, sizeof(desc_string)) == 0; +} + +/* Helper to open a libusb device that matches vid, pid, product string and/or serial string. + * Set any field to 0 as a wildcard. If the device is found true is returned, with ctx containing + * the already opened handle. ctx->interface must be set to the desired interface (channel) number + * prior to calling this function. */ +static bool open_matching_device(struct mpsse_ctx *ctx, const uint16_t *vid, const uint16_t *pid, + const char *product, const char *serial) +{ + libusb_device **list; + struct libusb_device_descriptor desc; + struct libusb_config_descriptor *config0; + int err; + bool found = false; + ssize_t cnt = libusb_get_device_list(ctx->usb_ctx, &list); + if (cnt < 0) + LOG_ERROR("libusb_get_device_list() failed with %zi", cnt); + + for (ssize_t i = 0; i < cnt; i++) { + libusb_device *device = list[i]; + + err = libusb_get_device_descriptor(device, &desc); + if (err != LIBUSB_SUCCESS) { + LOG_ERROR("libusb_get_device_descriptor() failed with %d", err); + continue; + } + + if (vid && *vid != desc.idVendor) + continue; + if (pid && *pid != desc.idProduct) + continue; + + err = libusb_open(device, &ctx->usb_dev); + if (err != LIBUSB_SUCCESS) { + LOG_ERROR("libusb_open() failed with %d", err); + continue; + } + + if (product && !string_descriptor_equal(ctx->usb_dev, desc.iProduct, product)) { + libusb_close(ctx->usb_dev); + continue; + } + + if (serial && !string_descriptor_equal(ctx->usb_dev, desc.iSerialNumber, serial)) { + libusb_close(ctx->usb_dev); + continue; + } + + found = true; + break; + } + + libusb_free_device_list(list, 1); + + if (!found) { + LOG_ERROR("no device found"); + return false; + } + + err = libusb_get_config_descriptor(libusb_get_device(ctx->usb_dev), 0, &config0); + if (err != LIBUSB_SUCCESS) { + LOG_ERROR("libusb_get_config_descriptor() failed with %d", err); + libusb_close(ctx->usb_dev); + return false; + } + + /* Make sure the first configuration is selected */ + int cfg; + err = libusb_get_configuration(ctx->usb_dev, &cfg); + if (err != LIBUSB_SUCCESS) { + LOG_ERROR("libusb_get_configuration() failed with %d", err); + goto error; + } + + if (desc.bNumConfigurations > 0 && cfg != config0->bConfigurationValue) { + err = libusb_set_configuration(ctx->usb_dev, config0->bConfigurationValue); + if (err != LIBUSB_SUCCESS) { + LOG_ERROR("libusb_set_configuration() failed with %d", err); + goto error; + } + } + + /* Try to detach ftdi_sio kernel module */ + err = libusb_detach_kernel_driver(ctx->usb_dev, ctx->interface); + if (err != LIBUSB_SUCCESS && err != LIBUSB_ERROR_NOT_FOUND + && err != LIBUSB_ERROR_NOT_SUPPORTED) { + LOG_ERROR("libusb_detach_kernel_driver() failed with %d", err); + goto error; + } + + err = libusb_claim_interface(ctx->usb_dev, ctx->interface); + if (err != LIBUSB_SUCCESS) { + LOG_ERROR("libusb_claim_interface() failed with %d", err); + goto error; + } + + /* Reset FTDI device */ + err = libusb_control_transfer(ctx->usb_dev, FTDI_DEVICE_OUT_REQTYPE, + SIO_RESET_REQUEST, SIO_RESET_SIO, + ctx->index, NULL, 0, ctx->usb_write_timeout); + if (err < 0) { + LOG_ERROR("failed to reset FTDI device: %d", err); + goto error; + } + + switch (desc.bcdDevice) { + case 0x500: + ctx->type = TYPE_FT2232C; + break; + case 0x700: + ctx->type = TYPE_FT2232H; + break; + case 0x800: + ctx->type = TYPE_FT4232H; + break; + default: + LOG_ERROR("unsupported FTDI chip type: 0x%04x", desc.bcdDevice); + goto error; + } + + /* Determine maximum packet size and endpoint addresses */ + if (!(desc.bNumConfigurations > 0 && ctx->interface < config0->bNumInterfaces + && config0->interface[ctx->interface].num_altsetting > 0)) + goto desc_error; + + const struct libusb_interface_descriptor *descriptor; + descriptor = &config0->interface[ctx->interface].altsetting[0]; + if (descriptor->bNumEndpoints != 2) + goto desc_error; + + ctx->in_ep = 0; + ctx->out_ep = 0; + for (int i = 0; i < descriptor->bNumEndpoints; i++) { + if (descriptor->endpoint[i].bEndpointAddress & 0x80) { + ctx->in_ep = descriptor->endpoint[i].bEndpointAddress; + ctx->max_packet_size = + descriptor->endpoint[i].wMaxPacketSize; + } else { + ctx->out_ep = descriptor->endpoint[i].bEndpointAddress; + } + } + + if (ctx->in_ep == 0 || ctx->out_ep == 0) + goto desc_error; + + libusb_free_config_descriptor(config0); + return true; + +desc_error: + LOG_ERROR("unrecognized USB device descriptor"); +error: + libusb_free_config_descriptor(config0); + libusb_close(ctx->usb_dev); + return false; +} + +struct mpsse_ctx *mpsse_open(const uint16_t *vid, const uint16_t *pid, const char *description, + const char *serial, int channel) +{ + struct mpsse_ctx *ctx = calloc(1, sizeof(*ctx)); + int err; + + if (!ctx) + return 0; + + bit_copy_queue_init(&ctx->read_queue); + ctx->read_chunk_size = 16384; + ctx->read_size = 16384; + ctx->write_size = 16384; + ctx->read_chunk = malloc(ctx->read_chunk_size); + ctx->read_buffer = malloc(ctx->read_size); + ctx->write_buffer = malloc(ctx->write_size); + if (!ctx->read_chunk || !ctx->read_buffer || !ctx->write_buffer) + goto error; + + ctx->interface = channel; + ctx->index = channel + 1; + ctx->usb_read_timeout = 5000; + ctx->usb_write_timeout = 5000; + + err = libusb_init(&ctx->usb_ctx); + if (err != LIBUSB_SUCCESS) { + LOG_ERROR("libusb_init() failed with %d", err); + goto error; + } + + if (!open_matching_device(ctx, vid, pid, description, serial)) { + /* Four hex digits plus terminating zero each */ + char vidstr[5]; + char pidstr[5]; + LOG_ERROR("unable to open ftdi device with vid %s, pid %s, description '%s' and " + "serial '%s'", + vid ? sprintf(vidstr, "%04x", *vid), vidstr : "*", + pid ? sprintf(pidstr, "%04x", *pid), pidstr : "*", + description ? description : "*", + serial ? serial : "*"); + ctx->usb_dev = 0; + goto error; + } + + err = libusb_control_transfer(ctx->usb_dev, FTDI_DEVICE_OUT_REQTYPE, + SIO_SET_LATENCY_TIMER_REQUEST, 255, ctx->index, NULL, 0, + ctx->usb_write_timeout); + if (err < 0) { + LOG_ERROR("unable to set latency timer: %d", err); + goto error; + } + + err = libusb_control_transfer(ctx->usb_dev, + FTDI_DEVICE_OUT_REQTYPE, + SIO_SET_BITMODE_REQUEST, + 0x0b | (BITMODE_MPSSE << 8), + ctx->index, + NULL, + 0, + ctx->usb_write_timeout); + if (err < 0) { + LOG_ERROR("unable to set MPSSE bitmode: %d", err); + goto error; + } + + mpsse_purge(ctx); + + return ctx; +error: + mpsse_close(ctx); + return 0; +} + +void mpsse_close(struct mpsse_ctx *ctx) +{ + if (ctx->usb_dev) + libusb_close(ctx->usb_dev); + if (ctx->usb_ctx) + libusb_exit(ctx->usb_ctx); + bit_copy_discard(&ctx->read_queue); + if (ctx->write_buffer) + free(ctx->write_buffer); + if (ctx->read_buffer) + free(ctx->read_buffer); + if (ctx->read_chunk) + free(ctx->read_chunk); + + free(ctx); +} + +bool mpsse_is_high_speed(struct mpsse_ctx *ctx) +{ + return ctx->type != TYPE_FT2232C; +} + +void mpsse_purge(struct mpsse_ctx *ctx) +{ + int err; + LOG_DEBUG("-"); + ctx->write_count = 0; + ctx->read_count = 0; + bit_copy_discard(&ctx->read_queue); + err = libusb_control_transfer(ctx->usb_dev, FTDI_DEVICE_OUT_REQTYPE, SIO_RESET_REQUEST, + SIO_RESET_PURGE_RX, ctx->index, NULL, 0, ctx->usb_write_timeout); + if (err < 0) { + LOG_ERROR("unable to purge ftdi rx buffers: %d", err); + return; + } + + err = libusb_control_transfer(ctx->usb_dev, FTDI_DEVICE_OUT_REQTYPE, SIO_RESET_REQUEST, + SIO_RESET_PURGE_TX, ctx->index, NULL, 0, ctx->usb_write_timeout); + if (err < 0) { + LOG_ERROR("unable to purge ftdi tx buffers: %d", err); + return; + } +} + +static unsigned buffer_write_space(struct mpsse_ctx *ctx) +{ + /* Reserve one byte for SEND_IMMEDIATE */ + return ctx->write_size - ctx->write_count - 1; +} + +static unsigned buffer_read_space(struct mpsse_ctx *ctx) +{ + return ctx->read_size - ctx->read_count; +} + +static void buffer_write_byte(struct mpsse_ctx *ctx, uint8_t data) +{ + DEBUG_IO("%02x", data); + assert(ctx->write_count < ctx->write_size); + ctx->write_buffer[ctx->write_count++] = data; +} + +static unsigned buffer_write(struct mpsse_ctx *ctx, const uint8_t *out, unsigned out_offset, + unsigned bit_count) +{ + DEBUG_IO("%d bits", bit_count); + assert(ctx->write_count + DIV_ROUND_UP(bit_count, 8) <= ctx->write_size); + bit_copy(ctx->write_buffer + ctx->write_count, 0, out, out_offset, bit_count); + ctx->write_count += DIV_ROUND_UP(bit_count, 8); + return bit_count; +} + +static unsigned buffer_add_read(struct mpsse_ctx *ctx, uint8_t *in, unsigned in_offset, + unsigned bit_count, unsigned offset) +{ + DEBUG_IO("%d bits, offset %d", bit_count, offset); + assert(ctx->read_count + DIV_ROUND_UP(bit_count, 8) <= ctx->read_size); + bit_copy_queued(&ctx->read_queue, in, in_offset, ctx->read_buffer + ctx->read_count, offset, + bit_count); + ctx->read_count += DIV_ROUND_UP(bit_count, 8); + return bit_count; +} + +int mpsse_clock_data_out(struct mpsse_ctx *ctx, const uint8_t *out, unsigned out_offset, + unsigned length, uint8_t mode) +{ + return mpsse_clock_data(ctx, out, out_offset, 0, 0, length, mode); +} + +int mpsse_clock_data_in(struct mpsse_ctx *ctx, uint8_t *in, unsigned in_offset, unsigned length, + uint8_t mode) +{ + return mpsse_clock_data(ctx, 0, 0, in, in_offset, length, mode); +} + +int mpsse_clock_data(struct mpsse_ctx *ctx, const uint8_t *out, unsigned out_offset, uint8_t *in, + unsigned in_offset, unsigned length, uint8_t mode) +{ + /* TODO: Fix MSB first modes */ + DEBUG_IO("%s%s %d bits", in ? "in" : "", out ? "out" : "", length); + int retval = ERROR_OK; + + /* TODO: On H chips, use command 0x8E/0x8F if in and out are both 0 */ + if (out || (!out && !in)) + mode |= 0x10; + if (in) + mode |= 0x20; + + while (length > 0) { + /* Guarantee buffer space enough for a minimum size transfer */ + if (buffer_write_space(ctx) + (length < 8) < (out ? 4 : 3) + || (in && buffer_read_space(ctx) < 1)) + retval = mpsse_flush(ctx); + + if (length < 8) { + /* Transfer remaining bits in bit mode */ + buffer_write_byte(ctx, 0x02 | mode); + buffer_write_byte(ctx, length - 1); + if (out) + out_offset += buffer_write(ctx, out, out_offset, length); + if (in) + in_offset += buffer_add_read(ctx, in, in_offset, length, 8 - length); + if (!out && !in) + buffer_write_byte(ctx, 0x00); + length = 0; + } else { + /* Byte transfer */ + unsigned this_bytes = length / 8; + /* MPSSE command limit */ + if (this_bytes > 65536) + this_bytes = 65536; + /* Buffer space limit. We already made sure there's space for the minimum + *transfer. */ + if (out && this_bytes + 3 > buffer_write_space(ctx)) + this_bytes = buffer_write_space(ctx) - 3; + if (in && this_bytes > buffer_read_space(ctx)) + this_bytes = buffer_read_space(ctx); + + if (this_bytes > 0) { + buffer_write_byte(ctx, mode); + buffer_write_byte(ctx, (this_bytes - 1) & 0xff); + buffer_write_byte(ctx, (this_bytes - 1) >> 8); + if (out) + out_offset += buffer_write(ctx, + out, + out_offset, + this_bytes * 8); + if (in) + in_offset += buffer_add_read(ctx, + in, + in_offset, + this_bytes * 8, + 0); + if (!out && !in) + for (unsigned n = 0; n < this_bytes; n++) + buffer_write_byte(ctx, 0x00); + length -= this_bytes * 8; + } + } + } + return retval; +} + +int mpsse_clock_tms_cs_out(struct mpsse_ctx *ctx, const uint8_t *out, unsigned out_offset, + unsigned length, bool tdi, uint8_t mode) +{ + return mpsse_clock_tms_cs(ctx, out, out_offset, 0, 0, length, tdi, mode); +} + +int mpsse_clock_tms_cs(struct mpsse_ctx *ctx, const uint8_t *out, unsigned out_offset, uint8_t *in, + unsigned in_offset, unsigned length, bool tdi, uint8_t mode) +{ + DEBUG_IO("%sout %d bits, tdi=%d", in ? "in" : "", length, tdi); + assert(out); + int retval = ERROR_OK; + + mode |= 0x42; + if (in) + mode |= 0x20; + + while (length > 0) { + /* Guarantee buffer space enough for a minimum size transfer */ + if (buffer_write_space(ctx) < 3 || (in && buffer_read_space(ctx) < 1)) + retval = mpsse_flush(ctx); + + /* Byte transfer */ + unsigned this_bits = length; + /* MPSSE command limit */ + /* NOTE: there's a report of an FT2232 bug in this area, where shifting + * exactly 7 bits can make problems with TMS signaling for the last + * clock cycle: + * + * http://developer.intra2net.com/mailarchive/html/libftdi/2009/msg00292.html + */ + if (this_bits > 7) + this_bits = 7; + + if (this_bits > 0) { + buffer_write_byte(ctx, mode); + buffer_write_byte(ctx, this_bits - 1); + uint8_t data = 0; + /* TODO: Fix MSB first, if allowed in MPSSE */ + bit_copy(&data, 0, out, out_offset, this_bits); + out_offset += this_bits; + buffer_write_byte(ctx, data | (tdi ? 0x80 : 0x00)); + if (in) + in_offset += buffer_add_read(ctx, + in, + in_offset, + this_bits, + 8 - this_bits); + length -= this_bits; + } + } + return retval; +} + +int mpsse_set_data_bits_low_byte(struct mpsse_ctx *ctx, uint8_t data, uint8_t dir) +{ + DEBUG_IO("-"); + int retval = ERROR_OK; + + if (buffer_write_space(ctx) < 3) + retval = mpsse_flush(ctx); + + buffer_write_byte(ctx, 0x80); + buffer_write_byte(ctx, data); + buffer_write_byte(ctx, dir); + + return retval; +} + +int mpsse_set_data_bits_high_byte(struct mpsse_ctx *ctx, uint8_t data, uint8_t dir) +{ + DEBUG_IO("-"); + int retval = ERROR_OK; + + if (buffer_write_space(ctx) < 3) + retval = mpsse_flush(ctx); + + buffer_write_byte(ctx, 0x82); + buffer_write_byte(ctx, data); + buffer_write_byte(ctx, dir); + + return retval; +} + +int mpsse_read_data_bits_low_byte(struct mpsse_ctx *ctx, uint8_t *data) +{ + DEBUG_IO("-"); + int retval = ERROR_OK; + + if (buffer_write_space(ctx) < 1) + retval = mpsse_flush(ctx); + + buffer_write_byte(ctx, 0x81); + buffer_add_read(ctx, data, 0, 8, 0); + + return retval; +} + +int mpsse_read_data_bits_high_byte(struct mpsse_ctx *ctx, uint8_t *data) +{ + DEBUG_IO("-"); + int retval = ERROR_OK; + + if (buffer_write_space(ctx) < 1) + retval = mpsse_flush(ctx); + + buffer_write_byte(ctx, 0x83); + buffer_add_read(ctx, data, 0, 8, 0); + + return retval; +} + +static int single_byte_boolean_helper(struct mpsse_ctx *ctx, bool var, uint8_t val_if_true, + uint8_t val_if_false) +{ + int retval = ERROR_OK; + + if (buffer_write_space(ctx) < 1) + retval = mpsse_flush(ctx); + + buffer_write_byte(ctx, var ? val_if_true : val_if_false); + + return retval; +} + +int mpsse_loopback_config(struct mpsse_ctx *ctx, bool enable) +{ + LOG_DEBUG("%s", enable ? "on" : "off"); + return single_byte_boolean_helper(ctx, enable, 0x84, 0x85); +} + +int mpsse_set_divisor(struct mpsse_ctx *ctx, uint16_t divisor) +{ + LOG_DEBUG("%d", divisor); + int retval = ERROR_OK; + + if (buffer_write_space(ctx) < 3) + retval = mpsse_flush(ctx); + + buffer_write_byte(ctx, 0x86); + buffer_write_byte(ctx, divisor & 0xff); + buffer_write_byte(ctx, divisor >> 8); + + return retval; +} + +int mpsse_divide_by_5_config(struct mpsse_ctx *ctx, bool enable) +{ + if (!mpsse_is_high_speed(ctx)) + return ERROR_FAIL; + + LOG_DEBUG("%s", enable ? "on" : "off"); + + return single_byte_boolean_helper(ctx, enable, 0x8b, 0x8a); +} + +int mpsse_rtck_config(struct mpsse_ctx *ctx, bool enable) +{ + if (!mpsse_is_high_speed(ctx)) + return ERROR_FAIL; + + LOG_DEBUG("%s", enable ? "on" : "off"); + + return single_byte_boolean_helper(ctx, enable, 0x96, 0x97); +} + +int mpsse_set_frequency(struct mpsse_ctx *ctx, int frequency) +{ + LOG_DEBUG("target %d Hz", frequency); + assert(frequency >= 0); + int base_clock; + + if (frequency == 0) + return mpsse_rtck_config(ctx, true); + + mpsse_rtck_config(ctx, false); /* just try */ + + if (frequency > 60000000 / 2 / 65536 && mpsse_is_high_speed(ctx)) { + int retval = mpsse_divide_by_5_config(ctx, false); + if (retval != ERROR_OK) + return retval; + base_clock = 60000000; + } else { + mpsse_divide_by_5_config(ctx, true); /* just try */ + base_clock = 12000000; + } + + int divisor = (base_clock / 2 + frequency - 1) / frequency - 1; + if (divisor > 65535) + divisor = 65535; + assert(divisor >= 0); + + int retval = mpsse_set_divisor(ctx, divisor); + if (retval != ERROR_OK) + return retval; + + frequency = base_clock / 2 / (1 + divisor); + LOG_DEBUG("actually %d Hz", frequency); + + return frequency; +} + +/* Context needed by the callbacks */ +struct transfer_result { + struct mpsse_ctx *ctx; + bool done; + unsigned transferred; +}; + +static LIBUSB_CALL void read_cb(struct libusb_transfer *transfer) +{ + struct transfer_result *res = (struct transfer_result *)transfer->user_data; + struct mpsse_ctx *ctx = res->ctx; + + unsigned packet_size = ctx->max_packet_size; + + DEBUG_PRINT_BUF(transfer->buffer, transfer->actual_length); + + /* Strip the two status bytes sent at the beginning of each USB packet + * while copying the chunk buffer to the read buffer */ + unsigned num_packets = DIV_ROUND_UP(transfer->actual_length, packet_size); + unsigned chunk_remains = transfer->actual_length; + for (unsigned i = 0; i < num_packets && chunk_remains > 2; i++) { + unsigned this_size = packet_size - 2; + if (this_size > chunk_remains - 2) + this_size = chunk_remains - 2; + if (this_size > ctx->read_count - res->transferred) + this_size = ctx->read_count - res->transferred; + memcpy(ctx->read_buffer + res->transferred, + ctx->read_chunk + packet_size * i + 2, + this_size); + res->transferred += this_size; + chunk_remains -= this_size + 2; + if (res->transferred == ctx->read_count) { + res->done = true; + break; + } + } + + DEBUG_IO("raw chunk %d, transferred %d of %d", transfer->actual_length, res->transferred, + ctx->read_count); + + if (!res->done) + if (libusb_submit_transfer(transfer) != LIBUSB_SUCCESS) + res->done = true; +} + +static LIBUSB_CALL void write_cb(struct libusb_transfer *transfer) +{ + struct transfer_result *res = (struct transfer_result *)transfer->user_data; + struct mpsse_ctx *ctx = res->ctx; + + res->transferred += transfer->actual_length; + + DEBUG_IO("transferred %d of %d", res->transferred, ctx->write_count); + + DEBUG_PRINT_BUF(transfer->buffer, transfer->actual_length); + + if (res->transferred == ctx->write_count) + res->done = true; + else { + transfer->length = ctx->write_count - res->transferred; + transfer->buffer = ctx->write_buffer + res->transferred; + if (libusb_submit_transfer(transfer) != LIBUSB_SUCCESS) + res->done = true; + } +} + +int mpsse_flush(struct mpsse_ctx *ctx) +{ + DEBUG_IO("write %d%s, read %d", ctx->write_count, ctx->read_count ? "+1" : "", + ctx->read_count); + assert(ctx->write_count > 0 || ctx->read_count == 0); /* No read data without write data */ + int retval = ERROR_OK; + + if (ctx->write_count == 0) + return retval; + + struct libusb_transfer *read_transfer = 0; + struct transfer_result read_result = { .ctx = ctx, .done = true }; + if (ctx->read_count) { + buffer_write_byte(ctx, 0x87); /* SEND_IMMEDIATE */ + read_result.done = false; + read_transfer = libusb_alloc_transfer(0); + libusb_fill_bulk_transfer(read_transfer, ctx->usb_dev, ctx->in_ep, ctx->read_chunk, + ctx->read_chunk_size, read_cb, &read_result, + ctx->usb_read_timeout); + retval = libusb_submit_transfer(read_transfer); + } + + struct transfer_result write_result = { .ctx = ctx, .done = false }; + struct libusb_transfer *write_transfer = libusb_alloc_transfer(0); + libusb_fill_bulk_transfer(write_transfer, ctx->usb_dev, ctx->out_ep, ctx->write_buffer, + ctx->write_count, write_cb, &write_result, ctx->usb_write_timeout); + retval = libusb_submit_transfer(write_transfer); + + /* Polling loop, more or less taken from libftdi */ + while (!write_result.done || !read_result.done) { + retval = libusb_handle_events(ctx->usb_ctx); + keep_alive(); + if (retval != LIBUSB_SUCCESS && retval != LIBUSB_ERROR_INTERRUPTED) { + libusb_cancel_transfer(write_transfer); + if (read_transfer) + libusb_cancel_transfer(read_transfer); + while (!write_result.done || !read_result.done) + if (libusb_handle_events(ctx->usb_ctx) != LIBUSB_SUCCESS) + break; + } + } + + if (retval != LIBUSB_SUCCESS) { + LOG_ERROR("libusb_handle_events() failed with %d", retval); + retval = ERROR_FAIL; + } else if (write_result.transferred < ctx->write_count) { + LOG_ERROR("ftdi device did not accept all data: %d, tried %d", + write_result.transferred, + ctx->write_count); + retval = ERROR_FAIL; + } else if (read_result.transferred < ctx->read_count) { + LOG_ERROR("ftdi device did not return all data: %d, expected %d", + read_result.transferred, + ctx->read_count); + retval = ERROR_FAIL; + } else if (ctx->read_count) { + ctx->write_count = 0; + ctx->read_count = 0; + bit_copy_execute(&ctx->read_queue); + retval = ERROR_OK; + } else { + ctx->write_count = 0; + bit_copy_discard(&ctx->read_queue); + retval = ERROR_OK; + } + + libusb_free_transfer(write_transfer); + if (read_transfer) + libusb_free_transfer(read_transfer); + + if (retval != ERROR_OK) + mpsse_purge(ctx); + + return retval; +} diff --git a/src/jtag/drivers/mpsse.h b/src/jtag/drivers/mpsse.h new file mode 100644 index 0000000000..766a215824 --- /dev/null +++ b/src/jtag/drivers/mpsse.h @@ -0,0 +1,79 @@ +/************************************************************************** + * Copyright (C) 2012 by Andreas Fritiofson * + * andreas.fritiofson@gmail.com * + * * + * 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., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#ifndef MPSSE_H_ +#define MPSSE_H_ + +#include +#include "helper/binarybuffer.h" + +/* Mode flags */ +#define POS_EDGE_OUT 0x00 +#define NEG_EDGE_OUT 0x01 +#define POS_EDGE_IN 0x00 +#define NEG_EDGE_IN 0x04 +#define MSB_FIRST 0x00 +#define LSB_FIRST 0x08 + +enum ftdi_chip_type { + TYPE_FT2232C, + TYPE_FT2232H, + TYPE_FT4232H, +}; + +struct mpsse_ctx; + +/* Device handling */ +struct mpsse_ctx *mpsse_open(const uint16_t *vid, const uint16_t *pid, const char *description, + const char *serial, int channel); +void mpsse_close(struct mpsse_ctx *ctx); +bool mpsse_is_high_speed(struct mpsse_ctx *ctx); + +/* Command queuing. These correspond to the MPSSE commands with the same names, but no need to care + * about bit/byte transfer or data length limitation. Read data is guaranteed to be available only + * after the following mpsse_flush(). */ +int mpsse_clock_data_out(struct mpsse_ctx *ctx, const uint8_t *out, unsigned out_offset, + unsigned length, uint8_t mode); +int mpsse_clock_data_in(struct mpsse_ctx *ctx, uint8_t *in, unsigned in_offset, unsigned length, + uint8_t mode); +int mpsse_clock_data(struct mpsse_ctx *ctx, const uint8_t *out, unsigned out_offset, uint8_t *in, + unsigned in_offset, unsigned length, uint8_t mode); +int mpsse_clock_tms_cs_out(struct mpsse_ctx *ctx, const uint8_t *out, unsigned out_offset, + unsigned length, bool tdi, uint8_t mode); +int mpsse_clock_tms_cs(struct mpsse_ctx *ctx, const uint8_t *out, unsigned out_offset, uint8_t *in, + unsigned in_offset, unsigned length, bool tdi, uint8_t mode); +int mpsse_set_data_bits_low_byte(struct mpsse_ctx *ctx, uint8_t data, uint8_t dir); +int mpsse_set_data_bits_high_byte(struct mpsse_ctx *ctx, uint8_t data, uint8_t dir); +int mpsse_read_data_bits_low_byte(struct mpsse_ctx *ctx, uint8_t *data); +int mpsse_read_data_bits_high_byte(struct mpsse_ctx *ctx, uint8_t *data); +int mpsse_loopback_config(struct mpsse_ctx *ctx, bool enable); +int mpsse_set_divisor(struct mpsse_ctx *ctx, uint16_t divisor); +int mpsse_divide_by_5_config(struct mpsse_ctx *ctx, bool enable); +int mpsse_rtck_config(struct mpsse_ctx *ctx, bool enable); + +/* Helper to set frequency in Hertz. Returns actual realizable frequency or negative error. + * Frequency 0 means RTCK. */ +int mpsse_set_frequency(struct mpsse_ctx *ctx, int frequency); + +/* Queue handling */ +int mpsse_flush(struct mpsse_ctx *ctx); +void mpsse_purge(struct mpsse_ctx *ctx); + +#endif /* MPSSE_H_ */ -- 2.30.2