From: gilles grimaud <[email protected]>

Model the six QSPI GPIO control pairs and the PROC0, PROC1 and dormant-wake 
interrupt registers, including atomic aliases and documented masks. Wire the 
register block into the RP2040 SoC and add focused qtests for reset, atomic 
control updates and forced interrupt status. The QSPI chip-select connection is 
deferred to the XIP controller patch.

Signed-off-by: gilles grimaud <[email protected]>
---
 hw/arm/Kconfig                   |   1 +
 hw/arm/rp2040.c                  |   8 +-
 hw/misc/Kconfig                  |   3 +
 hw/misc/meson.build              |   1 +
 hw/misc/rp2040_ioqspi.c          | 263 +++++++++++++++++++++++++++++++
 include/hw/arm/rp2040.h          |   2 +
 include/hw/misc/rp2040_ioqspi.h  |  33 ++++
 tests/qtest/meson.build          |   1 +
 tests/qtest/rp2040-ioqspi-test.c |  81 ++++++++++
 9 files changed, 392 insertions(+), 1 deletion(-)
 create mode 100644 hw/misc/rp2040_ioqspi.c
 create mode 100644 include/hw/misc/rp2040_ioqspi.h
 create mode 100644 tests/qtest/rp2040-ioqspi-test.c

diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
index 7e3d23e81c..a1b94532b4 100644
--- a/hw/arm/Kconfig
+++ b/hw/arm/Kconfig
@@ -380,6 +380,7 @@ config RP2040
     select PL011
     select RP2040_CLOCKS
     select RP2040_IOBANK0
+    select RP2040_IOQSPI
     select RP2040_NYI
     select RP2040_PADS
     select RP2040_PLL
diff --git a/hw/arm/rp2040.c b/hw/arm/rp2040.c
index 4cceec863e..28c17232a5 100644
--- a/hw/arm/rp2040.c
+++ b/hw/arm/rp2040.c
@@ -49,7 +49,6 @@ static const struct {
     hwaddr base;
     hwaddr size;
 } rp2040_unimplemented[] = {
-    { "rp2040.ioqspi",   0x40018000, 0x4000 },
     { "rp2040.busctrl",  0x40030000, 0x4000 },
     { "rp2040.uart0_aliases", 0x40035000, 0x3000 },
     { "rp2040.uart1_aliases", 0x40039000, 0x3000 },
@@ -166,6 +165,8 @@ static void rp2040_soc_init(Object *obj)
     object_initialize_child(obj, "clocks", &s->clocks, TYPE_RP2040_CLOCKS);
     object_initialize_child(obj, "iobank0", &s->iobank0,
                             TYPE_RP2040_IOBANK0);
+    object_initialize_child(obj, "ioqspi", &s->ioqspi,
+                            TYPE_RP2040_IOQSPI);
     object_initialize_child(obj, "pads-bank0", &s->pads_bank0,
                             TYPE_RP2040_PADS_BANK0);
     object_initialize_child(obj, "pads-qspi", &s->pads_qspi,
@@ -376,6 +377,11 @@ static void rp2040_soc_realize(DeviceState *dev, Error 
**errp)
     sysbus_connect_irq(SYS_BUS_DEVICE(&s->iobank0), 0,
                        s->irq[RP2040_IO_IRQ_BANK0]);
 
+    if (!sysbus_realize(SYS_BUS_DEVICE(&s->ioqspi), errp)) {
+        return;
+    }
+    sysbus_mmio_map(SYS_BUS_DEVICE(&s->ioqspi), 0, RP2040_IOQSPI_BASE);
+
     if (!sysbus_realize(SYS_BUS_DEVICE(&s->rosc), errp)) {
         return;
     }
diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig
index daf513766c..553a92d4b6 100644
--- a/hw/misc/Kconfig
+++ b/hw/misc/Kconfig
@@ -107,6 +107,9 @@ config RP2040_CLOCKS
 config RP2040_IOBANK0
     bool
 
+config RP2040_IOQSPI
+    bool
+
 config RP2040_NYI
     bool
 
diff --git a/hw/misc/meson.build b/hw/misc/meson.build
index e84cbd735f..5e4910e83d 100644
--- a/hw/misc/meson.build
+++ b/hw/misc/meson.build
@@ -100,6 +100,7 @@ system_ss.add(when: 'CONFIG_RASPI', if_true: files(
 ))
 system_ss.add(when: 'CONFIG_RP2040_CLOCKS', if_true: files('rp2040_clocks.c'))
 system_ss.add(when: 'CONFIG_RP2040_IOBANK0', if_true: 
files('rp2040_iobank0.c'))
+system_ss.add(when: 'CONFIG_RP2040_IOQSPI', if_true: files('rp2040_ioqspi.c'))
 system_ss.add(when: 'CONFIG_RP2040_NYI', if_true: files('rp2040_nyi.c'))
 system_ss.add(when: 'CONFIG_RP2040_PADS', if_true: files('rp2040_pads.c'))
 system_ss.add(when: 'CONFIG_RP2040_PLL', if_true: files('rp2040_pll.c'))
diff --git a/hw/misc/rp2040_ioqspi.c b/hw/misc/rp2040_ioqspi.c
new file mode 100644
index 0000000000..eafd5e9eef
--- /dev/null
+++ b/hw/misc/rp2040_ioqspi.c
@@ -0,0 +1,263 @@
+/*
+ * RP2040 QSPI IO bank emulation
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "hw/misc/rp2040_nyi.h"
+#include "hw/misc/rp2040_ioqspi.h"
+#include "migration/vmstate.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+
+#define IOQSPI_INTR             0x30
+#define IOQSPI_PROC0_INTE       0x34
+#define IOQSPI_PROC0_INTF       0x38
+#define IOQSPI_PROC0_INTS       0x3c
+#define IOQSPI_PROC1_INTE       0x40
+#define IOQSPI_PROC1_INTF       0x44
+#define IOQSPI_PROC1_INTS       0x48
+#define IOQSPI_DORMANT_INTE     0x4c
+#define IOQSPI_DORMANT_INTF     0x50
+#define IOQSPI_DORMANT_INTS     0x54
+
+#define IOQSPI_CTRL_RESET       0x1f
+#define IOQSPI_CTRL_RW_MASK     0x33333f
+#define IOQSPI_INTR_EDGE_MASK   0x00cccccc
+#define IOQSPI_IRQ_MASK         0x00ffffff
+
+#define ATOMIC_ALIAS_MASK       0x3000
+#define ATOMIC_XOR              0x1000
+#define ATOMIC_SET              0x2000
+#define ATOMIC_CLR              0x3000
+
+static uint32_t rp2040_ioqspi_apply_alias(uint32_t old, uint32_t value,
+                                          hwaddr alias)
+{
+    switch (alias) {
+    case ATOMIC_XOR:
+        return old ^ value;
+    case ATOMIC_SET:
+        return old | value;
+    case ATOMIC_CLR:
+        return old & ~value;
+    default:
+        return value;
+    }
+}
+
+static bool rp2040_ioqspi_ctrl_offset(hwaddr offset, unsigned *index)
+{
+    if (offset > 0x2c || (offset & 0x7) != 0x4) {
+        return false;
+    }
+
+    *index = offset / 8;
+    return *index < ARRAY_SIZE(((RP2040IoQspiState *)0)->ctrl);
+}
+
+static bool rp2040_ioqspi_status_offset(hwaddr offset)
+{
+    return offset <= 0x28 && (offset & 0x7) == 0;
+}
+
+static uint32_t rp2040_ioqspi_ints(uint32_t intr, uint32_t inte,
+                                   uint32_t intf)
+{
+    return (intr & inte) | intf;
+}
+
+static uint64_t rp2040_ioqspi_read(void *opaque, hwaddr addr, unsigned size)
+{
+    RP2040IoQspiState *s = opaque;
+    hwaddr offset = addr & 0xfff;
+    unsigned index;
+    uint64_t value;
+
+    if (rp2040_ioqspi_status_offset(offset)) {
+        value = 0;
+    } else if (rp2040_ioqspi_ctrl_offset(offset, &index)) {
+        value = s->ctrl[index];
+    } else {
+        switch (offset) {
+        case IOQSPI_INTR:
+            value = s->intr;
+            break;
+        case IOQSPI_PROC0_INTE:
+            value = s->proc0_inte;
+            break;
+        case IOQSPI_PROC0_INTF:
+            value = s->proc0_intf;
+            break;
+        case IOQSPI_PROC0_INTS:
+            value = rp2040_ioqspi_ints(s->intr, s->proc0_inte,
+                                       s->proc0_intf);
+            break;
+        case IOQSPI_PROC1_INTE:
+            value = s->proc1_inte;
+            break;
+        case IOQSPI_PROC1_INTF:
+            value = s->proc1_intf;
+            break;
+        case IOQSPI_PROC1_INTS:
+            value = rp2040_ioqspi_ints(s->intr, s->proc1_inte,
+                                       s->proc1_intf);
+            break;
+        case IOQSPI_DORMANT_INTE:
+            value = s->dormant_wake_inte;
+            break;
+        case IOQSPI_DORMANT_INTF:
+            value = s->dormant_wake_intf;
+            break;
+        case IOQSPI_DORMANT_INTS:
+            value = rp2040_ioqspi_ints(s->intr, s->dormant_wake_inte,
+                                       s->dormant_wake_intf);
+            break;
+        default:
+            value = 0;
+            rp2040_log_unimplemented_read("ioqspi", size,
+                                          RP2040_IOQSPI_BASE + addr, offset,
+                                          value);
+            break;
+        }
+    }
+
+    return value;
+}
+
+static void rp2040_ioqspi_write(void *opaque, hwaddr addr,
+                                uint64_t value64, unsigned size)
+{
+    RP2040IoQspiState *s = opaque;
+    hwaddr alias = addr & ATOMIC_ALIAS_MASK;
+    hwaddr offset = addr & 0xfff;
+    unsigned index;
+    uint32_t value = value64;
+
+    if (rp2040_ioqspi_ctrl_offset(offset, &index)) {
+        s->ctrl[index] =
+            rp2040_ioqspi_apply_alias(s->ctrl[index], value, alias) &
+            IOQSPI_CTRL_RW_MASK;
+    } else {
+        switch (offset) {
+        case IOQSPI_INTR:
+            s->intr &= ~(value & IOQSPI_INTR_EDGE_MASK);
+            break;
+        case IOQSPI_PROC0_INTE:
+            s->proc0_inte =
+                rp2040_ioqspi_apply_alias(s->proc0_inte, value, alias) &
+                IOQSPI_IRQ_MASK;
+            break;
+        case IOQSPI_PROC0_INTF:
+            s->proc0_intf =
+                rp2040_ioqspi_apply_alias(s->proc0_intf, value, alias) &
+                IOQSPI_IRQ_MASK;
+            break;
+        case IOQSPI_PROC1_INTE:
+            s->proc1_inte =
+                rp2040_ioqspi_apply_alias(s->proc1_inte, value, alias) &
+                IOQSPI_IRQ_MASK;
+            break;
+        case IOQSPI_PROC1_INTF:
+            s->proc1_intf =
+                rp2040_ioqspi_apply_alias(s->proc1_intf, value, alias) &
+                IOQSPI_IRQ_MASK;
+            break;
+        case IOQSPI_DORMANT_INTE:
+            s->dormant_wake_inte =
+                rp2040_ioqspi_apply_alias(s->dormant_wake_inte, value,
+                                          alias) &
+                IOQSPI_IRQ_MASK;
+            break;
+        case IOQSPI_DORMANT_INTF:
+            s->dormant_wake_intf =
+                rp2040_ioqspi_apply_alias(s->dormant_wake_intf, value,
+                                          alias) &
+                IOQSPI_IRQ_MASK;
+            break;
+        default:
+            if (!rp2040_ioqspi_status_offset(offset)) {
+                rp2040_log_unimplemented_write("ioqspi", size,
+                                               RP2040_IOQSPI_BASE + addr,
+                                               offset, value64);
+            }
+            break;
+        }
+    }
+}
+
+static const MemoryRegionOps rp2040_ioqspi_ops = {
+    .read = rp2040_ioqspi_read,
+    .write = rp2040_ioqspi_write,
+    .endianness = DEVICE_LITTLE_ENDIAN,
+    .valid = {
+        .min_access_size = 4,
+        .max_access_size = 4,
+    },
+};
+
+static void rp2040_ioqspi_reset(DeviceState *dev)
+{
+    RP2040IoQspiState *s = RP2040_IOQSPI(dev);
+    int i;
+
+    for (i = 0; i < ARRAY_SIZE(s->ctrl); i++) {
+        s->ctrl[i] = IOQSPI_CTRL_RESET;
+    }
+    s->intr = 0;
+    s->proc0_inte = 0;
+    s->proc0_intf = 0;
+    s->proc1_inte = 0;
+    s->proc1_intf = 0;
+    s->dormant_wake_inte = 0;
+    s->dormant_wake_intf = 0;
+}
+
+static void rp2040_ioqspi_init(Object *obj)
+{
+    RP2040IoQspiState *s = RP2040_IOQSPI(obj);
+
+    memory_region_init_io(&s->iomem, obj, &rp2040_ioqspi_ops, s,
+                          "rp2040.ioqspi", RP2040_IOQSPI_SIZE);
+    sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->iomem);
+}
+
+static const VMStateDescription rp2040_ioqspi_vmstate = {
+    .name = TYPE_RP2040_IOQSPI,
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .fields = (const VMStateField[]) {
+        VMSTATE_UINT32_ARRAY(ctrl, RP2040IoQspiState, 6),
+        VMSTATE_UINT32(intr, RP2040IoQspiState),
+        VMSTATE_UINT32(proc0_inte, RP2040IoQspiState),
+        VMSTATE_UINT32(proc0_intf, RP2040IoQspiState),
+        VMSTATE_UINT32(proc1_inte, RP2040IoQspiState),
+        VMSTATE_UINT32(proc1_intf, RP2040IoQspiState),
+        VMSTATE_UINT32(dormant_wake_inte, RP2040IoQspiState),
+        VMSTATE_UINT32(dormant_wake_intf, RP2040IoQspiState),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+static void rp2040_ioqspi_class_init(ObjectClass *klass, const void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+
+    device_class_set_legacy_reset(dc, rp2040_ioqspi_reset);
+    dc->vmsd = &rp2040_ioqspi_vmstate;
+}
+
+static const TypeInfo rp2040_ioqspi_info = {
+    .name          = TYPE_RP2040_IOQSPI,
+    .parent        = TYPE_SYS_BUS_DEVICE,
+    .instance_size = sizeof(RP2040IoQspiState),
+    .instance_init = rp2040_ioqspi_init,
+    .class_init    = rp2040_ioqspi_class_init,
+};
+
+static void rp2040_ioqspi_register_types(void)
+{
+    type_register_static(&rp2040_ioqspi_info);
+}
+type_init(rp2040_ioqspi_register_types)
diff --git a/include/hw/arm/rp2040.h b/include/hw/arm/rp2040.h
index 1ed6fa1449..068aab584f 100644
--- a/include/hw/arm/rp2040.h
+++ b/include/hw/arm/rp2040.h
@@ -15,6 +15,7 @@
 #include "hw/core/sysbus.h"
 #include "hw/misc/rp2040_clocks.h"
 #include "hw/misc/rp2040_iobank0.h"
+#include "hw/misc/rp2040_ioqspi.h"
 #include "hw/misc/rp2040_pads.h"
 #include "hw/misc/rp2040_pll.h"
 #include "hw/misc/rp2040_psm.h"
@@ -49,6 +50,7 @@ struct RP2040State {
     PL011State uart[2];
     RP2040ClocksState clocks;
     RP2040IoBank0State iobank0;
+    RP2040IoQspiState ioqspi;
     RP2040PadsBank0State pads_bank0;
     RP2040PadsQspiState pads_qspi;
     RP2040PllState pll_sys;
diff --git a/include/hw/misc/rp2040_ioqspi.h b/include/hw/misc/rp2040_ioqspi.h
new file mode 100644
index 0000000000..e671d2a1b5
--- /dev/null
+++ b/include/hw/misc/rp2040_ioqspi.h
@@ -0,0 +1,33 @@
+/*
+ * RP2040 QSPI IO bank emulation
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef HW_MISC_RP2040_IOQSPI_H
+#define HW_MISC_RP2040_IOQSPI_H
+
+#include "hw/core/sysbus.h"
+#include "qom/object.h"
+
+#define TYPE_RP2040_IOQSPI "rp2040-ioqspi"
+OBJECT_DECLARE_SIMPLE_TYPE(RP2040IoQspiState, RP2040_IOQSPI)
+
+#define RP2040_IOQSPI_BASE 0x40018000
+#define RP2040_IOQSPI_SIZE 0x4000
+
+struct RP2040IoQspiState {
+    SysBusDevice parent_obj;
+
+    MemoryRegion iomem;
+    uint32_t ctrl[6];
+    uint32_t intr;
+    uint32_t proc0_inte;
+    uint32_t proc0_intf;
+    uint32_t proc1_inte;
+    uint32_t proc1_intf;
+    uint32_t dormant_wake_inte;
+    uint32_t dormant_wake_intf;
+};
+
+#endif
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index 372d412b9b..a232813014 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -258,6 +258,7 @@ qtests_arm = \
   (config_all_devices.has_key('CONFIG_RASPI_PICO') ? 
['rp2040-sysinfo-syscfg-test',
                                                        'rp2040-clocks-test',
                                                        'rp2040-iobank0-test',
+                                                       'rp2040-ioqspi-test',
                                                        'rp2040-pads-test',
                                                        'rp2040-resets-test',
                                                        'rp2040-rosc-test',
diff --git a/tests/qtest/rp2040-ioqspi-test.c b/tests/qtest/rp2040-ioqspi-test.c
new file mode 100644
index 0000000000..4f94b5f3a2
--- /dev/null
+++ b/tests/qtest/rp2040-ioqspi-test.c
@@ -0,0 +1,81 @@
+/*
+ * QTest testcase for the RP2040 QSPI IO bank block.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "libqtest.h"
+#include "qemu/bitops.h"
+
+#define IOQSPI_BASE             0x40018000
+#define IOQSPI_SCLK_STATUS      0x00
+#define IOQSPI_SCLK_CTRL        0x04
+#define IOQSPI_SD1_CTRL         0x1c
+#define IOQSPI_PROC0_INTE       0x34
+#define IOQSPI_PROC0_INTF       0x38
+#define IOQSPI_PROC0_INTS       0x3c
+
+#define IOQSPI_CTRL_RESET       0x1f
+#define IOQSPI_SD1_CTRL_SET     0x201c
+#define IOQSPI_SD1_CTRL_CLR     0x301c
+
+static QTestState *rp2040_start(void)
+{
+    return qtest_init("-machine raspi-pico");
+}
+
+static void test_ioqspi_reset_values(void)
+{
+    QTestState *qts = rp2040_start();
+
+    g_assert_cmphex(qtest_readl(qts, IOQSPI_BASE + IOQSPI_SCLK_STATUS), ==,
+                    0);
+    g_assert_cmphex(qtest_readl(qts, IOQSPI_BASE + IOQSPI_SCLK_CTRL), ==,
+                    IOQSPI_CTRL_RESET);
+    g_assert_cmphex(qtest_readl(qts, IOQSPI_BASE + IOQSPI_SD1_CTRL), ==,
+                    IOQSPI_CTRL_RESET);
+
+    qtest_quit(qts);
+}
+
+static void test_ioqspi_atomic_ctrl_aliases(void)
+{
+    QTestState *qts = rp2040_start();
+
+    qtest_writel(qts, IOQSPI_BASE + IOQSPI_SD1_CTRL, 0);
+    qtest_writel(qts, IOQSPI_BASE + IOQSPI_SD1_CTRL_SET, BIT(17));
+    qtest_writel(qts, IOQSPI_BASE + IOQSPI_SD1_CTRL_CLR, BIT(17));
+    qtest_writel(qts, IOQSPI_BASE + IOQSPI_SD1_CTRL_SET, BIT(9));
+
+    g_assert_cmphex(qtest_readl(qts, IOQSPI_BASE + IOQSPI_SD1_CTRL), ==,
+                    BIT(9));
+
+    qtest_quit(qts);
+}
+
+static void test_ioqspi_interrupt_force_status(void)
+{
+    QTestState *qts = rp2040_start();
+
+    qtest_writel(qts, IOQSPI_BASE + IOQSPI_PROC0_INTE, BIT(1));
+    qtest_writel(qts, IOQSPI_BASE + IOQSPI_PROC0_INTF, BIT(2));
+
+    g_assert_cmphex(qtest_readl(qts, IOQSPI_BASE + IOQSPI_PROC0_INTS), ==,
+                    BIT(2));
+
+    qtest_quit(qts);
+}
+
+int main(int argc, char **argv)
+{
+    g_test_init(&argc, &argv, NULL);
+
+    qtest_add_func("/rp2040-ioqspi/reset-values", test_ioqspi_reset_values);
+    qtest_add_func("/rp2040-ioqspi/atomic-ctrl-aliases",
+                   test_ioqspi_atomic_ctrl_aliases);
+    qtest_add_func("/rp2040-ioqspi/interrupt-force-status",
+                   test_ioqspi_interrupt_force_status);
+
+    return g_test_run();
+}
-- 
2.55.0


Reply via email to