[PATCH 3/4] tests/qtest: add K230 CMU coverage

Simon Law <[email protected]>
Newsgroups org.nongnu.qemu-devel,org.nongnu.qemu-riscv
Message-ID <[email protected]>
Signed-off-by: Simon Law <[email protected]>
---
 tests/qtest/k230-cmu-test.c | 310 ++++++++++++++++++++++++++++++++++++++++++++
 tests/qtest/meson.build     |   3 +-
 2 files changed, 312 insertions(+), 1 deletion(-)

diff --git a/tests/qtest/k230-cmu-test.c b/tests/qtest/k230-cmu-test.c
new file mode 100644
index 0000000000..2be6400cd8
--- /dev/null
+++ b/tests/qtest/k230-cmu-test.c
@@ -0,0 +1,310 @@
+/*
+ * QTest testcase for Kendryte K230 Clock Management Unit
+ *
+ * Copyright (c) 2026 Simon Law <[email protected]>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * K230 Technical Reference Manual V0.3.1 (2024-11-18), CMU chapter.
+ *
+ * The reset and mask oracle in this file is intentionally independent from
+ * hw/misc/k230_cmu.c, so a descriptor-table error cannot mask itself.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/bitops.h"
+#include "libqtest.h"
+
+#define K230_PLL_BASE       0x91102000
+#define K230_SYSCLK_BASE    0x91100000
+#define K230_PLL_COUNT      4
+#define K230_PLL_STRIDE     0x10
+
+#define PLL_CFG0            0x0
+#define PLL_CFG1            0x4
+#define PLL_CTL             0x8
+#define PLL_STAT            0xc
+
+#define PLL_CFG0_RW_MASK    0x0f3f1fff
+#define PLL_CFG1_RW_MASK    0x000f0fff
+#define PLL_CTL_RW_MASK     0x00000ff4
+
+typedef struct K230SysclkOracle {
+    uint32_t offset;
+    uint32_t reset;
+    uint32_t rw_mask;
+    uint32_t w1t_mask;
+} K230SysclkOracle;
+
+static const uint32_t pll_reset[K230_PLL_COUNT][4] = {
+    { 0x0102018f, 0x000200c7, 0x00000034, 0x00000021 },
+    { 0x00000062, 0x00020031, 0x00000034, 0x00000021 },
+    { 0x00070378, 0x000201bb, 0x00000034, 0x00000021 },
+    { 0x0102018f, 0x000200c7, 0x00000074, 0x00000021 },
+};
+
+static const K230SysclkOracle sysclk_oracle[] = {
+    { 0x000, 0x0000e65f, 0x0003ffdf, BIT(31) },
+    { 0x004, 0x0009900d, 0x000f803f, BIT(31) },
+    { 0x008, 0x0000043d, 0x0000043d, BIT(31) },
+    { 0x00c, 0x00000c7f, 0x00007fff, BIT(31) },
+    { 0x010, 0x00000001, 0x00000001, 0 },
+    { 0x018, 0x7f7fffff, 0x7fffffff, 0 },
+    { 0x01c, 0x000b9248, 0x000fffff, BIT(31) },
+    { 0x020, 0x00039048, 0x0007fff8, BIT(31) },
+    { 0x024, 0xffffffff, 0xffffffff, 0 },
+    { 0x028, 0x000000ff, 0x000000ff, 0 },
+    { 0x02c, 0x1b6d9249, 0x3fffffff, BIT(31) },
+    { 0x030, 0x01dda0c3, 0x7fffffff, BIT(31) },
+    { 0x034, 0x01b9f424, 0x7fffffff, BIT(31) },
+    { 0x038, 0x006e7d09, 0x07ffffff, BIT(31) },
+    { 0x03c, 0x006e7d09, 0x07ffffff, BIT(31) },
+    { 0x040, 0x0000f424, 0x0001ffff, 0 },
+    { 0x044, 0x000001b9, 0x0000ffff, BIT(31) },
+    { 0x050, 0x003ffffe, 0x003ffffe, 0 },
+    { 0x054, 0x0003ffff, 0x0003ffff, BIT(31) },
+    { 0x058, 0x1efabef9, 0x7ffffff8, BIT(31) },
+    { 0x05c, 0x000c7ffd, 0x001c7ffd, BIT(31) },
+    { 0x060, 0x000043fd, 0x0003ffff, BIT(31) },
+    { 0x064, 0x000fffff, 0x000fffff, 0 },
+    { 0x068, 0x00820835, 0x3fffffff, BIT(31) },
+    { 0x06c, 0x003a74ef, 0x00ffffff, BIT(31) },
+    { 0x070, 0x000001ff, 0x000003ff, BIT(31) },
+    { 0x074, 0x00001fff, 0x00001fff, 0 },
+    { 0x078, 0x0000a83b, 0x00ffffff, BIT(31) },
+    { 0x080, 0x000002f3, 0x00003fff, BIT(31) },
+    { 0x100, 0x00000039, 0x0000007f, BIT(31) },
+    { 0x104, 0x00000009, 0x0000001f, BIT(31) },
+    /* TRM summary stops at 0x104; SDK/Linux ABI requires SPI2AXI at 0x108. */
+    { 0x108, 0x00000007, 0x0000000f, BIT(31) },
+};
+
+static QTestState *k230_cmu_start(void)
+{
+    return qtest_init("-machine k230");
+}
+
+static uint32_t pll_addr(unsigned index, uint32_t offset)
+{
+    g_assert_cmpuint(index, <, K230_PLL_COUNT);
+    return K230_PLL_BASE + index * K230_PLL_STRIDE + offset;
+}
+
+static uint32_t merge_rw(uint32_t old_value, uint32_t value, uint32_t mask)
+{
+    return (old_value & ~mask) | (value & mask);
+}
+
+static void assert_pll_reset(QTestState *qts)
+{
+    static const uint32_t offsets[] = { PLL_CFG0, PLL_CFG1, PLL_CTL, PLL_STAT };
+
+    for (unsigned pll = 0; pll < K230_PLL_COUNT; pll++) {
+        for (unsigned reg = 0; reg < G_N_ELEMENTS(offsets); reg++) {
+            g_assert_cmphex(qtest_readl(qts, pll_addr(pll, offsets[reg])), ==,
+                            pll_reset[pll][reg]);
+        }
+    }
+}
+
+static void assert_sysclk_reset(QTestState *qts)
+{
+    for (unsigned i = 0; i < G_N_ELEMENTS(sysclk_oracle); i++) {
+        const K230SysclkOracle *reg = &sysclk_oracle[i];
+
+        g_assert_cmphex(qtest_readl(qts, K230_SYSCLK_BASE + reg->offset), ==,
+                        reg->reset);
+    }
+}
+
+static void test_reset_pll(void)
+{
+    QTestState *qts = k230_cmu_start();
+
+    assert_pll_reset(qts);
+    qtest_quit(qts);
+}
+
+static void test_reset_sysclk(void)
+{
+    QTestState *qts = k230_cmu_start();
+
+    assert_sysclk_reset(qts);
+    qtest_quit(qts);
+}
+
+static void test_pll_cfg_masks(void)
+{
+    static const uint32_t offsets[] = { PLL_CFG0, PLL_CFG1 };
+    static const uint32_t masks[] = { PLL_CFG0_RW_MASK, PLL_CFG1_RW_MASK };
+    QTestState *qts = k230_cmu_start();
+
+    for (unsigned pll = 0; pll < K230_PLL_COUNT; pll++) {
+        for (unsigned reg = 0; reg < G_N_ELEMENTS(offsets); reg++) {
+            uint32_t addr = pll_addr(pll, offsets[reg]);
+            uint32_t reset = pll_reset[pll][reg];
+
+            qtest_writel(qts, addr, 0);
+            g_assert_cmphex(qtest_readl(qts, addr), ==,
+                            merge_rw(reset, 0, masks[reg]));
+            qtest_writel(qts, addr, UINT32_MAX);
+            g_assert_cmphex(qtest_readl(qts, addr), ==,
+                            merge_rw(merge_rw(reset, 0, masks[reg]),
+                                     UINT32_MAX, masks[reg]));
+
+            qtest_system_reset(qts);
+            qtest_writel(qts, addr, BIT(ctz32(masks[reg])));
+            g_assert_cmphex(qtest_readl(qts, addr), ==,
+                            merge_rw(reset, BIT(ctz32(masks[reg])),
+                                     masks[reg]));
+
+            qtest_system_reset(qts);
+            qtest_writel(qts, addr, reset | BIT(ctz32(~masks[reg])));
+            g_assert_cmphex(qtest_readl(qts, addr), ==, reset);
+        }
+    }
+
+    qtest_quit(qts);
+}
+
+static void test_pll_ctl_write_enable(void)
+{
+    QTestState *qts = k230_cmu_start();
+    uint32_t addr = pll_addr(0, PLL_CTL);
+
+    qtest_writel(qts, addr, 0);
+    g_assert_cmphex(qtest_readl(qts, addr), ==, pll_reset[0][2]);
+
+    qtest_writel(qts, addr, BIT(16 + 2));
+    g_assert_cmphex(qtest_readl(qts, addr), ==,
+                    pll_reset[0][2] & ~BIT(2));
+
+    qtest_writel(qts, addr, BIT(2) | BIT(16 + 2) | BIT(16 + 4));
+    g_assert_cmphex(qtest_readl(qts, addr), ==,
+                    merge_rw(pll_reset[0][2] & ~BIT(2),
+                             BIT(2), BIT(2) | BIT(4)));
+
+    qtest_writel(qts, addr, UINT32_MAX);
+    g_assert_cmphex(qtest_readl(qts, addr), ==, PLL_CTL_RW_MASK);
+    qtest_quit(qts);
+}
+
+static void test_pll_status(void)
+{
+    QTestState *qts = k230_cmu_start();
+    uint32_t ctl = pll_addr(0, PLL_CTL);
+    uint32_t stat = pll_addr(0, PLL_STAT);
+
+    qtest_writel(qts, stat, 0);
+    g_assert_cmphex(qtest_readl(qts, stat), ==, 0x00000021);
+
+    qtest_writel(qts, ctl, BIT(0) | BIT(16));
+    g_assert_cmphex(qtest_readl(qts, stat), ==, 0);
+
+    qtest_writel(qts, ctl, BIT(1) | BIT(17));
+    g_assert_cmphex(qtest_readl(qts, stat), ==, 0x00000021);
+
+    qtest_writel(qts, ctl, BIT(0) | BIT(1) | BIT(16) | BIT(17));
+    g_assert_cmphex(qtest_readl(qts, stat), ==, 0x00000021);
+    qtest_quit(qts);
+}
+
+static void test_sysclk_masks(void)
+{
+    QTestState *qts = k230_cmu_start();
+
+    for (unsigned i = 0; i < G_N_ELEMENTS(sysclk_oracle); i++) {
+        const K230SysclkOracle *reg = &sysclk_oracle[i];
+        uint32_t addr = K230_SYSCLK_BASE + reg->offset;
+        uint32_t after_zero = merge_rw(reg->reset, 0, reg->rw_mask);
+
+        qtest_writel(qts, addr, 0);
+        g_assert_cmphex(qtest_readl(qts, addr), ==, after_zero);
+        qtest_writel(qts, addr, UINT32_MAX);
+        g_assert_cmphex(qtest_readl(qts, addr), ==,
+                        merge_rw(after_zero, UINT32_MAX, reg->rw_mask));
+    }
+
+    qtest_quit(qts);
+}
+
+static void test_sysclk_update(void)
+{
+    QTestState *qts = k230_cmu_start();
+
+    for (unsigned i = 0; i < G_N_ELEMENTS(sysclk_oracle); i++) {
+        const K230SysclkOracle *reg = &sysclk_oracle[i];
+        uint32_t addr;
+
+        if (!reg->w1t_mask) {
+            continue;
+        }
+
+        addr = K230_SYSCLK_BASE + reg->offset;
+        qtest_writel(qts, addr, reg->reset | reg->w1t_mask);
+        g_assert_cmphex(qtest_readl(qts, addr), ==, reg->reset);
+    }
+
+    qtest_quit(qts);
+}
+
+static void test_spi2axi(void)
+{
+    QTestState *qts = k230_cmu_start();
+    uint32_t addr = K230_SYSCLK_BASE + 0x108;
+
+    g_assert_cmphex(qtest_readl(qts, addr), ==, 0x00000007);
+    qtest_writel(qts, addr, 0x00000001);
+    g_assert_cmphex(qtest_readl(qts, addr), ==, 0x00000001);
+    qtest_writel(qts, addr, 0x8000000f);
+    g_assert_cmphex(qtest_readl(qts, addr), ==, 0x0000000f);
+    qtest_quit(qts);
+}
+
+static void test_unknown_offsets(void)
+{
+    QTestState *qts = k230_cmu_start();
+    /* The 0x40 PLL window has no undefined aligned 32-bit word. */
+    uint32_t addr = K230_SYSCLK_BASE + 0x014;
+
+    g_assert_cmphex(qtest_readl(qts, addr), ==, 0);
+    qtest_writel(qts, addr, UINT32_MAX);
+    g_assert_cmphex(qtest_readl(qts, addr), ==, 0);
+    qtest_quit(qts);
+}
+
+static void test_system_reset(void)
+{
+    QTestState *qts = k230_cmu_start();
+
+    qtest_writel(qts, pll_addr(0, PLL_CFG0), 0);
+    qtest_writel(qts, pll_addr(1, PLL_CFG1), 0);
+    qtest_writel(qts, pll_addr(2, PLL_CTL), BIT(16 + 2));
+    qtest_writel(qts, pll_addr(3, PLL_CTL), BIT(0) | BIT(16));
+    qtest_writel(qts, K230_SYSCLK_BASE + 0x000, 0);
+    qtest_writel(qts, K230_SYSCLK_BASE + 0x050, 0);
+    qtest_writel(qts, K230_SYSCLK_BASE + 0x108, 0);
+
+    qtest_system_reset(qts);
+    assert_pll_reset(qts);
+    assert_sysclk_reset(qts);
+    qtest_quit(qts);
+}
+
+int main(int argc, char *argv[])
+{
+    g_test_init(&argc, &argv, NULL);
+
+    qtest_add_func("/k230-cmu/reset/pll", test_reset_pll);
+    qtest_add_func("/k230-cmu/reset/sysclk", test_reset_sysclk);
+    qtest_add_func("/k230-cmu/pll/cfg-masks", test_pll_cfg_masks);
+    qtest_add_func("/k230-cmu/pll/ctl-write-enable", test_pll_ctl_write_enable);
+    qtest_add_func("/k230-cmu/pll/status", test_pll_status);
+    qtest_add_func("/k230-cmu/sysclk/masks", test_sysclk_masks);
+    qtest_add_func("/k230-cmu/sysclk/update", test_sysclk_update);
+    qtest_add_func("/k230-cmu/sysclk/spi2axi", test_spi2axi);
+    qtest_add_func("/k230-cmu/unknown-offsets", test_unknown_offsets);
+    qtest_add_func("/k230-cmu/system-reset", test_system_reset);
+
+    return g_test_run();
+}
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index 56ff860e21..9f5c46e0bf 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -297,7 +297,8 @@ qtests_riscv64 = ['riscv-csr-test'] + \
   (config_all_devices.has_key('CONFIG_IOMMU_TESTDEV') and
    config_all_devices.has_key('CONFIG_RISCV_IOMMU') ?
    ['iommu-riscv-test'] : []) + \
-  (config_all_devices.has_key('CONFIG_K230') ? ['k230-wdt-test'] : [])
+  (config_all_devices.has_key('CONFIG_K230') ?
+   ['k230-cmu-test', 'k230-wdt-test'] : [])
 
 qtests_hexagon = ['boot-serial-test']
 

-- 
2.43.0
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.