Hello,

On 8/5/2026 12:14 PM, [email protected] wrote:
From: guochun wang <[email protected]>

Implement a K230 SoC APB GPIO controller model for QEMU, derived from
the Synopsys DesignWare APB GPIO (the Linux gpio-k230 driver is based
on gpio-dwapb.c and shares the same register layout), with a
Canaan-specific compatible ("canaan,k230-apb-gpio") and hardlock
integration. The model is capable of running the Linux gpio-k230 driver
and driving external input/output lines. Behaviour follows the K230
Technical Reference Manual v0.3.1.

Implemented:
   - Port A registers: SWPORTA_DR/DDR/CTL, EXT_PORTA, INTEN, INTMASK,
     INTTYPE_LEVEL, INT_POLARITY, INTSTATUS, RAW_INTSTATUS, DEBOUNCE,
     LS_SYNC, INT_BOTHEDGE, PORTA_EOI, ID_CODE, VER_ID_CODE,
     CONFIG_REG1/2
   - Software control mode: DR drives output pad, DDR selects direction,
     EXT_PORTA multiplexes external input vs. DR by DDR
   - Per-pin IRQ output lines (single interrupt scheme), 32 per group
   - Edge detection: rising / falling / both-edge via INT_BOTHEDGE
   - Level-sensitive interrupts: active-high / active-low
   - PORTA_EOI clears edge interrupts only; level interrupts cleared by
     source removal or INTMASK
   - INTMASK masking and INTEN gating
   - DDR/CTL mode switching: no new interrupts while output/hardware mode,
     pending interrupts preserved; on switch back to input/software mode,
     level interrupts re-evaluated against current ext level, edge left
     untouched, per TRM v0.3.1
   - Three-phase Resettable API (enter/hold); VMState migration

Not implemented (no observable effect under QEMU):
   - Hardware control mode (aux_porta_out/en/in signals, single-ctl params)
   - Debounce logic (dbclk, glitch filtering, both-edge debounce timing)
   - Combined interrupt output (gpio_intr_flag OR of all lines)
   - Clock-domain synchronization (pclk_intr, ls_sync metastability
     registers, gpio_intrclk_en output)
   - Config-time parameters GPIO_PA_SYNC_EXT_DATA / GPIO_PA_SYNC_INTERRUPTS
     / GPIO_INT_BOTH_EDGE / GPIO_PORTX_SINGLE_CTL

Two controllers are instantiated in the K230 SoC (gpio0 at 0x9140B000,
gpio1 at 0x9140C000), replacing the previous unimplemented-device
placeholders. Each controller exposes 32 per-pin IRQ lines connected
to the PLIC:
   - GPIO0: PLIC sources 32..63
   - GPIO1: PLIC sources 64..95

Signed-off-by: guochun wang <[email protected]>
---

LGTM.  Some comments below:

  MAINTAINERS                 |   2 +
  hw/gpio/Kconfig             |   3 +
  hw/gpio/k230_gpio.c         | 362 ++++++++++++++++++++++++++++++++++++
  hw/gpio/meson.build         |   1 +
  hw/riscv/Kconfig            |   1 +
  hw/riscv/k230.c             |  27 ++-
  include/hw/gpio/k230_gpio.h |  73 ++++++++
  include/hw/riscv/k230.h     |   5 +
  8 files changed, 468 insertions(+), 6 deletions(-)
  create mode 100644 hw/gpio/k230_gpio.c
  create mode 100644 include/hw/gpio/k230_gpio.h

diff --git a/MAINTAINERS b/MAINTAINERS
index 902db77218..4301d29f7e 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1827,8 +1827,10 @@ M: Chao Liu <[email protected]>
  L: [email protected]
  S: Maintained
  F: docs/system/riscv/k230.rst
+F: hw/gpio/k230_gpio.c
  F: hw/riscv/k230.c
  F: hw/watchdog/k230_wdt.c
+F: include/hw/gpio/k230_gpio.h
  F: include/hw/riscv/k230.h
  F: include/hw/watchdog/k230_wdt.h
  F: tests/functional/riscv64/test_k230.py
diff --git a/hw/gpio/Kconfig b/hw/gpio/Kconfig
index a209294c20..2d37a8bb6f 100644
--- a/hw/gpio/Kconfig
+++ b/hw/gpio/Kconfig
@@ -16,6 +16,9 @@ config SIFIVE_GPIO
  config STM32L4X5_GPIO
      bool
+config K230_GPIO
+    bool
+
  config PCA9552
      bool
      depends on I2C
diff --git a/hw/gpio/k230_gpio.c b/hw/gpio/k230_gpio.c
new file mode 100644
index 0000000000..a589d94712
--- /dev/null
+++ b/hw/gpio/k230_gpio.c
@@ -0,0 +1,362 @@
+/*
+ * QEMU K230 GPIO Controller
+ *
+ * Copyright (c) 2025 Wang Guochun <[email protected]>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * K230 Technical Reference Manual V0.3.1 (2024-11-18), section 12.5 GPIO
+ * 
https://github.com/revyos/external-docs/blob/master/K230/en-us/K230_Technical_Reference_Manual_V0.3.1_20241118.pdf
+ */
+
+#include "hw/gpio/k230_gpio.h"
+#include "hw/core/irq.h"
+#include "migration/vmstate.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+
+static void k230_gpio_update_int(K230GPIOState *s)
+{
+    uint32_t masked_status = s->raw_intstatus & ~s->intmask & s->inten;
+    for (int i = 0; i < K230_GPIO_PINS_PER_GROUP; i++) {
+        if (s->irq[i]) {
+            qemu_set_irq(s->irq[i], extract32(masked_status, i, 1));
+        }
+    }
+}
+
+static void k230_gpio_set_int_line(K230GPIOState *s, int line, int level)
+{
+    if (line >= K230_GPIO_PINS_PER_GROUP) {
+        return;
+    }
+
+    uint32_t prev_level = extract32(s->ext_porta, line, 1);
+    uint32_t curr_level = level;
+
+    s->ext_porta = deposit32(s->ext_porta, line, 1, curr_level);
+
+    if (extract32(s->swporta_ddr, line, 1)) {
+        return;
+    }
+
+    if (extract32(s->swporta_ctl, line, 1)) {
+        return;
+    }
+
+    uint32_t pol = extract32(s->int_polarity, line, 1);
+    uint32_t both = extract32(s->int_bothedge, line, 1);
+    uint32_t is_level = !extract32(s->inttype_level, line, 1);
+
+    if (both) {
+        if (prev_level != curr_level) {
+            s->raw_intstatus |= (1U << line);
+        }
+    } else if (is_level) {
+        if (curr_level == pol) {
+            s->raw_intstatus |= (1U << line);
+        } else {
+            s->raw_intstatus &= ~(1U << line);
+        }
+    } else {
+        if (prev_level != curr_level && curr_level == pol) {
+            s->raw_intstatus |= (1U << line);
+        }
+    }
+}
+
+static void k230_gpio_set(void *opaque, int line, int level)
+{
+    K230GPIOState *s = K230_GPIO(opaque);
+
+    k230_gpio_set_int_line(s, line, level);
+    k230_gpio_update_int(s);
+}
+
+static void k230_gpio_set_all_output_lines(K230GPIOState *s)
+{
+    int i;
+
+    for (i = 0; i < K230_GPIO_PINS_PER_GROUP; i++) {
+        if (extract32(s->swporta_ddr, i, 1) && s->output[i]) {
+            qemu_set_irq(s->output[i], extract32(s->swporta_dr, i, 1));
+        }
+    }
+}
+
+static uint64_t k230_gpio_read(void *opaque, hwaddr offset, unsigned size)
+{
+    K230GPIOState *s = K230_GPIO(opaque);
+
+    switch (offset) {
+    case K230_GPIO_SWPORTA_DR:
+        return s->swporta_dr;
+    case K230_GPIO_SWPORTA_DDR:
+        return s->swporta_ddr;
+    case K230_GPIO_SWPORTA_CTL:
+        return s->swporta_ctl;
+    case K230_GPIO_INTEN:
+        return s->inten;
+    case K230_GPIO_INTMASK:
+        return s->intmask;
+    case K230_GPIO_INTTYPE_LEVEL:
+        return s->inttype_level;
+    case K230_GPIO_INT_POLARITY:
+        return s->int_polarity;
+    case K230_GPIO_INTSTATUS:
+        return s->raw_intstatus & ~s->intmask;
+    case K230_GPIO_RAW_INTSTATUS:
+        return s->raw_intstatus;
+    case K230_GPIO_DEBOUNCE:
+        return s->debounce;
+    case K230_GPIO_EXT_PORTA:
+        return (s->ext_porta & ~s->swporta_ddr) |
+               (s->swporta_dr & s->swporta_ddr);
+    case K230_GPIO_LS_SYNC:
+        return s->ls_sync;
+    case K230_GPIO_ID_CODE:
+        return s->id_code;
+    case K230_GPIO_INT_BOTHEDGE:
+        return s->int_bothedge;
+    case K230_GPIO_VER_ID_CODE:
+        return s->ver_id_code;
+    case K230_GPIO_CONFIG_REG2:
+        return s->config_reg2;
+    case K230_GPIO_CONFIG_REG1:
+        return s->config_reg1;
+    case K230_GPIO_PORTA_EOI:
+        return 0;
+    default:
+        qemu_log_mask(LOG_GUEST_ERROR, "[%s]%s: Bad register at offset 0x%"
+                      HWADDR_PRIx "\n", TYPE_K230_GPIO, __func__, offset);
+        return 0;
+    }
+}
+
+static void k230_gpio_write(void *opaque, hwaddr offset, uint64_t value,
+                            unsigned size)
+{
+    K230GPIOState *s = K230_GPIO(opaque);
+
+    switch (offset) {
+    case K230_GPIO_SWPORTA_DR:
+        s->swporta_dr = value;
+        k230_gpio_set_all_output_lines(s);
+        break;
+    case K230_GPIO_SWPORTA_DDR: {
+        uint32_t prev_ddr = s->swporta_ddr;
+        s->swporta_ddr = value;
+        k230_gpio_set_all_output_lines(s);
+        for (int i = 0; i < K230_GPIO_PINS_PER_GROUP; i++) {
+            /* Skip pins whose direction did not change */
+            if (extract32(prev_ddr, i, 1) == extract32(s->swporta_ddr, i, 1)) {
+                continue;
+            }
+            /*
+             * When switching back to input mode (and still in software
+             * control), re-evaluate level-sensitive interrupts. Edge
+             * interrupts are left untouched, matching the manual TRM v0.3.1.
+             */
+            if (!extract32(s->swporta_ddr, i, 1) &&
+                !extract32(s->swporta_ctl, i, 1)) {
+                uint32_t is_level = !extract32(s->inttype_level, i, 1);
+                if (is_level) {
+                    uint32_t curr_level = extract32(s->ext_porta, i, 1);
+                    uint32_t pol = extract32(s->int_polarity, i, 1);
+                    if (curr_level == pol) {
+                        s->raw_intstatus |= (1U << i);
+                    } else {
+                        s->raw_intstatus &= ~(1U << i);
+                    }
+                }
+            }


This is a cosmetic/style suggestion so feel free to ignore it.

I like what you did in the 'skip pins' code above with the early 'continue'. IMO
you should do the same here since you're only doing things after 3 conditions
are met.  I.e. this:


> +            if (!extract32(s->swporta_ddr, i, 1) &&
> +                !extract32(s->swporta_ctl, i, 1)) {
> +                uint32_t is_level = !extract32(s->inttype_level, i, 1);
> +                if (is_level) {
> +                    uint32_t curr_level = extract32(s->ext_porta, i, 1);
> +                    uint32_t pol = extract32(s->int_polarity, i, 1);
> +                    if (curr_level == pol) {
> +                        s->raw_intstatus |= (1U << i);
> +                    } else {
> +                        s->raw_intstatus &= ~(1U << i);
> +                    }
> +                }
> +            }

Can be written as:

              if (extract32(s->swporta_ddr, i, 1)
                  || extract32(s->swporta_ctl, i, 1)
                  || extract32(s->inttype_level, i, 1))
                 continue;

              /* At this point is_level is true */
              uint32_t curr_level = extract32(s->ext_porta, i, 1);
              uint32_t pol = extract32(s->int_polarity, i, 1);
              if (curr_level == pol) {
                  s->raw_intstatus |= (1U << i);
              } else {
                  s->raw_intstatus &= ~(1U << i);
              }

This will spare us 2 identation levels and, at least to me, is a bit clearer.


 In case you agree I believe you'll have to declare 'curr_level' and 'pol' at
k230_gpio_write() start to avoid creating variables in the middle of a block
too.

Again, just a cosmetic suggestion.


+        }
+        k230_gpio_update_int(s);
+        break;
+    }
+    case K230_GPIO_SWPORTA_CTL: {
+        uint32_t prev_ctl = s->swporta_ctl;
+        s->swporta_ctl = value;
+        for (int i = 0; i < K230_GPIO_PINS_PER_GROUP; i++) {
+            /* Skip pins whose control mode did not change */
+            if (extract32(prev_ctl, i, 1) == extract32(s->swporta_ctl, i, 1)) {
+                continue;
+            }
+            /*
+             * When switching back from hardware to software control (and DDR
+             * is input), re-evaluate level-sensitive interrupts. Edge
+             * interrupts are left untouched, matching the manual TRM v0.3.1.
+             */
+            if (!extract32(s->swporta_ddr, i, 1) &&
+                !extract32(s->swporta_ctl, i, 1)) {
+                uint32_t is_level = !extract32(s->inttype_level, i, 1);
+                if (is_level) {
+                    uint32_t curr_level = extract32(s->ext_porta, i, 1);
+                    uint32_t pol = extract32(s->int_polarity, i, 1);
+                    if (curr_level == pol) {
+                        s->raw_intstatus |= (1U << i);
+                    } else {
+                        s->raw_intstatus &= ~(1U << i);
+                    }
+                }
+            }

Likewise.

+        }
+        k230_gpio_update_int(s);
+        break;
+    }
+    case K230_GPIO_INTEN:
+        s->inten = value;
+        k230_gpio_update_int(s);
+        break;
+    case K230_GPIO_INTMASK:
+        s->intmask = value;
+        k230_gpio_update_int(s);
+        break;
+    case K230_GPIO_INTTYPE_LEVEL:
+        s->inttype_level = value;
+        break;
+    case K230_GPIO_INT_POLARITY:
+        s->int_polarity = value;
+        break;
+    case K230_GPIO_DEBOUNCE:
+        s->debounce = value;
+        break;
+    case K230_GPIO_PORTA_EOI:
+        for (int i = 0; i < K230_GPIO_PINS_PER_GROUP; i++) {
+            if (extract32(value, i, 1) && extract32(s->inttype_level, i, 1)) {
+                s->raw_intstatus = deposit32(s->raw_intstatus, i, 1, 0);
+            }
+        }
+        k230_gpio_update_int(s);
+        break;
+    case K230_GPIO_LS_SYNC:
+        s->ls_sync = value;
+        break;
+    case K230_GPIO_ID_CODE:
+        break;
+    case K230_GPIO_INT_BOTHEDGE:
+        s->int_bothedge = value;
+        break;
+    case K230_GPIO_VER_ID_CODE:
+        break;
+    case K230_GPIO_CONFIG_REG2:
+        break;
+    case K230_GPIO_CONFIG_REG1:
+        break;
+    case K230_GPIO_INTSTATUS:
+        break;
+    case K230_GPIO_RAW_INTSTATUS:
+        break;
+    case K230_GPIO_EXT_PORTA:
+        break;
+    default:
+        qemu_log_mask(LOG_GUEST_ERROR, "[%s]%s: Bad register at offset 0x%"
+                      HWADDR_PRIx "\n", TYPE_K230_GPIO, __func__, offset);
+        break;
+    }
+}
+
+static const MemoryRegionOps k230_gpio_ops = {
+    .read = k230_gpio_read,
+    .write = k230_gpio_write,
+    .valid.min_access_size = 4,
+    .valid.max_access_size = 4,
+    .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static const VMStateDescription vmstate_k230_gpio = {
+    .name = TYPE_K230_GPIO,
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .fields = (const VMStateField[]) {
+        VMSTATE_UINT32(swporta_dr, K230GPIOState),
+        VMSTATE_UINT32(swporta_ddr, K230GPIOState),
+        VMSTATE_UINT32(swporta_ctl, K230GPIOState),
+        VMSTATE_UINT32(inten, K230GPIOState),
+        VMSTATE_UINT32(intmask, K230GPIOState),
+        VMSTATE_UINT32(inttype_level, K230GPIOState),
+        VMSTATE_UINT32(int_polarity, K230GPIOState),
+        VMSTATE_UINT32(intstatus, K230GPIOState),

It seems to me that you're not using/writing s->intstatus througout the
code.  IIUC you're using 'raw_intstatus' for everything, and when intstatus
is required you use raw_intstatus and intmask to derive it.  E.g.:

> +    case K230_GPIO_INTSTATUS:
> +        return s->raw_intstatus & ~s->intmask;


I believe we're better off by removing s->intstatus entirely.  The logic
doesn't use it and we'll spare a field in the migration stream.

One more thing:


+        VMSTATE_UINT32(raw_intstatus, K230GPIOState),
+        VMSTATE_UINT32(debounce, K230GPIOState),
+        VMSTATE_UINT32(ext_porta, K230GPIOState),
+        VMSTATE_UINT32(ls_sync, K230GPIOState),
+        VMSTATE_UINT32(int_bothedge, K230GPIOState),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+static void k230_gpio_enter_reset(Object *obj, ResetType type)
+{
+    K230GPIOState *s = K230_GPIO(obj);
+
+    s->swporta_dr = 0;
+    s->swporta_ddr = 0;
+    s->swporta_ctl = 0;
+    s->inten = 0;
+    s->intmask = 0;
+    s->inttype_level = 0;
+    s->int_polarity = 0;
+    s->intstatus = 0;
+    s->raw_intstatus = 0;
+    s->debounce = 0;
+    s->porta_eoi = 0;
+    s->ext_porta = 0;
+    s->ls_sync = 0;
+    s->id_code = 0;
+    s->int_bothedge = 0;
+    s->ver_id_code = 0;
+    s->config_reg2 = 0;
+    s->config_reg1 = 0;
+}
+
+static void k230_gpio_hold_reset(Object *obj, ResetType type)
+{
+    K230GPIOState *s = K230_GPIO(obj);
+
+    k230_gpio_update_int(s);
+}
+
+static void k230_gpio_realize(DeviceState *dev, Error **errp)
+{
+    K230GPIOState *s = K230_GPIO(dev);
+    SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+    int i;
+
+    memory_region_init_io(&s->iomem, OBJECT(s), &k230_gpio_ops, s,
+                          TYPE_K230_GPIO, K230_GPIO_MEM_SIZE);
+    sysbus_init_mmio(sbd, &s->iomem);
+
+    qdev_init_gpio_in(DEVICE(s), k230_gpio_set, K230_GPIO_PINS_PER_GROUP);
+    qdev_init_gpio_out(DEVICE(s), s->output, K230_GPIO_PINS_PER_GROUP);
+
+    for (i = 0; i < K230_GPIO_PINS_PER_GROUP; i++) {
+        sysbus_init_irq(sbd, &s->irq[i]);
+    }
+}
+
+static void k230_gpio_class_init(ObjectClass *klass, const void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+    ResettableClass *rc = RESETTABLE_CLASS(klass);
+
+    dc->realize = k230_gpio_realize;
+    rc->phases.enter = k230_gpio_enter_reset;
+    rc->phases.hold = k230_gpio_hold_reset;
+    dc->vmsd = &vmstate_k230_gpio;
+    dc->desc = "K230 GPIO controller";
+}
+
+static const TypeInfo k230_gpio_info = {
+    .name = TYPE_K230_GPIO,
+    .parent = TYPE_SYS_BUS_DEVICE,
+    .instance_size = sizeof(K230GPIOState),
+    .class_init = k230_gpio_class_init,
+};
+
+static void k230_gpio_register_types(void)
+{
+    type_register_static(&k230_gpio_info);
+}
+
+type_init(k230_gpio_register_types)
diff --git a/hw/gpio/meson.build b/hw/gpio/meson.build
index 6a67ee958f..94b5d9e2fa 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_K230_GPIO', if_true: files('k230_gpio.c'))
diff --git a/hw/riscv/Kconfig b/hw/riscv/Kconfig
index de37c08cae..410877425c 100644
--- a/hw/riscv/Kconfig
+++ b/hw/riscv/Kconfig
@@ -162,3 +162,4 @@ config K230
      select SERIAL_MM
      select UNIMP
      select K230_WDT
+    select K230_GPIO
diff --git a/hw/riscv/k230.c b/hw/riscv/k230.c
index 656f28190c..62967a67d0 100644
--- a/hw/riscv/k230.c
+++ b/hw/riscv/k230.c
@@ -110,6 +110,8 @@ static void k230_soc_init(Object *obj)
      object_initialize_child(obj, "c908-cpu", cpu0, TYPE_RISCV_HART_ARRAY);
      object_initialize_child(obj, "k230-wdt0", &s->wdt[0], TYPE_K230_WDT);
      object_initialize_child(obj, "k230-wdt1", &s->wdt[1], TYPE_K230_WDT);
+    object_initialize_child(obj, "k230-gpio0", &s->gpio[0], TYPE_K230_GPIO);
+    object_initialize_child(obj, "k230-gpio1", &s->gpio[1], TYPE_K230_GPIO);
qdev_prop_set_uint32(DEVICE(cpu0), "hartid-base", 0);
      qdev_prop_set_string(DEVICE(cpu0), "cpu-type", TYPE_RISCV_CPU_THEAD_C908);
@@ -206,6 +208,25 @@ static void k230_soc_realize(DeviceState *dev, Error 
**errp)
      sysbus_connect_irq(SYS_BUS_DEVICE(&s->wdt[1]), 0,
                         qdev_get_gpio_in(DEVICE(s->c908_plic), K230_WDT1_IRQ));
+ /* GPIO */
+    sysbus_realize(SYS_BUS_DEVICE(&s->gpio[0]), &error_fatal);
+    sysbus_mmio_map(SYS_BUS_DEVICE(&s->gpio[0]), 0,
+                    memmap[K230_DEV_GPIO0].base);
+    for (int i = 0; i < K230_GPIO_PINS_PER_GROUP; i++) {
+        sysbus_connect_irq(SYS_BUS_DEVICE(&s->gpio[0]), i,
+                           qdev_get_gpio_in(DEVICE(s->c908_plic),
+                                            K230_GPIO0_IRQ_BASE + i));
+    }
+
+    sysbus_realize(SYS_BUS_DEVICE(&s->gpio[1]), &error_fatal);
+    sysbus_mmio_map(SYS_BUS_DEVICE(&s->gpio[1]), 0,
+                    memmap[K230_DEV_GPIO1].base);
+    for (int i = 0; i < K230_GPIO_PINS_PER_GROUP; i++) {
+        sysbus_connect_irq(SYS_BUS_DEVICE(&s->gpio[1]), i,
+                           qdev_get_gpio_in(DEVICE(s->c908_plic),
+                                            K230_GPIO1_IRQ_BASE + i));
+    }
+
      /* unimplemented devices */
      create_unimplemented_device("kpu.l2-cache",
                                  memmap[K230_DEV_KPU_L2_CACHE].base,
@@ -322,12 +343,6 @@ static void k230_soc_realize(DeviceState *dev, Error 
**errp)
      create_unimplemented_device("pwm", memmap[K230_DEV_PWM].base,
                                  memmap[K230_DEV_PWM].size);
- create_unimplemented_device("gpio0", memmap[K230_DEV_GPIO0].base,
-                                memmap[K230_DEV_GPIO0].size);
-
-    create_unimplemented_device("gpio1", memmap[K230_DEV_GPIO1].base,
-                                memmap[K230_DEV_GPIO1].size);
-
      create_unimplemented_device("adc", memmap[K230_DEV_ADC].base,
                                  memmap[K230_DEV_ADC].size);
diff --git a/include/hw/gpio/k230_gpio.h b/include/hw/gpio/k230_gpio.h
new file mode 100644
index 0000000000..2e52bbf760
--- /dev/null
+++ b/include/hw/gpio/k230_gpio.h
@@ -0,0 +1,73 @@
+/*
+ * QEMU K230 GPIO Controller
+ *
+ * Copyright (c) 2025 Wang Guochun <[email protected]>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * K230 Technical Reference Manual V0.3.1 (2024-11-18), section 12.5 GPIO
+ * 
https://github.com/revyos/external-docs/blob/master/K230/en-us/K230_Technical_Reference_Manual_V0.3.1_20241118.pdf
+ */
+
+#ifndef HW_K230_GPIO_H
+#define HW_K230_GPIO_H
+
+#include "qemu/osdep.h"

Please do not add osdep.h in header files.  Move it to k230_gpio.c.


Thanks,
Daniel

+#include "hw/core/sysbus.h"
+#include "qom/object.h"
+
+#define TYPE_K230_GPIO "k230.gpio"
+OBJECT_DECLARE_SIMPLE_TYPE(K230GPIOState, K230_GPIO)
+
+#define K230_GPIO_MEM_SIZE 0x1000
+
+#define K230_GPIO_SWPORTA_DR      0x00
+#define K230_GPIO_SWPORTA_DDR     0x04
+#define K230_GPIO_SWPORTA_CTL     0x08
+#define K230_GPIO_INTEN           0x30
+#define K230_GPIO_INTMASK         0x34
+#define K230_GPIO_INTTYPE_LEVEL   0x38
+#define K230_GPIO_INT_POLARITY    0x3c
+#define K230_GPIO_INTSTATUS       0x40
+#define K230_GPIO_RAW_INTSTATUS   0x44
+#define K230_GPIO_DEBOUNCE        0x48
+#define K230_GPIO_PORTA_EOI       0x4c
+#define K230_GPIO_EXT_PORTA       0x50
+#define K230_GPIO_LS_SYNC         0x60
+#define K230_GPIO_ID_CODE         0x64
+#define K230_GPIO_INT_BOTHEDGE    0x68
+#define K230_GPIO_VER_ID_CODE     0x6c
+#define K230_GPIO_CONFIG_REG2     0x70
+#define K230_GPIO_CONFIG_REG1     0x74
+
+#define K230_GPIO_PINS_PER_GROUP 32
+
+struct K230GPIOState {
+    SysBusDevice parent_obj;
+
+    MemoryRegion iomem;
+
+    uint32_t swporta_dr;
+    uint32_t swporta_ddr;
+    uint32_t swporta_ctl;
+    uint32_t inten;
+    uint32_t intmask;
+    uint32_t inttype_level;
+    uint32_t int_polarity;
+    uint32_t intstatus;
+    uint32_t raw_intstatus;
+    uint32_t debounce;
+    uint32_t porta_eoi;
+    uint32_t ext_porta;
+    uint32_t ls_sync;
+    uint32_t id_code;
+    uint32_t int_bothedge;
+    uint32_t ver_id_code;
+    uint32_t config_reg2;
+    uint32_t config_reg1;
+
+    qemu_irq irq[K230_GPIO_PINS_PER_GROUP];
+    qemu_irq output[K230_GPIO_PINS_PER_GROUP];
+};
+
+#endif
diff --git a/include/hw/riscv/k230.h b/include/hw/riscv/k230.h
index 592e1c26bf..5fccea7fd5 100644
--- a/include/hw/riscv/k230.h
+++ b/include/hw/riscv/k230.h
@@ -18,6 +18,7 @@
  #include "hw/core/boards.h"
  #include "hw/riscv/riscv_hart.h"
  #include "hw/watchdog/k230_wdt.h"
+#include "hw/gpio/k230_gpio.h"
#define C908_CPU_HARTID (0) @@ -33,6 +34,7 @@ typedef struct K230SoCState {
      RISCVHartArrayState c908_cpu; /* Small core */
K230WdtState wdt[2];
+    K230GPIOState gpio[2];
      MemoryRegion sram;
      MemoryRegion bootrom;
@@ -127,8 +129,11 @@ enum {
      K230_UART2_IRQ  = 18,
      K230_UART3_IRQ  = 19,
      K230_UART4_IRQ  = 20,
+    K230_GPIO0_IRQ_BASE  = 32,
+    K230_GPIO1_IRQ_BASE  = 64,
      K230_WDT0_IRQ   = 107,
      K230_WDT1_IRQ   = 108,
+
  };
#define K230_UART_COUNT 5


Reply via email to