fpga: virtex2: Add slave serial programming support

This adds support for slave serial programming, in addition to the
previously supported slave SelectMAP mode. There are two ways that this
can be used:

-Using the clk and wdata callbacks in order to write image data one bit
at a time using pure bit-banging. This works, but is rather painfully
slow with typical image sizes.

-By specifying the wbulkdata callback instead, the image loading process
can be offloaded to SPI hardware. In this mode the clk and wdata
callbacks do not need to be specified. This allows the image to be
loaded much faster, taking only a few seconds with even relatively large
images.

Slave serial programming has been tested on the Kintex-7 series of
FPGAs.

Signed-off-by: Robert Hancock <hancock@sedsystems.ca>
Signed-off-by: Michal Simek <michal.simek@xilinx.com>
diff --git a/drivers/fpga/virtex2.c b/drivers/fpga/virtex2.c
index 5028244..3957368 100644
--- a/drivers/fpga/virtex2.c
+++ b/drivers/fpga/virtex2.c
@@ -3,6 +3,8 @@
  * (C) Copyright 2002
  * Rich Ireland, Enterasys Networks, rireland@enterasys.com.
  * Keith Outwater, keith_outwater@mvis.com
+ *
+ * Copyright (c) 2019 SED Systems, a division of Calian Ltd.
  */
 
 /*
@@ -141,8 +143,8 @@
 }
 
 /*
- * Virtex-II Slave SelectMap configuration loader. Configuration via
- * SelectMap is as follows:
+ * Virtex-II Slave SelectMap or Serial configuration loader. Configuration
+ * is as follows:
  * 1. Set the FPGA's PROG_B line low.
  * 2. Set the FPGA's PROG_B line high.  Wait for INIT_B to go high.
  * 3. Write data to the SelectMap port.  If INIT_B goes low at any time
@@ -154,7 +156,7 @@
  *    INIT_B and DONE lines.  If both are high, configuration has
  *    succeeded. Congratulations!
  */
-static int virtex2_slave_pre(xilinx_virtex2_slave_selectmap_fns *fn, int cookie)
+static int virtex2_slave_pre(xilinx_virtex2_slave_fns *fn, int cookie)
 {
 	unsigned long ts;
 
@@ -243,7 +245,7 @@
 	return FPGA_SUCCESS;
 }
 
-static int virtex2_slave_post(xilinx_virtex2_slave_selectmap_fns *fn,
+static int virtex2_slave_post(xilinx_virtex2_slave_fns *fn,
 			      int cookie)
 {
 	int ret_val = FPGA_SUCCESS;
@@ -284,11 +286,16 @@
 			ret_val = FPGA_FAIL;
 			break;
 		}
-		(*fn->wdata) (0xff, true, cookie);
-		CONFIG_FPGA_DELAY();
-		(*fn->clk) (false, true, cookie);
-		CONFIG_FPGA_DELAY();
-		(*fn->clk) (true, true, cookie);
+		if (fn->wbulkdata) {
+			unsigned char dummy = 0xff;
+			(*fn->wbulkdata)(&dummy, 1, true, cookie);
+		} else {
+			(*fn->wdata)(0xff, true, cookie);
+			CONFIG_FPGA_DELAY();
+			(*fn->clk)(false, true, cookie);
+			CONFIG_FPGA_DELAY();
+			(*fn->clk)(true, true, cookie);
+		}
 	}
 
 	if (ret_val == FPGA_SUCCESS) {
@@ -312,7 +319,7 @@
 static int virtex2_ssm_load(xilinx_desc *desc, const void *buf, size_t bsize)
 {
 	int ret_val = FPGA_FAIL;
-	xilinx_virtex2_slave_selectmap_fns *fn = desc->iface_fns;
+	xilinx_virtex2_slave_fns *fn = desc->iface_fns;
 	size_t bytecount = 0;
 	unsigned char *data = (unsigned char *)buf;
 	int cookie = desc->cookie;
@@ -387,7 +394,7 @@
 static int virtex2_ssm_dump(xilinx_desc *desc, const void *buf, size_t bsize)
 {
 	int ret_val = FPGA_FAIL;
-	xilinx_virtex2_slave_selectmap_fns *fn = desc->iface_fns;
+	xilinx_virtex2_slave_fns *fn = desc->iface_fns;
 
 	if (fn) {
 		unsigned char *data = (unsigned char *)buf;
@@ -438,8 +445,71 @@
 
 static int virtex2_ss_load(xilinx_desc *desc, const void *buf, size_t bsize)
 {
-	printf("%s: Slave Serial Loading is unsupported\n", __func__);
-	return FPGA_FAIL;
+	int ret_val = FPGA_FAIL;
+	xilinx_virtex2_slave_fns *fn = desc->iface_fns;
+	unsigned char *data = (unsigned char *)buf;
+	int cookie = desc->cookie;
+
+	ret_val = virtex2_slave_pre(fn, cookie);
+	if (ret_val != FPGA_SUCCESS)
+		return ret_val;
+
+	if (fn->wbulkdata) {
+		/* Load the data in a single chunk */
+		(*fn->wbulkdata)(data, bsize, true, cookie);
+	} else {
+		size_t bytecount = 0;
+
+		/*
+		 * Load the data bit by bit
+		 */
+		while (bytecount < bsize) {
+			unsigned char curr_data = data[bytecount++];
+			int bit;
+
+#ifdef CONFIG_SYS_FPGA_CHECK_CTRLC
+			if (ctrlc()) {
+				(*fn->abort) (cookie);
+				return FPGA_FAIL;
+			}
+#endif
+
+			if ((*fn->done)(cookie) == FPGA_SUCCESS) {
+				PRINTF("%s:%d:done went active early, bytecount = %d\n",
+				       __func__, __LINE__, bytecount);
+				break;
+			}
+
+#ifdef CONFIG_SYS_FPGA_CHECK_ERROR
+			if ((*fn->init)(cookie)) {
+				printf("\n%s:%d:  ** Error: INIT asserted during configuration\n",
+				       __func__, __LINE__);
+				printf("%zu = buffer offset, %zu = buffer size\n",
+				       bytecount, bsize);
+				(*fn->abort)(cookie);
+				return FPGA_FAIL;
+			}
+#endif
+
+			for (bit = 7; bit >= 0; --bit) {
+				unsigned char curr_bit = (curr_data >> bit) & 1;
+				(*fn->wdata)(curr_bit, true, cookie);
+				CONFIG_FPGA_DELAY();
+				(*fn->clk)(false, true, cookie);
+				CONFIG_FPGA_DELAY();
+				(*fn->clk)(true, true, cookie);
+			}
+
+			/* Slave serial never uses a busy pin */
+
+#ifdef CONFIG_SYS_FPGA_PROG_FEEDBACK
+			if (bytecount % (bsize / 40) == 0)
+				putc('.');
+#endif
+		}
+	}
+
+	return virtex2_slave_post(fn, cookie);
 }
 
 static int virtex2_ss_dump(xilinx_desc *desc, const void *buf, size_t bsize)