ft2232_spi: reintroduce generic GPIOL control

This reintroduces a reworked version of the GPIOL pin control first
introduced in commit 3207844 (CB:49637), which was reverted in commit
6518cf3 (CB:55692) due to breakage.

This change introduces a new argument `gpiolX` to allow use of the four
GPIOL pins either as generic gpios or as additional CS# signal(s). `X`
specifies the GPIOL pin (0-3) to be set to one of [HLC] with the
following meaning:

 * H - set the pin as output high
 * L - set the pin as output low
 * C - use the pin as additional CS# signal

The third value, `C`, aims to replace the parameter `csgpiol`, that is
now marked as deprecated and can be removed at some point in the future.
`gpiol` and `csgpiol` are mutually exclusive and use of both results in
an error.

Multiple pins may be set by specifying the parameter multiple times.

Documentation was updated/added accordingly.

Test: All pin levels/modes have been verified to behave correctly with a
      logic analyzer.

Change-Id: I3989f0f9596c090de52dca67183b1363dae59d3a
Signed-off-by: Alan Green <avg@google.com>
Signed-off-by: Michael Niewöhner <foss@mniewoehner.de>
Original-Reviewed-on: https://review.coreboot.org/c/flashrom/+/57810
Original-Reviewed-by: Nico Huber <nico.h@gmx.de>
Reviewed-on: https://review.coreboot.org/c/flashrom-stable/+/71431
Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
Reviewed-by: Nico Huber <nico.h@gmx.de>
Reviewed-by: Angel Pons <th3fanbus@gmail.com>
diff --git a/ft2232_spi.c b/ft2232_spi.c
index fadd3a4..1106a0e 100644
--- a/ft2232_spi.c
+++ b/ft2232_spi.c
@@ -93,9 +93,10 @@
  * to high and will be toggled during SPI transactions. All other
  * output pins will be kept low all the time. For some programmers,
  * some reserved GPIOL* pins are used as outputs. Free GPIOL* pins
- * are configured as inputs, while it's possible to use one of them
- * as additional CS# signal through the parameter `csgpiol`. On exit,
- * all pins will be reconfigured as inputs.
+ * are configured as inputs, while it's possible to use them either
+ * as generic gpios or as additional CS# signal(s) through the
+ * parameter(s) `gpiolX`. On exit, all pins will be reconfigured
+ * as inputs.
  *
  * The pin offsets are as follows:
  * TCK/SK is bit 0.
@@ -114,6 +115,7 @@
  */
 struct ft2232_data {
 	uint8_t cs_bits;
+	uint8_t aux_bits;
 	uint8_t pindir;
 	struct ftdi_context ftdi_context;
 };
@@ -231,7 +233,8 @@
 
 		msg_pspew("Assert CS#\n");
 		buf[i++] = SET_BITS_LOW;
-		buf[i++] = 0; /* assert CS# pins, all other output pins stay low */
+		/* assert CS# pins, keep aux_bits, all other output pins stay low */
+		buf[i++] = spi_data->aux_bits;
 		buf[i++] = spi_data->pindir;
 
 		/* WREN, OP(PROGRAM, ERASE), ADDR, DATA */
@@ -253,7 +256,7 @@
 		/* Add final de-assert CS# */
 		msg_pspew("De-assert CS#\n");
 		buf[i++] = SET_BITS_LOW;
-		buf[i++] = spi_data->cs_bits;
+		buf[i++] = spi_data->cs_bits | spi_data->aux_bits;
 		buf[i++] = spi_data->pindir;
 
 		/* continue if there is no read-cmd and further cmds exist */
@@ -320,6 +323,7 @@
 	char *arg, *arg2;
 	double mpsse_clk;
 	uint8_t cs_bits = 0x08;
+	uint8_t aux_bits = 0x00;
 	uint8_t pindir = 0x0b;
 
 	struct ft2232_data *const spi_data = calloc(1, sizeof(*spi_data));
@@ -472,8 +476,13 @@
 	}
 	free(arg);
 
+	bool csgpiol_set = false;
 	arg = extract_programmer_param("csgpiol");
 	if (arg) {
+		csgpiol_set = true;
+		msg_pwarn("Deprecation warning: `csgpiol` is deprecated and will be removed "
+			 "in the future.\nUse `gpiolX=C` instead.\n");
+
 		char *endptr;
 		unsigned int temp = strtoul(arg, &endptr, 10);
 		if (*endptr || endptr == arg || temp > 3) {
@@ -483,8 +492,8 @@
 			ret = -2;
 			goto init_err;
 		}
-		unsigned int pin = temp + 4;
 
+		unsigned int pin = temp + 4;
 		if (rsv_bits & 1 << pin) {
 			msg_perr("Error: Invalid GPIOL specified: \"%s\".\n"
 				 "The pin is reserved on this programmer.\n",
@@ -494,10 +503,71 @@
 		}
 
 		cs_bits |= 1 << pin;
-		pindir |= 1 << pin;
+		pindir  |= 1 << pin;
 	}
 	free(arg);
 
+	/* gpiolX */
+	int pin;
+	for (pin = 0; pin < 4; pin++) {
+		char gpiol_param[7];
+		snprintf(gpiol_param, sizeof(gpiol_param), "gpiol%d", pin);
+		arg = extract_programmer_param(gpiol_param);
+
+		if (!arg)
+			continue;
+
+		if (csgpiol_set) {
+			msg_perr("Error: `csgpiol` and `gpiolX` are mutually exclusive.\n"
+				 "Since `csgpiol` is deprecated and will be removed in the "
+				 "future, use of `gpiolX=C` is recommended.\n");
+			free(arg);
+			return -2;
+		}
+
+		uint8_t bit = 1 << (pin + 4);
+		if (rsv_bits & bit) {
+			msg_perr("Error: Invalid GPIOL specified: \"gpiol%d=%s\".\n"
+				 "Pin GPIOL%i is reserved on this programmer.\n",
+				 pin, arg, pin);
+			free(arg);
+			return -2;
+		}
+
+		if (strlen(arg) != 1)
+			goto format_error;
+
+		switch (toupper((unsigned char)arg[0])) {
+			case 'H':
+				aux_bits |= bit;
+				pindir   |= bit;
+				break;
+			case 'L':
+				pindir   |= bit;
+				break;
+			case 'C':
+				cs_bits  |= bit;
+				pindir   |= bit;
+				break;
+			default:
+				goto format_error;
+		}
+
+		free(arg);
+		continue;
+
+format_error:
+		msg_perr("Error: Invalid GPIOL specified: \"gpiol%d=%s\".\n"
+			 "Valid values are H, L and C.\n"
+			 "    H - Set GPIOL output high\n"
+			 "    L - Set GPIOL output low\n"
+			 "    C - Use GPIOL as additional CS# output\n",
+			 pin, arg);
+
+		free(arg);
+		return -2;
+	}
+
 	msg_pdbg("Using device type %s %s ",
 		 get_ft2232_vendorname(ft2232_vid, ft2232_type),
 		 get_ft2232_devicename(ft2232_vid, ft2232_type));
@@ -582,7 +652,7 @@
 
 	msg_pdbg("Set data bits\n");
 	buf[0] = SET_BITS_LOW;
-	buf[1] = cs_bits;
+	buf[1] = cs_bits | aux_bits;
 	buf[2] = pindir;
 	if (send_buf(ftdic, buf, 3)) {
 		ret = -8;
@@ -590,6 +660,7 @@
 	}
 
 	spi_data->cs_bits = cs_bits;
+	spi_data->aux_bits = aux_bits;
 	spi_data->pindir = pindir;
 	spi_master_ft2232.data = spi_data;