feat(io): add generic gpio spi bit-bang driver

When using a tpm breakout board with rpi3, we elected to bit-bang
gpio pins to emulate a spi interface, this implementation required a
driver to interface with the platform specific pins and emulate spi
functionality. The generic driver provides the ability to pass in a
gpio_spi_data structure that contains the necessary gpio pins in
order to simulate spi operations (get_access, start, stop, xfer).

Change-Id: I88919e8a294c05e0cabb8224e35ae5c1ba5f2413
Signed-off-by: Tushar Khandelwal <tushar.khandelwal@arm.com>
Signed-off-by: Abhi Singh <abhi.singh@arm.com>
diff --git a/drivers/gpio/gpio_spi.c b/drivers/gpio/gpio_spi.c
new file mode 100644
index 0000000..2913b41
--- /dev/null
+++ b/drivers/gpio/gpio_spi.c
@@ -0,0 +1,130 @@
+/*
+ * Copyright (c) 2025, ARM Limited and Contributors. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#include <stdint.h>
+#include <stdlib.h>
+
+#include <common/debug.h>
+#include <drivers/delay_timer.h>
+#include <drivers/gpio.h>
+#include <drivers/gpio_spi.h>
+#include <platform_def.h>
+
+static struct spi_plat gpio_spidev;
+
+static void gpio_spi_delay_us(void)
+{
+	udelay(gpio_spidev.gpio_data.spi_delay_us);
+}
+
+static int gpio_spi_miso(void)
+{
+	return gpio_get_value(gpio_spidev.gpio_data.miso_gpio);
+}
+
+static void gpio_spi_sclk(int bit)
+{
+	gpio_set_value(gpio_spidev.gpio_data.sclk_gpio, bit);
+}
+
+static void gpio_spi_mosi(int bit)
+{
+	gpio_set_value(gpio_spidev.gpio_data.mosi_gpio, bit);
+}
+
+static void gpio_spi_cs(int bit)
+{
+	gpio_set_value(gpio_spidev.gpio_data.cs_gpio, bit);
+}
+
+static void gpio_spi_start(void)
+{
+	gpio_spi_cs(1);
+	gpio_spi_sclk(0);
+	gpio_spi_cs(0);
+}
+
+static void gpio_spi_stop(void)
+{
+	gpio_spi_cs(1);
+}
+
+/* set sclk to a known state (0) before performing any further action */
+static void gpio_spi_get_access(void)
+{
+	gpio_spi_sclk(0);
+}
+
+static void xfer(unsigned int bytes, const void *out, void *in, int cpol, int cpha)
+{
+	for (unsigned int j = 0U; j < bytes; j++) {
+		unsigned char in_byte  = 0U;
+		unsigned char out_byte = (out != NULL) ? *(const uint8_t *)out++ : 0xFF;
+
+		for (int i = 7; i >= 0; i--) {
+			if (cpha) {
+				gpio_spi_sclk(!cpol);
+			}
+
+			gpio_spi_mosi(!!(out_byte & (1 << i)));
+
+			gpio_spi_delay_us();
+			gpio_spi_sclk(cpha ? cpol : !cpol);
+			gpio_spi_delay_us();
+
+			in_byte |= gpio_spi_miso() << i;
+
+			if (!cpha) {
+				gpio_spi_sclk(cpol);
+			}
+		}
+
+		if (in != NULL) {
+			*(uint8_t *)in++ = in_byte;
+		}
+	}
+}
+
+static int gpio_spi_xfer(unsigned int bytes, const void *out, void *in)
+{
+	if ((out == NULL) && (in == NULL)) {
+		return -1;
+	}
+
+	switch (gpio_spidev.gpio_data.spi_mode) {
+	case 0:
+		xfer(bytes, out, in, 0, 0);
+		break;
+	case 1:
+		xfer(bytes, out, in, 0, 1);
+		break;
+	case 2:
+		xfer(bytes, out, in, 1, 0);
+		break;
+	case 3:
+		xfer(bytes, out, in, 1, 1);
+		break;
+	default:
+		return -1;
+	}
+
+	return 0;
+}
+
+struct spi_ops gpio_spidev_ops = {
+	.get_access	= gpio_spi_get_access,
+	.start		= gpio_spi_start,
+	.stop		= gpio_spi_stop,
+	.xfer		= gpio_spi_xfer,
+};
+
+struct spi_plat *gpio_spi_init(struct gpio_spi_data *gpio_spi_data)
+{
+	gpio_spidev.gpio_data = *gpio_spi_data;
+	gpio_spidev.ops = &gpio_spidev_ops;
+
+	return &gpio_spidev;
+}
diff --git a/include/drivers/gpio_spi.h b/include/drivers/gpio_spi.h
new file mode 100644
index 0000000..a926553
--- /dev/null
+++ b/include/drivers/gpio_spi.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2025, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#ifndef GPIO_SPI_H
+#define GPIO_SPI_H
+
+#include <stdint.h>
+
+struct gpio_spi_data {
+	uint8_t cs_gpio, sclk_gpio, mosi_gpio, miso_gpio, reset_gpio;
+	uint32_t spi_delay_us;
+	unsigned int spi_mode;
+};
+
+struct spi_ops {
+	void (*get_access)(void);
+	void (*start)(void);
+	void (*stop)(void);
+	int (*xfer)(unsigned int bitlen, const void *dout, void *din);
+};
+
+struct spi_plat {
+	struct gpio_spi_data gpio_data;
+	const struct spi_ops *ops;
+};
+
+struct spi_plat *gpio_spi_init(struct gpio_spi_data *gpio_spi_data);
+
+#endif /* GPIO_SPI_H */