Qemu-NUC980(八):GPIO Controller
概述
本文描述了添加980 GPIO控制器功能代码的步骤,在描述过程中,为了清晰的描述添加框架,部分代码的细节被删除,详细的代码,请参考文末的工程链接。
添加步骤
1、修改hw/arm/Kconfig,如下所示:
+号部分为新增加内容
--- a/hw/arm/Kconfig
+++ b/hw/arm/Kconfig
@@ -121,6 +121,7 @@ config NUC980select NUC980_UARTselect NUC980_AICselect NUC980_TIMER
+ select NUC980_GPIOconfig NUC980_EVBbool
2、修改hw/arm/nuc980_soc.c,如下所示:
+号部分为新增加内容
index c56402a8..f4d68719 100644
--- a/hw/arm/nuc980_soc.c
+++ b/hw/arm/nuc980_soc.c
@@ -39,6 +39,16 @@ static struct devinfo timer_table[TIMER_COUNT] = {{ TIMER5_BASE, 34},};+static struct devinfo gpio_table[GPIO_COUNT] = {
+ { GPIOA_BASE, 8},
+ { GPIOB_BASE, 9},
+ { GPIOC_BASE, 10},
+ { GPIOD_BASE, 11},
+ { GPIOE_BASE, 49},
+ { GPIOF_BASE, 57},
+ { GPIOG_BASE, 63},
+};
+static void nuc980_init(Object *obj){int i;
@@ -69,6 +79,12 @@ static void nuc980_init(Object *obj)sysbus_init_child_obj(obj, name, &s->timer[i], sizeof(s->timer[i]),TYPE_NUC980_TIMER);}
+ /* gpio */
+ for (i = 0; i < GPIO_COUNT; i++) {
+ snprintf(name, NAME_SIZE, "gpio%d", i + 1);
+ sysbus_init_child_obj(obj, name, &s->gpio[i], sizeof(s->gpio[i]),
+ TYPE_NUC980_GPIO);
+ }}static void nuc980_realize(DeviceState *dev, Error **errp)
@@ -135,6 +151,18 @@ static void nuc980_realize(DeviceState *dev, Error **errp)qdev_get_gpio_in(DEVICE(&s->aic),timer_table[i].irq));}
+ /* gpio */
+ for (i = 0; i < GPIO_COUNT; i++) {
+ object_property_set_bool(OBJECT(&s->gpio[i]), true, "realized", &err);
+ if (err) {
+ error_propagate(errp, err);
+ return;
+ }
+ sysbus_mmio_map(SYS_BUS_DEVICE(&s->gpio[i]), 0, gpio_table[i].addr);
+ sysbus_connect_irq(SYS_BUS_DEVICE(&s->gpio[i]), 0,
+ qdev_get_gpio_in(DEVICE(&s->aic),
+ gpio_table[i].irq));
+ }}static void nuc980_class_init(ObjectClass *oc, void *data)
3、修改hw/gpio/Kconfig,如下所示:
+号部分为新增加内容
index 9227cb55..231a42b5 100644
--- a/hw/gpio/Kconfig
+++ b/hw/gpio/Kconfig
@@ -7,3 +7,6 @@ config PL061config GPIO_KEYbool
+
+config NUC980_GPIO
+ bool
4、修改hw/gpio/Makefile.objs,如下所示:
+号部分为新增加内容
index d305b3b2..ff5e31a3 100644
--- a/hw/gpio/Makefile.objs
+++ b/hw/gpio/Makefile.objs
@@ -10,3 +10,4 @@ obj-$(CONFIG_IMX) += imx_gpio.oobj-$(CONFIG_RASPI) += bcm2835_gpio.oobj-$(CONFIG_NRF51_SOC) += nrf51_gpio.oobj-$(CONFIG_ASPEED_SOC) += aspeed_gpio.o
+obj-$(CONFIG_NUC980_GPIO) += nuc980_gpio.o
5、创建hw/gpio/nuc980_gpio.c,如下所示:
+号部分为新增加内容
new file mode 100644
index 00000000..75a51995
--- /dev/null
+++ b/hw/gpio/nuc980_gpio.c
@@ -0,0 +1,195 @@
+/*
+ * 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 "qapi/error.h"
+#include "qemu/log.h"
+#include "hw/gpio/nuc980_gpio.h"
+#include "hw/irq.h"
+#include "hw/qdev-properties.h"
+#include "migration/vmstate.h"
+#include "qemu/module.h"
+
+#ifndef STM_GPIO_ERR_DEBUG
+#define STM_GPIO_ERR_DEBUG 0
+#endif
+
+#define DB_PRINT_L(lvl, fmt, args...) do { \
+ if (STM_GPIO_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_gpio_reset(DeviceState *dev)
+{
+ NUC980GPIOState *s = NUC980_GPIO(dev);
+ s->gpio_mode = 0x00000000;
+ s->gpio_dinoff = 0x00000000;
+ s->gpio_dout = 0x0000ffff;
+ s->gpio_datmsk = 0x00000000;
+ s->gpio_pin = 0x00000000;
+ s->gpio_dben = 0x00000000;
+ s->gpio_inttype = 0x00000000;
+ s->gpio_inten = 0x00000000;
+ s->gpio_intsrc = 0x00000000;
+ s->gpio_smten = 0x00000000;
+ s->gpio_slewctl = 0x00000000;
+ s->gpio_pusel = 0x00000000;
+}
+
+static uint64_t nuc980_gpio_read(void *opaque, hwaddr addr,
+ unsigned int size)
+{
+ NUC980GPIOState *s = opaque;
+ uint64_t retvalue = 0;
+
+ DB_PRINT("Address: 0x%" HWADDR_PRIx "\n", addr);
+ switch(addr) {
+ case GPIO_MODE:
+ retvalue = s->gpio_mode;
+ break;
+ case GPIO_DINOFF:
+ retvalue = s->gpio_dinoff;
+ break;
+ case GPIO_DOUT:
+ retvalue = s->gpio_dout;
+ break;
+ case GPIO_DATMSK:
+ retvalue = s->gpio_datmsk;
+ break;
+ case GPIO_PIN:
+ retvalue = s->gpio_pin;
+ break;
+ case GPIO_DBEN:
+ retvalue = s->gpio_dben;
+ break;
+ case GPIO_INTTYPE:
+ retvalue = s->gpio_inttype;
+ break;
+ case GPIO_INTEN:
+ retvalue = s->gpio_inten;
+ break;
+ case GPIO_INTSRC:
+ retvalue = s->gpio_intsrc;
+ break;
+ case GPIO_SMTEN:
+ retvalue = s->gpio_smten;
+ break;
+ case GPIO_SLEWCTL:
+ retvalue = s->gpio_slewctl;
+ break;
+ case GPIO_PUSEL:
+ retvalue = s->gpio_pusel;
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset 0x%" HWADDR_PRIx "\n",
+ __func__, addr);
+ retvalue = 0;
+ break;
+ }
+ return retvalue;
+}
+
+static void nuc980_gpio_write(void *opaque, hwaddr addr,
+ uint64_t val64, unsigned int size)
+{
+ NUC980GPIOState *s = opaque;
+ uint32_t value = val64;
+
+ DB_PRINT("Address: 0x%" HWADDR_PRIx ", Value: 0x%x\n", addr, value);
+ switch(addr) {
+ case GPIO_MODE:
+ s->gpio_mode = value;
+ break;
+ case GPIO_DINOFF:
+ s->gpio_dinoff = value;
+ break;
+ case GPIO_DOUT:
+ s->gpio_dout = value;
+ break;
+ case GPIO_DATMSK:
+ s->gpio_datmsk = value;
+ break;
+ case GPIO_DBEN:
+ s->gpio_dben = value;
+ break;
+ case GPIO_INTTYPE:
+ s->gpio_inttype = value;
+ break;
+ case GPIO_INTEN:
+ s->gpio_inten = value;
+ break;
+ case GPIO_INTSRC:
+ s->gpio_intsrc = value;
+ break;
+ case GPIO_SMTEN:
+ s->gpio_smten = value;
+ break;
+ case GPIO_SLEWCTL:
+ s->gpio_slewctl = value;
+ break;
+ case GPIO_PUSEL:
+ s->gpio_pusel = value;
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad offset 0x%" HWADDR_PRIx "\n",
+ __func__, addr);
+ }
+}
+
+static const MemoryRegionOps nuc980_gpio_ops = {
+ .read = nuc980_gpio_read,
+ .write = nuc980_gpio_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static const VMStateDescription vmstate_nuc980_gpio = {
+ .name = TYPE_NUC980_GPIO,
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(gpio_mode, NUC980GPIOState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void nuc980_gpio_init(Object *obj)
+{
+ NUC980GPIOState *s = NUC980_GPIO(obj);
+
+ memory_region_init_io(&s->mmio, obj, &nuc980_gpio_ops, s,
+ TYPE_NUC980_GPIO, 0x34);
+ sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->mmio);
+
+ sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->irq);
+}
+
+static void nuc980_gpio_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->reset = nuc980_gpio_reset;
+ dc->vmsd = &vmstate_nuc980_gpio;
+}
+
+static const TypeInfo nuc980_gpio_info = {
+ .name = TYPE_NUC980_GPIO,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(NUC980GPIOState),
+ .instance_init = nuc980_gpio_init,
+ .class_init = nuc980_gpio_class_init,
+};
+
+static void nuc980_gpio_register_types(void)
+{
+ type_register_static(&nuc980_gpio_info);
+}
+
+type_init(nuc980_gpio_register_types)
6、修改include/hw/arm/nuc980.h,如下所示:
+号部分为新增加内容
diff --git a/include/hw/arm/nuc980.h b/include/hw/arm/nuc980.h
index 82e8aad2..4e34be38 100644
--- a/include/hw/arm/nuc980.h
+++ b/include/hw/arm/nuc980.h
@@ -19,6 +19,7 @@#include "hw/intc/nuc980_aic.h"#include "hw/char/nuc980_uart.h"#include "hw/timer/nuc980_timer.h"
+#include "hw/gpio/nuc980_gpio.h"#define SDRAM_BASE 0x0000000#define SDRAM_SIZE (64 *1024 * 1024)
@@ -44,6 +45,15 @@#define TIMER5_BASE 0xb0052100#define TIMER_COUNT 6+#define GPIOA_BASE 0xb0004000
+#define GPIOB_BASE 0xb0004040
+#define GPIOC_BASE 0xb0004080
+#define GPIOD_BASE 0xb00040c0
+#define GPIOE_BASE 0xb0004100
+#define GPIOF_BASE 0xb0004140
+#define GPIOG_BASE 0xb0004180
+#define GPIO_COUNT 7
+#define AIC_BASE 0xb0042000#define TYPE_NUC980 "nuc980"
@@ -64,6 +74,7 @@ typedef struct NUC980State {NUC980UartState uart[UART_COUNT];NUC980AICState aic;NUC980TimerState timer[TIMER_COUNT];
+ NUC980GPIOState gpio[GPIO_COUNT];} NUC980State;
7、创建include/hw/gpio/nuc980_gpio.h,如下所示:
+号部分为新增加内容
new file mode 100644
index 00000000..66ead156
--- /dev/null
+++ b/include/hw/gpio/nuc980_gpio.h
@@ -0,0 +1,55 @@
+/*
+ * NUC980 SOC System emulation.
+ *
+ * Copyright (c) 2023- yanl1229@163.com.
+ * Written by yanl1229
+ *
+ * This code is licensed under the GPL.
+ */
+#ifndef NUC980_GPIO_H
+#define NUC980_GPIO_H
+
+#include "hw/sysbus.h"
+
+#define GPIO_MODE 0x00
+#define GPIO_DINOFF 0x04
+#define GPIO_DOUT 0x08
+#define GPIO_DATMSK 0x0c
+#define GPIO_PIN 0x10
+#define GPIO_DBEN 0x14
+#define GPIO_INTTYPE 0x18
+#define GPIO_INTEN 0x1c
+#define GPIO_INTSRC 0x20
+#define GPIO_SMTEN 0x24
+#define GPIO_SLEWCTL 0x28
+#define GPIO_PUSEL 0x30
+
+#define TYPE_NUC980_GPIO "nuc980-gpio"
+#define NUC980_GPIO(obj) \
+ OBJECT_CHECK(NUC980GPIOState, (obj), TYPE_NUC980_GPIO)
+
+typedef struct {
+ /* <private> */
+ SysBusDevice parent_obj;
+
+ /* <public> */
+ MemoryRegion mmio;
+
+ uint32_t gpio_mode;
+ uint32_t gpio_dinoff;
+ uint32_t gpio_dout;
+ uint32_t gpio_datmsk;
+ uint32_t gpio_pin;
+ uint32_t gpio_dben;
+ uint32_t gpio_inttype;
+ uint32_t gpio_inten;
+ uint32_t gpio_intsrc;
+ uint32_t gpio_smten;
+ uint32_t gpio_slewctl;
+ uint32_t gpio_pusel;
+
+
+ qemu_irq irq;
+} NUC980GPIOState;
+
+#endif
总结
本文描述了980平台上GPIO控制器的实现,这里主要描述的是框架代码,因为qemu模拟不了硬件的电气特性,只能模拟寄存器功能,因此GPIO的模拟中,仅仅添加了寄存器读写功能。
工程链接
https://gitee.com/yanl1229/qemu.git