From cdd8928a560252e3c7f11911864fe01a1864a0bf Mon Sep 17 00:00:00 2001 From: Andreas Fritiofson Date: Sat, 10 May 2014 11:31:49 +0200 Subject: [PATCH] Add FTDI SWD driver This is usable on most or all FTDI adapters using a small hardware tweak. TCK goes to SWCLK as expected. TDO should be wired to SWDIO. For TDI there are two options: Either add a 74HC126 or similar tri-state buffer between TDI and SWDIO, with OE controlled by a signal named SWDIO_OE. Or simply connect TDI and SWDIO together via a suitable resistor (220-470 ohms or so depending on the drive capability of the target and adapter). nSRST (and of course Vcc, GND) may be connected too but all other signals should be NC. Change-Id: Id36cf4577439be96bd4e5955c3026236e1cabced Signed-off-by: Andreas Fritiofson Reviewed-on: http://openocd.zylin.com/1958 Tested-by: jenkins Reviewed-by: Paul Fertser --- src/jtag/drivers/ftdi.c | 259 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 255 insertions(+), 4 deletions(-) diff --git a/src/jtag/drivers/ftdi.c b/src/jtag/drivers/ftdi.c index f427a6bd..be02d209 100644 --- a/src/jtag/drivers/ftdi.c +++ b/src/jtag/drivers/ftdi.c @@ -72,6 +72,7 @@ /* project specific includes */ #include +#include #include #include @@ -85,11 +86,14 @@ #include "mpsse.h" #define JTAG_MODE (LSB_FIRST | POS_EDGE_IN | NEG_EDGE_OUT) +#define SWD_MODE (LSB_FIRST | POS_EDGE_IN | NEG_EDGE_OUT) static char *ftdi_device_desc; static char *ftdi_serial; static uint8_t ftdi_channel; +static bool swd_mode; + #define MAX_USB_IDS 8 /* vid = pid = 0 marks the end of the list */ static uint16_t ftdi_vid[MAX_USB_IDS + 1] = { 0 }; @@ -108,8 +112,25 @@ struct signal { static struct signal *signals; +/* FIXME: Where to store per-instance data? We need an SWD context. */ +static struct swd_cmd_queue_entry { + uint8_t cmd; + uint32_t *dst; + uint8_t trn_ack_data_parity_trn[DIV_ROUND_UP(4 + 3 + 32 + 1 + 4, 8)]; +} *swd_cmd_queue; +static size_t swd_cmd_queue_length; +static size_t swd_cmd_queue_alloced; +static int queued_retval; +static int freq; + static uint16_t output; static uint16_t direction; +static uint16_t jtag_output_init; +static uint16_t jtag_direction_init; +static uint16_t swd_output_init; +static uint16_t swd_direction_init; + +static int ftdi_swd_switch_seq(struct adiv5_dap *dap, enum swd_special_seq seq); static struct signal *find_signal_by_name(const char *name) { @@ -613,11 +634,22 @@ static int ftdi_initialize(void) if (!mpsse_ctx) return ERROR_JTAG_INIT_FAILED; + output = swd_mode ? swd_output_init : jtag_output_init; + direction = swd_mode ? swd_direction_init : jtag_direction_init; + mpsse_set_data_bits_low_byte(mpsse_ctx, output & 0xff, direction & 0xff); mpsse_set_data_bits_high_byte(mpsse_ctx, output >> 8, direction >> 8); mpsse_loopback_config(mpsse_ctx, false); + /* Set a low default */ + freq = mpsse_set_frequency(mpsse_ctx, 1000); + + if (swd_mode) + ftdi_swd_switch_seq(NULL, JTAG_TO_SWD); + else + ftdi_swd_switch_seq(NULL, SWD_TO_JTAG); + return mpsse_flush(mpsse_ctx); } @@ -669,8 +701,19 @@ COMMAND_HANDLER(ftdi_handle_layout_init_command) if (CMD_ARGC != 2) return ERROR_COMMAND_SYNTAX_ERROR; - COMMAND_PARSE_NUMBER(u16, CMD_ARGV[0], output); - COMMAND_PARSE_NUMBER(u16, CMD_ARGV[1], direction); + COMMAND_PARSE_NUMBER(u16, CMD_ARGV[0], jtag_output_init); + COMMAND_PARSE_NUMBER(u16, CMD_ARGV[1], jtag_direction_init); + + return ERROR_OK; +} + +COMMAND_HANDLER(ftdi_handle_layout_init_swd_command) +{ + if (CMD_ARGC != 2) + return ERROR_COMMAND_SYNTAX_ERROR; + + COMMAND_PARSE_NUMBER(u16, CMD_ARGV[0], swd_output_init); + COMMAND_PARSE_NUMBER(u16, CMD_ARGV[1], swd_direction_init); return ERROR_OK; } @@ -807,7 +850,17 @@ static const struct command_registration ftdi_command_handlers[] = { .handler = &ftdi_handle_layout_init_command, .mode = COMMAND_CONFIG, .help = "initialize the FTDI GPIO signals used " - "to control output-enables and reset signals", + "to control output-enables and reset signals" + "when JTAG mode is selected", + .usage = "data direction", + }, + { + .name = "ftdi_layout_init_swd", + .handler = &ftdi_handle_layout_init_swd_command, + .mode = COMMAND_CONFIG, + .help = "initialize the FTDI GPIO signals used " + "to control output-enables and reset signals" + "when SWD mode is selected", .usage = "data direction", }, { @@ -835,11 +888,209 @@ static const struct command_registration ftdi_command_handlers[] = { COMMAND_REGISTRATION_DONE }; +static int ftdi_swd_init(void) +{ + LOG_INFO("FTDI SWD mode enabled"); + swd_mode = true; + + swd_cmd_queue_alloced = 10; + swd_cmd_queue = malloc(swd_cmd_queue_alloced * sizeof(*swd_cmd_queue)); + + return swd_cmd_queue != NULL ? ERROR_OK : ERROR_FAIL; +} + +static void ftdi_swd_swdio_en(bool enable) +{ + struct signal *oe = find_signal_by_name("SWDIO_OE"); + if (oe) + ftdi_set_signal(oe, enable ? '1' : '0'); +} + +/** + * Flush the MPSSE queue and process the SWD transaction queue + * @param dap + * @return + */ +static int ftdi_swd_run_queue(struct adiv5_dap *dap) +{ + LOG_DEBUG("Executing %zu queued transactions", swd_cmd_queue_length); + int retval; + struct signal *led = find_signal_by_name("LED"); + + if (queued_retval != ERROR_OK) { + LOG_DEBUG("Skipping due to previous errors: %d", queued_retval); + goto skip; + } + + /* A transaction must be followed by another transaction or at least 8 idle cycles to + * ensure that data is clocked through the AP. */ + mpsse_clock_data_out(mpsse_ctx, NULL, 0, 8, SWD_MODE); + + /* Terminate the "blink", if the current layout has that feature */ + if (led) + ftdi_set_signal(led, '0'); + + queued_retval = mpsse_flush(mpsse_ctx); + if (queued_retval != ERROR_OK) { + LOG_ERROR("MPSSE failed"); + goto skip; + } + + for (size_t i = 0; i < swd_cmd_queue_length; i++) { + int ack = buf_get_u32(&swd_cmd_queue[i].trn_ack_data_parity_trn, 1, 3); + + LOG_DEBUG("%s %s %s reg %X = %08"PRIx32, + ack == SWD_ACK_OK ? "OK" : ack == SWD_ACK_WAIT ? "WAIT" : ack == SWD_ACK_FAULT ? "FAULT" : "JUNK", + swd_cmd_queue[i].cmd & SWD_CMD_APnDP ? "AP" : "DP", + swd_cmd_queue[i].cmd & SWD_CMD_RnW ? "read" : "write", + (swd_cmd_queue[i].cmd & SWD_CMD_A32) >> 1, + buf_get_u32(swd_cmd_queue[i].trn_ack_data_parity_trn, + 1 + 3 + (swd_cmd_queue[i].cmd & SWD_CMD_RnW ? 0 : 1), 32)); + + if (ack != SWD_ACK_OK) { + queued_retval = ack; + goto skip; + + } else if (swd_cmd_queue[i].cmd & SWD_CMD_RnW) { + uint32_t data = buf_get_u32(swd_cmd_queue[i].trn_ack_data_parity_trn, 1 + 3, 32); + int parity = buf_get_u32(swd_cmd_queue[i].trn_ack_data_parity_trn, 1 + 3 + 32, 1); + + if (parity != parity_u32(data)) { + LOG_ERROR("SWD Read data parity mismatch"); + queued_retval = ERROR_FAIL; + goto skip; + } + + if (swd_cmd_queue[i].dst != NULL) + *swd_cmd_queue[i].dst = data; + } + } + +skip: + swd_cmd_queue_length = 0; + retval = queued_retval; + queued_retval = ERROR_OK; + + /* Queue a new "blink" */ + if (led && retval == ERROR_OK) + ftdi_set_signal(led, '1'); + + return retval; +} + +static void ftdi_swd_queue_cmd(struct adiv5_dap *dap, uint8_t cmd, uint32_t *dst, uint32_t data) +{ + if (swd_cmd_queue_length >= swd_cmd_queue_alloced) { + /* Not enough room in the queue. Run the queue and increase its size for next time. + * Note that it's not possible to avoid running the queue here, because mpsse contains + * pointers into the queue which may be invalid after the realloc. */ + queued_retval = ftdi_swd_run_queue(dap); + struct swd_cmd_queue_entry *q = realloc(swd_cmd_queue, swd_cmd_queue_alloced * 2 * sizeof(*swd_cmd_queue)); + if (q != NULL) { + swd_cmd_queue = q; + swd_cmd_queue_alloced *= 2; + LOG_DEBUG("Increased SWD command queue to %zu elements", swd_cmd_queue_alloced); + } + } + + if (queued_retval != ERROR_OK) + return; + + size_t i = swd_cmd_queue_length++; + swd_cmd_queue[i].cmd = cmd | SWD_CMD_START | SWD_CMD_PARK; + + mpsse_clock_data_out(mpsse_ctx, &swd_cmd_queue[i].cmd, 0, 8, SWD_MODE); + + if (swd_cmd_queue[i].cmd & SWD_CMD_RnW) { + /* Queue a read transaction */ + swd_cmd_queue[i].dst = dst; + + ftdi_swd_swdio_en(false); + mpsse_clock_data_in(mpsse_ctx, swd_cmd_queue[i].trn_ack_data_parity_trn, + 0, 1 + 3 + 32 + 1 + 1, SWD_MODE); + ftdi_swd_swdio_en(true); + } else { + /* Queue a write transaction */ + ftdi_swd_swdio_en(false); + + mpsse_clock_data_in(mpsse_ctx, swd_cmd_queue[i].trn_ack_data_parity_trn, + 0, 1 + 3 + 1, SWD_MODE); + + ftdi_swd_swdio_en(true); + + buf_set_u32(swd_cmd_queue[i].trn_ack_data_parity_trn, 1 + 3 + 1, 32, data); + buf_set_u32(swd_cmd_queue[i].trn_ack_data_parity_trn, 1 + 3 + 1 + 32, 1, parity_u32(data)); + + mpsse_clock_data_out(mpsse_ctx, swd_cmd_queue[i].trn_ack_data_parity_trn, + 1 + 3 + 1, 32 + 1, SWD_MODE); + } + + /* Insert idle cycles after AP accesses to avoid WAIT */ + if (cmd & SWD_CMD_APnDP) + mpsse_clock_data_out(mpsse_ctx, NULL, 0, dap->memaccess_tck, SWD_MODE); + +} + +static void ftdi_swd_read_reg(struct adiv5_dap *dap, uint8_t cmd, uint32_t *value) +{ + assert(cmd & SWD_CMD_RnW); + ftdi_swd_queue_cmd(dap, cmd, value, 0); +} + +static void ftdi_swd_write_reg(struct adiv5_dap *dap, uint8_t cmd, uint32_t value) +{ + assert(!(cmd & SWD_CMD_RnW)); + ftdi_swd_queue_cmd(dap, cmd, NULL, value); +} + +static int_least32_t ftdi_swd_frequency(struct adiv5_dap *dap, int_least32_t hz) +{ + if (hz > 0) + freq = mpsse_set_frequency(mpsse_ctx, hz); + + return freq; +} + +static int ftdi_swd_switch_seq(struct adiv5_dap *dap, enum swd_special_seq seq) +{ + switch (seq) { + case LINE_RESET: + LOG_DEBUG("SWD line reset"); + mpsse_clock_data_out(mpsse_ctx, swd_seq_line_reset, 0, swd_seq_line_reset_len, SWD_MODE); + break; + case JTAG_TO_SWD: + LOG_DEBUG("JTAG-to-SWD"); + mpsse_clock_data_out(mpsse_ctx, swd_seq_jtag_to_swd, 0, swd_seq_jtag_to_swd_len, SWD_MODE); + break; + case SWD_TO_JTAG: + LOG_DEBUG("SWD-to-JTAG"); + mpsse_clock_data_out(mpsse_ctx, swd_seq_swd_to_jtag, 0, swd_seq_swd_to_jtag_len, SWD_MODE); + break; + default: + LOG_ERROR("Sequence %d not supported", seq); + return ERROR_FAIL; + } + + return ERROR_OK; +} + +static const struct swd_driver ftdi_swd = { + .init = ftdi_swd_init, + .frequency = ftdi_swd_frequency, + .switch_seq = ftdi_swd_switch_seq, + .read_reg = ftdi_swd_read_reg, + .write_reg = ftdi_swd_write_reg, + .run = ftdi_swd_run_queue, +}; + +static const char * const ftdi_transports[] = { "jtag", "swd", NULL }; + struct jtag_interface ftdi_interface = { .name = "ftdi", .supported = DEBUG_CAP_TMS_SEQ, .commands = ftdi_command_handlers, - .transports = jtag_only, + .transports = ftdi_transports, + .swd = &ftdi_swd, .init = ftdi_initialize, .quit = ftdi_quit, -- 2.39.5