概述
本文描述了添加NUC980 SPI控制器功能代码的步骤,在描述过程中,为了清晰的描述添加框架步骤,部分代码的细节被删除,详细的代码,请参考文末的工程链接。
添加步骤
1、修改hw/arm/Kconfig,如下所示:
+号部分为新增加内容
diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
index 35c90c59..9fa2d4ba 100644
--- a/hw/arm/Kconfig
+++ b/hw/arm/Kconfig
@@ -124,6 +124,7 @@ config NUC980
select NUC980_GPIO
select NUC980_SD
select NUC980_I2C
+ select NUC980_SPI
2、修改hw/arm/nuc980_soc.c,如下所示:
+号部分为新增加内容
diff --git a/hw/arm/nuc980_soc.c b/hw/arm/nuc980_soc.c
index 1ab5b48d..85b8086a 100644
--- a/hw/arm/nuc980_soc.c
+++ b/hw/arm/nuc980_soc.c
@@ -94,6 +94,8 @@ static void nuc980_init(Object *obj)
}
/* sd */
sysbus_init_child_obj(obj, "sd", &s->sd, sizeof(s->sd), TYPE_NUC980_SD);
+ /* spi0 */
+ sysbus_init_child_obj(obj, "spi0", &s->spi0, sizeof(s->spi0), TYPE_NUC980_SPI);
/* i2c */
for (i = 0; i < I2C_COUNT; i++) {
snprintf(name, NAME_SIZE, "i2c%d", i + 1);
@@ -107,6 +109,8 @@ static void nuc980_realize(DeviceState *dev, Error **errp)
NUC980State *s = NUC980(dev);
Error *err = NULL;
int i;
+ SSIBus *spi_bus;
+ DeviceState *spi_dev;
/* By default A9,A15 and ARM1176 CPUs have EL3 enabled. This board
* does not currently support EL3 so the CPU EL3 property is disabled
* before realization.
@@ -205,6 +209,19 @@ static void nuc980_realize(DeviceState *dev, Error **errp)
qdev_get_gpio_in(DEVICE(&s->aic),
i2c_table[i].irq));
}
+ /* spi0 */
+ object_property_set_bool(OBJECT(&s->spi0), true, "realized", &err);
+ if (err) {
+ error_propagate(errp, err);
+ return;
+ }
+ sysbus_mmio_map(SYS_BUS_DEVICE(&s->spi0), 0, SPI0_BASE);
+ sysbus_connect_irq(SYS_BUS_DEVICE(&s->spi0), 0,
+ qdev_get_gpio_in(DEVICE(&s->aic), SPI0_IRQ));
+ spi_dev = DEVICE(&s->spi0);
+ spi_bus = (SSIBus *)qdev_get_child_bus(spi_dev, "spi");
+ if (spi_bus)
+ ssi_create_slave(spi_bus, "w25q256fv");
}
3、修改hw/ssi/Kconfig,如下所示:
+号部分为新增加内容
--- a/hw/ssi/Kconfig
+++ b/hw/ssi/Kconfig
@@ -16,3 +16,7 @@ config XILINX_SPIPS
config STM32F2XX_SPI
bool
select SSI
+
+config NUC980_SPI
+ bool
+ select SSI
4、修改hw/i2c/Makefile.objs,如下所示:
+号部分为新增加内容
diff --git a/hw/ssi/Makefile.objs b/hw/ssi/Makefile.objs
index f5bcc65f..af879b51 100644
--- a/hw/ssi/Makefile.objs
+++ b/hw/ssi/Makefile.objs
@@ -8,3 +8,4 @@ common-obj-$(CONFIG_MSF2) += mss-spi.o
obj-$(CONFIG_OMAP) += omap_spi.o
obj-$(CONFIG_IMX) += imx_spi.o
+obj-$(CONFIG_NUC980_SPI) += nuc980_spi.o
5、添加hw/i2c/nuc980_i2c.c,如下所示:
+号部分为新增加内容
new file mode 100644
index 00000000..b64f38aa
--- /dev/null
+++ b/hw/ssi/nuc980_spi.c
@@ -0,0 +1,259 @@
+/*
+ * NUC980 SOC System emulation.
+ *
+ * Copyright (c) 2023- yanl1229@163.com.
+ * Written by yanl1229
+ *
+ * This code is licensed under the GPL.
+ */
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "hw/irq.h"
+#include "hw/ssi/nuc980_spi.h"
+#include "migration/vmstate.h"
+
+#ifndef QSPI_ERR_DEBUG
+#define QSPI_ERR_DEBUG 0
+#endif
+
+#define DB_PRINT_L(lvl, fmt, args...) do { \
+ if (QSPI_ERR_DEBUG >= lvl) { \
+ qemu_log("%s: " fmt, __func__, ## args); \
+ } \
+} while (0)
+
+#define DB_PRINT(fmt, args...) DB_PRINT_L(1, fmt, ## args)
+
+static void nuc980_spi_fifo_clear(NUC980SPIState *s)
+{
+ s->tx_fifo.fifo_pos = 0;
+ s->tx_fifo.fifo_len = 0;
+ s->rx_fifo.fifo_pos = 0;
+ s->rx_fifo.fifo_len = 0;
+}
+
+static void nuc980_spi_fifo_update(NUC980SPIState *s, int dir)
+{
+ spi_fifo *fifo;
+
+}
+
+/* just support 8bit thransfer */
+static void nuc980_spi_fifo_push(NUC980SPIState *s, int dir, uint32_t value)
+{
+ nuc980_spi_fifo_update(s, dir);
+}
+
+static uint32_t nuc980_spi_fifo_pop(NUC980SPIState *s, int dir)
+{
+ return value;
+}
+
+static void nuc980_spi_reset(DeviceState *dev)
+{
+ NUC980SPIState *s = NUC980_SPI(dev);
+}
+
+static uint64_t nuc980_spi_read(void *opaque, hwaddr addr,
+ unsigned int size)
+{
+ NUC980SPIState *s = opaque;
+
+ return 0;
+}
+
+static void nuc980_spi_write(void *opaque, hwaddr addr,
+ uint64_t val64, unsigned int size)
+{
+ NUC980SPIState *s = opaque;
+ uint32_t value = val64;
+ uint8_t rx;
+}
+
+static const MemoryRegionOps nuc980_spi_ops = {
+ .read = nuc980_spi_read,
+ .write = nuc980_spi_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static const VMStateDescription vmstate_nuc980_spi = {
+ .name = TYPE_NUC980_SPI,
+ }
+};
+
+static void nuc980_spi_init(Object *obj)
+{
+ NUC980SPIState *s = NUC980_SPI(obj);
+ DeviceState *dev = DEVICE(obj);
+
+ memory_region_init_io(&s->mmio, obj, &nuc980_spi_ops, s,
+ TYPE_NUC980_SPI, 0x1000);
+ sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->mmio);
+
+ sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->irq);
+
+ s->ssi = ssi_create_bus(dev, "spi");
+}
+
+static void nuc980_spi_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->reset = nuc980_spi_reset;
+ dc->vmsd = &vmstate_nuc980_spi;
+}
+
+static const TypeInfo nuc980_spi_info = {
+ .name = TYPE_NUC980_SPI,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(NUC980SPIState),
+ .instance_init = nuc980_spi_init,
+ .class_init = nuc980_spi_class_init,
+};
+
+static void nuc980_spi_register_types(void)
+{
+ type_register_static(&nuc980_spi_info);
+}
+
+type_init(nuc980_spi_register_types)
\ No newline at end of file
6、修改include/hw/arm/nuc980.h,如下所示:
+号部分为新增加内容
diff --git a/include/hw/arm/nuc980.h b/include/hw/arm/nuc980.h
index 8fd8663a..038af1fd 100644
--- a/include/hw/arm/nuc980.h
+++ b/include/hw/arm/nuc980.h
@@ -22,6 +22,7 @@
#include "hw/gpio/nuc980_gpio.h"
#include "hw/sd/nuc980_sd.h"
#include "hw/i2c/nuc980_i2c.h"
+#include "hw/ssi/nuc980_spi.h"
#define SDRAM_BASE 0x0000000
#define SDRAM_SIZE (64 *1024 * 1024)
@@ -62,6 +63,9 @@
#define I2C2_BASE 0xb0082000
#define I2C3_BASE 0xb0083000
+#define SPI0_BASE 0xb0061000
+#define SPI0_IRQ 52
+
#define AIC_BASE 0xb0042000
#define SDH_BASE 0xb0018000
@@ -88,6 +92,7 @@ typedef struct NUC980State {
NUC980GPIOState gpio[GPIO_COUNT];
NUC980SDState sd;
NUC980I2CState i2c[I2C_COUNT];
+ NUC980SPIState spi0;
} NUC980State;
7、添加include/hw/ssi/nuc980_spi.h,如下所示:
+号部分为新增加内容
new file mode 100644
index 00000000..8a839352
--- /dev/null
+++ b/include/hw/ssi/nuc980_spi.h
@@ -0,0 +1,73 @@
+/*
+ * NUC980 SOC System emulation.
+ *
+ * Copyright (c) 2023- yanl1229@163.com.
+ * Written by yanl1229
+ *
+ * This code is licensed under the GPL.
+ */
+#ifndef HW_NUC980_SPI_H
+#define HW_NUC980_SPI_H
+
+#include "hw/sysbus.h"
+#include "hw/hw.h"
+#include "hw/ssi/ssi.h"
+
+#define SPI_CTL 0x00
+#define SPI_CLKDIV 0x04
+#define SPI_SSCTL 0x08
+#define SPI_PDMACTl 0x0c
+#define SPI_FIFOCTL 0x10
+#define SPI_STATUS 0x14
+#define SPI_TX 0x20
+#define SPI_RX 0x30
+
+#define SPI_FIFOCTL_TXFULL (1 << 17)
+#define SPI_FIFOCTL_TXEMPTY (1 << 16)
+
+#define SPI_FIFOCTL_RXFULL (1 << 9)
+#define SPI_FIFOCTL_RXEMPTY (1 << 8)
+#define SPI_FIFOCTL_TXRST (1 << 1)
+#define SPI_FIFOCTL_RXRST (1 << 0)
+
+#define SPI_FIFOCTL_RST (SPI_FIFOCTL_TXRST|SPI_FIFOCTL_RXRST)
+
+#define SPI_STATUS_TXRXRST (1 << 23)
+
+#define TYPE_NUC980_SPI "nuc980-spi"
+#define NUC980_SPI(obj) \
+ OBJECT_CHECK(NUC980SPIState, (obj), TYPE_NUC980_SPI)
+
+#define FIFO_SIZE 4*8
+
+typedef struct spi_fifo {
+ int32_t fifo_pos;
+ int32_t fifo_len;
+ uint8_t fifo[FIFO_SIZE];
+}spi_fifo;
+
+typedef struct {
+ /* <private> */
+ SysBusDevice parent_obj;
+
+ /* <public> */
+ MemoryRegion mmio;
+
+ uint32_t spi_ctl;
+ uint32_t spi_clkdiv;
+ uint32_t spi_ssctl;
+ uint32_t spi_pdmactl;
+ uint32_t spi_fifoctl;
+ uint32_t spi_status;
+ uint32_t spi_tx;
+ uint32_t spi_rx;
+
+ spi_fifo tx_fifo;
+ spi_fifo rx_fifo;
+
+ qemu_irq irq;
+ SSIBus *ssi;
+
+} NUC980SPIState;
+
+#endif
\ No newline at end of file
总结
本文概要描述了使用qemu模拟NUC980 SPI控制器的框架代码,详细的代码修改,请参考文末的工程连接。