From: Manuele Conti <[email protected]>

RM0090 7.3.1/7.3.3: oscillator ready bits follow their
corresponding enable bits, and SWS follows SW. Upstream stored raw
values, so firmware polling for ready or checking SWS never saw the
expected state.

Mirror the enable bits into the ready bits on CR writes and SW into
SWS on CFGR writes. Clock switching is instantaneous in QEMU. No
oscillator startup or PLL lock delay is modelled.

Add qtest coverage for HSION, HSEON, PLLON, PLLI2SON and SW,
including clearing the ready bits again when the oscillators are
disabled.

Signed-off-by: Manuele Conti <[email protected]>
Reviewed-by: Peter Maydell <[email protected]>
Signed-off-by: Peter Maydell <[email protected]>
---
 hw/misc/stm32_rcc.c              | 19 ++++++++++
 tests/qtest/stm32f405-rcc-test.c | 62 ++++++++++++++++++++++++++++++++
 tests/qtest/meson.build          |  2 +-
 3 files changed, 82 insertions(+), 1 deletion(-)
 create mode 100644 tests/qtest/stm32f405-rcc-test.c

diff --git a/hw/misc/stm32_rcc.c b/hw/misc/stm32_rcc.c
index 5cfb39e5607..814c5ea3b01 100644
--- a/hw/misc/stm32_rcc.c
+++ b/hw/misc/stm32_rcc.c
@@ -90,6 +90,25 @@ static void stm32_rcc_write(void *opaque, hwaddr addr,
     }
 
     switch (addr) {
+    case STM32_RCC_CR:
+        /*
+         * Oscillators become ready immediately in QEMU.
+         * Mirror enable bits into ready bits. See RM0090 7.3.1.
+         */
+        value = deposit32(value, 1, 1, extract32(value, 0, 1));
+        value = deposit32(value, 17, 1, extract32(value, 16, 1));
+        value = deposit32(value, 25, 1, extract32(value, 24, 1));
+        value = deposit32(value, 27, 1, extract32(value, 26, 1));
+        s->regs[addr >> 2] = value;
+        return;
+    case STM32_RCC_CFGR:
+        /*
+         * System clock switching is instantaneous in QEMU.
+         * Mirror SW[1:0] into SWS[3:2]. See RM0090 7.3.3.
+         */
+        value = deposit32(value, 2, 2, extract32(value, 0, 2));
+        s->regs[addr >> 2] = value;
+        return;
     case STM32_RCC_AHB1_RSTR ... STM32_RCC_AHB3_RSTR:
     case STM32_RCC_APB1_RSTR ... STM32_RCC_APB2_RSTR:
         prev_value = s->regs[addr / 4];
diff --git a/tests/qtest/stm32f405-rcc-test.c b/tests/qtest/stm32f405-rcc-test.c
new file mode 100644
index 00000000000..1a8819b6f5b
--- /dev/null
+++ b/tests/qtest/stm32f405-rcc-test.c
@@ -0,0 +1,62 @@
+/*
+ * STM32F405 RCC tests
+ *
+ * Copyright (c) 2026 Manuele Conti <[email protected]>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "libqtest-single.h"
+
+#define RCC_BASE        0x40023800
+#define RCC_CR          (RCC_BASE + 0x00)
+#define RCC_CFGR        (RCC_BASE + 0x08)
+
+#define CR_HSION        (1U << 0)
+#define CR_HSIRDY       (1U << 1)
+#define CR_HSEON        (1U << 16)
+#define CR_HSERDY       (1U << 17)
+#define CR_PLLON        (1U << 24)
+#define CR_PLLRDY       (1U << 25)
+#define CR_PLLI2SON     (1U << 26)
+#define CR_PLLI2SRDY    (1U << 27)
+
+static void test_cr_ready_bits(void)
+{
+    uint32_t on = CR_HSION | CR_HSEON | CR_PLLON | CR_PLLI2SON;
+    uint32_t ready = CR_HSIRDY | CR_HSERDY | CR_PLLRDY | CR_PLLI2SRDY;
+
+    writel(RCC_CR, on);
+    g_assert_cmphex(readl(RCC_CR), ==, on | ready);
+
+    writel(RCC_CR, 0);
+    g_assert_cmphex(readl(RCC_CR), ==, 0);
+}
+
+static void test_cfgr_switch_status(void)
+{
+    unsigned int sw;
+
+    for (sw = 0; sw < 4; sw++) {
+        writel(RCC_CFGR, sw);
+        g_assert_cmphex(readl(RCC_CFGR), ==, sw | (sw << 2));
+    }
+}
+
+int main(int argc, char **argv)
+{
+    int ret;
+
+    g_test_init(&argc, &argv, NULL);
+
+    qtest_add_func("/stm32f405/rcc/cr-ready-bits", test_cr_ready_bits);
+    qtest_add_func("/stm32f405/rcc/cfgr-switch-status",
+                   test_cfgr_switch_status);
+
+    qtest_start("-machine olimex-stm32-h405");
+    ret = g_test_run();
+    qtest_end();
+
+    return ret;
+}
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index 69bebe5a396..2f63621e393 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -254,7 +254,7 @@ qtests_exynos4210 = \
 
 qtests_arm = \
   (config_all_devices.has_key('CONFIG_OLIMEX_STM32_H405') ? \
-   ['stm32f405-timer-test'] : []) + \
+   ['stm32f405-timer-test', 'stm32f405-rcc-test'] : []) + \
   (config_all_devices.has_key('CONFIG_MPS2') ? ['sse-timer-test'] : []) + \
   (config_all_devices.has_key('CONFIG_CMSDK_APB_DUALTIMER') ? 
['cmsdk-apb-dualtimer-test'] : []) + \
   (config_all_devices.has_key('CONFIG_CMSDK_APB_TIMER') ? 
['cmsdk-apb-timer-test'] : []) + \
-- 
2.43.0


Reply via email to