[PATCH 1/5] hw/gpio: add K230 GPIO controller.
Leo Cheng <[email protected]>
| Newsgroups | org.nongnu.qemu-devel,org.nongnu.qemu-riscv |
|---|---|
| Message-ID | <[email protected]> |
Model the Synopsys DW_apb_gpio controller on the Kendryte K230: direction, output, per-pin edge/level interrupts with EOI and output loopback in EXT_PORTA. Each pin drives its own PLIC line. Register layout from the K230 TRM v0.3.1. Covered by tests/qtest/k230-gpio-test. Signed-off-by: Leo Cheng <[email protected]> --- hw/gpio/Kconfig | 3 + hw/gpio/k230_gpio.c | 270 +++++++++++++++++++++++++++++++++++ hw/gpio/meson.build | 1 + hw/gpio/trace-events | 4 + include/hw/gpio/k230_gpio.h | 68 +++++++++ tests/qtest/k230-gpio-test.c | 143 +++++++++++++++++++ 6 files changed, 489 insertions(+) create mode 100644 hw/gpio/k230_gpio.c create mode 100644 include/hw/gpio/k230_gpio.h create mode 100644 tests/qtest/k230-gpio-test.c diff --git a/hw/gpio/Kconfig b/hw/gpio/Kconfig index a209294..07ef5a2 100644 --- a/hw/gpio/Kconfig +++ b/hw/gpio/Kconfig @@ -30,3 +30,6 @@ config PCF8574 config ZAURUS_SCOOP bool + +config K230_GPIO + bool diff --git a/hw/gpio/k230_gpio.c b/hw/gpio/k230_gpio.c new file mode 100644 index 0000000..da4448f --- /dev/null +++ b/hw/gpio/k230_gpio.c @@ -0,0 +1,270 @@ +/* + * K230 GPIO controller (Synopsys DW_apb_gpio) compatible with Kendryte K230 SDK + * + * Copyright (c) 2026 Leo Cheng <[email protected]> + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * K230 Technical Reference Manual V0.3.1 (2024-11-18): + * https://github.com/revyos/external-docs/blob/master/K230/en-us/K230_Technical_Reference_Manual_V0.3.1_20241118.pdf + * + * For more information, see <https://www.kendryte.com/en/proDetail/230> + */ +#include "qemu/osdep.h" +#include "qemu/log.h" +#include "qemu/module.h" +#include "hw/core/irq.h" +#include "migration/vmstate.h" +#include "hw/gpio/k230_gpio.h" +#include "trace.h" + +/* Live pin levels: output pins reflect the driven value, inputs the source. */ +static uint32_t k230_gpio_pin_level(K230GpioState *s) +{ + return (s->in & ~s->ddr) | (s->dr & s->ddr); +} + +static void k230_gpio_update(K230GpioState *s, uint32_t old_level) +{ + uint32_t level = k230_gpio_pin_level(s); + + for (int i = 0; i < K230_GPIO_PINS; i++) { + uint32_t bit = 1u << i; + + if (!(s->inten & bit)) { + continue; + } + + bool active = (s->int_polarity & bit) ? (level & bit) : !(level & bit); + + if (s->inttype_level & bit) { + bool was = (s->int_polarity & bit) ? (old_level & bit) + : !(old_level & bit); + if (active && !was) { + s->raw_intstatus |= bit; + } + } else if (active) { + s->raw_intstatus |= bit; + } else { + s->raw_intstatus &= ~bit; + } + } + + s->intstatus = s->raw_intstatus & ~s->intmask; + + for (int i = 0; i < K230_GPIO_PINS; i++) { + qemu_set_irq(s->irq[i], (s->intstatus >> i) & 1); + } +} + +static void k230_gpio_set_input(void *opaque, int line, int level) +{ + K230GpioState *s = K230_GPIO(opaque); + uint32_t old = k230_gpio_pin_level(s); + + if (level) { + s->in |= (1u << line); + } else { + s->in &= ~(1u << line); + } + k230_gpio_update(s, old); +} + +static uint64_t k230_gpio_read(void *opaque, hwaddr addr, unsigned int size) +{ + K230GpioState *s = K230_GPIO(opaque); + uint32_t value = 0; + + switch (addr) { + case K230_GPIO_SWPORTA_DR: + value = s->dr; + break; + case K230_GPIO_SWPORTA_DDR: + value = s->ddr; + break; + case K230_GPIO_SWPORTA_CTL: + value = s->ctl; + break; + case K230_GPIO_INTEN: + value = s->inten; + break; + case K230_GPIO_INTMASK: + value = s->intmask; + break; + case K230_GPIO_INTTYPE_LEVEL: + value = s->inttype_level; + break; + case K230_GPIO_INT_POLARITY: + value = s->int_polarity; + break; + case K230_GPIO_INTSTATUS: + value = s->intstatus; + break; + case K230_GPIO_RAW_INTSTATUS: + value = s->raw_intstatus; + break; + case K230_GPIO_DEBOUNCE: + value = s->debounce; + break; + case K230_GPIO_EXT_PORTA: + value = k230_gpio_pin_level(s); + break; + case K230_GPIO_LS_SYNC: + value = s->ls_sync; + break; + case K230_GPIO_VER_ID_CODE: + value = K230_GPIO_VER_ID_VALUE; + break; + case K230_GPIO_CONFIG_REG2: + /* Port A width - 1 in bits [4:0]; single 32-bit port. */ + value = K230_GPIO_PINS - 1; + break; + default: + break; + } + + trace_k230_gpio_read(addr, value); + return value; +} + +static void k230_gpio_write(void *opaque, hwaddr addr, + uint64_t value, unsigned int size) +{ + K230GpioState *s = K230_GPIO(opaque); + uint32_t old = k230_gpio_pin_level(s); + + trace_k230_gpio_write(addr, value); + + switch (addr) { + case K230_GPIO_SWPORTA_DR: + s->dr = value; + k230_gpio_update(s, old); + break; + case K230_GPIO_SWPORTA_DDR: + s->ddr = value; + k230_gpio_update(s, old); + break; + case K230_GPIO_SWPORTA_CTL: + s->ctl = value; + break; + case K230_GPIO_INTEN: + s->inten = value; + k230_gpio_update(s, old); + break; + case K230_GPIO_INTMASK: + s->intmask = value; + k230_gpio_update(s, old); + break; + case K230_GPIO_INTTYPE_LEVEL: + s->inttype_level = value; + k230_gpio_update(s, old); + break; + case K230_GPIO_INT_POLARITY: + s->int_polarity = value; + k230_gpio_update(s, old); + break; + case K230_GPIO_DEBOUNCE: + s->debounce = value; + break; + case K230_GPIO_PORTA_EOI: + /* Write-1-to-clear latched edge interrupts only. */ + s->raw_intstatus &= ~(value & s->inttype_level); + k230_gpio_update(s, k230_gpio_pin_level(s)); + break; + case K230_GPIO_LS_SYNC: + s->ls_sync = value; + break; + default: + break; + } +} + +static const MemoryRegionOps k230_gpio_ops = { + .read = k230_gpio_read, + .write = k230_gpio_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl = { + .min_access_size = 4, + .max_access_size = 4, + .unaligned = false, + }, +}; + +static void k230_gpio_reset(DeviceState *dev) +{ + K230GpioState *s = K230_GPIO(dev); + + s->dr = 0; + s->ddr = 0; + s->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->ls_sync = 0; + s->in = 0; + + for (int i = 0; i < K230_GPIO_PINS; i++) { + qemu_set_irq(s->irq[i], 0); + } +} + +static const VMStateDescription vmstate_k230_gpio = { + .name = "k230.gpio", + .fields = (const VMStateField[]) { + VMSTATE_UINT32(dr, K230GpioState), + VMSTATE_UINT32(ddr, K230GpioState), + VMSTATE_UINT32(ctl, K230GpioState), + VMSTATE_UINT32(inten, K230GpioState), + VMSTATE_UINT32(intmask, K230GpioState), + VMSTATE_UINT32(inttype_level, K230GpioState), + VMSTATE_UINT32(int_polarity, K230GpioState), + VMSTATE_UINT32(intstatus, K230GpioState), + VMSTATE_UINT32(raw_intstatus, K230GpioState), + VMSTATE_UINT32(debounce, K230GpioState), + VMSTATE_UINT32(ls_sync, K230GpioState), + VMSTATE_UINT32(in, K230GpioState), + VMSTATE_END_OF_LIST() + } +}; + +static void k230_gpio_realize(DeviceState *dev, Error **errp) +{ + K230GpioState *s = K230_GPIO(dev); + SysBusDevice *sbd = SYS_BUS_DEVICE(dev); + + memory_region_init_io(&s->mmio, OBJECT(dev), &k230_gpio_ops, s, + TYPE_K230_GPIO, K230_GPIO_MMIO_SIZE); + sysbus_init_mmio(sbd, &s->mmio); + + for (int i = 0; i < K230_GPIO_PINS; i++) { + sysbus_init_irq(sbd, &s->irq[i]); + } + qdev_init_gpio_in(dev, k230_gpio_set_input, K230_GPIO_PINS); +} + +static void k230_gpio_class_init(ObjectClass *klass, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->realize = k230_gpio_realize; + device_class_set_legacy_reset(dc, k230_gpio_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_type(void) +{ + type_register_static(&k230_gpio_info); +} +type_init(k230_gpio_register_type) diff --git a/hw/gpio/meson.build b/hw/gpio/meson.build index 6a67ee9..94b5d9e 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/gpio/trace-events b/hw/gpio/trace-events index cea896b..ac5d7fd 100644 --- a/hw/gpio/trace-events +++ b/hw/gpio/trace-events @@ -46,3 +46,7 @@ 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" + +# k230_gpio.c +k230_gpio_read(uint64_t addr, uint32_t data) "K230 GPIO read: [0x%" PRIx64 "] -> 0x%" PRIx32 +k230_gpio_write(uint64_t addr, uint64_t data) "K230 GPIO write: [0x%" PRIx64 "] <- 0x%" PRIx64 diff --git a/include/hw/gpio/k230_gpio.h b/include/hw/gpio/k230_gpio.h new file mode 100644 index 0000000..10c123d --- /dev/null +++ b/include/hw/gpio/k230_gpio.h @@ -0,0 +1,68 @@ +/* + * K230 GPIO controller (Synopsys DW_apb_gpio) compatible with Kendryte K230 SDK + * + * Copyright (c) 2026 Leo Cheng <[email protected]> + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * K230 Technical Reference Manual V0.3.1 (2024-11-18): + * https://github.com/revyos/external-docs/blob/master/K230/en-us/K230_Technical_Reference_Manual_V0.3.1_20241118.pdf + * + * For more information, see <https://www.kendryte.com/en/proDetail/230> + */ +#ifndef HW_GPIO_K230_GPIO_H +#define HW_GPIO_K230_GPIO_H + +#include "hw/core/sysbus.h" +#include "qom/object.h" + +#define TYPE_K230_GPIO "riscv.k230.gpio" +OBJECT_DECLARE_SIMPLE_TYPE(K230GpioState, K230_GPIO) + +#define K230_GPIO_PINS 32 +#define K230_GPIO_MMIO_SIZE 0x1000 + +enum { + K230_GPIO_SWPORTA_DR = 0x00, + K230_GPIO_SWPORTA_DDR = 0x04, + K230_GPIO_SWPORTA_CTL = 0x08, + K230_GPIO_INTEN = 0x30, + K230_GPIO_INTMASK = 0x34, + K230_GPIO_INTTYPE_LEVEL = 0x38, + K230_GPIO_INT_POLARITY = 0x3c, + K230_GPIO_INTSTATUS = 0x40, + K230_GPIO_RAW_INTSTATUS = 0x44, + K230_GPIO_DEBOUNCE = 0x48, + K230_GPIO_PORTA_EOI = 0x4c, + K230_GPIO_EXT_PORTA = 0x50, + K230_GPIO_LS_SYNC = 0x60, + K230_GPIO_ID_CODE = 0x64, + K230_GPIO_VER_ID_CODE = 0x6c, + K230_GPIO_CONFIG_REG2 = 0x70, + K230_GPIO_CONFIG_REG1 = 0x74, +}; + +#define K230_GPIO_VER_ID_VALUE 0x3230312a + +struct K230GpioState { + SysBusDevice parent_obj; + + MemoryRegion mmio; + qemu_irq irq[K230_GPIO_PINS]; + + uint32_t dr; + uint32_t ddr; + uint32_t 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 ls_sync; + + uint32_t in; +}; + +#endif diff --git a/tests/qtest/k230-gpio-test.c b/tests/qtest/k230-gpio-test.c new file mode 100644 index 0000000..42ef137 --- /dev/null +++ b/tests/qtest/k230-gpio-test.c @@ -0,0 +1,143 @@ +/* + * QTest testcase for K230 GPIO (DW_apb_gpio) + * + * Copyright (c) 2026 Leo Cheng <[email protected]> + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * K230 Technical Reference Manual V0.3.1 (2024-11-18): + * https://github.com/revyos/external-docs/blob/master/K230/en-us/K230_Technical_Reference_Manual_V0.3.1_20241118.pdf + */ +#include "qemu/osdep.h" +#include "libqtest.h" +#include "hw/gpio/k230_gpio.h" + +#define GPIO0_BASE 0x9140B000 +#define GPIO1_BASE 0x9140C000 + +#define PLIC_BASE 0xF00000000ULL +#define PLIC_PENDING (PLIC_BASE + 0x1000) +#define GPIO0_IRQ_BASE 32 + +static bool plic_pending(QTestState *qts, int irq) +{ + uint32_t word = qtest_readl(qts, PLIC_PENDING + (irq / 32) * 4); + return (word >> (irq % 32)) & 1; +} + +static void test_reset_values(void) +{ + QTestState *qts = qtest_init("-machine k230"); + + g_assert_cmphex(qtest_readl(qts, GPIO0_BASE + K230_GPIO_SWPORTA_DR), ==, 0); + g_assert_cmphex(qtest_readl(qts, GPIO0_BASE + K230_GPIO_SWPORTA_DDR), + ==, 0); + g_assert_cmphex(qtest_readl(qts, GPIO0_BASE + K230_GPIO_INTSTATUS), ==, 0); + g_assert_cmphex(qtest_readl(qts, GPIO0_BASE + K230_GPIO_EXT_PORTA), ==, 0); + + qtest_quit(qts); +} + +/* Output pins loop back into EXT_PORTA */ +static void test_output_loopback(void) +{ + QTestState *qts = qtest_init("-machine k230"); + + qtest_writel(qts, GPIO0_BASE + K230_GPIO_SWPORTA_DDR, 0x1); + qtest_writel(qts, GPIO0_BASE + K230_GPIO_SWPORTA_DR, 0x1); + g_assert_cmphex(qtest_readl(qts, GPIO0_BASE + K230_GPIO_EXT_PORTA) & 0x1, + ==, 0x1); + + qtest_writel(qts, GPIO0_BASE + K230_GPIO_SWPORTA_DR, 0x0); + g_assert_cmphex(qtest_readl(qts, GPIO0_BASE + K230_GPIO_EXT_PORTA) & 0x1, + ==, 0x0); + + /* Independent pins */ + qtest_writel(qts, GPIO0_BASE + K230_GPIO_SWPORTA_DDR, 0xFFFFFFFF); + qtest_writel(qts, GPIO0_BASE + K230_GPIO_SWPORTA_DR, 0x80000001); + g_assert_cmphex(qtest_readl(qts, GPIO0_BASE + K230_GPIO_EXT_PORTA), + ==, 0x80000001); + + qtest_quit(qts); +} + +/* Rising-edge interrupt via DR toggle on an output pin, cleared by EOI */ +static void test_edge_interrupt(void) +{ + QTestState *qts = qtest_init("-machine k230"); + + qtest_writel(qts, GPIO0_BASE + K230_GPIO_SWPORTA_DDR, 0x1); + qtest_writel(qts, GPIO0_BASE + K230_GPIO_INTTYPE_LEVEL, 0x1); /* edge */ + qtest_writel(qts, GPIO0_BASE + K230_GPIO_INT_POLARITY, 0x1); /* rising */ + qtest_writel(qts, GPIO0_BASE + K230_GPIO_INTEN, 0x1); + + qtest_writel(qts, GPIO0_BASE + K230_GPIO_SWPORTA_DR, 0x0); + g_assert_cmphex(qtest_readl(qts, GPIO0_BASE + K230_GPIO_INTSTATUS) & 0x1, + ==, 0); + + qtest_writel(qts, GPIO0_BASE + K230_GPIO_SWPORTA_DR, 0x1); + g_assert_cmphex(qtest_readl(qts, GPIO0_BASE + K230_GPIO_INTSTATUS) & 0x1, + ==, 0x1); + g_assert_true(plic_pending(qts, GPIO0_IRQ_BASE + 0)); + + /* Write-1 EOI clears the latched edge */ + qtest_writel(qts, GPIO0_BASE + K230_GPIO_PORTA_EOI, 0x1); + g_assert_cmphex(qtest_readl(qts, GPIO0_BASE + K230_GPIO_INTSTATUS) & 0x1, + ==, 0); + + qtest_quit(qts); +} + +/* Level interrupt follows the pin; EOI does not clear it */ +static void test_level_interrupt(void) +{ + QTestState *qts = qtest_init("-machine k230"); + + qtest_writel(qts, GPIO0_BASE + K230_GPIO_SWPORTA_DDR, 0x1); + qtest_writel(qts, GPIO0_BASE + K230_GPIO_INTTYPE_LEVEL, 0x0); /* level */ + qtest_writel(qts, GPIO0_BASE + K230_GPIO_INT_POLARITY, 0x1); /* high */ + qtest_writel(qts, GPIO0_BASE + K230_GPIO_INTEN, 0x1); + + qtest_writel(qts, GPIO0_BASE + K230_GPIO_SWPORTA_DR, 0x1); + g_assert_cmphex(qtest_readl(qts, GPIO0_BASE + K230_GPIO_INTSTATUS) & 0x1, + ==, 0x1); + + qtest_writel(qts, GPIO0_BASE + K230_GPIO_SWPORTA_DR, 0x0); + g_assert_cmphex(qtest_readl(qts, GPIO0_BASE + K230_GPIO_INTSTATUS) & 0x1, + ==, 0); + + qtest_quit(qts); +} + +/* INTMASK hides the interrupt from INTSTATUS/PLIC but not RAW_INTSTATUS */ +static void test_mask(void) +{ + QTestState *qts = qtest_init("-machine k230"); + + qtest_writel(qts, GPIO0_BASE + K230_GPIO_SWPORTA_DDR, 0x1); + qtest_writel(qts, GPIO0_BASE + K230_GPIO_INTTYPE_LEVEL, 0x0); + qtest_writel(qts, GPIO0_BASE + K230_GPIO_INT_POLARITY, 0x1); + qtest_writel(qts, GPIO0_BASE + K230_GPIO_INTMASK, 0x1); + qtest_writel(qts, GPIO0_BASE + K230_GPIO_INTEN, 0x1); + + qtest_writel(qts, GPIO0_BASE + K230_GPIO_SWPORTA_DR, 0x1); + g_assert_cmphex(qtest_readl(qts, GPIO0_BASE + K230_GPIO_INTSTATUS) & 0x1, + ==, 0); + g_assert_cmphex(qtest_readl(qts, + GPIO0_BASE + K230_GPIO_RAW_INTSTATUS) & 0x1, ==, 0x1); + + qtest_quit(qts); +} + +int main(int argc, char **argv) +{ + g_test_init(&argc, &argv, NULL); + + qtest_add_func("/k230-gpio/reset_values", test_reset_values); + qtest_add_func("/k230-gpio/output_loopback", test_output_loopback); + qtest_add_func("/k230-gpio/edge_interrupt", test_edge_interrupt); + qtest_add_func("/k230-gpio/level_interrupt", test_level_interrupt); + qtest_add_func("/k230-gpio/mask", test_mask); + + return g_test_run(); +} -- 2.43.0