dirtyjtag_spi: Add DJTAG2 support

The new DJTAG2 protocol supports special command modifiers to speed
things up:

* NO_READ allows a write without returning the sampled MISO data.
* EXTEND_LENGTH allows to send 32B extra per transfer.

As SPI flash commands are basically half-duplex, we utilize NO_READ
to send all the write data in a first loop and then receive data in
a second loop. Only flash writes can benefit from this and due to
their programming delay the difference is marginal ~2%.

Long reads, OTOH, benefit from the increased transfer size and we
are about 45% faster.

Tested with DJTAG2 on a Blue Pill (STM32F103).

Change-Id: Ie38b309f5865c7704c6b2a85e7437fe2621623d3
Signed-off-by: Nico Huber <nico.h@gmx.de>
Reviewed-on: https://review.coreboot.org/c/flashrom-stable/+/73265
Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
Reviewed-by: Jean THOMAS <virgule@jeanthomas.me>
diff --git a/dirtyjtag_spi.c b/dirtyjtag_spi.c
index a7484f9..adedc0d 100644
--- a/dirtyjtag_spi.c
+++ b/dirtyjtag_spi.c
@@ -62,6 +62,14 @@
 	CMD_CLK = 0x06
 };
 
+enum dirtyjtag_command_modifier {
+	/* CMD_XFER */
+	NO_READ		= 0x80,
+	EXTEND_LENGTH	= 0x40,
+	/* CMD_CLK */
+	READOUT		= 0x80,
+};
+
 enum dirtyjtag_signal_identifier {
 	SIG_TCK = 1 << 1,
 	SIG_TDI = 1 << 2,
@@ -150,6 +158,18 @@
 	return 0;
 }
 
+static int dirtyjtag_reset_tms(struct dirtyjtag_spi_data *context)
+{
+	uint8_t tms_reset_buffer[] = {
+		CMD_SETSIG,
+		SIG_TMS,
+		SIG_TMS,
+
+		CMD_STOP,
+	};
+	return dirtyjtag_send(context, tms_reset_buffer, sizeof(tms_reset_buffer));
+}
+
 static int dirtyjtag_djtag1_spi_send_command(const struct flashctx *flash,
 					     unsigned int writecnt, unsigned int readcnt,
 					     const unsigned char *writearr, unsigned char *readarr)
@@ -191,14 +211,7 @@
 
 	free(rxtx_buffer);
 
-	uint8_t tms_reset_buffer[] = {
-		CMD_SETSIG,
-		SIG_TMS,
-		SIG_TMS,
-
-		CMD_STOP,
-	};
-	dirtyjtag_send(context, tms_reset_buffer, sizeof(tms_reset_buffer));
+	dirtyjtag_reset_tms(context);
 
 	return 0;
 
@@ -207,6 +220,54 @@
 	return -1;
 }
 
+static int dirtyjtag_djtag2_spi_send_command(const struct flashctx *flash,
+					     unsigned int writecnt, unsigned int readcnt,
+					     const unsigned char *writearr, unsigned char *readarr)
+{
+	struct dirtyjtag_spi_data *const context = flash->mst->spi.data;
+	const size_t max_xfer_size = 62; /* max transfer size in DJTAG2 */
+	uint8_t transfer_buffer[2 + max_xfer_size]; /* 1B command + 1B len + payload */
+	size_t i;
+
+	i = 0;
+	while (i < writecnt) {
+		const size_t txn_size = MIN(max_xfer_size, writecnt - i);
+
+		transfer_buffer[0] = CMD_XFER | NO_READ;
+		if (txn_size * 8 >= 256)
+			transfer_buffer[0] |= EXTEND_LENGTH;
+		transfer_buffer[1] = (txn_size * 8) % 256;
+		memcpy(transfer_buffer + 2, writearr + i, txn_size);
+
+		if (dirtyjtag_send(context, transfer_buffer, 2 + txn_size))
+			return -1;
+
+		i += txn_size;
+	}
+
+	i = 0;
+	while (i < readcnt) {
+		const size_t rxn_size = MIN(max_xfer_size, readcnt - i);
+
+		transfer_buffer[0] = CMD_XFER;
+		if (rxn_size * 8 >= 256)
+			transfer_buffer[0] |= EXTEND_LENGTH;
+		transfer_buffer[1] = (rxn_size * 8) % 256;
+
+		if (dirtyjtag_send(context, transfer_buffer, 2 + rxn_size))
+			return -1;
+
+		if (dirtyjtag_receive(context, readarr + i, rxn_size, rxn_size) < 0)
+			return -1;
+
+		i += rxn_size;
+	}
+
+	dirtyjtag_reset_tms(context);
+
+	return 0;
+}
+
 static const struct spi_master spi_master_dirtyjtag_spi = {
 	.features	= SPI_MASTER_4BA,
 	.max_data_read	= MAX_DATA_READ_UNLIMITED,
@@ -290,7 +351,7 @@
 		}
 
 		if (freq > UINT16_MAX) {
-			msg_perr("%s: Frequency set above DJTAG1 limits (%d kHz)", __func__, UINT16_MAX);
+			msg_perr("%s: Frequency set above DJTAG1/2 limits (%d kHz)\n", __func__, UINT16_MAX);
 			free(tmp);
 			goto cleanup_libusb_handle;
 		}
@@ -309,9 +370,12 @@
 	const unsigned int djtag_version = dirtyjtag_version(info);
 	switch (djtag_version) {
 	default:
-		msg_pwarn("Warning: Unknown DJTAG version %u. Assuming DJTAG1 compatibility.\n",
+		msg_pwarn("Warning: Unknown DJTAG version %u. Assuming DJTAG2 compatibility.\n",
 			  djtag_version);
 		/* fall-through */
+	case 2:
+		dirtyjtag_spi.command = dirtyjtag_djtag2_spi_send_command;
+		break;
 	case 1:
 		dirtyjtag_spi.command = dirtyjtag_djtag1_spi_send_command;
 		break;