From: Strahinja Jankovic <strahinja.p.jankovic@gmail.com>
To: qemu-devel@nongnu.org
Cc: Paolo Bonzini <pbonzini@redhat.com>,
Peter Maydell <peter.maydell@linaro.org>,
qemu-arm@nongnu.org,
Pierrick Bouvier <pierrick.bouvier@oss.qualcomm.com>,
Fabiano Rosas <farosas@suse.de>,
Laurent Vivier <lvivier@redhat.com>,
Strahinja Jankovic <strahinja.p.jankovic@gmail.com>
Subject: [PATCH 1/3] hw/gpio: Allwinner A10 GPIO emulation
Date: Wed, 19 Aug 2026 00:04:51 +0200 [thread overview]
Message-ID: <20260819-allwinner-gpio-v1-1-2a64866dff11@gmail.com> (raw)
In-Reply-To: <20260819-allwinner-gpio-v1-0-2a64866dff11@gmail.com>
This patch implements Allwinner A10 GPIO peripheral emulation.
The emulation focuses on input/output and interrupt functionality,
whereas the drive and pull up/down functionality is not emulated.
Signed-off-by: Strahinja Jankovic <strahinja.p.jankovic@gmail.com>
---
MAINTAINERS | 2 +
hw/gpio/Kconfig | 3 +
hw/gpio/allwinner-gpio.c | 465 ++++++++++++++++++++++++++++++++++
hw/gpio/meson.build | 1 +
hw/gpio/trace-events | 6 +
include/hw/gpio/allwinner-gpio-regs.h | 228 +++++++++++++++++
include/hw/gpio/allwinner-gpio.h | 41 +++
7 files changed, 746 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index b51f5c3e60..6cefdde70b 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -743,6 +743,8 @@ F: include/hw/*/allwinner*
F: hw/arm/cubieboard.c
F: docs/system/arm/cubieboard.rst
F: hw/misc/axp2xx.c
+F: hw/gpio/allwinner-gpio.c
+F: include/hw/gpio/allwinner-gpio*
F: tests/functional/arm/test_cubieboard.py
Allwinner-h3
diff --git a/hw/gpio/Kconfig b/hw/gpio/Kconfig
index fcc7c70bd5..ea0fb72b40 100644
--- a/hw/gpio/Kconfig
+++ b/hw/gpio/Kconfig
@@ -33,3 +33,6 @@ config ZAURUS_SCOOP
config CADENCE_GPIO
bool
+
+config ALLWINNER_GPIO
+ bool
diff --git a/hw/gpio/allwinner-gpio.c b/hw/gpio/allwinner-gpio.c
new file mode 100644
index 0000000000..fb0f3f7ca1
--- /dev/null
+++ b/hw/gpio/allwinner-gpio.c
@@ -0,0 +1,465 @@
+/*
+ * Allwinner GPIO Emulation
+ *
+ * Copyright (C) 2026 Strahinja Jankovic <strahinja.p.jankovic@gmail.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "hw/gpio/allwinner-gpio.h"
+#include "hw/gpio/allwinner-gpio-regs.h"
+#include "hw/core/irq.h"
+#include "hw/core/qdev-properties.h"
+#include "migration/vmstate.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "trace.h"
+
+#define REG_INDEX(offset) (offset / sizeof(uint32_t))
+
+#define AW_GPIO_SET(port) \
+ static inline void allwinner_gpio_set_##port(void *opaque, \
+ int line, \
+ int level) { \
+ allwinner_gpio_set(opaque, port, line, level); \
+ }
+
+static void allwinner_gpio_set(void *opaque, int port, int line, int level);
+
+AW_GPIO_SET(GPIO_PA);
+AW_GPIO_SET(GPIO_PB);
+AW_GPIO_SET(GPIO_PC);
+AW_GPIO_SET(GPIO_PD);
+AW_GPIO_SET(GPIO_PE);
+AW_GPIO_SET(GPIO_PF);
+AW_GPIO_SET(GPIO_PG);
+AW_GPIO_SET(GPIO_PH);
+AW_GPIO_SET(GPIO_PI);
+
+static void (*AW_GPIO_IN_HANDLER[AW_GPIO_PORTS_NUM]) (void *opaque,
+ int line,
+ int level) = {
+ allwinner_gpio_set_GPIO_PA,
+ allwinner_gpio_set_GPIO_PB,
+ allwinner_gpio_set_GPIO_PC,
+ allwinner_gpio_set_GPIO_PD,
+ allwinner_gpio_set_GPIO_PE,
+ allwinner_gpio_set_GPIO_PF,
+ allwinner_gpio_set_GPIO_PG,
+ allwinner_gpio_set_GPIO_PH,
+ allwinner_gpio_set_GPIO_PI,
+};
+
+static char *allwinner_gpio_get_regname(unsigned offset)
+{
+ switch (offset) {
+ case 0 ... GPIO_Pn_PUL1(GPIO_PI):
+ {
+ const char *regname = "?";
+ switch (offset % PORT_STRIDE) {
+ case CFG0:
+ regname = "CFG0";
+ break;
+ case CFG1:
+ regname = "CFG1";
+ break;
+ case CFG2:
+ regname = "CFG2";
+ break;
+ case CFG3:
+ regname = "CFG3";
+ break;
+ case DAT:
+ regname = "DAT";
+ break;
+ case DRV0:
+ regname = "DRV0";
+ break;
+ case DRV1:
+ regname = "DRV1";
+ break;
+ case PUL0:
+ regname = "PUL0";
+ break;
+ case PUL1:
+ regname = "PUL1";
+ break;
+ }
+ return g_strdup_printf("%s:%s",
+ portname(offset / PORT_STRIDE),
+ regname);
+ }
+ case GPIO_INT_CFG0:
+ return g_strdup("INT_CFG0");
+ case GPIO_INT_CFG1:
+ return g_strdup("INT_CFG1");
+ case GPIO_INT_CFG2:
+ return g_strdup("INT_CFG2");
+ case GPIO_INT_CFG3:
+ return g_strdup("INT_CFG3");
+ case GPIO_INT_CTL:
+ return g_strdup("INT_CTL");
+ case GPIO_INT_STA:
+ return g_strdup("INT_STA");
+ case GPIO_INT_DEB:
+ return g_strdup("INT_DEB");
+ default:
+ return g_strdup("[?]");
+ }
+}
+
+static bool gpio_is_input(AWPortMap *port, uint32_t pin);
+static int int_ctl_cfg(AWGPIOState *s, int irq_line);
+
+static inline void allwinner_gpio_update_int(AWGPIOState *s)
+{
+ qemu_set_irq(
+ s->irq,
+ !!(s->regs[REG_INDEX(GPIO_INT_CTL)] & s->regs[REG_INDEX(GPIO_INT_STA)])
+ );
+}
+
+static void allwinner_gpio_set_int_line(AWGPIOState *s, int port,
+ int line, AWGPIOLevel level)
+{
+ AWPortsOverlay *o = (AWPortsOverlay *)s->regs;
+ int irq_line = irq_nr(port, line);
+ bool raise_irq = false;
+ unsigned input_val;
+ int int_cfg;
+
+ /* Check if pin can actually trigger an interrupt */
+ if (irq_line == -1) {
+ return;
+ }
+
+ /* if this signal isn't configured as an input signal, nothing to do */
+ if (!gpio_is_input(&o->ports[port], line)) {
+ return;
+ }
+
+ /* Get current input value */
+ input_val = extract32(o->ports[port].dat, line, 1);
+ int_cfg = int_ctl_cfg(s, irq_line);
+
+ switch (int_cfg) {
+ case AW_GPIO_IRQ_CFG_RISING_EDGE:
+ if (!input_val && level) {
+ raise_irq = true;
+ }
+ break;
+ case AW_GPIO_IRQ_CFG_FALLING_EDGE:
+ if (input_val && !level) {
+ raise_irq = true;
+ }
+ break;
+ case AW_GPIO_IRQ_CFG_HIGH_LEVEL:
+ if (level) {
+ raise_irq = true;
+ }
+ break;
+ case AW_GPIO_IRQ_CFG_LOW_LEVEL:
+ if (!level) {
+ raise_irq = true;
+ }
+ break;
+ case AW_GPIO_IRQ_CFG_BOTH_EDGE:
+ if (input_val != level) {
+ raise_irq = true;
+ }
+ break;
+ default:
+ /* Unexpected */
+ break;
+ }
+
+ if (raise_irq) {
+ s->regs[REG_INDEX(GPIO_INT_STA)] |= 1u << irq_line;
+ }
+}
+
+static void port_set_all_int_lines(AWGPIOState *s, int port)
+{
+ AWPortsOverlay *o = (AWPortsOverlay *)s->regs;
+ int i;
+
+ for (i = 0; i < AW_PINS_PER_PORT[port]; i++) {
+ AWGPIOLevel aw_level = extract32(o->ports[port].dat, i, 1);
+ allwinner_gpio_set_int_line(s, port, i, aw_level);
+ }
+
+ allwinner_gpio_update_int(s);
+}
+
+static void allwinner_set_all_int_lines(AWGPIOState *s)
+{
+ int i;
+
+ for (i = 0; i < AW_GPIO_PORTS_NUM; i++) {
+ port_set_all_int_lines(s, i);
+ }
+}
+
+static void allwinner_gpio_set(void *opaque, int port, int line, int level)
+{
+ AWGPIOState *s = AW_GPIO(opaque);
+ AWPortsOverlay *o = (AWPortsOverlay *)s->regs;
+ AWGPIOLevel aw_level = level ? AW_GPIO_LEVEL_HIGH : AW_GPIO_LEVEL_LOW;
+
+ if (gpio_is_input(&o->ports[port], line)) {
+
+ trace_allwinner_gpio_set(portname(port), line, aw_level);
+
+ allwinner_gpio_set_int_line(s, port, line, aw_level);
+
+ o->ports[port].dat = (o->ports[port].dat & ~(1u << line)) |
+ (aw_level << line);
+ }
+ allwinner_gpio_update_int(s);
+}
+
+
+static inline bool gpio_is_output(AWPortMap *port, uint32_t pin)
+{
+ uint32_t cfg_n = pin / CFG_PINS_PER_REG;
+ uint32_t pin_shift = (pin % CFG_PINS_PER_REG) * CFG_PIN_STRIDE;
+ return (extract32(port->cfg[cfg_n], pin_shift, CFG_PIN_STRIDE - 1) ==
+ CFG_OUTPUT_MASK);
+}
+
+static inline bool gpio_is_input(AWPortMap *port, uint32_t pin)
+{
+ uint32_t cfg_n = pin / CFG_PINS_PER_REG;
+ uint32_t pin_shift = (pin % CFG_PINS_PER_REG) * CFG_PIN_STRIDE;
+ return (extract32(port->cfg[cfg_n], pin_shift, CFG_PIN_STRIDE - 1) ==
+ CFG_INPUT_MASK);
+}
+
+static inline int int_ctl_cfg(AWGPIOState *s, int irq_line)
+{
+ unsigned int_cfg_n = s->regs[REG_INDEX(GPIO_INT_CFG0) + irq_line / 8];
+ return extract32(int_cfg_n,
+ (irq_line % INT_CFG_IRQ_PER_REG) * INT_CFG_IRQ_STRIDE,
+ INT_CFG_IRQ_STRIDE - 1);
+}
+
+static inline void port_update_output_lines(AWGPIOState *s, uint32_t port)
+{
+ AWPortsOverlay *o = (AWPortsOverlay *)s->regs;
+ int pin;
+
+ for (pin = 0; pin < AW_PINS_PER_PORT[port]; pin++) {
+ if (gpio_is_output(&o->ports[port], pin)) {
+ qemu_set_irq(s->output[port][pin],
+ !!(o->ports[port].dat & BIT_MASK(pin)));
+ trace_allwinner_gpio_out_pin(
+ portname(port), pin,
+ !!(o->ports[port].dat & BIT_MASK(pin)));
+ }
+ else if (gpio_is_input(&o->ports[port], pin)) {
+ qemu_irq_lower(s->output[port][pin]);
+ }
+ }
+}
+
+static inline void allwinner_gpio_update_all_output_lines(AWGPIOState *s)
+{
+ for (int port = 0; port < AW_GPIO_PORTS_NUM; port++) {
+ port_update_output_lines(s, port);
+ }
+}
+
+
+static uint64_t allwinner_gpio_read(void *opaque, hwaddr offset, unsigned size)
+{
+ AWGPIOState *s = AW_GPIO(opaque);
+ uint32_t reg_value = 0;
+ g_autofree char *regname = allwinner_gpio_get_regname(offset);
+
+ switch (offset) {
+ case 0 ... GPIO_Pn_PUL1(GPIO_PI):
+ case GPIO_INT_CFG0:
+ case GPIO_INT_CFG1:
+ case GPIO_INT_CFG2:
+ case GPIO_INT_CFG3:
+ case GPIO_INT_CTL:
+ case GPIO_INT_STA:
+ case GPIO_INT_DEB:
+ reg_value = s->regs[REG_INDEX(offset)];
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR, "[%s]%s: Bad register at offset 0x%"
+ HWADDR_PRIx "\n", TYPE_AW_GPIO, __func__, offset);
+ break;
+ }
+
+ trace_allwinner_gpio_read(regname, reg_value);
+
+ return reg_value;
+}
+
+/* update output values */
+/* update interrupts */
+static void allwinner_port_write(AWGPIOState *s, hwaddr offset, uint64_t value)
+{
+ AWPortsOverlay *o = (AWPortsOverlay *)s->regs;
+ uint32_t port = offset / PORT_STRIDE;
+ uint32_t reg = offset % PORT_STRIDE;
+
+ switch (reg) {
+ case CFG0:
+ o->ports[port].cfg[0] =
+ value & GPIO_CFG0_PINS_MASK(AW_PINS_PER_PORT[port]);
+ break;
+ case CFG1:
+ o->ports[port].cfg[1] =
+ value & GPIO_CFG1_PINS_MASK(AW_PINS_PER_PORT[port]);
+ break;
+ case CFG2:
+ o->ports[port].cfg[2] =
+ value & GPIO_CFG2_PINS_MASK(AW_PINS_PER_PORT[port]);
+ break;
+ case CFG3:
+ o->ports[port].cfg[3] =
+ value & GPIO_CFG3_PINS_MASK(AW_PINS_PER_PORT[port]);
+ break;
+ case DAT:
+ o->ports[port].dat =
+ value & GPIO_DAT_PINS_MASK(AW_PINS_PER_PORT[port]);
+ break;
+ case DRV0:
+ o->ports[port].drv[0] =
+ value & GPIO_DRV0_PINS_MASK(AW_PINS_PER_PORT[port]);
+ break;
+ case DRV1:
+ o->ports[port].drv[1] =
+ value & GPIO_DRV1_PINS_MASK(AW_PINS_PER_PORT[port]);
+ break;
+ case PUL0:
+ o->ports[port].pul[0] =
+ value & GPIO_PUL0_PINS_MASK(AW_PINS_PER_PORT[port]);
+ break;
+ case PUL1:
+ o->ports[port].pul[1] =
+ value & GPIO_PUL1_PINS_MASK(AW_PINS_PER_PORT[port]);
+ break;
+ }
+ port_update_output_lines(s, port);
+ port_set_all_int_lines(s, port);
+}
+
+static void allwinner_gpio_write(void *opaque, hwaddr offset, uint64_t value,
+ unsigned size)
+{
+ AWGPIOState *s = AW_GPIO(opaque);
+ g_autofree char *regname = allwinner_gpio_get_regname(offset);
+
+ trace_allwinner_gpio_write(regname, value);
+
+ switch (offset) {
+ case 0 ... GPIO_Pn_PUL1(GPIO_PI):
+ allwinner_port_write(s, offset, value);
+ break;
+ case GPIO_INT_CFG0:
+ case GPIO_INT_CFG1:
+ case GPIO_INT_CFG2:
+ case GPIO_INT_CFG3:
+ case GPIO_INT_CTL:
+ case GPIO_INT_DEB:
+ s->regs[REG_INDEX(offset)] = value;
+ /* update interrupts */
+ allwinner_set_all_int_lines(s);
+ break;
+ case GPIO_INT_STA:
+ /* W1C */
+ s->regs[REG_INDEX(offset)] &= ~value;
+ allwinner_set_all_int_lines(s);
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR, "[%s]%s: Bad register at offset 0x%"
+ HWADDR_PRIx "\n", TYPE_AW_GPIO, __func__, offset);
+ break;
+ }
+}
+
+static const MemoryRegionOps allwinner_gpio_ops = {
+ .read = allwinner_gpio_read,
+ .write = allwinner_gpio_write,
+ .valid.min_access_size = 4,
+ .valid.max_access_size = 4,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static const VMStateDescription vmstate_allwinner_gpio = {
+ .name = TYPE_AW_GPIO,
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (const VMStateField[]) {
+ VMSTATE_UINT32_ARRAY(regs, AWGPIOState, AW_GPIO_REGS_NUM),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void allwinner_gpio_reset(DeviceState *dev)
+{
+ AWGPIOState *s = AW_GPIO(dev);
+ AWPortsOverlay *o = (AWPortsOverlay *)s->regs;
+ int port = 0;
+
+ memset(s->regs, 0, AW_GPIO_IOSIZE);
+
+ for (port = 0; port < AW_GPIO_PORTS_NUM; port++) {
+ o->ports[port].drv[0] = aw_gpio_port_reset[port].drv[0];
+ o->ports[port].drv[1] = aw_gpio_port_reset[port].drv[1];
+ o->ports[port].pul[0] = aw_gpio_port_reset[port].pul[0];
+ o->ports[port].pul[1] = aw_gpio_port_reset[port].pul[1];
+ }
+
+ allwinner_gpio_update_all_output_lines(s);
+ allwinner_gpio_update_int(s);
+}
+
+static void allwinner_gpio_realize(DeviceState *dev, Error **errp)
+{
+ AWGPIOState *s = AW_GPIO(dev);
+ int port;
+
+ memory_region_init_io(&s->iomem, OBJECT(s), &allwinner_gpio_ops, s,
+ TYPE_AW_GPIO, AW_GPIO_IOSIZE);
+
+ for (port = 0; port < AW_GPIO_PORTS_NUM; port++) {
+ g_autofree char *in_name = portname_in(port);
+ g_autofree char *out_name = portname_out(port);
+ qdev_init_gpio_in_named(dev, AW_GPIO_IN_HANDLER[port],
+ in_name, AW_PINS_PER_PORT[port]);
+ qdev_init_gpio_out_named(dev, s->output[port],
+ out_name, AW_PINS_PER_PORT[port]);
+ }
+ sysbus_init_irq(SYS_BUS_DEVICE(dev), &s->irq);
+ sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->iomem);
+}
+
+static void allwinner_gpio_class_init(ObjectClass *klass, const void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = allwinner_gpio_realize;
+ device_class_set_legacy_reset(dc, allwinner_gpio_reset);
+ dc->vmsd = &vmstate_allwinner_gpio;
+ dc->desc = "Allwinner GPIO controller";
+}
+
+static const TypeInfo allwinner_gpio_info = {
+ .name = TYPE_AW_GPIO,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(AWGPIOState),
+ .class_init = allwinner_gpio_class_init,
+};
+
+static void allwinner_gpio_register_types(void)
+{
+ type_register_static(&allwinner_gpio_info);
+}
+
+type_init(allwinner_gpio_register_types)
diff --git a/hw/gpio/meson.build b/hw/gpio/meson.build
index 0555f44b6a..8ede5e88ae 100644
--- a/hw/gpio/meson.build
+++ b/hw/gpio/meson.build
@@ -20,3 +20,4 @@ system_ss.add(when: 'CONFIG_ASPEED_SOC', if_true: files('aspeed_sgpio.c'))
system_ss.add(when: 'CONFIG_SIFIVE_GPIO', if_true: files('sifive_gpio.c'))
system_ss.add(when: 'CONFIG_PCF8574', if_true: files('pcf8574.c'))
system_ss.add(when: 'CONFIG_CADENCE_GPIO', if_true: files('cadence_gpio.c'))
+system_ss.add(when: 'CONFIG_ALLWINNER_GPIO', if_true: files('allwinner-gpio.c'))
diff --git a/hw/gpio/trace-events b/hw/gpio/trace-events
index 80ca783a03..5ef3e0b8fd 100644
--- a/hw/gpio/trace-events
+++ b/hw/gpio/trace-events
@@ -51,3 +51,9 @@ stm32l4x5_gpio_pins(char *gpio, uint16_t disconnected, uint16_t high) "GPIO%s di
cdns_gpio_read(const char *path, uint64_t offset, uint32_t value) "%s:reg[0x%04" PRIx64 "] -> 0x%" PRIx32
cdns_gpio_write(const char *path, uint64_t offset, uint64_t value) "%s:reg[0x%04" PRIx64 "] <- 0x%04" PRIx64
cdns_gpio_set(const char *path, int line, int level) "%s:[%d] <- %d"
+
+# allwinner_gpio.c
+allwinner_gpio_set(const char *port, int line, int level) "[%s:%d] <- %d"
+allwinner_gpio_read(const char *reg, uint32_t value) "[%s] -> 0x%" PRIx32
+allwinner_gpio_write(const char *reg, uint32_t value) "[%s] <- 0x%" PRIx32
+allwinner_gpio_out_pin(const char *reg, uint32_t pin, uint32_t value) "[%s.%d] <- 0x%" PRIx32
diff --git a/include/hw/gpio/allwinner-gpio-regs.h b/include/hw/gpio/allwinner-gpio-regs.h
new file mode 100644
index 0000000000..52d72c0f25
--- /dev/null
+++ b/include/hw/gpio/allwinner-gpio-regs.h
@@ -0,0 +1,228 @@
+/*
+ * Allwinner GPIO registers definition
+ *
+ * Copyright (C) 2026 Strahinja Jankovic. <strahinja.p.jankovic@gmail.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef ALLWINNER_GPIO_REGS_H
+#define ALLWINNER_GPIO_REGS_H
+
+/* GPIO ports index (n) */
+#define GPIO_PA 0
+#define GPIO_PB 1
+#define GPIO_PC 2
+#define GPIO_PD 3
+#define GPIO_PE 4
+#define GPIO_PF 5
+#define GPIO_PG 6
+#define GPIO_PH 7
+#define GPIO_PI 8
+
+#ifndef AW_GPIO_PORTS_NUM
+#define AW_GPIO_PORTS_NUM 9
+#endif
+
+#ifndef AW_GPIO_PIN_COUNT
+#define AW_GPIO_PIN_COUNT 32
+#endif
+
+typedef struct AWPortMap {
+ uint32_t cfg[4];
+ uint32_t dat;
+ uint32_t drv[2];
+ uint32_t pul[2];
+} AWPortMap;
+
+typedef struct AWPortsOverlay {
+ AWPortMap ports[AW_GPIO_PORTS_NUM];
+} AWPortsOverlay;
+
+static const uint32_t AW_PINS_PER_PORT[AW_GPIO_PORTS_NUM] = {
+ 18,
+ 24,
+ 25,
+ 28,
+ 12,
+ 6,
+ 12,
+ 28,
+ 22
+};
+
+typedef struct {
+ uint32_t drv[2];
+ uint32_t pul[2];
+} AWPortResetVals;
+
+static const AWPortResetVals aw_gpio_port_reset[] = {
+ [GPIO_PA] = { .drv = { 0x55555555, 0x00000005 } },
+ [GPIO_PB] = { .drv = { 0x55555555, 0x00005555 } },
+ [GPIO_PC] = { .drv = { 0x55555555, 0x00015555 },
+ .pul = { 0x00005140, 0x00004016 } },
+ [GPIO_PD] = { .drv = { 0x55555555, 0x00555555 } },
+ [GPIO_PE] = { .drv = { 0x00555555, 0x0 } },
+ [GPIO_PF] = { .drv = { 0x00000555, 0x0 } },
+ [GPIO_PG] = { .drv = { 0x05555555, 0x0 } },
+ [GPIO_PH] = { .drv = { 0x55555555, 0x00555555 } },
+ [GPIO_PI] = { .drv = { 0x55555555, 0x00000555 } },
+};
+
+#define DEFAULT_CFG_MASK 0x77777777
+#define DEFAULT_DRV_MASK 0xffffffff
+#define DEFAULT_PUL_MASK 0xffffffff
+
+#define CFG_INPUT_MASK 0x0
+#define CFG_OUTPUT_MASK 0x1
+#define CFG_IO_MASK 0x1
+#define CFG_PIN_STRIDE 4
+#define CFG_PINS_PER_REG (AW_GPIO_PIN_COUNT / CFG_PIN_STRIDE)
+
+#define AW_INT_GPIO_COUNT 32
+#define INT_CFG_IRQ_STRIDE 4
+#define INT_CFG_IRQ_PER_REG (AW_INT_GPIO_COUNT / INT_CFG_IRQ_STRIDE)
+
+/* GPIO masks per number of pins */
+#define GPIO_CFG0_PINS_MASK(pins) \
+ ((pins >= CFG_PINS_PER_REG) ? DEFAULT_CFG_MASK : \
+ ((1 << (pins * CFG_PIN_STRIDE)) - 1) & DEFAULT_CFG_MASK)
+
+#define GPIO_CFG1_PINS_MASK(pins) \
+ ((pins >= 16) ? DEFAULT_CFG_MASK : \
+ ((pins > 8) ? ((1 << ((pins - 8) * 4)) - 1) & DEFAULT_CFG_MASK : 0))
+
+#define GPIO_CFG2_PINS_MASK(pins) \
+ ((pins >= 24) ? DEFAULT_CFG_MASK : \
+ ((pins > 16) ? ((1 << ((pins - 16) * 4)) - 1) & DEFAULT_CFG_MASK : 0))
+
+#define GPIO_CFG3_PINS_MASK(pins) \
+ ((pins > 24) ? ((1 << ((pins - 24) * 4)) - 1) & DEFAULT_CFG_MASK : 0)
+
+#define GPIO_DAT_PINS_MASK(pins) ((1 << pins) - 1)
+
+#define GPIO_DRV0_PINS_MASK(pins) \
+ ((pins > 16) ? DEFAULT_DRV_MASK : ((1 << (pins * 2)) - 1))
+
+#define GPIO_DRV1_PINS_MASK(pins) \
+ ((pins > 16) ? ((1 << ((pins - 16) * 2)) - 1) : 0)
+
+#define GPIO_PUL0_PINS_MASK(pins) \
+ ((pins > 16) ? DEFAULT_PUL_MASK : ((1 << (pins * 2)) - 1))
+
+#define GPIO_PUL1_PINS_MASK(pins) \
+ ((pins > 16) ? ((1 << ((pins - 16) * 2)) - 1) : 0)
+
+
+#define PORT_STRIDE 0x24
+
+/* Allwinner GPIO memory map */
+#define CFG0 0x00 /* Configure register 0 */
+#define CFG1 0x04 /* Configure register 1 */
+#define CFG2 0x08 /* Configure register 2 */
+#define CFG3 0x0c /* Configure register 3 */
+#define DAT 0x10 /* Data register */
+#define DRV0 0x14 /* Multi-driving register 0 */
+#define DRV1 0x18 /* Multi-driving register 1 */
+#define PUL0 0x1c /* Pull register 0 */
+#define PUL1 0x20 /* Pull register 1 */
+
+/* Port n configure register 0 */
+#define GPIO_Pn_CFG0(n) (n*PORT_STRIDE + CFG0)
+/* Port n configure register 1 */
+#define GPIO_Pn_CFG1(n) (n*PORT_STRIDE + CFG1)
+/* Port n configure register 2 */
+#define GPIO_Pn_CFG2(n) (n*PORT_STRIDE + CFG2)
+/* Port n configure register 3 */
+#define GPIO_Pn_CFG3(n) (n*PORT_STRIDE + CFG3)
+/* Port n data register */
+#define GPIO_Pn_DAT(n) (n*PORT_STRIDE + DAT)
+/* Port n Multi-driving register 0 */
+#define GPIO_Pn_DRV0(n) (n*PORT_STRIDE + DRV0)
+/* Port n Multi-driving register 1 */
+#define GPIO_Pn_DRV1(n) (n*PORT_STRIDE + DRV1)
+/* Port n Pull register 0 */
+#define GPIO_Pn_PUL0(n) (n*PORT_STRIDE + PUL0)
+/* Port n Pull register 1 */
+#define GPIO_Pn_PUL1(n) (n*PORT_STRIDE + PUL1)
+/* PIO interrupt configure register 0 */
+#define GPIO_INT_CFG0 0x200
+/* PIO interrupt configure register 1 */
+#define GPIO_INT_CFG1 0x204
+/* PIO interrupt configure register 2 */
+#define GPIO_INT_CFG2 0x208
+/* PIO interrupt configure register 3 */
+#define GPIO_INT_CFG3 0x20c
+/* PIO interrupt control register */
+#define GPIO_INT_CTL 0x210
+/* PIO interrupt status register */
+#define GPIO_INT_STA 0x214
+/* PIO interrupt debounce register */
+#define GPIO_INT_DEB 0x218
+/* SDRAM Pad Multi-driving register */
+#define SDR_PAD_DRV 0x220
+/* SDRAM Pad Pull register */
+#define SDR_PAD_PUL 0x224
+
+typedef enum AWGPIOLevel {
+ AW_GPIO_LEVEL_LOW = 0,
+ AW_GPIO_LEVEL_HIGH = 1,
+} AWGPIOLevel;
+
+typedef enum AWGPIOCfg {
+ AW_GPIO_CFG_IN = 0,
+ AW_GPIO_CFG_OUT = 1,
+ AW_GPIO_CFG_FUN = 2,
+ /* Use only fun for testing */
+} AWGPIOCfg;
+
+typedef enum AWGPIOIrqCfg {
+ AW_GPIO_IRQ_CFG_RISING_EDGE = 0,
+ AW_GPIO_IRQ_CFG_FALLING_EDGE = 1,
+ AW_GPIO_IRQ_CFG_HIGH_LEVEL = 2,
+ AW_GPIO_IRQ_CFG_LOW_LEVEL = 3,
+ AW_GPIO_IRQ_CFG_BOTH_EDGE = 4
+} AWGPIOIrqCfg;
+
+static const char *const port_name_table[] = {
+ "PA", "PB", "PC", "PD", "PE", "PF", "PG", "PH", "PI"
+};
+
+static const char *portname(unsigned index)
+{
+ return index < AW_GPIO_PORTS_NUM ? port_name_table[index] : "?";
+}
+
+static inline char *portname_in(int port)
+{
+ return g_strconcat(portname(port), "_IN", NULL);
+}
+
+static inline char *portname_out(int port)
+{
+ return g_strconcat(portname(port), "_OUT", NULL);
+}
+
+static int irq_nr(int port, int line)
+{
+ int irq = -1;
+
+ switch (port) {
+ case GPIO_PH:
+ if (line <= 21) {
+ irq = line;
+ }
+ break;
+ case GPIO_PI:
+ if ((line >= 10) && (line <= 19)) {
+ irq = line + 12;
+ }
+ break;
+ default:
+ break;
+ }
+ return irq;
+}
+
+
+#endif /* ALLWINNER_GPIO_REGS_H */
diff --git a/include/hw/gpio/allwinner-gpio.h b/include/hw/gpio/allwinner-gpio.h
new file mode 100644
index 0000000000..f6778d1893
--- /dev/null
+++ b/include/hw/gpio/allwinner-gpio.h
@@ -0,0 +1,41 @@
+/*
+ * Allwinner GPIO registers definition
+ *
+ * Copyright (C) 2026 Strahinja Jankovic. <strahinja.p.jankovic@gmail.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef ALLWINNER_GPIO_H
+#define ALLWINNER_GPIO_H
+
+#include "hw/core/sysbus.h"
+#include "qom/object.h"
+
+/** Size of register I/O address space used by GPIO device */
+#define AW_GPIO_IOSIZE (0x400)
+
+/** Total number of known registers */
+#define AW_GPIO_REGS_NUM (AW_GPIO_IOSIZE / sizeof(uint32_t))
+
+/** Max number of ports */
+#define AW_GPIO_PORTS_NUM 9
+/** Max number of pins per ports */
+#define AW_GPIO_PIN_COUNT 32
+
+#define TYPE_AW_GPIO "allwinner.gpio"
+OBJECT_DECLARE_SIMPLE_TYPE(AWGPIOState, AW_GPIO)
+
+struct AWGPIOState {
+ /*< private >*/
+ SysBusDevice parent_obj;
+
+ /*< public >*/
+ MemoryRegion iomem;
+ qemu_irq irq;
+ qemu_irq output[AW_GPIO_PORTS_NUM][AW_GPIO_PIN_COUNT];
+
+ uint32_t regs[AW_GPIO_REGS_NUM];
+};
+
+#endif /* ALLWINNER_GPIO_H */
--
2.55.0
next prev parent reply other threads:[~2026-08-18 22:06 UTC|newest]
Thread overview: 6+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-18 22:04 [PATCH 0/3] Allwinner A10 GPIO support Strahinja Jankovic
2026-08-18 22:04 ` Strahinja Jankovic [this message]
2026-08-20 3:05 ` [PATCH 1/3] hw/gpio: Allwinner A10 GPIO emulation Jack Wang
2026-08-25 20:28 ` Strahinja Jankovic
2026-08-18 22:04 ` [PATCH 2/3] hw/arm: Add GPIO to Allwinner A10 model Strahinja Jankovic
2026-08-18 22:04 ` [PATCH 3/3] tests/qtest: Allwinner GPIO tests Strahinja Jankovic
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260819-allwinner-gpio-v1-1-2a64866dff11@gmail.com \
--to=strahinja.p.jankovic@gmail.com \
--cc=farosas@suse.de \
--cc=lvivier@redhat.com \
--cc=pbonzini@redhat.com \
--cc=peter.maydell@linaro.org \
--cc=pierrick.bouvier@oss.qualcomm.com \
--cc=qemu-arm@nongnu.org \
--cc=qemu-devel@nongnu.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.