+static int create_default_signal(const char *name, uint16_t data_mask)
+{
+ struct signal *sig = create_signal(name);
+ if (!sig) {
+ LOG_ERROR("failed to create signal %s", name);
+ return ERROR_FAIL;
+ }
+ sig->invert_data = false;
+ sig->data_mask = data_mask;
+ sig->invert_oe = false;
+ sig->oe_mask = 0;
+
+ return ERROR_OK;
+}
+
+static int create_signals(void)
+{
+ if (create_default_signal("TCK", 0x01) != ERROR_OK)
+ return ERROR_FAIL;
+ if (create_default_signal("TDI", 0x02) != ERROR_OK)
+ return ERROR_FAIL;
+ if (create_default_signal("TDO", 0x04) != ERROR_OK)
+ return ERROR_FAIL;
+ if (create_default_signal("TMS", 0x08) != ERROR_OK)
+ return ERROR_FAIL;
+ return ERROR_OK;
+}
+
+static int ftdi_swd_init(void)
+{
+ LOG_INFO("FTDI SWD mode enabled");
+ swd_mode = true;
+
+ if (create_signals() != ERROR_OK)
+ return ERROR_FAIL;
+
+ 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) {
+ if (oe->data_mask)
+ ftdi_set_signal(oe, enable ? '1' : '0');
+ else {
+ /* Sets TDI/DO pin (pin 2) to input during rx when both pins are connected
+ to SWDIO */
+ if (enable)
+ direction |= jtag_direction_init & 0x0002U;
+ else
+ direction &= ~0x0002U;
+ mpsse_set_data_bits_low_byte(mpsse_ctx, output & 0xff, direction & 0xff);
+ }
+ }
+}
+
+/**
+ * Flush the MPSSE queue and process the SWD transaction queue
+ * @param dap
+ * @return
+ */
+static int ftdi_swd_run_queue(void)
+{
+ LOG_DEBUG_IO("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_IO("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_IO("%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 == SWD_ACK_WAIT ? ERROR_WAIT : ERROR_FAIL;
+ 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(uint8_t cmd, uint32_t *dst, uint32_t data, uint32_t ap_delay_clk)
+{
+ 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();
+ 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, ap_delay_clk, SWD_MODE);
+
+}
+
+static void ftdi_swd_read_reg(uint8_t cmd, uint32_t *value, uint32_t ap_delay_clk)
+{
+ assert(cmd & SWD_CMD_RnW);
+ ftdi_swd_queue_cmd(cmd, value, 0, ap_delay_clk);
+}
+
+static void ftdi_swd_write_reg(uint8_t cmd, uint32_t value, uint32_t ap_delay_clk)
+{
+ assert(!(cmd & SWD_CMD_RnW));
+ ftdi_swd_queue_cmd(cmd, NULL, value, ap_delay_clk);
+}
+
+static int_least32_t ftdi_swd_frequency(int_least32_t hz)
+{
+ if (hz > 0)
+ freq = mpsse_set_frequency(mpsse_ctx, hz);
+
+ return freq;
+}
+
+static int ftdi_swd_switch_seq(enum swd_special_seq seq)
+{
+ switch (seq) {
+ case LINE_RESET:
+ LOG_DEBUG("SWD line reset");
+ ftdi_swd_swdio_en(true);
+ 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");
+ ftdi_swd_swdio_en(true);
+ 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");
+ ftdi_swd_swdio_en(true);
+ 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 };
+