Signed-off-by: Iris Chen <irische...@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 --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 */ -- 2.30.2