diff mbox series

[RFC,2/3] hw: spi_gpio: add spi gpio model

Message ID 20220728232322.2921703-3-irischenlj@fb.com
State New
Headers show
Series Add Generic SPI GPIO model | expand

Commit Message

Iris Chen July 28, 2022, 11:23 p.m. UTC
Signed-off-by: Iris Chen <irischenlj@fb.com>
---
 hw/ssi/spi_gpio.c         | 166 ++++++++++++++++++++++++++++++++++++++
 include/hw/ssi/spi_gpio.h |  38 +++++++++
 2 files changed, 204 insertions(+)
 create mode 100644 hw/ssi/spi_gpio.c
 create mode 100644 include/hw/ssi/spi_gpio.h
diff mbox series

Patch

diff --git a/hw/ssi/spi_gpio.c b/hw/ssi/spi_gpio.c
new file mode 100644
index 0000000000..1e99c55933
--- /dev/null
+++ b/hw/ssi/spi_gpio.c
@@ -0,0 +1,166 @@ 
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates. (http://www.meta.com)
+ *
+ * This code is licensed under the GPL version 2 or later. See the COPYING
+ * file in the top-level directory.
+ */
+
+#include "hw/ssi/spi_gpio.h"
+#include "hw/irq.h"
+
+#define SPI_CPHA BIT(0) /* clock phase (1 = SPI_CLOCK_PHASE_SECOND) */
+#define SPI_CPOL BIT(1) /* clock polarity (1 = SPI_POLARITY_HIGH) */
+
+static void do_leading_edge(SpiGpioState *s)
+{
+    if (!s->CPHA) {
+        s->input_bits |= object_property_get_bool(OBJECT(s->controller_state),
+                                                  "gpioX4", NULL);
+        /*
+         * According to SPI protocol:
+         * CPHA=0 leading half clock cycle is sampling phase
+         * We technically should not drive out miso
+         * However, when the kernel bitbang driver is setting the clk pin,
+         * it will overwrite the miso value, so we are driving out miso in
+         * the sampling half clock cycle as well to workaround this issue
+         */
+        s->miso = !!(s->output_bits & 0x80);
+        object_property_set_bool(OBJECT(s->controller_state), "gpioX5", s->miso,
+                                 NULL);
+    }
+}
+
+static void do_trailing_edge(SpiGpioState *s)
+{
+    if (s->CPHA) {
+        s->input_bits |= object_property_get_bool(OBJECT(s->controller_state),
+                                                  "gpioX4", NULL);
+        /*
+         * According to SPI protocol:
+         * CPHA=1 trailing half clock cycle is sampling phase
+         * We technically should not drive out miso
+         * However, when the kernel bitbang driver is setting the clk pin,
+         * it will overwrite the miso value, so we are driving out miso in
+         * the sampling half clock cycle as well to workaround this issue
+         */
+        s->miso = !!(s->output_bits & 0x80);
+        object_property_set_bool(OBJECT(s->controller_state), "gpioX5", s->miso,
+                                 NULL);
+    }
+}
+
+static void cs_set_level(void *opaque, int n, int level)
+{
+    SpiGpioState *s = SPI_GPIO(opaque);
+    s->cs = !!level;
+
+    /* relay the CS value to the CS output pin */
+    qemu_set_irq(s->cs_output_pin, s->cs);
+
+    s->miso = !!(s->output_bits & 0x80);
+    object_property_set_bool(OBJECT(s->controller_state),
+                             "gpioX5", s->miso, NULL);
+
+    s->clk = !!(s->mode & SPI_CPOL);
+}
+
+static void clk_set_level(void *opaque, int n, int level)
+{
+    SpiGpioState *s = SPI_GPIO(opaque);
+
+    bool cur = !!level;
+
+    /* CS# is high/not selected, do nothing */
+    if (s->cs) {
+        return;
+    }
+
+    /* When the lock has not changed, do nothing */
+    if (s->clk == cur) {
+        return;
+    }
+
+    s->clk = cur;
+
+    /* Leading edge */
+    if (s->clk != s->CIDLE) {
+        do_leading_edge(s);
+    }
+
+    /* Trailing edge */
+    if (s->clk == s->CIDLE) {
+        do_trailing_edge(s);
+        s->clk_counter++;
+
+        /*
+         * Deliver the input to and
+         * get the next output byte
+         * from the SPI device
+         */
+        if (s->clk_counter == 8) {
+            s->output_bits = ssi_transfer(s->spi, s->input_bits);
+            s->clk_counter = 0;
+            s->input_bits = 0;
+         } else {
+            s->input_bits <<= 1;
+            s->output_bits <<= 1;
+         }
+    }
+}
+
+static void spi_gpio_realize(DeviceState *dev, Error **errp)
+{
+    SpiGpioState *s = SPI_GPIO(dev);
+
+    s->spi = ssi_create_bus(dev, "spi");
+    s->spi->preread = true;
+
+    s->mode = 0;
+    s->clk_counter = 0;
+
+    s->cs = true;
+    s->clk = true;
+
+    /* Assuming the first output byte is 0 */
+    s->output_bits = 0;
+    s->CIDLE = !!(s->mode & SPI_CPOL);
+    s->CPHA = !!(s->mode & SPI_CPHA);
+
+    /* init the input GPIO lines */
+    /* SPI_CS_in connects to the Aspeed GPIO */
+    qdev_init_gpio_in_named(dev, cs_set_level, "SPI_CS_in", 1);
+    qdev_init_gpio_in_named(dev, clk_set_level, "SPI_CLK", 1);
+
+    /* init the output GPIO lines */
+    /* SPI_CS_out connects to the SSI_GPIO_CS */
+    qdev_init_gpio_out_named(dev, &s->cs_output_pin, "SPI_CS_out", 1);
+
+    qdev_connect_gpio_out_named(s->controller_state, "sysbus-irq",
+                                AST_GPIO_IRQ_X0_NUM, qdev_get_gpio_in_named(
+                                DEVICE(s), "SPI_CS_in", 0));
+    qdev_connect_gpio_out_named(s->controller_state, "sysbus-irq",
+                                AST_GPIO_IRQ_X3_NUM, qdev_get_gpio_in_named(
+                                DEVICE(s), "SPI_CLK", 0));
+    object_property_set_bool(OBJECT(s->controller_state), "gpioX5", true, NULL);
+}
+
+static void SPI_GPIO_class_init(ObjectClass *klass, void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+
+    dc->realize = spi_gpio_realize;
+}
+
+static const TypeInfo SPI_GPIO_info = {
+    .name           = TYPE_SPI_GPIO,
+    .parent         = TYPE_SYS_BUS_DEVICE,
+    .instance_size  = sizeof(SpiGpioState),
+    .class_init     = SPI_GPIO_class_init,
+};
+
+static void SPI_GPIO_register_types(void)
+{
+    type_register_static(&SPI_GPIO_info);
+}
+
+type_init(SPI_GPIO_register_types)
diff --git a/include/hw/ssi/spi_gpio.h b/include/hw/ssi/spi_gpio.h
new file mode 100644
index 0000000000..c47d1531e1
--- /dev/null
+++ b/include/hw/ssi/spi_gpio.h
@@ -0,0 +1,38 @@ 
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates. (http://www.meta.com)
+ *
+ * This code is licensed under the GPL version 2 or later. See the COPYING
+ * file in the top-level directory.
+ */
+
+#ifndef SPI_GPIO_H
+#define SPI_GPIO_H
+
+#include "qemu/osdep.h"
+#include "hw/ssi/ssi.h"
+#include "hw/gpio/aspeed_gpio.h"
+
+#define TYPE_SPI_GPIO "spi_gpio"
+OBJECT_DECLARE_SIMPLE_TYPE(SpiGpioState, SPI_GPIO);
+
+/* ASPEED GPIO propname values */
+#define AST_GPIO_IRQ_X0_NUM 185
+#define AST_GPIO_IRQ_X3_NUM 188
+
+struct SpiGpioState {
+    SysBusDevice parent;
+    SSIBus *spi;
+    DeviceState *controller_state;
+
+    int mode;
+    int clk_counter;
+
+    bool CIDLE, CPHA;
+    uint32_t output_bits;
+    uint32_t input_bits;
+
+    bool clk, cs, miso;
+    qemu_irq cs_output_pin;
+};
+
+#endif /* SPI_GPIO_H */