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]>
---
v2:
- Mirror HSION into HSIRDY.
- Mirror PLLI2SON into PLLI2SRDY.
- Extend the qtest to cover both additional clock ready bits.

 hw/misc/stm32_rcc.c              | 19 ++++++++++
 tests/qtest/meson.build          |  2 ++
 tests/qtest/stm32f405-rcc-test.c | 62 ++++++++++++++++++++++++++++++++
 3 files changed, 83 insertions(+)
 create mode 100644 tests/qtest/stm32f405-rcc-test.c

diff --git a/hw/misc/stm32_rcc.c b/hw/misc/stm32_rcc.c
index 5cfb39e560..814c5ea3b0 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/meson.build b/tests/qtest/meson.build
index 78b3010649..d6a306358f 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -238,6 +238,8 @@ qtests_stm32l4x5 = \
    'stm32l4x5_usart-test']
 
 qtests_arm = \
+  (config_all_devices.has_key('CONFIG_OLIMEX_STM32_H405') ? \
+   ['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'] : []) + \
diff --git a/tests/qtest/stm32f405-rcc-test.c b/tests/qtest/stm32f405-rcc-test.c
new file mode 100644
index 0000000000..1a8819b6f5
--- /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;
+}
-- 
2.54.0 (Apple Git-157)


Reply via email to