Model the RP2040 voltage regulator, brown-out detector, and chip reset.

Include documented reset values, writable masks, and APB atomic aliases.

Signed-off-by: Gilles Grimaud <[email protected]>
---
 hw/arm/rp2040.c                |   8 +-
 hw/misc/meson.build            |   1 +
 hw/misc/rp2040_vreg.c          | 149 +++++++++++++++++++++++++++++++++
 include/hw/arm/rp2040.h        |   2 +
 include/hw/misc/rp2040_vreg.h  |  46 ++++++++++
 tests/qtest/meson.build        |   6 +-
 tests/qtest/rp2040-vreg-test.c |  79 +++++++++++++++++
 7 files changed, 287 insertions(+), 4 deletions(-)
 create mode 100644 hw/misc/rp2040_vreg.c
 create mode 100644 include/hw/misc/rp2040_vreg.h
 create mode 100644 tests/qtest/rp2040-vreg-test.c

diff --git a/hw/arm/rp2040.c b/hw/arm/rp2040.c
index eac4dde9cd..638c47612b 100644
--- a/hw/arm/rp2040.c
+++ b/hw/arm/rp2040.c
@@ -73,8 +73,6 @@ static const struct {
     { "rp2040.watchdog", 0x40058000, 0x4000 },
     { "rp2040.rtc",      0x4005c000, 0x4000 },
     { "rp2040.rosc",     0x40060000, 0x4000 },
-    { "rp2040.vreg_and_chip_reset", 0x40064000, 0x4000 },
-    { "rp2040.tbman",    0x4006c000, 0x4000 },
     { "rp2040.dma",      0x50000000, 0x1000 },
     { "rp2040.usbctrl_dpram", 0x50100000, 0x10000 },
     { "rp2040.usbctrl_regs",  0x50110000, 0x10000 },
@@ -145,6 +143,7 @@ static void rp2040_soc_init(Object *obj)
 
     object_initialize_child(obj, "syscfg", &s->syscfg, TYPE_RP2040_SYSCFG);
     object_initialize_child(obj, "sysinfo", &s->sysinfo, TYPE_RP2040_SYSINFO);
+    object_initialize_child(obj, "vreg", &s->vreg, TYPE_RP2040_VREG);
 
     s->irq = qemu_allocate_irqs(rp2040_set_irq, s, RP2040_NUM_IRQS);
 
@@ -278,6 +277,11 @@ static void rp2040_soc_realize(DeviceState *dev, Error 
**errp)
     }
     sysbus_mmio_map(SYS_BUS_DEVICE(&s->sysinfo), 0, RP2040_SYSINFO_BASE);
 
+    if (!sysbus_realize(SYS_BUS_DEVICE(&s->vreg), errp)) {
+        return;
+    }
+    sysbus_mmio_map(SYS_BUS_DEVICE(&s->vreg), 0, RP2040_VREG_BASE);
+
     for (i = 0; i < ARRAY_SIZE(s->uart); i++) {
         qdev_connect_clock_in(DEVICE(&s->uart[i]), "clk", s->sysclk);
         if (!sysbus_realize(SYS_BUS_DEVICE(&s->uart[i]), errp)) {
diff --git a/hw/misc/meson.build b/hw/misc/meson.build
index baa2818d8a..26ce30fb38 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', if_true: files('rp2040_syscfg.c'))
 system_ss.add(when: 'CONFIG_RP2040', if_true: files('rp2040_sysinfo.c'))
+system_ss.add(when: 'CONFIG_RP2040', if_true: files('rp2040_vreg.c'))
 system_ss.add(when: 'CONFIG_SLAVIO', if_true: files('slavio_misc.c'))
 system_ss.add(when: 'CONFIG_ZYNQ', if_true: files('zynq_slcr.c'))
 system_ss.add(when: 'CONFIG_XLNX_ZYNQ_DDRC', if_true: 
files('xlnx-zynq-ddrc.c'))
diff --git a/hw/misc/rp2040_vreg.c b/hw/misc/rp2040_vreg.c
new file mode 100644
index 0000000000..2745bddd50
--- /dev/null
+++ b/hw/misc/rp2040_vreg.c
@@ -0,0 +1,149 @@
+/*
+ * RP2040 voltage regulator and chip reset emulation
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "hw/misc/rp2040.h"
+#include "hw/misc/rp2040_vreg.h"
+#include "migration/vmstate.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+
+#define VREG_RW_MASK \
+    (R_RP2040_VREG_VREG_VSEL_MASK | R_RP2040_VREG_VREG_HIZ_MASK | \
+     R_RP2040_VREG_VREG_EN_MASK)
+#define BOD_RW_MASK \
+    (R_RP2040_VREG_BOD_VSEL_MASK | R_RP2040_VREG_BOD_EN_MASK)
+#define CHIP_RESET_RW_MASK R_RP2040_VREG_CHIP_RESET_PSM_RESTART_FLAG_MASK
+
+static uint32_t rp2040_vreg_read_vreg(RP2040VregState *s)
+{
+    uint32_t value = s->vreg;
+
+    if (FIELD_EX32(value, RP2040_VREG_VREG, EN) &&
+        !FIELD_EX32(value, RP2040_VREG_VREG, HIZ)) {
+        value |= R_RP2040_VREG_VREG_ROK_MASK;
+    }
+
+    return value;
+}
+
+static uint64_t rp2040_vreg_read(void *opaque, hwaddr addr, unsigned size)
+{
+    RP2040VregState *s = opaque;
+    hwaddr offset = addr & 0xfff;
+    uint64_t value;
+
+    switch (offset) {
+    case A_RP2040_VREG_VREG:
+        value = rp2040_vreg_read_vreg(s);
+        break;
+    case A_RP2040_VREG_BOD:
+        value = s->bod;
+        break;
+    case A_RP2040_VREG_CHIP_RESET:
+        value = s->chip_reset;
+        break;
+    default:
+        value = 0;
+        qemu_log_mask(LOG_UNIMP,
+                      "%s: unimplemented read at offset 0x%"
+                      HWADDR_PRIx "\n", __func__, addr & 0xfff);
+        break;
+    }
+
+    return value;
+}
+
+static void rp2040_vreg_write(void *opaque, hwaddr addr,
+                              uint64_t value64, unsigned size)
+{
+    RP2040VregState *s = opaque;
+    hwaddr alias = addr & RP2040_ATOMIC_ALIAS_MASK;
+    hwaddr offset = addr & 0xfff;
+    uint32_t value = value64;
+
+    switch (offset) {
+    case A_RP2040_VREG_VREG:
+        s->vreg = rp2040_atomic_update(s->vreg, value, alias) &
+                  VREG_RW_MASK;
+        break;
+    case A_RP2040_VREG_BOD:
+        s->bod = rp2040_atomic_update(s->bod, value, alias) & BOD_RW_MASK;
+        break;
+    case A_RP2040_VREG_CHIP_RESET:
+        s->chip_reset =
+            rp2040_atomic_update(s->chip_reset, value, alias) &
+            CHIP_RESET_RW_MASK;
+        break;
+    default:
+        qemu_log_mask(LOG_UNIMP,
+                      "%s: unimplemented write at offset 0x%"
+                      HWADDR_PRIx "\n", __func__, addr & 0xfff);
+        break;
+    }
+}
+
+static const MemoryRegionOps rp2040_vreg_ops = {
+    .read = rp2040_vreg_read,
+    .write = rp2040_vreg_write,
+    .endianness = DEVICE_LITTLE_ENDIAN,
+    .valid = {
+        .min_access_size = 4,
+        .max_access_size = 4,
+    },
+};
+
+static void rp2040_vreg_reset(DeviceState *dev)
+{
+    RP2040VregState *s = RP2040_VREG(dev);
+
+    s->vreg = RP2040_VREG_VREG_RESET;
+    s->bod = RP2040_VREG_BOD_RESET;
+    s->chip_reset = 0;
+}
+
+static void rp2040_vreg_init(Object *obj)
+{
+    RP2040VregState *s = RP2040_VREG(obj);
+
+    memory_region_init_io(&s->iomem, obj, &rp2040_vreg_ops, s,
+                          "rp2040.vreg_and_chip_reset", RP2040_VREG_SIZE);
+    sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->iomem);
+}
+
+static const VMStateDescription rp2040_vreg_vmstate = {
+    .name = TYPE_RP2040_VREG,
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .fields = (const VMStateField[]) {
+        VMSTATE_UINT32(vreg, RP2040VregState),
+        VMSTATE_UINT32(bod, RP2040VregState),
+        VMSTATE_UINT32(chip_reset, RP2040VregState),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+static void rp2040_vreg_class_init(ObjectClass *klass, const void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+
+    device_class_set_legacy_reset(dc, rp2040_vreg_reset);
+    dc->vmsd = &rp2040_vreg_vmstate;
+}
+
+static const TypeInfo rp2040_vreg_info = {
+    .name          = TYPE_RP2040_VREG,
+    .parent        = TYPE_SYS_BUS_DEVICE,
+    .instance_size = sizeof(RP2040VregState),
+    .instance_init = rp2040_vreg_init,
+    .class_init    = rp2040_vreg_class_init,
+};
+
+static void rp2040_vreg_register_types(void)
+{
+    type_register_static(&rp2040_vreg_info);
+}
+type_init(rp2040_vreg_register_types)
diff --git a/include/hw/arm/rp2040.h b/include/hw/arm/rp2040.h
index f6050f255d..4b6f7c2faa 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_sysinfo.h"
 #include "hw/misc/rp2040_syscfg.h"
+#include "hw/misc/rp2040_vreg.h"
 #include "qom/object.h"
 
 #define TYPE_RP2040 "rp2040"
@@ -39,6 +40,7 @@ struct RP2040State {
     PL011State uart[2];
     RP2040SysCfgState syscfg;
     RP2040SysInfoState sysinfo;
+    RP2040VregState vreg;
 
     MemoryRegion *board_memory;
     MemoryRegion cpu_memory[RP2040_NUM_CORES];
diff --git a/include/hw/misc/rp2040_vreg.h b/include/hw/misc/rp2040_vreg.h
new file mode 100644
index 0000000000..eab6cd8221
--- /dev/null
+++ b/include/hw/misc/rp2040_vreg.h
@@ -0,0 +1,46 @@
+/*
+ * RP2040 voltage regulator and chip reset emulation
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef HW_MISC_RP2040_VREG_H
+#define HW_MISC_RP2040_VREG_H
+
+#include "hw/core/registerfields.h"
+#include "hw/core/sysbus.h"
+#include "qom/object.h"
+
+#define TYPE_RP2040_VREG "rp2040-vreg"
+OBJECT_DECLARE_SIMPLE_TYPE(RP2040VregState, RP2040_VREG)
+
+#define RP2040_VREG_BASE 0x40064000
+#define RP2040_VREG_SIZE 0x4000
+
+REG32(RP2040_VREG_VREG, 0x00)
+    FIELD(RP2040_VREG_VREG, ROK, 12, 1)
+    FIELD(RP2040_VREG_VREG, VSEL, 4, 4)
+    FIELD(RP2040_VREG_VREG, HIZ, 1, 1)
+    FIELD(RP2040_VREG_VREG, EN, 0, 1)
+REG32(RP2040_VREG_BOD, 0x04)
+    FIELD(RP2040_VREG_BOD, VSEL, 4, 4)
+    FIELD(RP2040_VREG_BOD, EN, 0, 1)
+REG32(RP2040_VREG_CHIP_RESET, 0x08)
+    FIELD(RP2040_VREG_CHIP_RESET, PSM_RESTART_FLAG, 24, 1)
+    FIELD(RP2040_VREG_CHIP_RESET, HAD_PSM_RESTART, 20, 1)
+    FIELD(RP2040_VREG_CHIP_RESET, HAD_RUN, 16, 1)
+    FIELD(RP2040_VREG_CHIP_RESET, HAD_POR, 8, 1)
+
+#define RP2040_VREG_VREG_RESET 0x000000b1
+#define RP2040_VREG_BOD_RESET  0x00000091
+
+struct RP2040VregState {
+    SysBusDevice parent_obj;
+
+    MemoryRegion iomem;
+    uint32_t vreg;
+    uint32_t bod;
+    uint32_t chip_reset;
+};
+
+#endif
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index fdc7dbc84b..6596e93755 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -248,8 +248,6 @@ qtests_stm32l4x5 = \
    'stm32l4x5_usart-test']
 
 qtests_arm = \
-  (config_all_devices.has_key('CONFIG_RP2040') ?
-   ['rp2040-syscfg-test', 'rp2040-sysinfo-test'] : []) + \
   (config_all_devices.has_key('CONFIG_ARM_VIRT') and
    config_all_devices.has_key('CONFIG_PL011_C') ? ['pl011-test'] : []) + \
   (config_all_devices.has_key('CONFIG_MPS2') ? ['sse-timer-test'] : []) + \
@@ -264,6 +262,10 @@ qtests_arm = \
   (config_all_devices.has_key('CONFIG_TPM_TIS_I2C') ? ['tpm-tis-i2c-test', 
'tpm-tis-i2c-swtpm-test'] : []) + \
   (config_all_devices.has_key('CONFIG_VEXPRESS') ? ['test-arm-mptimer'] : []) 
+ \
   (config_all_devices.has_key('CONFIG_MICROBIT') ? ['microbit-test'] : []) + \
+  (config_all_devices.has_key('CONFIG_RP2040') ?
+   ['rp2040-syscfg-test',
+    'rp2040-sysinfo-test',
+    'rp2040-vreg-test'] : []) + \
   (config_all_devices.has_key('CONFIG_STM32L4X5_SOC') ? qtests_stm32l4x5 : []) 
+ \
   (config_all_devices.has_key('CONFIG_FSI_APB2OPB_ASPEED') ? 
['aspeed_fsi-test'] : []) + \
   (config_all_devices.has_key('CONFIG_CAN_FLEXCAN') ? ['flexcan-test'] : []) + 
\
diff --git a/tests/qtest/rp2040-vreg-test.c b/tests/qtest/rp2040-vreg-test.c
new file mode 100644
index 0000000000..cb47d7d527
--- /dev/null
+++ b/tests/qtest/rp2040-vreg-test.c
@@ -0,0 +1,79 @@
+/*
+ * QTest testcase for the RP2040 vreg_and_chip_reset block.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "hw/misc/rp2040.h"
+#include "hw/misc/rp2040_vreg.h"
+#include "libqtest.h"
+
+static QTestState *rp2040_start(void)
+{
+    return qtest_init("-machine raspi-pico");
+}
+
+static void test_vreg_reset_values(void)
+{
+    QTestState *qts = rp2040_start();
+
+    g_assert_cmphex(qtest_readl(qts,
+                    RP2040_VREG_BASE + A_RP2040_VREG_VREG), ==,
+                    RP2040_VREG_VREG_RESET |
+                    R_RP2040_VREG_VREG_ROK_MASK);
+    g_assert_cmphex(qtest_readl(qts,
+                    RP2040_VREG_BASE + A_RP2040_VREG_BOD), ==,
+                    RP2040_VREG_BOD_RESET);
+    g_assert_cmphex(qtest_readl(qts,
+                    RP2040_VREG_BASE + A_RP2040_VREG_CHIP_RESET), ==, 0);
+
+    qtest_quit(qts);
+}
+
+static void test_vreg_rw_masks(void)
+{
+    QTestState *qts = rp2040_start();
+
+    qtest_writel(qts, RP2040_VREG_BASE + A_RP2040_VREG_VREG, 0xffffffff);
+    qtest_writel(qts, RP2040_VREG_BASE + A_RP2040_VREG_BOD, 0xffffffff);
+    qtest_writel(qts, RP2040_VREG_BASE + A_RP2040_VREG_CHIP_RESET,
+                 0xffffffff);
+
+    g_assert_cmphex(qtest_readl(qts,
+                    RP2040_VREG_BASE + A_RP2040_VREG_VREG), ==, 0x000000f3);
+    g_assert_cmphex(qtest_readl(qts,
+                    RP2040_VREG_BASE + A_RP2040_VREG_BOD), ==, 0x000000f1);
+    g_assert_cmphex(qtest_readl(qts,
+                    RP2040_VREG_BASE + A_RP2040_VREG_CHIP_RESET), ==,
+                    R_RP2040_VREG_CHIP_RESET_PSM_RESTART_FLAG_MASK);
+
+    qtest_quit(qts);
+}
+
+static void test_vreg_atomic_aliases(void)
+{
+    QTestState *qts = rp2040_start();
+
+    qtest_writel(qts, RP2040_VREG_BASE + A_RP2040_VREG_BOD, 0);
+    qtest_writel(qts, RP2040_VREG_BASE + RP2040_ATOMIC_SET +
+                 A_RP2040_VREG_BOD, 0x00000011);
+    qtest_writel(qts, RP2040_VREG_BASE + RP2040_ATOMIC_CLR +
+                 A_RP2040_VREG_BOD, 0x00000010);
+
+    g_assert_cmphex(qtest_readl(qts,
+                    RP2040_VREG_BASE + A_RP2040_VREG_BOD), ==, 0x00000001);
+
+    qtest_quit(qts);
+}
+
+int main(int argc, char **argv)
+{
+    g_test_init(&argc, &argv, NULL);
+
+    qtest_add_func("/rp2040-vreg/reset-values", test_vreg_reset_values);
+    qtest_add_func("/rp2040-vreg/rw-masks", test_vreg_rw_masks);
+    qtest_add_func("/rp2040-vreg/atomic-aliases", test_vreg_atomic_aliases);
+
+    return g_test_run();
+}
-- 
2.55.0


Reply via email to