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;
+}