commit 2ddfb05c859b6c4532100a47e2a14772bb7fe576
Author: David Mosberger-Tang <davidm@egauge.net>
Date:   Thu May 4 15:36:17 2017 -0600

    Add TPI support to the ftdi_syncbb driver.
    While this gets TPI programming working, it's excruciatingly slow:
    
    ----
    $ avrdude -Cavrdude.conf -pt9 -ctc2030 -b375000 -Uflash:w:out.hex
    
    avrdude: AVR device initialized and ready to accept instructions
    
    Reading | ################################################## | 100% 0.44s
    
    avrdude: Device signature = 0x1e9008 (probably t9)
    avrdude: NOTE: "flash" memory has been specified, an erase cycle will be performed
             To disable this feature, specify the -D option.
    	 avrdude: erasing chip
    	 avrdude: reading input file "out.hex"
    	 avrdude: input file out.hex auto detected as Intel Hex
    	 avrdude: writing flash (994 bytes):
    
    Writing | ################################################## | 100% 85.40s
    
    avrdude: 994 bytes of flash written
    avrdude: verifying flash memory against out.hex:
    avrdude: load data flash data from input file out.hex:
    avrdude: input file out.hex auto detected as Intel Hex
    avrdude: input file out.hex contains 994 bytes
    avrdude: reading on-chip flash data:
    
    Reading | ################################################## | 100% 42.92s
    
    avrdude: verifying ...
    avrdude: 994 bytes of flash verified
    
    avrdude done.  Thank you.
    ---

diff --git a/avrdude.conf.in b/avrdude.conf.in
index b247e52..58ff5db 100644
--- a/avrdude.conf.in
+++ b/avrdude.conf.in
@@ -771,6 +771,18 @@ programmer
   reset = 7;  # RI  X3(4)
 ;
 
+programmer
+  id    = "tc2030";
+  desc  = "Tag-Connect TC2030";
+  type  = "ftdi_syncbb";
+  connection_type = usb;
+  #                      FOR TPI devices:
+  mosi  = 0;  # TxD = D0 (wire to TPIDATA via 1k resistor)
+  miso  = 1;  # RxD = D1 (wire to TPIDATA directly)
+  sck   = 2;  # RTS = D2 (wire to SCK)
+  reset = 3;  # CTS = D3 (wire to ~RESET)
+;
+
 # website mentioned above uses this id
 programmer parent "arduino-ft232r"
   id    = "diecimila";
diff --git a/ft245r.c b/ft245r.c
index cc1061d..625ea03 100644
--- a/ft245r.c
+++ b/ft245r.c
@@ -67,6 +67,11 @@
 #include "ft245r.h"
 #include "usbdevs.h"
 
+#include "tpi.h"
+
+#define TPIPCR_GT_0b	0x07
+#define TPI_STOP_BITS	0x03
+
 #if defined(_WIN32)
 #include <windows.h>
 #endif
@@ -235,6 +240,9 @@ static int ft245r_chip_erase(PROGRAMMER * pgm, AVRPART * p) {
     unsigned char cmd[4] = {0,0,0,0};
     unsigned char res[4];
 
+    if (p->flags & AVRPART_HAS_TPI)
+      return avr_tpi_chip_erase(pgm, p);
+
     if (p->op[AVR_OP_CHIP_ERASE] == NULL) {
         avrdude_message(MSG_INFO, "chip erase instruction not defined for part \"%s\"\n",
                 p->desc);
@@ -274,6 +282,16 @@ static int ft245r_set_bitclock(PROGRAMMER * pgm) {
     return 0;
 }
 
+static int get_pin(PROGRAMMER *pgm, int pinname) {
+  uint8_t byte;
+
+  if (ftdi_read_pins(handle, &byte) != 0)
+    return -1;
+  if (FT245R_DEBUG)
+    avrdude_message(MSG_INFO, "%s: in 0x%02x\n", __func__, byte);
+  return GET_BITS_0(byte, pgm, pinname) != 0;
+}
+
 static int set_pin(PROGRAMMER * pgm, int pinname, int val) {
     unsigned char buf[1];
 
@@ -369,6 +387,8 @@ static void ft245r_enable(PROGRAMMER * pgm) {
 
 static int ft245r_cmd(PROGRAMMER * pgm, const unsigned char *cmd,
                       unsigned char *res);
+static int ft245r_tpi_tx(PROGRAMMER * pgm, uint8_t byte);
+static int ft245r_tpi_rx(PROGRAMMER * pgm, uint8_t *bytep);
 /*
  * issue the 'program enable' command to the AVR device
  */
@@ -377,6 +397,9 @@ static int ft245r_program_enable(PROGRAMMER * pgm, AVRPART * p) {
     unsigned char res[4];
     int i;
 
+    if (p->flags & AVRPART_HAS_TPI)
+      return avr_tpi_program_enable(pgm, p, TPIPCR_GT_0b);
+
     if (p->op[AVR_OP_PGM_ENABLE] == NULL) {
         avrdude_message(MSG_INFO, "%s: AVR_OP_PGM_ENABLE command not defined for %s\n",
                         progname, p->desc);
@@ -437,25 +460,73 @@ static int ft245r_initialize(PROGRAMMER * pgm, AVRPART * p) {
      */
     usleep(20000); // 20ms
 
+    if (p->flags & AVRPART_HAS_TPI) {
+	bool io_link_ok = true;
+	uint8_t byte;
+	int i;
+
+	/* Since there is a single TPIDATA line, MOSI and MISO must be
+	   linked together through a 1kOhm resistor.  Verify that
+	   everything we send on MOSI gets mirrored back on MISO.  */
+	set_pin(pgm, PIN_AVR_MOSI, 0);
+	if (get_pin(pgm, PIN_AVR_MISO) != 0) {
+	    io_link_ok = false;
+	    avrdude_message(MSG_INFO, "MOSI->MISO 0 failed\n");
+	    if (!ovsigck)
+		return -1;
+	}
+	set_pin(pgm, PIN_AVR_MOSI, 1);
+	if (get_pin(pgm, PIN_AVR_MISO) != 1) {
+	    io_link_ok = false;
+	    avrdude_message(MSG_INFO, "MOSI->MISO 1 failed\n");
+	    if (!ovsigck)
+		return -1;
+	}
+
+	if (io_link_ok)
+	    avrdude_message(MSG_NOTICE2, "MOSI-MISO link present\n");
+
+	/* keep TPIDATA high for 16 clock cycles */
+	set_pin(pgm, PIN_AVR_MOSI, 1);
+	for (i = 0; i < 16; i++) {
+	    set_sck(pgm, 1);
+	    set_sck(pgm, 0);
+	}
+
+	/* remove extra guard timing bits */
+	ft245r_tpi_tx(pgm, TPI_CMD_SSTCS | TPI_REG_TPIPCR);
+	ft245r_tpi_tx(pgm, 0x7);
+
+	/* read TPI ident reg */
+	ft245r_tpi_tx(pgm, TPI_CMD_SLDCS | TPI_REG_TPIIR);
+	ft245r_tpi_rx(pgm, &byte);
+	if (byte != 0x80) {
+	    avrdude_message(MSG_INFO, "TPIIR 0x%02x not correct\n", byte);
+	    return -1;
+	}
+    }
     return ft245r_program_enable(pgm, p);
 }
 
+static inline void add_bit(PROGRAMMER * pgm, unsigned char *buf, int *buf_pos,
+			   uint8_t bit) {
+    ft245r_out = SET_BITS_0(ft245r_out,pgm,PIN_AVR_MOSI, bit);
+    ft245r_out = SET_BITS_0(ft245r_out,pgm,PIN_AVR_SCK,0);
+    buf[*buf_pos] = ft245r_out;
+    (*buf_pos)++;
+
+    ft245r_out = SET_BITS_0(ft245r_out,pgm,PIN_AVR_SCK,1);
+    buf[*buf_pos] = ft245r_out;
+    (*buf_pos)++;
+}
+
 static inline int set_data(PROGRAMMER * pgm, unsigned char *buf, unsigned char data) {
     int j;
     int buf_pos = 0;
     unsigned char bit = 0x80;
 
     for (j=0; j<8; j++) {
-        ft245r_out = SET_BITS_0(ft245r_out,pgm,PIN_AVR_MOSI,data & bit);
-
-        ft245r_out = SET_BITS_0(ft245r_out,pgm,PIN_AVR_SCK,0);
-        buf[buf_pos] = ft245r_out;
-        buf_pos++;
-
-        ft245r_out = SET_BITS_0(ft245r_out,pgm,PIN_AVR_SCK,1);
-        buf[buf_pos] = ft245r_out;
-        buf_pos++;
-
+	add_bit(pgm, buf, &buf_pos, (data & bit) != 0);
         bit >>= 1;
     }
     return buf_pos;
@@ -523,6 +594,123 @@ static int ft245r_cmd(PROGRAMMER * pgm, const unsigned char *cmd,
     return 0;
 }
 
+static inline uint8_t extract_tpi_data(PROGRAMMER * pgm, unsigned char *buf,
+				       int *buf_pos) {
+    uint8_t bit = 0x1, byte = 0;
+    int j;
+
+    for (j = 0; j < 8; j++) {
+	(*buf_pos)++;	// skip over falling clock edge
+        if (GET_BITS_0(buf[(*buf_pos)++], pgm, PIN_AVR_MISO))
+            byte |= bit;
+        bit <<= 1;
+    }
+    return byte;
+}
+
+static inline int set_tpi_data(PROGRAMMER * pgm, unsigned char *buf,
+			       uint8_t byte) {
+    uint8_t bit = 0x1, parity = 0;
+    int j, buf_pos = 0;
+
+    // start bit:
+    add_bit(pgm, buf, &buf_pos, 0);
+
+    // 8 data bits:
+    for (j = 0; j < 8; j++) {
+	add_bit(pgm, buf, &buf_pos, (byte & bit) != 0);
+	parity ^= (byte & bit) != 0;
+        bit <<= 1;
+    }
+
+    // parity bit:
+    add_bit(pgm, buf, &buf_pos, parity);
+    // stop bits:
+    add_bit(pgm, buf, &buf_pos, 1);
+    add_bit(pgm, buf, &buf_pos, 1);
+    return buf_pos;
+}
+
+static int ft245r_tpi_tx(PROGRAMMER * pgm, uint8_t byte) {
+    uint8_t buf[128];
+    int len;
+
+    len = set_tpi_data(pgm, buf, byte);
+    ft245r_send(pgm, buf, len);
+    ft245r_recv(pgm, buf, len);
+    return 0;
+}
+
+static int ft245r_tpi_rx(PROGRAMMER * pgm, uint8_t *bytep) {
+    uint8_t buf[128], bit, parity;
+    int i, buf_pos = 0, len = 0;
+    uint32_t res, m, byte;
+
+    /* Allow for up to 4 bits before we must see start bit; during
+       that time, we must keep the MOSI line high. */
+    for (i = 0; i < 2; ++i)
+	len += set_data(pgm, &buf[len], 0xff);
+
+    ft245r_send(pgm, buf, len);
+    ft245r_recv(pgm, buf, len);
+
+    res = (extract_tpi_data(pgm, buf, &buf_pos)
+	   | ((uint32_t) extract_tpi_data(pgm, buf, &buf_pos) << 8));
+
+    /* Look for start bit: */
+    m = 0x1;
+    while (m & res)
+	m <<= 1;
+    if (m >= 0x10) {
+	avrdude_message(MSG_INFO, "%s: start bit missing (res=0x%04x)\n",
+			__func__, res);
+	return -1;
+    }
+    byte = parity = 0;
+    for (i = 0; i < 8; ++i) {
+	m <<= 1;
+	bit = (res & m) != 0;
+	parity ^= bit;
+	byte |= bit << i;
+    }
+    m <<= 1;
+    if (((res & m) != 0) != parity) {
+	avrdude_message(MSG_INFO, "%s: parity bit wrong\n", __func__);
+	return -1;
+    }
+    if (((res & (m << 1)) == 0) || ((res & (m << 2))) == 0) {
+	avrdude_message(MSG_INFO, "%s: stop bits wrong\n", __func__);
+	return -1;
+    }
+    *bytep = (uint8_t) byte;
+    return 0;
+}
+
+static int ft245r_cmd_tpi(PROGRAMMER * pgm, const unsigned char *cmd,
+			  int cmd_len, unsigned char *res, int res_len) {
+    int i, ret = 0;
+
+    pgm->pgm_led(pgm, ON);
+
+    for (i = 0; i < cmd_len; ++i)
+	ft245r_tpi_tx(pgm, cmd[i]);
+    for (i = 0; i < res_len; ++i)
+	if ((ret = ft245r_tpi_rx(pgm, &res[i])) < 0)
+	    break;
+    if (verbose >= 2) {
+	avrdude_message(MSG_NOTICE2, "%s: [ ", __func__);
+	for (i = 0; i < cmd_len; i++)
+	    avrdude_message(MSG_NOTICE2, "%02X ", cmd[i]);
+	avrdude_message(MSG_NOTICE2, "] [ ");
+	for(i = 0; i < res_len; i++)
+	    avrdude_message(MSG_NOTICE2, "%02X ", res[i]);
+	avrdude_message(MSG_NOTICE2, "]\n");
+    }
+
+    pgm->pgm_led(pgm, OFF);
+    return ret;
+}
+
 /* lower 8 pins are accepted, they might be also inverted */
 static const struct pindef_t valid_pins = {{0xff},{0xff}} ;
 
@@ -937,6 +1125,7 @@ void ft245r_initpgm(PROGRAMMER * pgm) {
     pgm->program_enable = ft245r_program_enable;
     pgm->chip_erase     = ft245r_chip_erase;
     pgm->cmd            = ft245r_cmd;
+    pgm->cmd_tpi        = ft245r_cmd_tpi;
     pgm->open           = ft245r_open;
     pgm->close          = ft245r_close;
     pgm->read_byte      = avr_read_byte_default;
