Commit cc57a945fe for qemu.org
commit cc57a945feb0906af6b56591c790f60bfd65945c
Author: Kuan-Jui Chiu <kchiu@axiado.com>
Date: Tue Aug 11 20:14:11 2026 +0100
hw/gpio: Add Cadence GPIO controller
This patch add a new model for Cadence GPIO controller which
supports 32 pins and interrupts for level-triggered/edge-triggered type on
input pins.
Also define new trace functions for analysis purpose and new configuration to
enable this model.
Signed-off-by: Kuan-Jui Chiu <kchiu@axiado.com>
Message-id: 20260713073033.3883619-8-kchiu@axiado.com
[PMM: drop unnecessary <private> and <public> marker comments]
Reviewed-by: Peter Maydell <peter.maydell@linaro.org>
Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
diff --git a/hw/gpio/Kconfig b/hw/gpio/Kconfig
index a209294c20..fcc7c70bd5 100644
--- a/hw/gpio/Kconfig
+++ b/hw/gpio/Kconfig
@@ -30,3 +30,6 @@ config PCF8574
config ZAURUS_SCOOP
bool
+
+config CADENCE_GPIO
+ bool
diff --git a/hw/gpio/cadence_gpio.c b/hw/gpio/cadence_gpio.c
new file mode 100644
index 0000000000..ce8256b381
--- /dev/null
+++ b/hw/gpio/cadence_gpio.c
@@ -0,0 +1,292 @@
+/*
+ * Cadence GPIO emulation.
+ *
+ * Author: Kuan-Jui Chiu <kchiu@axiado.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "hw/gpio/cadence_gpio.h"
+#include "hw/core/irq.h"
+#include "migration/vmstate.h"
+#include "qemu/log.h"
+#include "trace.h"
+
+static void cdns_gpio_update_irq(CadenceGPIOState *s)
+{
+ qemu_set_irq(s->irq, s->isr ? 1 : 0);
+}
+
+static void cdns_gpio_update_isr_with_inpvr(CadenceGPIOState *s, uint32_t new)
+{
+ uint32_t new_isr = 0;
+ uint32_t any_edges, rising_edges, falling_edges, deassert_mask;
+
+ /*
+ * If ITR is set, this is level triggered:
+ * set corresponding ISR bits when IVR matches new inpvr value.
+ */
+ new_isr |= s->itr & ~(s->ivr ^ new);
+
+ /*
+ * If ITR is not set, this is edge-triggered:
+ * If IOAR bit is set, trigger on any edge;
+ * otherwise trigger on rising edge if IVR is set,
+ * trigger on falling edge if IVR bit is 0.
+ */
+ any_edges = s->ioar & (s->inpvr ^ new);
+ rising_edges = s->ivr & ~s->inpvr & new;
+ falling_edges = ~s->ivr & s->inpvr & ~new;
+ new_isr |= ~s->itr & (any_edges | rising_edges | falling_edges);
+
+ /*
+ * In bypass mode or if this isn't an input pin, the corresponding ISR
+ * bit is forced to zero.
+ */
+ deassert_mask = s->bmr | ~s->dmr | s->imr;
+
+ new_isr &= ~deassert_mask;
+ s->isr = new_isr;
+
+ cdns_gpio_update_irq(s);
+}
+
+static void cdns_gpio_update_isr(CadenceGPIOState *s)
+{
+ cdns_gpio_update_isr_with_inpvr(s, s->inpvr);
+}
+
+static void cdns_gpio_set(void *opaque, int line, int level)
+{
+ CadenceGPIOState *s = CADENCE_GPIO(opaque);
+ uint32_t new_inpvr = deposit32(s->inpvr, line, 1, level ? 1 : 0);
+
+ trace_cdns_gpio_set(DEVICE(s)->canonical_path, line, level);
+
+ cdns_gpio_update_isr_with_inpvr(s, new_inpvr);
+
+ /* Sync INPVR with new value */
+ s->inpvr = new_inpvr;
+}
+
+static inline void cdns_gpio_update_output_irq(CadenceGPIOState *s)
+{
+ uint32_t is_output = ~s->bmr & ~s->dmr & s->oer;
+
+ for (int i = 0; i < CDNS_GPIO_NUM; i++) {
+ if (extract32(is_output, i, 1)) {
+ /* Forward the output value to corresponding irq */
+ qemu_set_irq(s->output[i], extract32(s->ovr, i, 1));
+ }
+ }
+}
+
+static uint64_t cdns_gpio_read(void *opaque, hwaddr offset, unsigned size)
+{
+ CadenceGPIOState *s = CADENCE_GPIO(opaque);
+ uint32_t reg_value = 0x0;
+
+ switch (offset) {
+ case CDNS_GPIO_BYPASS_MODE:
+ reg_value = s->bmr;
+ break;
+
+ case CDNS_GPIO_DIRECTION_MODE:
+ reg_value = s->dmr;
+ break;
+
+ case CDNS_GPIO_OUTPUT_EN:
+ reg_value = s->oer;
+ break;
+
+ case CDNS_GPIO_OUTPUT_VALUE:
+ reg_value = s->ovr;
+ break;
+
+ case CDNS_GPIO_INPUT_VALUE:
+ reg_value = s->inpvr;
+ break;
+
+ case CDNS_GPIO_IRQ_MASK:
+ reg_value = s->imr;
+ break;
+
+ case CDNS_GPIO_IRQ_STATUS:
+ reg_value = s->isr;
+ break;
+
+ case CDNS_GPIO_IRQ_TYPE:
+ reg_value = s->itr;
+ break;
+
+ case CDNS_GPIO_IRQ_VALUE:
+ reg_value = s->ivr;
+ break;
+
+ case CDNS_GPIO_IRQ_ANY_EDGE:
+ reg_value = s->ioar;
+ break;
+
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR, "[%s]%s: Bad register at offset 0x%"
+ HWADDR_PRIx "\n", TYPE_CADENCE_GPIO, __func__, offset);
+ break;
+ }
+
+ trace_cdns_gpio_read(DEVICE(s)->canonical_path, offset, reg_value);
+
+ return reg_value;
+}
+
+static void cdns_gpio_write(void *opaque, hwaddr offset, uint64_t value,
+ unsigned size)
+{
+ CadenceGPIOState *s = CADENCE_GPIO(opaque);
+
+ trace_cdns_gpio_write(DEVICE(s)->canonical_path, offset, value);
+
+ switch (offset) {
+ case CDNS_GPIO_BYPASS_MODE:
+ s->bmr = value;
+ cdns_gpio_update_output_irq(s);
+ cdns_gpio_update_isr(s);
+ break;
+
+ case CDNS_GPIO_DIRECTION_MODE:
+ s->dmr = value;
+ cdns_gpio_update_output_irq(s);
+ cdns_gpio_update_isr(s);
+ break;
+
+ case CDNS_GPIO_OUTPUT_EN:
+ s->oer = value;
+ cdns_gpio_update_output_irq(s);
+ break;
+
+ case CDNS_GPIO_OUTPUT_VALUE:
+ s->ovr = value;
+ cdns_gpio_update_output_irq(s);
+ break;
+
+ case CDNS_GPIO_IRQ_EN:
+ s->imr &= ~value;
+ cdns_gpio_update_isr(s);
+ break;
+
+ case CDNS_GPIO_IRQ_DIS:
+ s->imr |= value;
+ cdns_gpio_update_isr(s);
+ break;
+
+ case CDNS_GPIO_IRQ_TYPE:
+ s->itr = value;
+ break;
+
+ case CDNS_GPIO_IRQ_VALUE:
+ s->ivr = value;
+ break;
+
+ case CDNS_GPIO_IRQ_ANY_EDGE:
+ s->ioar = value;
+ break;
+
+ case CDNS_GPIO_INPUT_VALUE:
+ case CDNS_GPIO_IRQ_MASK:
+ case CDNS_GPIO_IRQ_STATUS:
+ /* Read-Only */
+ break;
+
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR, "[%s]%s: Bad register at offset 0x%"
+ HWADDR_PRIx "\n", TYPE_CADENCE_GPIO, __func__, offset);
+ break;
+ }
+}
+
+static const MemoryRegionOps cdns_gpio_ops = {
+ .read = cdns_gpio_read,
+ .write = cdns_gpio_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .impl = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ }
+};
+
+static const VMStateDescription vmstate_cdns_gpio = {
+ .name = TYPE_CADENCE_GPIO,
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (const VMStateField[]) {
+ VMSTATE_UINT32(bmr, CadenceGPIOState),
+ VMSTATE_UINT32(dmr, CadenceGPIOState),
+ VMSTATE_UINT32(oer, CadenceGPIOState),
+ VMSTATE_UINT32(ovr, CadenceGPIOState),
+ VMSTATE_UINT32(inpvr, CadenceGPIOState),
+ VMSTATE_UINT32(imr, CadenceGPIOState),
+ VMSTATE_UINT32(isr, CadenceGPIOState),
+ VMSTATE_UINT32(itr, CadenceGPIOState),
+ VMSTATE_UINT32(ivr, CadenceGPIOState),
+ VMSTATE_UINT32(ioar, CadenceGPIOState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void cdns_gpio_reset(DeviceState *dev)
+{
+ CadenceGPIOState *s = CADENCE_GPIO(dev);
+
+ s->bmr = 0;
+ s->dmr = 0;
+ s->oer = 0;
+ s->ovr = 0;
+ s->inpvr = 0;
+ s->imr = 0xffffffff;
+ s->isr = 0;
+ s->itr = 0;
+ s->ivr = 0;
+ s->ioar = 0;
+}
+
+static void cdns_gpio_init(Object *obj)
+{
+ CadenceGPIOState *s = CADENCE_GPIO(obj);
+
+ memory_region_init_io(&s->iomem, obj, &cdns_gpio_ops, s,
+ TYPE_CADENCE_GPIO, CDNS_GPIO_REG_SIZE);
+
+ qdev_init_gpio_in(DEVICE(s), cdns_gpio_set, CDNS_GPIO_NUM);
+ qdev_init_gpio_out(DEVICE(s), s->output, CDNS_GPIO_NUM);
+
+ sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->irq);
+ sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->iomem);
+}
+
+static void cdns_gpio_class_init(ObjectClass *klass, const void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ device_class_set_legacy_reset(dc, cdns_gpio_reset);
+ dc->vmsd = &vmstate_cdns_gpio;
+ dc->desc = "Cadence GPIO controller";
+}
+
+static const TypeInfo cdns_gpio_info = {
+ .name = TYPE_CADENCE_GPIO,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(CadenceGPIOState),
+ .instance_init = cdns_gpio_init,
+ .class_init = cdns_gpio_class_init,
+};
+
+static void cdns_gpio_register_types(void)
+{
+ type_register_static(&cdns_gpio_info);
+}
+
+type_init(cdns_gpio_register_types)
diff --git a/hw/gpio/meson.build b/hw/gpio/meson.build
index 6a67ee958f..0555f44b6a 100644
--- a/hw/gpio/meson.build
+++ b/hw/gpio/meson.build
@@ -19,3 +19,4 @@ system_ss.add(when: 'CONFIG_ASPEED_SOC', if_true: files('aspeed_gpio.c'))
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'))
diff --git a/hw/gpio/trace-events b/hw/gpio/trace-events
index cea896b28f..80ca783a03 100644
--- a/hw/gpio/trace-events
+++ b/hw/gpio/trace-events
@@ -46,3 +46,8 @@ stm32l4x5_gpio_read(char *gpio, uint64_t addr) "GPIO%s addr: 0x%" PRIx64 " "
stm32l4x5_gpio_write(char *gpio, uint64_t addr, uint64_t data) "GPIO%s addr: 0x%" PRIx64 " val: 0x%" PRIx64 ""
stm32l4x5_gpio_update_idr(char *gpio, uint32_t old_idr, uint32_t new_idr) "GPIO%s from: 0x%x to: 0x%x"
stm32l4x5_gpio_pins(char *gpio, uint16_t disconnected, uint16_t high) "GPIO%s disconnected pins: 0x%x levels: 0x%x"
+
+# cadence_gpio.c
+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"
diff --git a/include/hw/gpio/cadence_gpio.h b/include/hw/gpio/cadence_gpio.h
new file mode 100644
index 0000000000..69646ba639
--- /dev/null
+++ b/include/hw/gpio/cadence_gpio.h
@@ -0,0 +1,53 @@
+/*
+ * Cadence GPIO registers definition.
+ *
+ * Author: Kuan-Jui Chiu <kchiu@axiado.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef CADENCE_GPIO_H
+#define CADENCE_GPIO_H
+
+#include "hw/core/sysbus.h"
+#include "qom/object.h"
+
+#define TYPE_CADENCE_GPIO "cadence_gpio"
+OBJECT_DECLARE_SIMPLE_TYPE(CadenceGPIOState, CADENCE_GPIO)
+
+#define CDNS_GPIO_REG_SIZE 0x400
+#define CDNS_GPIO_NUM 32
+
+#define CDNS_GPIO_BYPASS_MODE 0x00
+#define CDNS_GPIO_DIRECTION_MODE 0x04
+#define CDNS_GPIO_OUTPUT_EN 0x08
+#define CDNS_GPIO_OUTPUT_VALUE 0x0c
+#define CDNS_GPIO_INPUT_VALUE 0x10
+#define CDNS_GPIO_IRQ_MASK 0x14
+#define CDNS_GPIO_IRQ_EN 0x18
+#define CDNS_GPIO_IRQ_DIS 0x1c
+#define CDNS_GPIO_IRQ_STATUS 0x20
+#define CDNS_GPIO_IRQ_TYPE 0x24
+#define CDNS_GPIO_IRQ_VALUE 0x28
+#define CDNS_GPIO_IRQ_ANY_EDGE 0x2c
+
+struct CadenceGPIOState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+
+ uint32_t bmr;
+ uint32_t dmr;
+ uint32_t oer;
+ uint32_t ovr;
+ uint32_t inpvr;
+ uint32_t imr;
+ uint32_t isr;
+ uint32_t itr;
+ uint32_t ivr;
+ uint32_t ioar;
+ qemu_irq irq;
+ qemu_irq output[CDNS_GPIO_NUM];
+};
+
+#endif /* CADENCE_GPIO_H */