Signed-off-by: Simon Law <[email protected]>
---
 tests/qtest/k230-cmu-test.c | 312 ++++++++++++++++++++++++++++++++++++++++++++
 tests/qtest/meson.build     |   3 +-
 2 files changed, 314 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..ce3af909f0
--- /dev/null
+++ b/tests/qtest/k230-cmu-test.c
@@ -0,0 +1,312 @@
+/*
+ * 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;
+        uint32_t flipped;
+
+        if (!reg->w1t_mask) {
+            continue;
+        }
+
+        addr = K230_SYSCLK_BASE + reg->offset;
+        flipped = merge_rw(reg->reset, ~reg->reset, reg->rw_mask);
+        qtest_writel(qts, addr, flipped | reg->w1t_mask);
+        g_assert_cmphex(qtest_readl(qts, addr), ==, flipped);
+    }
+
+    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


Reply via email to